feat: page 404 RPG + onboarding nouveau joueur
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s

- 404: têtard perdu dans les marais, boutons retour jeu + guide
- Onboarding: 4 étapes guidées (quêtes, combat, craft, guide)
  - Visible niv 1-3, dismissable, grille 2×2 avec CTA par étape
- DashboardPage: STAT_LABELS importé depuis constants.ts
This commit is contained in:
2026-03-24 23:17:09 +01:00
parent 17c61a2bb8
commit e769c27a42
4 changed files with 143 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ import { QuestPage } from './pages/QuestPage';
import { AchievementsPage } from './pages/AchievementsPage';
import { ShopPage } from './pages/ShopPage';
import { GuidePage } from './pages/GuidePage';
import { NotFoundPage } from './pages/NotFoundPage';
const qc = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 30_000 } } });
@@ -42,7 +43,7 @@ function AppRoutes() {
<Route path="/forge" element={<ProtectedLayout><ForgePage /></ProtectedLayout>} />
<Route path="/achievements" element={<ProtectedLayout><AchievementsPage /></ProtectedLayout>} />
<Route path="/shop" element={<ProtectedLayout><ShopPage /></ProtectedLayout>} />
<Route path="*" element={<Navigate to="/dashboard" replace />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
);
}

View File

@@ -0,0 +1,93 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Swords, Scroll, Hammer, BookOpen, ChevronRight } from 'lucide-react';
const STEPS = [
{
icon: Scroll,
title: 'Accepte une quête',
desc: 'Les quêtes te guident dans ton aventure et donnent le plus d\'XP. Commence par les quêtes des Marais.',
cta: 'Voir les quêtes',
to: '/quests',
},
{
icon: Swords,
title: 'Combats un monstre',
desc: 'Chaque combat coûte 5 endurance. Tu gagneras de l\'XP, de l\'or et des matériaux pour crafter.',
cta: 'Aller au combat',
to: '/combat',
},
{
icon: Hammer,
title: 'Craft ton équipement',
desc: 'Avec les matériaux droppés, fabrique des armes et armures plus puissantes que celles de la boutique.',
cta: 'Voir l\'artisanat',
to: '/craft',
},
{
icon: BookOpen,
title: 'Consulte le guide',
desc: 'Le guide contient tout : bestiaire, recettes, zones, forge. Accessible depuis le bouton 📖 en bas de la sidebar.',
cta: 'Ouvrir le guide',
to: '/guide',
},
];
export function Onboarding({ level, onDismiss }: { level: number; onDismiss: () => void }) {
const navigate = useNavigate();
const [dismissed, setDismissed] = useState(false);
// Ne montrer que pour les joueurs niveau 1-3
if (level > 3 || dismissed) return null;
return (
<div className="card" style={{ marginBottom: '1rem', borderLeft: '3px solid #f4c94e' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
<h3 style={{ margin: 0, fontSize: 15, color: '#f4c94e' }}>
Bienvenue, aventurier !
</h3>
<button
onClick={() => { setDismissed(true); onDismiss(); }}
style={{ background: 'none', border: 'none', color: '#6b7a99', cursor: 'pointer', fontSize: 11 }}
>
Masquer
</button>
</div>
<p style={{ margin: '0 0 1rem', fontSize: 12, color: '#9ca3af' }}>
Voici les premières étapes pour bien démarrer ton aventure dans les Marais.
</p>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
{STEPS.map((step, i) => {
const Icon = step.icon;
return (
<button
key={i}
onClick={() => navigate(step.to)}
style={{
display: 'flex', alignItems: 'flex-start', gap: 10,
padding: '0.75rem', background: '#111620', border: '1px solid #2a3448',
borderRadius: 8, cursor: 'pointer', textAlign: 'left',
transition: 'border-color 0.15s',
}}
onMouseEnter={e => (e.currentTarget.style.borderColor = '#f4c94e')}
onMouseLeave={e => (e.currentTarget.style.borderColor = '#2a3448')}
>
<Icon size={18} color="#f4c94e" style={{ flexShrink: 0, marginTop: 2 }} />
<div style={{ flex: 1 }}>
<div style={{ fontSize: 12, fontWeight: 700, color: '#dce4f0', marginBottom: 2 }}>
{i + 1}. {step.title}
</div>
<div style={{ fontSize: 10, color: '#6b7a99', lineHeight: 1.4 }}>{step.desc}</div>
<div style={{ fontSize: 10, color: '#f4c94e', marginTop: 4, display: 'flex', alignItems: 'center', gap: 2 }}>
{step.cta} <ChevronRight size={10} />
</div>
</div>
</button>
);
})}
</div>
</div>
);
}

View File

@@ -3,12 +3,12 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { characterApi, itemApi } from '../api/endpoints';
import { api } from '../api/client';
import { Bar } from '../components/Bar';
import { Onboarding } from '../components/Onboarding';
import { STAT_LABELS as STAT_LABELS_MAP } from '../constants';
import { Zap, Heart, Star, Coins, Sword, Shield, BedDouble } from 'lucide-react';
const STATS = ['force', 'agilite', 'intelligence', 'chance', 'vitalite'] as const;
const STAT_LABELS: Record<string, string> = {
force: 'Force', agilite: 'Agilité', intelligence: 'Intelligence', chance: 'Chance', vitalite: 'Vitalité',
};
const STAT_LABELS = STAT_LABELS_MAP;
function CreateCharacter() {
const qc = useQueryClient();
@@ -275,6 +275,8 @@ export function DashboardPage() {
</div>
</div>
<Onboarding level={char.level} onDismiss={() => {}} />
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
{/* Barres vitales */}
<div className="card">

View File

@@ -0,0 +1,43 @@
import { useNavigate } from 'react-router-dom';
import { Gamepad2, BookOpen, MapPin } from 'lucide-react';
export function NotFoundPage() {
const navigate = useNavigate();
return (
<div style={{
minHeight: '100vh', display: 'flex', flexDirection: 'column',
alignItems: 'center', justifyContent: 'center',
background: '#0d0f14', color: '#dce4f0', textAlign: 'center',
padding: '2rem',
}}>
<div style={{ fontSize: 80, marginBottom: '0.5rem' }}>🐸</div>
<h1 style={{ fontSize: 48, fontWeight: 800, color: '#f4c94e', margin: '0 0 0.5rem' }}>404</h1>
<p style={{ fontSize: 16, color: '#6b7a99', maxWidth: 400, marginBottom: '2rem' }}>
Ce chemin ne mène nulle part Le têtard s'est perdu dans les marais.
</p>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', justifyContent: 'center' }}>
<button
onClick={() => navigate('/dashboard')}
className="btn btn-gold"
style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '0.75rem 1.5rem', fontSize: 14 }}
>
<Gamepad2 size={16} /> Retour au jeu
</button>
<button
onClick={() => navigate('/guide')}
className="btn btn-ghost"
style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '0.75rem 1.5rem', fontSize: 14 }}
>
<BookOpen size={16} /> Guide
</button>
</div>
<p style={{ fontSize: 11, color: '#2a3448', marginTop: '3rem' }}>
<MapPin size={10} style={{ display: 'inline', marginRight: 4 }} />
Zone inconnue coordonnées introuvables
</p>
</div>
);
}