fix: preserve destination through the login redirect
Auth gates redirected to a bare /login with no memory of where the user was headed, and the login page always sent them to /dashboard afterward — so opening the /m PWA while signed out landed you on the main dashboard instead of back on /m. Both gates now pass ?next=, and login honors it (falling back to /dashboard if missing/unsafe). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
2014814c84
commit
ac201cb0a1
5 changed files with 24 additions and 10 deletions
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useAuth } from "@/components/auth-provider";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { BrandStripe } from "@/components/brand-stripe";
|
||||
|
|
@ -9,12 +9,13 @@ import { BrandStripe } from "@/components/brand-stripe";
|
|||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
router.push("/login");
|
||||
router.push(`/login?next=${encodeURIComponent(pathname)}`);
|
||||
}
|
||||
}, [user, loading, router]);
|
||||
}, [user, loading, router, pathname]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useAuth } from "@/components/auth-provider";
|
||||
import { isSafeNextPath } from "@/lib/safe-next-path";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -67,6 +68,7 @@ const labelStyle = { color: BRAND.textSecondary };
|
|||
export default function AuthPage() {
|
||||
const { login } = useAuth();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [loginEmail, setLoginEmail] = useState("");
|
||||
|
|
@ -78,7 +80,8 @@ export default function AuthPage() {
|
|||
try {
|
||||
await login(loginEmail, loginPassword);
|
||||
toast.success("Logged in successfully");
|
||||
router.push("/dashboard");
|
||||
const next = searchParams.get("next");
|
||||
router.push(isSafeNextPath(next) ? next : "/dashboard");
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Login failed";
|
||||
toast.error(message);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
"use client";
|
||||
|
||||
import { useAuth } from "@/components/auth-provider";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
|
||||
export function MobileAuthGate({ children }: { children: ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
router.replace("/login");
|
||||
router.replace(`/login?next=${encodeURIComponent(pathname)}`);
|
||||
}
|
||||
}, [user, loading, router]);
|
||||
}, [user, loading, router, pathname]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export default function BossDashboardView() {
|
|||
const pb = getPocketBase();
|
||||
if (!pb.authStore?.isValid) {
|
||||
toast.error("Your session expired", { description: "Sign in again to continue." });
|
||||
router.replace("/login");
|
||||
router.replace(`/login?next=${encodeURIComponent("/m")}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ export default function BossDashboardView() {
|
|||
if (!res.ok) {
|
||||
if (res.status === 401) {
|
||||
toast.error("Your session expired", { description: "Sign in again to continue." });
|
||||
router.replace("/login");
|
||||
router.replace(`/login?next=${encodeURIComponent("/m")}`);
|
||||
return;
|
||||
}
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
|
|
|
|||
9
src/lib/safe-next-path.ts
Normal file
9
src/lib/safe-next-path.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Validates a `next` redirect target so login can send the user back
|
||||
* to where they came from without opening a redirect to an external
|
||||
* site (e.g. `?next=https://evil.example`). Only same-origin,
|
||||
* root-relative paths are allowed.
|
||||
*/
|
||||
export function isSafeNextPath(path: string | null | undefined): path is string {
|
||||
return !!path && path.startsWith("/") && !path.startsWith("//");
|
||||
}
|
||||
Loading…
Reference in a new issue