| import { useEffect, useState } from 'react' |
| import { |
| ArrowRight, |
| Eye, |
| EyeOff, |
| Globe, |
| Loader2, |
| LockKeyhole, |
| Sparkles, |
| UserRound, |
| } from 'lucide-react' |
| import Modal from './Modal' |
| import Button from './Button' |
|
|
| export default function AuthDialogPanel({ |
| open, |
| mode, |
| onModeChange, |
| onClose, |
| onLogin, |
| onRegister, |
| onGoogleLogin, |
| pending, |
| }) { |
| const [name, setName] = useState('') |
| const [username, setUsername] = useState('') |
| const [password, setPassword] = useState('') |
| const [error, setError] = useState('') |
| const [showPassword, setShowPassword] = useState(false) |
|
|
| const isRegister = mode === 'register' |
| const submitLabel = isRegister ? 'Create account' : 'Sign in' |
|
|
| useEffect(() => { |
| setError('') |
| setShowPassword(false) |
| }, [mode, open]) |
|
|
| const submit = async (event) => { |
| event.preventDefault() |
| setError('') |
|
|
| if (!username || !password || (isRegister && !name.trim())) { |
| setError('Fill all fields.') |
| return |
| } |
|
|
| try { |
| if (isRegister) { |
| await onRegister({ username, name: name.trim(), password }) |
| } else { |
| await onLogin({ username, password }) |
| } |
| } catch (authError) { |
| setError(authError.message || 'Authentication failed.') |
| } |
| } |
|
|
| return ( |
| <Modal |
| open={open} |
| onOpenChange={(next) => { |
| if (!next) onClose() |
| }} |
| size="sm" |
| title={isRegister ? 'Create account' : 'Sign in'} |
| className="auth-panel border-border bg-card" |
| > |
| <div className="relative space-y-5 overflow-hidden"> |
| <div className="auth-sweep" /> |
| |
| <div className="relative flex justify-center"> |
| <div className="flex h-11 w-11 items-center justify-center rounded-lg bg-secondary text-muted-foreground"> |
| <Sparkles className="h-5 w-5" /> |
| </div> |
| </div> |
| |
| <div className="relative grid grid-cols-2 gap-1 rounded-lg bg-panel p-1"> |
| <button |
| type="button" |
| onClick={() => onModeChange('login')} |
| className={`rounded-lg px-4 py-2 text-sm font-medium transition ${ |
| mode === 'login' |
| ? 'bg-background text-foreground' |
| : 'text-muted-foreground hover:text-foreground' |
| }`} |
| > |
| Login |
| </button> |
| <button |
| type="button" |
| onClick={() => onModeChange('register')} |
| className={`rounded-lg px-4 py-2 text-sm font-medium transition ${ |
| mode === 'register' |
| ? 'bg-background text-foreground' |
| : 'text-muted-foreground hover:text-foreground' |
| }`} |
| > |
| Register |
| </button> |
| </div> |
| |
| <Button |
| variant="secondary" |
| className="relative h-11 w-full justify-between" |
| onClick={onGoogleLogin} |
| disabled={pending} |
| > |
| <span className="inline-flex items-center gap-2"> |
| <Globe className="h-4 w-4" /> |
| Google |
| </span> |
| <ArrowRight className="h-4 w-4" /> |
| </Button> |
| |
| <form className="relative space-y-3" onSubmit={submit}> |
| {isRegister ? ( |
| <AuthField |
| icon={UserRound} |
| label="Name" |
| value={name} |
| onChange={setName} |
| placeholder="Your name" |
| autoComplete="name" |
| /> |
| ) : null} |
| |
| <AuthField |
| icon={UserRound} |
| label="Username" |
| value={username} |
| onChange={setUsername} |
| placeholder="Username" |
| autoComplete="username" |
| autoFocus |
| /> |
| |
| <label className="block space-y-1.5"> |
| <span className="text-sm font-medium text-foreground">Password</span> |
| <div className="flex items-center gap-3 rounded-lg border border-input bg-panel px-3 py-2.5 transition focus-within:border-accent/50 focus-within:ring-2 focus-within:ring-ring/20"> |
| <LockKeyhole className="h-4 w-4 shrink-0 text-muted-foreground" /> |
| <input |
| type={showPassword ? 'text' : 'password'} |
| className="w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground" |
| value={password} |
| onChange={(event) => setPassword(event.target.value)} |
| placeholder="Password" |
| autoComplete={isRegister ? 'new-password' : 'current-password'} |
| /> |
| <button |
| type="button" |
| onClick={() => setShowPassword((current) => !current)} |
| className="rounded-lg p-1 text-muted-foreground transition hover:bg-secondary hover:text-foreground" |
| aria-label={showPassword ? 'Hide password' : 'Show password'} |
| > |
| {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} |
| </button> |
| </div> |
| </label> |
| |
| {error ? ( |
| <div className="rounded-lg border border-danger/25 bg-danger/10 px-3 py-2 text-sm text-danger"> |
| {error} |
| </div> |
| ) : null} |
| |
| <Button type="submit" variant="primary" className="h-11 w-full" loading={pending}> |
| {pending ? <Loader2 className="h-4 w-4 animate-spin" /> : null} |
| {submitLabel} |
| </Button> |
| </form> |
| </div> |
| </Modal> |
| ) |
| } |
|
|
| function AuthField({ |
| icon: Icon, |
| label, |
| value, |
| onChange, |
| placeholder, |
| autoComplete, |
| autoFocus = false, |
| }) { |
| return ( |
| <label className="block space-y-1.5"> |
| <span className="text-sm font-medium text-foreground">{label}</span> |
| <div className="flex items-center gap-3 rounded-lg border border-input bg-panel px-3 py-2.5 transition focus-within:border-accent/50 focus-within:ring-2 focus-within:ring-ring/20"> |
| <Icon className="h-4 w-4 shrink-0 text-muted-foreground" /> |
| <input |
| className="w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground" |
| value={value} |
| onChange={(event) => onChange(event.target.value)} |
| placeholder={placeholder} |
| autoComplete={autoComplete} |
| autoFocus={autoFocus} |
| /> |
| </div> |
| </label> |
| ) |
| } |
|
|