Next.js disallows cookie writes inside Server Components, so the handoff
exchange had to move to a Route Handler. But a route.ts cannot coexist
with a sibling page.tsx in the same segment, so the handler now lives at
/activate/handoff/[token]/route.ts and the page.tsx at /activate/[token]
becomes a stale-link error page.
Additional fixes:
- Use an explicit relative Location header (Location: /) on the success
redirect instead of NextResponse.redirect(new URL("/", req.url)). In
dev, req.url resolves to the canonical host and can differ from the
host the user came in on (127.0.0.1 vs localhost), causing cookies set
on the response to drop on cross-host follow.
- Same fix for the error redirect to /activate/error.
- New /activate/error/page.tsx renders the failure UI from the ?reason=
query string.
- /activate/[token]/route.ts (the original location) is preserved as
route.ts.disabled so Next does not register it and the prior segment's
page.tsx can take over the error UI for stale links.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
31 lines
1.3 KiB
TypeScript
31 lines
1.3 KiB
TypeScript
import Link from "next/link"
|
|
import { AlertCircle, ShieldCheck } from "lucide-react"
|
|
|
|
export default async function ActivateErrorPage(props: {
|
|
searchParams: Promise<{ reason?: string }>
|
|
}) {
|
|
const { reason } = await props.searchParams
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 p-6">
|
|
<div className="max-w-md w-full text-center space-y-3 bg-white rounded-xl shadow-sm border p-8">
|
|
<AlertCircle className="size-12 mx-auto text-rose-500" />
|
|
<h1 className="text-lg font-semibold">Activation failed</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
{reason ?? "Could not exchange the handoff token."}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground pt-2 flex items-center justify-center gap-1.5">
|
|
<ShieldCheck className="size-3.5" />
|
|
Re-open the garage from the SaaS dashboard to get a fresh link.
|
|
</p>
|
|
<Link
|
|
href={process.env.NEXT_PUBLIC_SAAS_URL ?? "/login"}
|
|
className="inline-block text-sm underline text-primary"
|
|
>
|
|
Back to SaaS
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|