Spaces:
Sleeping
Sleeping
| import { useState, useEffect, ReactNode } from "react"; | |
| import { trpc } from "@/lib/trpc"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { Lock, Loader2 } from "lucide-react"; | |
| const ACCESS_KEY_STORAGE = "access_key_verified"; | |
| export function AccessGate({ children }: { children: ReactNode }) { | |
| const [key, setKey] = useState(""); | |
| const [error, setError] = useState(""); | |
| const [verified, setVerified] = useState<boolean | null>(null); | |
| const accessKeyEnabled = trpc.auth.accessKeyEnabled.useQuery(); | |
| const verifyMutation = trpc.auth.verifyAccessKey.useMutation(); | |
| // Check if already verified in session | |
| useEffect(() => { | |
| if (accessKeyEnabled.data?.enabled === false) { | |
| setVerified(true); | |
| return; | |
| } | |
| const stored = sessionStorage.getItem(ACCESS_KEY_STORAGE); | |
| if (stored === "true") { | |
| setVerified(true); | |
| } else if (accessKeyEnabled.data?.enabled) { | |
| setVerified(false); | |
| } | |
| }, [accessKeyEnabled.data]); | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| setError(""); | |
| try { | |
| const result = await verifyMutation.mutateAsync({ key }); | |
| if (result.valid) { | |
| sessionStorage.setItem(ACCESS_KEY_STORAGE, "true"); | |
| setVerified(true); | |
| } else { | |
| setError("Invalid access key"); | |
| } | |
| } catch { | |
| setError("Failed to verify key"); | |
| } | |
| }; | |
| // Still loading | |
| if (accessKeyEnabled.isLoading || verified === null) { | |
| return ( | |
| <div className="min-h-screen flex items-center justify-center"> | |
| <Loader2 className="w-8 h-8 animate-spin text-primary" /> | |
| </div> | |
| ); | |
| } | |
| // Already verified or access key disabled | |
| if (verified) { | |
| return <>{children}</>; | |
| } | |
| // Show access key prompt | |
| return ( | |
| <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-50 via-white to-blue-50"> | |
| <Card className="w-full max-w-md mx-4"> | |
| <CardHeader className="text-center"> | |
| <div className="inline-flex items-center justify-center w-12 h-12 bg-primary/10 rounded-xl mx-auto mb-4"> | |
| <Lock className="w-6 h-6 text-primary" /> | |
| </div> | |
| <CardTitle>Access Required</CardTitle> | |
| <CardDescription> | |
| Enter the access key to continue | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <form onSubmit={handleSubmit} className="space-y-4"> | |
| <Input | |
| type="password" | |
| placeholder="Access key" | |
| value={key} | |
| onChange={(e) => setKey(e.target.value)} | |
| autoFocus | |
| /> | |
| {error && ( | |
| <p className="text-sm text-red-500">{error}</p> | |
| )} | |
| <Button | |
| type="submit" | |
| className="w-full" | |
| disabled={verifyMutation.isPending || !key} | |
| > | |
| {verifyMutation.isPending ? ( | |
| <Loader2 className="w-4 h-4 animate-spin mr-2" /> | |
| ) : null} | |
| Continue | |
| </Button> | |
| </form> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| } | |