fix: enforce step validation guards and prevent step bypass in AddProduct
Some checks failed
Beta CI/CD Pipeline / deploy-beta (push) Blocked by required conditions
Beta CI/CD Pipeline / build-and-test (push) Has been cancelled

This commit is contained in:
vickytechkey 2026-11-01 16:08:07 +05:30
parent 2344022175
commit ad3bde264e

View file

@ -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} />