212 lines
9.2 KiB
TypeScript
212 lines
9.2 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { createClient } from '@/lib/supabase/client'
|
|
import { Eye, EyeOff, HelpCircle, Shield } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
export default function LoginPage() {
|
|
const router = useRouter()
|
|
const [username, setUsername] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [showPassword, setShowPassword] = useState(false)
|
|
const [rememberMe, setRememberMe] = useState(false)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
const supabase = createClient()
|
|
|
|
// Supabase Auth utilise l'email — on convertit le username en email fictif
|
|
// ou on utilise l'email directement selon la configuration
|
|
const email = username.includes('@') ? username : `${username}@solyquiz.local`
|
|
|
|
const { error: authError } = await supabase.auth.signInWithPassword({
|
|
email,
|
|
password,
|
|
})
|
|
|
|
if (authError) {
|
|
setError('Identifiants incorrects. Vérifiez votre nom d\'utilisateur et mot de passe.')
|
|
setLoading(false)
|
|
return
|
|
}
|
|
|
|
router.push('/dashboard')
|
|
router.refresh()
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background flex flex-col">
|
|
{/* Header */}
|
|
<header className="flex items-center justify-between px-10 py-5 border-b border-border">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-9 h-9 bg-primary rounded-lg flex items-center justify-center shadow-lg shadow-primary/30">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z" fill="white"/>
|
|
</svg>
|
|
</div>
|
|
<span className="text-xl font-bold text-text-primary">SolyQuiz</span>
|
|
</div>
|
|
<button className="text-text-secondary hover:text-text-primary transition-colors">
|
|
<HelpCircle size={20} />
|
|
</button>
|
|
</header>
|
|
|
|
{/* Background decorations */}
|
|
<div className="flex-1 relative overflow-hidden flex items-center justify-center">
|
|
<div className="absolute w-96 h-96 rounded-full bg-primary/5 blur-3xl -left-20 top-20" />
|
|
<div className="absolute w-96 h-96 rounded-full bg-blue-500/5 blur-3xl right-20 bottom-20" />
|
|
|
|
{/* Login Card */}
|
|
<div className="relative z-10 w-full max-w-md mx-4">
|
|
<div className="card overflow-hidden shadow-2xl shadow-black/40">
|
|
{/* Card header gradient */}
|
|
<div className="relative h-32 bg-gradient-to-br from-primary/80 to-blue-600/60 flex items-center justify-center overflow-hidden">
|
|
<div className="absolute w-48 h-48 rounded-full bg-white/10 blur-2xl -right-10 -top-10" />
|
|
<div className="absolute w-40 h-40 rounded-full bg-white/5 blur-xl -left-10 bottom-0" />
|
|
<div className="relative z-10 w-16 h-18 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl flex items-center justify-center p-3">
|
|
<svg width="36" height="44" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z" fill="white"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Card body */}
|
|
<div className="p-8">
|
|
<div className="text-center mb-8">
|
|
<h1 className="text-2xl font-bold text-text-primary mb-1">Espace Formateur</h1>
|
|
<p className="text-text-secondary text-sm">
|
|
Connectez-vous pour gérer vos quiz et évaluations.
|
|
</p>
|
|
</div>
|
|
|
|
<form onSubmit={handleLogin} className="space-y-5">
|
|
{/* Username */}
|
|
<div>
|
|
<label className="block text-xs font-medium text-text-secondary uppercase tracking-wider mb-1.5 ml-1">
|
|
Nom d'utilisateur
|
|
</label>
|
|
<div className="relative">
|
|
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-text-muted">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
<circle cx="12" cy="7" r="4" />
|
|
</svg>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
placeholder="ex: formateur.dupont"
|
|
className="input-field pl-10"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Password */}
|
|
<div>
|
|
<label className="block text-xs font-medium text-text-secondary uppercase tracking-wider mb-1.5 ml-1">
|
|
Mot de passe
|
|
</label>
|
|
<div className="relative">
|
|
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-text-muted">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
</svg>
|
|
</div>
|
|
<input
|
|
type={showPassword ? 'text' : 'password'}
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="••••••••"
|
|
className="input-field pl-10 pr-10"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-muted hover:text-text-secondary transition-colors"
|
|
>
|
|
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Remember me + forgot password */}
|
|
<div className="flex items-center justify-between">
|
|
<label className="flex items-center gap-2 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={rememberMe}
|
|
onChange={(e) => setRememberMe(e.target.checked)}
|
|
className="w-4 h-4 rounded border-border bg-background-elevated text-primary"
|
|
/>
|
|
<span className="text-sm text-text-secondary">Rester connecté</span>
|
|
</label>
|
|
<button type="button" className="text-sm text-primary hover:text-primary-light transition-colors">
|
|
Mot de passe oublié ?
|
|
</button>
|
|
</div>
|
|
|
|
{/* Error message */}
|
|
{error && (
|
|
<div className="bg-red-500/10 border border-red-500/30 text-red-400 text-sm px-4 py-3 rounded-lg">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Submit */}
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className={cn(
|
|
'btn-primary w-full justify-center py-3',
|
|
loading && 'opacity-70 cursor-not-allowed'
|
|
)}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<svg className="animate-spin w-4 h-4" viewBox="0 0 24 24" fill="none">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
</svg>
|
|
Connexion...
|
|
</>
|
|
) : (
|
|
'Se connecter'
|
|
)}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Card footer */}
|
|
<div className="border-t border-border px-8 py-4">
|
|
<p className="text-center text-xs text-text-muted">
|
|
En continuant, vous acceptez les{' '}
|
|
<button className="text-primary hover:text-primary-light transition-colors">
|
|
Conditions d'utilisation
|
|
</button>{' '}
|
|
de SolyQuiz.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Secure connection note */}
|
|
<div className="flex items-center justify-center gap-1.5 mt-4">
|
|
<Shield size={14} className="text-text-muted" />
|
|
<span className="text-xs text-text-muted">Connexion sécurisée par SSL</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|