harden(forms): prevent duplicate onboarding form submission on Back/Forward
Phase 4 self-audit: alreadySubmitted was a page-load snapshot, so a member who submitted a form/waiver step then navigated Back and Forward would insert a second submission. Track submitted steps in session state and treat them as already-submitted.
This commit is contained in:
@@ -60,6 +60,10 @@ export function OnboardingFlow({ spaceName, steps, canSkip, profile: initialProf
|
|||||||
const [skillsText, setSkillsText] = useState(initialProfile.skills.join(', '))
|
const [skillsText, setSkillsText] = useState(initialProfile.skills.join(', '))
|
||||||
const [formAnswers, setFormAnswers] = useState<Record<string, Record<string, unknown>>>({})
|
const [formAnswers, setFormAnswers] = useState<Record<string, Record<string, unknown>>>({})
|
||||||
const [formConsent, setFormConsent] = useState<Record<string, boolean>>({})
|
const [formConsent, setFormConsent] = useState<Record<string, boolean>>({})
|
||||||
|
// Form steps already submitted in THIS session. Prevents a second insert if
|
||||||
|
// the member navigates Back then Forward over a form/waiver step (the
|
||||||
|
// server-side alreadySubmitted prop is a snapshot from page load).
|
||||||
|
const [submittedNow, setSubmittedNow] = useState<Record<string, boolean>>({})
|
||||||
|
|
||||||
const step = steps[index]
|
const step = steps[index]
|
||||||
const isLast = index === steps.length - 1
|
const isLast = index === steps.length - 1
|
||||||
@@ -69,7 +73,8 @@ export function OnboardingFlow({ spaceName, steps, canSkip, profile: initialProf
|
|||||||
|
|
||||||
const isFormStep = step.step_type === 'form'
|
const isFormStep = step.step_type === 'form'
|
||||||
const formDef = step.formDef ?? null
|
const formDef = step.formDef ?? null
|
||||||
const formActionable = isFormStep && formDef && !step.alreadySubmitted
|
const effectiveSubmitted = step.alreadySubmitted === true || submittedNow[step.id] === true
|
||||||
|
const formActionable = isFormStep && !!formDef && !effectiveSubmitted
|
||||||
const isWaiver = formDef?.kind === 'waiver'
|
const isWaiver = formDef?.kind === 'waiver'
|
||||||
const consentOk = !formActionable || !isWaiver || formConsent[step.id] === true
|
const consentOk = !formActionable || !isWaiver || formConsent[step.id] === true
|
||||||
|
|
||||||
@@ -131,6 +136,7 @@ export function OnboardingFlow({ spaceName, steps, canSkip, profile: initialProf
|
|||||||
toast.error(res.error)
|
toast.error(res.error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setSubmittedNow(p => ({ ...p, [step.id]: true }))
|
||||||
}
|
}
|
||||||
await markOnboardingStepDone(step.id)
|
await markOnboardingStepDone(step.id)
|
||||||
setBusy(false)
|
setBusy(false)
|
||||||
@@ -243,7 +249,7 @@ export function OnboardingFlow({ spaceName, steps, canSkip, profile: initialProf
|
|||||||
<p className="font-sans text-sm text-muted-foreground">
|
<p className="font-sans text-sm text-muted-foreground">
|
||||||
This form is not available right now. You can continue.
|
This form is not available right now. You can continue.
|
||||||
</p>
|
</p>
|
||||||
) : step.alreadySubmitted ? (
|
) : effectiveSubmitted ? (
|
||||||
<p className="font-sans text-sm text-muted-foreground">
|
<p className="font-sans text-sm text-muted-foreground">
|
||||||
You have already completed this. You can continue.
|
You have already completed this. You can continue.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user