feat: page 404 RPG + onboarding nouveau joueur
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
93
frontend/src/components/Onboarding.tsx
Normal file
93
frontend/src/components/Onboarding.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
43
frontend/src/pages/NotFoundPage.tsx
Normal file
43
frontend/src/pages/NotFoundPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user