diff --git a/src/pages/login/login-page.test.tsx b/src/pages/login/login-page.test.tsx index 5050ee2..83e289c 100644 --- a/src/pages/login/login-page.test.tsx +++ b/src/pages/login/login-page.test.tsx @@ -41,4 +41,19 @@ describe("LoginPage", () => { }) expect(screen.getByText("Sign up")).toBeInTheDocument() }) + + it("preserves ?redirect= on the Sign up link", async () => { + await renderWithFileRoutes(<>, { + initialLocation: + "/login?redirect=https%3A%2F%2Fhera-streamer-invitational-2026.criticalbit.gg%2F", + routerContext: unauthContext, + }) + const link = await screen.findByRole("link", { name: "Sign up" }) + // TanStack Router serializes search params alphabetically and re-encodes — + // the important thing is that the redirect target survives the hop, not + // the exact encoding. + expect(link.getAttribute("href")).toMatch( + /^\/register\?redirect=.*hera-streamer-invitational-2026\.criticalbit\.gg/ + ) + }) }) diff --git a/src/pages/login/login-page.tsx b/src/pages/login/login-page.tsx index 696be0f..49d054a 100644 --- a/src/pages/login/login-page.tsx +++ b/src/pages/login/login-page.tsx @@ -177,7 +177,11 @@ export function LoginPage() {

Don't have an account?{" "} - + Sign up

diff --git a/src/pages/register/register-page.tsx b/src/pages/register/register-page.tsx index 8929138..b124d91 100644 --- a/src/pages/register/register-page.tsx +++ b/src/pages/register/register-page.tsx @@ -1,5 +1,5 @@ import { useState } from "react" -import { Link, useNavigate } from "@tanstack/react-router" +import { Link, useNavigate, useSearch } from "@tanstack/react-router" import { LoaderCircle } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" @@ -22,6 +22,8 @@ import { submitConsents, type ConsentInput } from "@/lib/consent" export function RegisterPage() { const auth = useAuth() const navigate = useNavigate() + const search = useSearch({ from: "/register" }) + const redirect = (search as { redirect?: string }).redirect const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [confirmPassword, setConfirmPassword] = useState("") @@ -70,9 +72,17 @@ export function RegisterPage() { // Swallow — user is signed up; they'll be prompted again on profile. } // Refresh the AuthContext (reads /auth/me with the new cookie) before - // navigating, so /profile's beforeLoad sees isAuthenticated=true. + // navigating, so the destination's beforeLoad sees isAuthenticated=true. await auth.checkAuth() - await navigate({ to: "/profile" }) + const target = redirect ?? "/profile" + if (target.startsWith("http")) { + // Cross-origin redirect (e.g. from a consumer app like + // hera-streamer-…) — must be a hard nav since useNavigate only + // handles in-app routes. + window.location.href = target + return + } + await navigate({ to: target }) } catch (error) { const message = await getErrorMessage(error, "Registration failed") toast.error(message) diff --git a/src/routes/register.tsx b/src/routes/register.tsx index ca76954..4abd3ef 100644 --- a/src/routes/register.tsx +++ b/src/routes/register.tsx @@ -1,7 +1,14 @@ import { createFileRoute, redirect } from "@tanstack/react-router" import { RegisterPage } from "@/pages/register/register-page" +type RegisterSearch = { + redirect?: string +} + export const Route = createFileRoute("/register")({ + validateSearch: (search: Record): RegisterSearch => ({ + redirect: (search.redirect as string) || undefined, + }), beforeLoad: ({ context }) => { if (context.auth.isAuthenticated) { throw redirect({ to: "/profile" })