feat(story-1.5): add React frontend with auth pages and dark theme

This commit is contained in:
Michael committed 2026-02-04 14:48:41 +01:00
1 parent d67e169a43
commit dbea4b47af
23 files changed
+4373 -2

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
import { useNavigate } from 'react-router-dom';
import { useAuthStore } from '../stores/authStore';
import { authApi } from '../api/auth';
export function DashboardPage() {
const navigate = useNavigate();
const { user, logout } = useAuthStore();
const handleLogout = async () => {
await authApi.logout();
logout();
navigate('/login');
};
return (
<div className="dashboard">
<header className="dashboard-header">
<h1>FlowReader</h1>
<div className="user-info">
<span>{user?.email}</span>
<button onClick={handleLogout} className="logout-btn">
Déconnexion
</button>
</div>
</header>
<main className="dashboard-main">
<div className="empty-state">
<h2>🎉 Bienvenue !</h2>
<p>Votre lecteur RSS est prêt.</p>
<p className="hint">
Les fonctionnalités de flux seront disponibles dans l'Epic 2.
</p>
</div>
</main>
</div>
);
}
+84
View File
@@ -0,0 +1,84 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@tanstack/react-query';
import { authApi, ApiError } from '../api/auth';
import { useAuthStore } from '../stores/authStore';
export function LoginPage() {
const navigate = useNavigate();
const setUser = useAuthStore((state) => state.setUser);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const loginMutation = useMutation({
mutationFn: authApi.login,
onSuccess: (data) => {
setUser(data.user);
navigate('/');
},
onError: (err: Error) => {
if (err instanceof ApiError) {
setError(err.message);
} else {
setError('Login failed. Please try again.');
}
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError('');
loginMutation.mutate({ email, password });
};
return (
<div className="login-container">
<div className="login-card">
<h1>FlowReader</h1>
<p className="subtitle">Connectez-vous à votre compte</p>
<form onSubmit={handleSubmit}>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="vous@example.com"
required
autoFocus
/>
</div>
<div className="form-group">
<label htmlFor="password">Mot de passe</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
minLength={8}
/>
</div>
<button type="submit" disabled={loginMutation.isPending}>
{loginMutation.isPending ? 'Connexion...' : 'Se connecter'}
</button>
</form>
<p className="register-link">
Pas encore de compte ?{' '}
<a href="/register" onClick={(e) => { e.preventDefault(); navigate('/register'); }}>
S'inscrire
</a>
</p>
</div>
</div>
);
}
+101
View File
@@ -0,0 +1,101 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@tanstack/react-query';
import { authApi, ApiError } from '../api/auth';
export function RegisterPage() {
const navigate = useNavigate();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const registerMutation = useMutation({
mutationFn: authApi.register,
onSuccess: () => {
navigate('/login', { state: { message: 'Compte créé ! Vous pouvez vous connecter.' } });
},
onError: (err: Error) => {
if (err instanceof ApiError) {
setError(err.message);
} else {
setError('Registration failed. Please try again.');
}
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (password !== confirmPassword) {
setError('Les mots de passe ne correspondent pas');
return;
}
registerMutation.mutate({ email, password });
};
return (
<div className="login-container">
<div className="login-card">
<h1>FlowReader</h1>
<p className="subtitle">Créer un nouveau compte</p>
<form onSubmit={handleSubmit}>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="vous@example.com"
required
autoFocus
/>
</div>
<div className="form-group">
<label htmlFor="password">Mot de passe</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
minLength={8}
/>
</div>
<div className="form-group">
<label htmlFor="confirmPassword">Confirmer le mot de passe</label>
<input
id="confirmPassword"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="••••••••"
required
minLength={8}
/>
</div>
<button type="submit" disabled={registerMutation.isPending}>
{registerMutation.isPending ? 'Création...' : 'Créer mon compte'}
</button>
</form>
<p className="register-link">
Déjà un compte ?{' '}
<a href="/login" onClick={(e) => { e.preventDefault(); navigate('/login'); }}>
Se connecter
</a>
</p>
</div>
</div>
);
}