Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/pages/login/login-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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/
)
})
})
6 changes: 5 additions & 1 deletion src/pages/login/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ export function LoginPage() {
<CardFooter className="justify-center">
<p className="text-muted-foreground text-sm">
Don't have an account?{" "}
<Link to="/register" className="text-primary underline">
<Link
to="/register"
search={redirect ? { redirect } : {}}
className="text-primary underline"
>
Sign up
</Link>
</p>
Expand Down
16 changes: 13 additions & 3 deletions src/pages/register/register-page.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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("")
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions src/routes/register.tsx
Original file line number Diff line number Diff line change
@@ -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<string, unknown>): RegisterSearch => ({
redirect: (search.redirect as string) || undefined,
}),
beforeLoad: ({ context }) => {
if (context.auth.isAuthenticated) {
throw redirect({ to: "/profile" })
Expand Down
Loading