From f462d0f0c7782a6a1d6b4e64981fabc5f99c26b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 20:47:33 +0000 Subject: [PATCH] Address code review feedback on settings/account deletion 1. Wrap anonymizeUserAccount in a DB transaction so partial failures (e.g. session delete succeeds but user update fails) can't leave accounts in an inconsistent state 2. Escape user email and notes with escapeHtml() before interpolating into the data request email body 3. Reset AlertDialog confirmed state when dialog is dismissed via backdrop click or Escape key (onOpenChange handler) 4. Extract accounts DB query to app/models/account.ts (findLinkedAccountsByUserId) to comply with the "always query through app/models/" convention 5. Replace dynamic imports() in action handlers with static top-level imports 6. Remove the unused hard-delete deleteUser() function 7. Add lastDataRequestAt timestamp to users (migration 0101) and enforce a 30-day server-side cooldown on data export requests 8. Replace fragile actionData type casts with a proper ActionData discriminated union; narrowing now works without `as` assertions 9. Strengthen tests: verify which schema tables are passed to delete() and that operations run inside the transaction https://claude.ai/code/session_017Hvmof82Xr3UwKFc3pnC4X --- .../user/settings/PrivacySection.tsx | 30 +- app/models/__tests__/commissioner.test.ts | 5 +- app/models/__tests__/user.test.ts | 50 +- app/models/account.ts | 16 + app/models/user.ts | 39 +- .../leagues/__tests__/team-management.test.ts | 5 +- app/routes/settings.tsx | 131 +- database/schema.ts | 1 + drizzle/0101_simple_black_bolt.sql | 1 + drizzle/meta/0101_snapshot.json | 5781 +++++++++++++++++ drizzle/meta/_journal.json | 7 + 11 files changed, 5945 insertions(+), 121 deletions(-) create mode 100644 app/models/account.ts create mode 100644 drizzle/0101_simple_black_bolt.sql create mode 100644 drizzle/meta/0101_snapshot.json diff --git a/app/components/user/settings/PrivacySection.tsx b/app/components/user/settings/PrivacySection.tsx index 4e6c860..8cbdc22 100644 --- a/app/components/user/settings/PrivacySection.tsx +++ b/app/components/user/settings/PrivacySection.tsx @@ -16,14 +16,27 @@ import { type Props = { userEmail: string; + dataRequestCooldownUntil: Date | null; dataRequestSuccess?: boolean; dataRequestError?: string; }; -export function PrivacySection({ userEmail, dataRequestSuccess, dataRequestError }: Props) { +export function PrivacySection({ + userEmail, + dataRequestCooldownUntil, + dataRequestSuccess, + dataRequestError, +}: Props) { const [confirmed, setConfirmed] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); + const handleDialogOpenChange = (open: boolean) => { + setDialogOpen(open); + if (!open) setConfirmed(false); + }; + + const inCooldown = dataRequestCooldownUntil != null && dataRequestCooldownUntil > new Date(); + return (
@@ -53,7 +66,14 @@ export function PrivacySection({ userEmail, dataRequestSuccess, dataRequestError

{dataRequestError}

)} - {!dataRequestSuccess && ( + {inCooldown && !dataRequestSuccess && ( +

+ Your data request is being processed. You can submit another request + after {dataRequestCooldownUntil!.toLocaleDateString()}. +

+ )} + + {!dataRequestSuccess && !inCooldown && (
- +
- setConfirmed(false)}> - Cancel - + Cancel