From b74fc56a3bbba06b8b30b46ac496599a6c552141 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 21 May 2026 18:23:44 +0200 Subject: [PATCH] feat(uat-batch-15): supplemental-info link reusable until expiry The supplemental-info token now stays valid for re-submissions until the 14-day TTL expires. Previously the link was single-use: `applySubmission` required `consumedAt IS NULL`, which locked clients out of correcting a typo or finishing a partial submission. - Service: drops the `isNull(consumedAt)` filter; TTL is the sole validity check. `consumedAt` is still stamped on each submit so the rep / loader can see "last submitted at" context. - Public form: the "already submitted" lockout screen is removed. Instead, when the token has been used before, the form renders with the prefill (already reflecting the latest data) plus a soft amber banner noting that changes overwrite the previous submission. - Drive-by em-dash fix on the post-submit thank-you copy (matches the Wave-1 lint guard). tsc clean. 1419/1419 vitest pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../public/supplemental-info/[token]/page.tsx | 25 ++++++++----------- .../services/supplemental-forms.service.ts | 14 ++++++----- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/app/public/supplemental-info/[token]/page.tsx b/src/app/public/supplemental-info/[token]/page.tsx index 3dc19f6d..2f477dcb 100644 --- a/src/app/public/supplemental-info/[token]/page.tsx +++ b/src/app/public/supplemental-info/[token]/page.tsx @@ -157,26 +157,17 @@ export default function SupplementalInfoPage({ params }: PageProps) { ); } - if (data?.token.consumed) { - return ( - -
- -

Thanks — we already have your details

-

- This form was already submitted. Your sales contact will be in touch shortly. -

-
-
- ); - } + // Tokens are now reusable until expiry — the consumed flag is kept + // so the form can show a soft "you've submitted this before" banner + // (and prefill the entered values) without locking the recipient out + // of updating their details. if (submitted) { return (
-

Thanks — got it

+

Thanks, got it

Your details have been sent to the team. Watch your inbox for your EOI document shortly.

@@ -194,6 +185,12 @@ export default function SupplementalInfoPage({ params }: PageProps) { We've pre-filled what we have on file. Please review, correct anything that's wrong, and add what's missing.

+ {data?.token.consumed ? ( +
+ You've submitted this form before. Feel free to update any details. Changes + overwrite the previous submission. +
+ ) : null}
diff --git a/src/lib/services/supplemental-forms.service.ts b/src/lib/services/supplemental-forms.service.ts index 4e0e7e50..84bcb9bb 100644 --- a/src/lib/services/supplemental-forms.service.ts +++ b/src/lib/services/supplemental-forms.service.ts @@ -8,7 +8,7 @@ * updates, consume token. All inside one transaction. */ -import { and, eq, isNull } from 'drizzle-orm'; +import { and, eq } from 'drizzle-orm'; import crypto from 'node:crypto'; import { db } from '@/lib/db'; @@ -193,14 +193,16 @@ export async function applySubmission(token: string, input: SubmissionInput): Pr } await db.transaction(async (tx) => { + // Reusable-until-expiry: the link stays valid for repeat + // submissions until it expires. `consumedAt` is still stamped on + // first submit so the rep / loader can show "last submitted at + //