111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
} from "@/components/ui/card"
|
|
import { ServerPicker } from "@/components/mobile/server-picker"
|
|
import { nativeStartLogin } from "@/lib/auth/native-auth"
|
|
import { getRuntimeConfig } from "@/lib/runtime-config"
|
|
import { getAuthentikEnrollmentUrl } from "@/lib/auth/oidc-config"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const CARD_CLASS = cn(
|
|
"w-full gap-4 border-0 bg-transparent px-4 py-6 shadow-none",
|
|
"sm:gap-5 sm:bg-card sm:px-8 sm:py-8"
|
|
)
|
|
|
|
/** Native login experience: server picker, then OIDC PKCE via the system browser. */
|
|
export function NativeLogin({ returnTo }: { returnTo: string }) {
|
|
const router = useRouter()
|
|
const [hasConfig, setHasConfig] = useState(() => Boolean(getRuntimeConfig()))
|
|
const [busy, setBusy] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
if (!hasConfig) {
|
|
return <ServerPicker onSelected={() => setHasConfig(true)} />
|
|
}
|
|
|
|
const cfg = getRuntimeConfig()
|
|
|
|
async function login() {
|
|
setBusy(true)
|
|
setError(null)
|
|
try {
|
|
await nativeStartLogin({ returnTo })
|
|
router.replace(returnTo.startsWith("/") ? returnTo : "/mail/inbox")
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Connexion échouée.")
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-1 flex-col items-center justify-center px-4">
|
|
<div className="mx-auto w-full max-w-sm">
|
|
<Card className={CARD_CLASS}>
|
|
<CardHeader className="gap-3 px-0 text-center">
|
|
<div className="flex flex-col items-center gap-3 py-2">
|
|
<img
|
|
src="/ultisuite-mark.svg"
|
|
alt=""
|
|
width={64}
|
|
height={64}
|
|
className="h-14 w-14 select-none"
|
|
aria-hidden
|
|
/>
|
|
<span className="text-xl font-bold tracking-tight">
|
|
Ulti<span className="text-[#4285F4]">Suite</span>
|
|
</span>
|
|
</div>
|
|
<CardDescription>
|
|
{cfg ? `Connecté à ${cfg.label}` : "Connecte-toi à ton compte Ulti."}
|
|
</CardDescription>
|
|
{error ? (
|
|
<p className="text-sm text-destructive" role="alert">
|
|
{error}
|
|
</p>
|
|
) : null}
|
|
</CardHeader>
|
|
|
|
<CardContent className="flex flex-col items-center gap-3 px-0">
|
|
<Button size="lg" className="w-full" disabled={busy} onClick={() => void login()}>
|
|
{busy ? "Connexion…" : "Se connecter"}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
className="w-full"
|
|
disabled={busy}
|
|
onClick={() => setHasConfig(false)}
|
|
>
|
|
Changer de serveur
|
|
</Button>
|
|
</CardContent>
|
|
|
|
<CardFooter className="px-0">
|
|
<p className="w-full text-center text-sm text-muted-foreground">
|
|
Pas encore de compte ?{" "}
|
|
<a
|
|
className="font-medium text-primary underline"
|
|
href={getAuthentikEnrollmentUrl()}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
Créer un compte
|
|
</a>
|
|
</p>
|
|
</CardFooter>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|