Panel admin, planning, tags, suivi du temps, thèmes clair/sombre

- Admin: création/suppression de comptes + rôles via Edge Function sécurisée
- Config Supabase intégrée au build (.env), écran Setup supprimé
- Page Planning (vue mois/semaine)
- Tags "jobs" sur les tâches + filtres (membre / tag)
- Suivi du temps par tâche, total par jalon
- Toasts + confirmations in-app (fini alert/confirm natifs)
- Thème Notion clair + mode sombre (accent monochrome, bascule persistée)
- .env suivi par git (public uniquement) pour synchro multi-machines

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 09:51:40 +02:00
parent 0806be135d
commit b054e36df0
30 changed files with 1991 additions and 411 deletions
+10 -63
View File
@@ -2,37 +2,19 @@ import { useState } from 'react'
import { motion } from 'framer-motion'
import { Gamepad2, Loader2 } from 'lucide-react'
import { supa } from '../lib/supabase'
import { Select } from '../components/ui'
import { MemberRole, ROLE_LABELS } from '../lib/types'
export default function Login(): JSX.Element {
const [mode, setMode] = useState<'signin' | 'signup'>('signin')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [fullName, setFullName] = useState('')
const [role, setRole] = useState<MemberRole>('programmer')
const [busy, setBusy] = useState(false)
const [error, setError] = useState('')
const [info, setInfo] = useState('')
const submit = async (): Promise<void> => {
setError('')
setInfo('')
setBusy(true)
try {
if (mode === 'signup') {
const { error } = await supa().auth.signUp({
email,
password,
options: { data: { full_name: fullName || email.split('@')[0], role } }
})
if (error) throw error
setInfo('Compte créé ! Tu peux maintenant te connecter.')
setMode('signin')
} else {
const { error } = await supa().auth.signInWithPassword({ email, password })
if (error) throw error
}
const { error } = await supa().auth.signInWithPassword({ email, password })
if (error) throw error
} catch (e) {
setError(translate((e as Error).message))
} finally {
@@ -52,36 +34,10 @@ export default function Login(): JSX.Element {
<div className="rounded-xl bg-accent/20 p-3 text-accent">
<Gamepad2 size={28} />
</div>
<h1 className="text-xl font-semibold text-white">GameDev Tracker</h1>
<p className="text-sm text-gray-400">
{mode === 'signin' ? 'Connecte-toi à ton équipe' : 'Crée ton compte membre'}
</p>
<h1 className="text-xl font-semibold text-ink">GameDev Tracker</h1>
<p className="text-sm text-muted">Connecte-toi à ton équipe</p>
</div>
{mode === 'signup' && (
<>
<label className="label">Nom complet</label>
<input
className="input mb-3"
placeholder="Alex Martin"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
/>
<label className="label">Rôle dans l'équipe</label>
<Select
className="mb-3"
value={role}
onChange={(e) => setRole(e.target.value as MemberRole)}
>
{Object.entries(ROLE_LABELS).map(([v, l]) => (
<option key={v} value={v}>
{l}
</option>
))}
</Select>
</>
)}
<label className="label">Email</label>
<input
className="input mb-3"
@@ -100,24 +56,16 @@ export default function Login(): JSX.Element {
onKeyDown={(e) => e.key === 'Enter' && submit()}
/>
{error && <p className="mb-3 text-sm text-rose-400">{error}</p>}
{info && <p className="mb-3 text-sm text-accent2">{info}</p>}
{error && <p className="mb-3 text-sm text-rose-600">{error}</p>}
<button className="btn-primary w-full" onClick={submit} disabled={busy}>
{busy && <Loader2 className="animate-spin" size={16} />}
{mode === 'signin' ? 'Se connecter' : "Créer le compte"}
Se connecter
</button>
<button
className="mt-4 w-full text-center text-sm text-gray-400 hover:text-white"
onClick={() => {
setMode(mode === 'signin' ? 'signup' : 'signin')
setError('')
setInfo('')
}}
>
{mode === 'signin' ? "Pas encore de compte ? S'inscrire" : 'Déjà un compte ? Se connecter'}
</button>
<p className="mt-4 text-center text-xs text-subtle">
Pas de compte ? Demande à ton administrateur de t'en créer un.
</p>
</motion.div>
</div>
)
@@ -125,7 +73,6 @@ export default function Login(): JSX.Element {
function translate(msg: string): string {
if (msg.includes('Invalid login')) return 'Email ou mot de passe incorrect.'
if (msg.includes('already registered')) return 'Cet email est déjà utilisé.'
if (msg.includes('at least 6')) return 'Le mot de passe doit faire au moins 6 caractères.'
if (msg.includes('Email not confirmed')) return "L'email n'a pas encore é confirmé."
return msg
}