| import { useState } from "react"; |
| import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; |
| import { Button } from "@/components/ui/button"; |
| import { Input } from "@/components/ui/input"; |
| import { Label } from "@/components/ui/label"; |
| import { Sparkles, Loader2, Eye, EyeOff } from "lucide-react"; |
| import { useAuth } from "@/contexts/AuthContext"; |
| import { useLang } from "@/contexts/LanguageContext"; |
|
|
| interface AuthModalProps { |
| open: boolean; |
| onOpenChange: (open: boolean) => void; |
| defaultTab?: "signin" | "signup"; |
| } |
|
|
| export function AuthModal({ open, onOpenChange, defaultTab = "signin" }: AuthModalProps) { |
| const { t } = useLang(); |
| const { signIn, signUp } = useAuth(); |
| const [tab, setTab] = useState<"signin" | "signup">(defaultTab); |
| const [email, setEmail] = useState(""); |
| const [password, setPassword] = useState(""); |
| const [displayName, setDisplayName] = useState(""); |
| const [showPassword, setShowPassword] = useState(false); |
| const [loading, setLoading] = useState(false); |
| const [error, setError] = useState(""); |
|
|
| const reset = () => { |
| setEmail(""); |
| setPassword(""); |
| setDisplayName(""); |
| setError(""); |
| setShowPassword(false); |
| }; |
|
|
| const switchTab = (next: "signin" | "signup") => { |
| setTab(next); |
| setError(""); |
| }; |
|
|
| const handleSubmit = async (e: React.FormEvent) => { |
| e.preventDefault(); |
| setError(""); |
| setLoading(true); |
| try { |
| if (tab === "signin") { |
| await signIn(email, password); |
| } else { |
| await signUp(email, password, displayName); |
| } |
| reset(); |
| onOpenChange(false); |
| } catch (err: unknown) { |
| setError(err instanceof Error ? err.message : t.authError); |
| } finally { |
| setLoading(false); |
| } |
| }; |
|
|
| const handleOpenChange = (next: boolean) => { |
| if (!next) reset(); |
| onOpenChange(next); |
| }; |
|
|
| return ( |
| <Dialog open={open} onOpenChange={handleOpenChange}> |
| <DialogContent className="sm:max-w-[400px] bg-card border-border"> |
| <DialogHeader> |
| <DialogTitle className="flex items-center gap-2 text-foreground"> |
| <div className="w-7 h-7 rounded-lg bg-primary/15 border border-primary/30 flex items-center justify-center"> |
| <Sparkles className="w-4 h-4 text-primary" /> |
| </div> |
| {t.appName} |
| </DialogTitle> |
| </DialogHeader> |
| |
| <div className="flex rounded-lg overflow-hidden border border-border/60 mb-4"> |
| {(["signin", "signup"] as const).map((t_) => ( |
| <button |
| key={t_} |
| type="button" |
| onClick={() => switchTab(t_)} |
| className={`flex-1 py-2 text-sm font-medium transition-colors ${ |
| tab === t_ |
| ? "bg-primary text-primary-foreground" |
| : "text-muted-foreground hover:text-foreground hover:bg-secondary" |
| }`} |
| > |
| {t_ === "signin" ? t.authSignIn : t.authSignUp} |
| </button> |
| ))} |
| </div> |
| |
| <form onSubmit={handleSubmit} className="space-y-4"> |
| {tab === "signup" && ( |
| <div className="space-y-1.5"> |
| <Label className="text-sm text-muted-foreground">{t.authDisplayName}</Label> |
| <Input |
| value={displayName} |
| onChange={(e) => setDisplayName(e.target.value)} |
| placeholder={t.authDisplayNamePlaceholder} |
| className="bg-background/50" |
| maxLength={50} |
| /> |
| </div> |
| )} |
| |
| <div className="space-y-1.5"> |
| <Label className="text-sm text-muted-foreground">{t.authEmail}</Label> |
| <Input |
| type="email" |
| value={email} |
| onChange={(e) => setEmail(e.target.value)} |
| placeholder={t.authEmailPlaceholder} |
| className="bg-background/50" |
| required |
| autoFocus |
| /> |
| </div> |
| |
| <div className="space-y-1.5"> |
| <Label className="text-sm text-muted-foreground">{t.authPassword}</Label> |
| <div className="relative"> |
| <Input |
| type={showPassword ? "text" : "password"} |
| value={password} |
| onChange={(e) => setPassword(e.target.value)} |
| placeholder={tab === "signup" ? t.authPasswordMinHint : t.authPasswordPlaceholder} |
| className="bg-background/50 pr-10" |
| required |
| minLength={tab === "signup" ? 6 : undefined} |
| /> |
| <button |
| type="button" |
| tabIndex={-1} |
| onClick={() => setShowPassword((v) => !v)} |
| className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" |
| > |
| {showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />} |
| </button> |
| </div> |
| </div> |
| |
| {error && ( |
| <p className="text-sm text-destructive bg-destructive/10 rounded-lg px-3 py-2">{error}</p> |
| )} |
| |
| <Button type="submit" className="w-full" disabled={loading}> |
| {loading ? ( |
| <><Loader2 className="mr-2 h-4 w-4 animate-spin" />{t.authLoading}</> |
| ) : ( |
| tab === "signin" ? t.authSignIn : t.authSignUp |
| )} |
| </Button> |
| |
| <p className="text-center text-xs text-muted-foreground"> |
| {tab === "signin" ? ( |
| <>{t.authNoAccount}{" "} |
| <button type="button" onClick={() => switchTab("signup")} className="text-primary hover:underline font-medium"> |
| {t.authSignUp} |
| </button> |
| </> |
| ) : ( |
| <>{t.authHasAccount}{" "} |
| <button type="button" onClick={() => switchTab("signin")} className="text-primary hover:underline font-medium"> |
| {t.authSignIn} |
| </button> |
| </> |
| )} |
| </p> |
| </form> |
| </DialogContent> |
| </Dialog> |
| ); |
| } |
|
|