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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
// <time>" context, but it no longer gates the submission. The TTL
|
||||
// gate below is the sole validity check.
|
||||
const row = await tx.query.supplementalFormTokens.findFirst({
|
||||
where: and(
|
||||
eq(supplementalFormTokens.token, token),
|
||||
isNull(supplementalFormTokens.consumedAt),
|
||||
),
|
||||
where: eq(supplementalFormTokens.token, token),
|
||||
});
|
||||
if (!row) {
|
||||
throw new ConflictError('This link has already been used or is no longer valid.');
|
||||
throw new ConflictError('This link is no longer valid.');
|
||||
}
|
||||
if (row.expiresAt.getTime() < Date.now()) {
|
||||
throw new ConflictError('This link has expired.');
|
||||
|
||||
Reference in New Issue
Block a user