ultisuite-client/components/auth/session-guard.tsx
R3D347HR4Y d6d18f911b
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
Lots of stuff and mobile app
2026-06-17 00:13:28 +02:00

114 lines
3.8 KiB
TypeScript

"use client"
import { useCallback, useEffect } from "react"
import { usePathname, useRouter } from "next/navigation"
import { Icon } from "@iconify/react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { useSessionGuardStore } from "@/lib/auth/session-guard-store"
import { tryRefreshSession } from "@/lib/auth/session-sync"
import { isAuthPublicPath } from "@/lib/auth/public-paths"
import { useNativeRuntime } from "@/lib/platform"
import { useAuthStore } from "@/lib/api/auth-store"
import { cn } from "@/lib/utils"
function isPublicPath(pathname: string) {
return isAuthPublicPath(pathname)
}
export function SessionGuard() {
const pathname = usePathname()
const router = useRouter()
const native = useNativeRuntime()
const authenticated = useAuthStore((s) => Boolean(s.accessToken))
const status = useSessionGuardStore((s) => s.status)
const returnTo = pathname.startsWith("/") ? pathname : "/mail/inbox"
const loginHref = native
? `/login?returnTo=${encodeURIComponent(returnTo)}`
: `/api/auth/login?returnTo=${encodeURIComponent(returnTo)}`
const retrySession = useCallback(async () => {
if (typeof navigator !== "undefined" && !navigator.onLine) return
await tryRefreshSession()
}, [])
useEffect(() => {
if (status !== "offline") return
const onOnline = () => {
void retrySession()
}
window.addEventListener("online", onOnline)
return () => window.removeEventListener("online", onOnline)
}, [status, retrySession])
if (isPublicPath(pathname)) return null
// NativeAuthGate handles login — don't block touches with an expired modal.
if (native && !authenticated) return null
return (
<>
<div
className={cn(
"pointer-events-none fixed inset-x-0 top-0 z-[60] overflow-hidden transition-all duration-300",
status === "offline" ? "max-h-10 opacity-100" : "max-h-0 opacity-0"
)}
aria-live="polite"
>
<div className="pointer-events-auto flex h-10 items-center justify-center gap-2 bg-amber-50 px-4 text-xs font-medium text-amber-900 dark:bg-amber-950/60 dark:text-amber-100">
<Icon icon="mdi:wifi-off" className="size-3.5 shrink-0" />
<span>Pas de connexion internet session non vérifiable pour le moment.</span>
<Button
type="button"
variant="outline"
size="sm"
className="h-7 border-amber-300 bg-transparent text-xs dark:border-amber-800"
onClick={() => void retrySession()}
>
Réessayer
</Button>
</div>
</div>
<AlertDialog open={status === "expired"}>
<AlertDialogContent
onEscapeKeyDown={(event) => event.preventDefault()}
onPointerDownOutside={(event) => event.preventDefault()}
>
<AlertDialogHeader>
<AlertDialogTitle>Vous avez é déconnecté</AlertDialogTitle>
<AlertDialogDescription>
Votre session a expiré ou n&apos;est plus valide. Reconnectez-vous
pour continuer à utiliser Ultimail.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction asChild>
{native ? (
<Button
type="button"
onClick={() => router.replace(loginHref)}
>
Se reconnecter
</Button>
) : (
<a href={loginHref}>Se reconnecter</a>
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}