Skip to content

Commit

Confirm page: its own frame, and the result shown after the code works

Seen in a browser against local identity and site: /confirm-email drew the app's sidebar for an account with nothing to show in it (it is now in the marketing frame, like /verify), and a right code left the button spinning, because the page's loader redirected away once the account was confirmed. The loader now answers "confirmed" instead, so the page shows what confirming did (the workspace joined, or why the invite lapsed) with Continue.

syntaqxcommitted Parent6db9fe3Browse files
3 files+11−80/3 viewed
+1−1
2525 });
2626
2727 test("a visitor gets the marketing frame on the front, pricing and sign-in pages", () => {
28− for (const path of ["/", "/pricing", "/pricing/", "/login", "/register", "/verify", "/forgot", "/reset", "/device", "/oauth/authorize"]) {
28+ for (const path of ["/", "/pricing", "/pricing/", "/login", "/register", "/verify", "/confirm-email", "/forgot", "/reset", "/device", "/oauth/authorize"]) {
2929 assert.equal(usesAppShell(path, false), false, path);
3030 }
3131 });
+1−0
1919 "/register",
2020 "/logout",
2121 "/verify",
22+ "/confirm-email",
2223 "/forgot",
2324 "/reset",
2425 "/device",
+9−7
11 import { CircleCheck, MailCheck } from "lucide-react";
2−import { Form, data, redirect } from "react-router";
2+import { Form, data } from "react-router";
33
44 import { CONFIRM_TTL_SECONDS, tidyConfirmCode } from "@g1t/contracts";
55
2727 export async function loader({ request, context }: Route.LoaderArgs) {
2828 const user = requireUser(context, request);
2929 const next = safeNext(new URL(request.url).searchParams.get("next"));
30− if (user.verified) throw redirect(next);
30+ // Confirmed already: from a bookmark, or the page loading again right
31+ // after the code worked, when it shows what that did.
32+ if (user.verified) return { username: user.username, address: null, next, verified: true };
3133 const emails = await accounts.listEmails(user);
3234 const list = emails.ok ? emails.value.emails : [];
3335 // The address the code went to: the primary, or (when another account
3436 // confirmed that first) the oldest address still to confirm.
3537 const address = (list.find((email) => email.primary && !email.verified) ?? list.find((email) => !email.verified))?.email ?? null;
36− return { username: user.username, address, next };
38+ return { username: user.username, address, next, verified: false };
3739 }
3840
3941 export async function action({ request, context }: Route.ActionArgs) {
7880 | undefined;
7981 const errorFor = (intent: string) => (said?.intent === intent ? (said.error ?? null) : null);
8082
81− if (said?.confirmed) {
83+ if (said?.confirmed || loaderData.verified) {
8284 return (
8385 <main className="mx-auto flex max-w-sm flex-col px-4 pt-20 pb-10">
8486 <CircleCheck size={36} className="text-success" aria-hidden="true" />
8587 <h1 className="mt-6 text-2xl font-semibold tracking-tight">Email confirmed</h1>
86− <p className={`mt-2 text-sm leading-6 ${said.lapsed ? "text-fg" : "text-muted"}`} role="status">
87− {said.line}
88+ <p className={`mt-2 text-sm leading-6 ${said?.lapsed ? "text-fg" : "text-muted"}`} role="status">
89+ {said?.line ?? "Your email address is confirmed."}
8890 </p>
8991 <div className="mt-8 *:w-full">
90− <ButtonLink to={said.to ?? "/"}>Continue</ButtonLink>
92+ <ButtonLink to={said?.to ?? loaderData.next}>Continue</ButtonLink>
9193 </div>
9294 </main>
9395 );