| "use client"; |
|
|
| import * as React from "react"; |
| import { useRouter, usePathname } from "next/navigation"; |
| import { Crown, Lock, ShieldAlert } from "lucide-react"; |
| import { Button } from "@/components/atoms/Button"; |
| import { Icon } from "@/components/atoms/Icon"; |
| import { Spinner } from "@/components/atoms/Spinner"; |
|
|
| |
| |
| |
| |
| |
| export function AuthGuard({ children }: { children: React.ReactNode }) { |
| const router = useRouter(); |
| const pathname = usePathname(); |
| |
| const [authState, setAuthState] = React.useState<{ |
| isAuthorized: boolean; |
| isPremium: boolean; |
| }>({ isAuthorized: false, isPremium: false }); |
|
|
| React.useEffect(() => { |
| const token = localStorage.getItem("token"); |
|
|
| |
| if (!token) { |
| router.push(`/login?callbackUrl=${encodeURIComponent(pathname)}`); |
| return; |
| } |
|
|
| try { |
| |
| const payload = JSON.parse(window.atob(token.split(".")[1])); |
| const isExpired = Math.floor(Date.now() / 1000) >= payload.exp; |
|
|
| if (isExpired) { |
| localStorage.removeItem("token"); |
| router.push(`/login?error=session_expired`); |
| return; |
| } |
|
|
| |
| setAuthState({ |
| isAuthorized: true, |
| isPremium: payload.is_premium === true, |
| }); |
| } catch (err) { |
| localStorage.removeItem("token"); |
| router.push("/login?error=invalid_session"); |
| } |
| }, [router, pathname]); |
|
|
| |
| if (!authState.isAuthorized) { |
| return ( |
| <div className="flex h-screen w-screen items-center justify-center bg-background"> |
| <div className="flex flex-col items-center gap-4"> |
| <Spinner size={32} /> |
| <p className="text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground animate-pulse"> |
| Secure Session Handshake |
| </p> |
| </div> |
| </div> |
| ); |
| } |
|
|
| |
| const isProRoute = pathname.startsWith("/editor"); |
| if (isProRoute && !authState.isPremium) { |
| return ( |
| <div className="flex h-screen w-screen flex-col items-center justify-center p-6 text-center"> |
| <div className="mb-6 flex h-20 w-20 items-center justify-center rounded-2xl bg-amber-500/10 text-amber-600 border border-amber-500/20 shadow-inner"> |
| <Icon icon={Crown} size={40} /> |
| </div> |
| <h2 className="text-2xl font-bold tracking-tight">Pro Access Required</h2> |
| <p className="mt-2 max-w-sm text-sm text-muted-foreground leading-relaxed"> |
| The **WriteSage** manuscript engine requires a Pro subscription. |
| Upgrade your tier to unlock AI-assisted drafting. |
| </p> |
| <div className="mt-8 flex gap-3"> |
| <Button variant="outline" onClick={() => router.push("/explore")}> |
| Return to Discovery |
| </Button> |
| <Button onClick={() => router.push("/settings")}> |
| View Tiers |
| </Button> |
| </div> |
| </div> |
| ); |
| } |
|
|
| return <>{children}</>; |
| } |
|
|