src/components/auth/goodbye-page-content.tsx
Rama main · réplica de lectura del repositorio original.
"use client";
import { Suspense, useState } from "react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { CalendarClock, Trash2 } from "lucide-react";
import { StandaloneShell } from "@/components/layout/standalone-shell";
import {
StandaloneCard,
StandaloneCardHeader,
} from "@/components/layout/standalone-card";
import { PageSpinner } from "@/components/ui/page-spinner";
import { formatPurgeDay, parsePurgeDate } from "@/lib/purge-date";
// The last screen of a deletion. `DELETE /user` composes the purge date and hands it
// back exactly once; the account is dark by the time this renders, so there is no
// session left to ask for it again and nothing to poll. It arrives on the URL because
// that is what survives the page load the deletion ends in - see
// `delete-account-card.tsx`. The restore screen names the same date from the other
// end of the window, and both parse it through `lib/purge-date.ts`.
//
// Reachable on its own terms as well: a reload, a bookmark, or the back button after
// leaving. A visit with no date is not an error, it is somebody who kept the tab -
// the copy just stops short of naming a day it cannot know.
//
// Chrome-free (see `NO_CHROME_ROUTES` in `app-shell.tsx`), like the other terminal
// auth-flow screens: the header's user menu belongs to a session that no longer
// exists, and the only way on from here is out.
export function GoodbyePageContent() {
// Not what makes the build pass. `useSearchParams()` is a context read on the
// client and suspends only while a *static* shell is being validated at build
// time, which `export const instant = false` on the root layout switches off for
// every route in this app - see `DECISIONS.md`, "The click paints the
// destination's frame, and the page is what paints it". Nothing under this
// boundary suspends, so this fallback never commits.
return (
<Suspense fallback={<PageSpinner />}>
<GoodbyeWithPurgeDate />
</Suspense>
);
}
function GoodbyeWithPurgeDate() {
const searchParams = useSearchParams();
return <GoodbyeContent purgeAfter={searchParams.get("purge_after")} />;
}
function GoodbyeContent({ purgeAfter }: { purgeAfter: string | null }) {
// The clock, read once and kept: whether the deadline has passed is a question
// about the moment this page opened, and re-reading it on every render would make
// the copy depend on when React happened to re-render rather than on the date.
const [loadedAt] = useState(() => Date.now());
// An unparseable value reads as no value at all. Anything could have edited the
// URL, and a date that renders as "Invalid Date" on the screen someone is reading
// for the one fact it carries is worse than a screen that admits it hasn't got it.
const purgeOn = parsePurgeDate(purgeAfter);
// Whether the deadline is behind us, which is all this can honestly claim to know.
// Two different visitors reach it: an instance running
// `ACCOUNT_DELETION_GRACE_DAYS=0`, whose `purge_after` is already past when the
// response is composed, and anyone reloading, bookmarking or going Back to this URL
// after their own window ran out. An elapsed timestamp cannot tell those apart, so
// the copy below talks about the date rather than about how the instance is
// configured.
const deadlinePassed = purgeOn !== null && purgeOn.getTime() <= loadedAt;
return (
<StandaloneShell className="text-center">
<StandaloneCard>
{deadlinePassed ? (
<StandaloneCardHeader
icon={Trash2}
iconClassName="text-muted-foreground"
title="Your account has been deleted"
description="The date it was due to be erased on has passed, so your dives, dive drafts, dive sites, trips, courses, certifications, gear, contacts, people and tags are no longer recoverable."
/>
) : (
<>
<StandaloneCardHeader
icon={CalendarClock}
iconClassName="text-muted-foreground"
title="Your account has been deleted"
description={
purgeOn ? (
<>
You're signed out everywhere, and nothing has been
erased yet. Your dives, dive drafts, dive sites, trips,
courses, certifications, gear, contacts, people and tags
will be permanently erased on{" "}
<span className="font-medium text-foreground">
{formatPurgeDay(purgeOn)}
</span>
.
</>
) : (
"You're signed out everywhere, and nothing has been erased yet. The confirmation email we've just sent you names the date everything is permanently erased on."
)
}
/>
{/* The way back, in the same words as the confirmation email and the
Danger Zone card - all three describe one behaviour, so reword them
together. Signing in reaches an offer to restore, not a session. */}
<p className="text-sm text-muted-foreground">
Deleted by mistake? Sign in again before{" "}
{purgeOn ? "that date" : "the date in that email"} and you'll
be offered your account back. Afterwards, nothing can be restored.
</p>
</>
)}
</StandaloneCard>
<p className="text-sm text-muted-foreground mt-8">
Thanks for diving with us.{" "}
<Link href="/" className="text-primary hover:underline">
Back to the home page
</Link>
</p>
</StandaloneShell>
);
}