fix: enforce step validation guards and prevent step bypass in AddProduct
This commit is contained in:
parent
2344022175
commit
ad3bde264e
1 changed files with 91 additions and 9 deletions
|
|
@ -243,7 +243,86 @@ export const AddProductPage: React.FC = () => {
|
|||
setTimeout(() => setAutoSaved(true), 600);
|
||||
};
|
||||
|
||||
// Step completion flags
|
||||
const isStep1Complete = Boolean(category);
|
||||
const isStep2Complete = Boolean(image || coverPreview);
|
||||
const isStep3Complete = Boolean(title.trim());
|
||||
const isStep4Complete = Boolean(price && Number(price) > 0);
|
||||
|
||||
const isStepDone = (stepNum: number) => {
|
||||
if (stepNum === 1) return isStep1Complete;
|
||||
if (stepNum === 2) return isStep2Complete;
|
||||
if (stepNum === 3) return isStep3Complete;
|
||||
if (stepNum === 4) return isStep4Complete;
|
||||
return false;
|
||||
};
|
||||
|
||||
const canAccessStep = (targetStep: number) => {
|
||||
if (targetStep <= step) return true;
|
||||
if (targetStep >= 2 && !isStep1Complete) return false;
|
||||
if (targetStep >= 3 && !isStep2Complete) return false;
|
||||
if (targetStep >= 4 && !isStep3Complete) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleStepClick = (targetStep: number) => {
|
||||
if (targetStep === step) return;
|
||||
if (targetStep < step) {
|
||||
setErrorMessage(null);
|
||||
setStep(targetStep);
|
||||
return;
|
||||
}
|
||||
if (targetStep >= 2 && !isStep1Complete) {
|
||||
setErrorMessage('Please select a craft category in Step 1 before proceeding.');
|
||||
setStep(1);
|
||||
return;
|
||||
}
|
||||
if (targetStep >= 3 && !isStep2Complete) {
|
||||
setErrorMessage('Please upload at least a primary cover photo in Step 2 before proceeding.');
|
||||
setStep(2);
|
||||
return;
|
||||
}
|
||||
if (targetStep >= 4 && !isStep3Complete) {
|
||||
setErrorMessage('Please provide a Product Title in Step 3 before proceeding.');
|
||||
setStep(3);
|
||||
return;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setStep(targetStep);
|
||||
};
|
||||
|
||||
const handleContinue = () => {
|
||||
if (step === 1) {
|
||||
if (!isStep1Complete) {
|
||||
setErrorMessage('Please select a craft category before continuing.');
|
||||
return;
|
||||
}
|
||||
} else if (step === 2) {
|
||||
if (!isStep2Complete) {
|
||||
setErrorMessage('Please upload at least a primary cover photo before continuing.');
|
||||
return;
|
||||
}
|
||||
} else if (step === 3) {
|
||||
if (!isStep3Complete) {
|
||||
setErrorMessage('Please provide a Product Title before continuing.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setStep(s => s + 1);
|
||||
};
|
||||
|
||||
const handleFinish = async () => {
|
||||
if (!category) {
|
||||
setErrorMessage('Please select a craft category in Step 1 before publishing.');
|
||||
setStep(1);
|
||||
return;
|
||||
}
|
||||
if (!image && !coverPreview) {
|
||||
setErrorMessage('Please upload at least a primary cover photo in Step 2 before publishing.');
|
||||
setStep(2);
|
||||
return;
|
||||
}
|
||||
if (!title.trim()) {
|
||||
setErrorMessage('Please provide a Product Title in Step 3 before publishing.');
|
||||
setStep(3);
|
||||
|
|
@ -383,12 +462,14 @@ export const AddProductPage: React.FC = () => {
|
|||
{/* ── Unified Stepper ── */}
|
||||
<div style={{ display: 'flex', gap: '8px', overflowX: 'auto', paddingBottom: '4px' }}>
|
||||
{STEPS.map((s) => {
|
||||
const isDone = step > s.num;
|
||||
const isDone = isStepDone(s.num);
|
||||
const isActive = step === s.num;
|
||||
const isAccessible = canAccessStep(s.num);
|
||||
return (
|
||||
<div
|
||||
key={s.num}
|
||||
onClick={() => setStep(s.num)}
|
||||
onClick={() => handleStepClick(s.num)}
|
||||
title={isAccessible ? `Go to Step ${s.num}: ${s.label}` : `Complete previous steps to unlock Step ${s.num}`}
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: '160px',
|
||||
|
|
@ -397,9 +478,10 @@ export const AddProductPage: React.FC = () => {
|
|||
gap: '10px',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '9999px',
|
||||
backgroundColor: isActive ? '#701A29' : isDone ? '#F9FAFB' : '#F9FAFB',
|
||||
border: isDone ? '1px solid #E5E7EB' : '1px solid transparent',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: isActive ? '#701A29' : isDone ? '#F0FDF4' : '#F9FAFB',
|
||||
border: isActive ? '1px solid #701A29' : isDone ? '1px solid #BBF7D0' : '1px solid #E5E7EB',
|
||||
cursor: isAccessible ? 'pointer' : 'not-allowed',
|
||||
opacity: isAccessible ? 1 : 0.6,
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
|
|
@ -407,16 +489,16 @@ export const AddProductPage: React.FC = () => {
|
|||
width: '24px', height: '24px', borderRadius: '50%',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: '0.7rem', fontWeight: 800, flexShrink: 0,
|
||||
backgroundColor: isDone ? '#DCFCE7' : isActive ? '#962337' : 'transparent',
|
||||
backgroundColor: isDone ? '#DCFCE7' : isActive ? '#962337' : '#E5E7EB',
|
||||
color: isDone ? '#166534' : isActive ? '#FFFFFF' : '#6B7280',
|
||||
}}>
|
||||
{isDone ? <Check size={12} strokeWidth={3} /> : s.num}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<span style={{ fontSize: '0.62rem', fontWeight: 700, color: isActive ? '#E5E7EB' : '#9CA3AF', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
|
||||
<span style={{ fontSize: '0.62rem', fontWeight: 700, color: isActive ? '#E5E7EB' : isDone ? '#15803D' : '#9CA3AF', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
|
||||
STEP {s.num}
|
||||
</span>
|
||||
<span style={{ fontSize: '0.8rem', fontWeight: isActive ? 700 : 600, color: isActive ? '#FFFFFF' : '#374151' }}>
|
||||
<span style={{ fontSize: '0.8rem', fontWeight: isActive ? 700 : 600, color: isActive ? '#FFFFFF' : isDone ? '#166534' : '#374151' }}>
|
||||
{s.label}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -954,7 +1036,7 @@ export const AddProductPage: React.FC = () => {
|
|||
)}
|
||||
|
||||
{step < 4 ? (
|
||||
<button type="button" onClick={() => setStep(s => s + 1)}
|
||||
<button type="button" onClick={handleContinue}
|
||||
style={{ fontSize: '0.86rem', fontWeight: 700, color: '#FFFFFF', backgroundColor: '#581020', border: 'none', borderRadius: '9999px', padding: '10px 24px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
Continue to {step === 1 ? 'Photos & Video' : step === 2 ? 'Story & Details' : 'Pricing & Shipping'}
|
||||
<ArrowRight size={16} />
|
||||
|
|
|
|||
Loading…
Reference in a new issue