feat: guide — barre de recherche live + lien sidebar (BookOpen, bas)
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 34s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 34s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { Swords, Package, Hammer, User, LogOut, Shield, Scroll, Trophy, ShoppingBag } from 'lucide-react';
|
||||
import { Swords, Package, Hammer, User, LogOut, Shield, Scroll, Trophy, ShoppingBag, BookOpen } from 'lucide-react';
|
||||
import { HudBar } from './HudBar';
|
||||
|
||||
const NAV = [
|
||||
@@ -83,6 +83,22 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<div style={{ flex: 1 }} />
|
||||
<Link to="/guide" title="Guide" style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
color: loc.pathname === '/guide' ? '#f4c94e' : '#6b7a99',
|
||||
background: loc.pathname === '/guide' ? '#1e2535' : 'transparent',
|
||||
border: loc.pathname === '/guide' ? '1px solid #c49c2e' : '1px solid transparent',
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.15s',
|
||||
}}>
|
||||
<BookOpen size={18} />
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
{/* Main content */}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '../api/client';
|
||||
import type { Monster, Item, Recipe } from '../api/types';
|
||||
import { Swords, Shield, Map as MapIcon, Hammer, ShoppingBag, BookOpen, Sparkles } from 'lucide-react';
|
||||
import { Swords, Shield, Map as MapIcon, Hammer, ShoppingBag, BookOpen, Sparkles, Search } from 'lucide-react';
|
||||
|
||||
// ── Data fetching (public endpoints, no auth) ──
|
||||
|
||||
@@ -419,12 +419,26 @@ function ShopTab({ items }: { items: Item[] }) {
|
||||
|
||||
export function GuidePage() {
|
||||
const [tab, setTab] = useState('start');
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const { data: monsters = [] } = useQuery({ queryKey: ['guide-monsters'], queryFn: guideApi.monsters });
|
||||
const { data: items = [] } = useQuery({ queryKey: ['guide-items'], queryFn: guideApi.items });
|
||||
const { data: materials = [] } = useQuery({ queryKey: ['guide-materials'], queryFn: guideApi.materials });
|
||||
const { data: recipes = [] } = useQuery({ queryKey: ['guide-recipes'], queryFn: guideApi.recipes });
|
||||
|
||||
const q = search.toLowerCase().trim();
|
||||
const filteredMonsters = useMemo(() => q ? monsters.filter(m => m.name.toLowerCase().includes(q) || m.zone?.toLowerCase().includes(q)) : monsters, [monsters, q]);
|
||||
const filteredItems = useMemo(() => q ? items.filter(i => i.name.toLowerCase().includes(q) || i.rarity.toLowerCase().includes(q) || i.description?.toLowerCase().includes(q)) : items, [items, q]);
|
||||
const filteredRecipes = useMemo(() => {
|
||||
if (!q) return recipes;
|
||||
const matMap = new Map<string, any>(materials.map(m => [m.id, m]));
|
||||
return recipes.filter(r =>
|
||||
r.name.toLowerCase().includes(q) ||
|
||||
r.resultItem?.name.toLowerCase().includes(q) ||
|
||||
r.ingredients.some(ing => matMap.get(ing.materialId)?.name.toLowerCase().includes(q))
|
||||
);
|
||||
}, [recipes, materials, q]);
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 900, margin: '0 auto', padding: '2rem 1rem' }}>
|
||||
{/* Header */}
|
||||
@@ -437,6 +451,31 @@ export function GuidePage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div style={{ position: 'relative', marginBottom: '1rem' }}>
|
||||
<Search size={14} style={{ position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', color: '#6b7a99' }} />
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
placeholder="Rechercher un monstre, item, matériau, recette…"
|
||||
style={{
|
||||
width: '100%', padding: '10px 12px 10px 34px', fontSize: 13,
|
||||
background: '#1e2535', border: '1px solid #2a3448', borderRadius: 8,
|
||||
color: '#dce4f0', outline: 'none', boxSizing: 'border-box',
|
||||
}}
|
||||
/>
|
||||
{search && (
|
||||
<button
|
||||
onClick={() => setSearch('')}
|
||||
style={{
|
||||
position: 'absolute', right: 10, top: '50%', transform: 'translateY(-50%)',
|
||||
background: 'none', border: 'none', color: '#6b7a99', cursor: 'pointer', fontSize: 14,
|
||||
}}
|
||||
>✕</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tab navigation */}
|
||||
<div style={{
|
||||
display: 'flex', gap: 4, marginBottom: '1.5rem', overflowX: 'auto',
|
||||
@@ -469,11 +508,11 @@ export function GuidePage() {
|
||||
{/* Tab content */}
|
||||
{tab === 'start' && <StartTab />}
|
||||
{tab === 'zones' && <ZonesTab />}
|
||||
{tab === 'bestiary' && <BestiaryTab monsters={monsters} materials={materials} />}
|
||||
{tab === 'items' && <ItemsTab items={items} />}
|
||||
{tab === 'craft' && <CraftTab recipes={recipes} materials={materials} />}
|
||||
{tab === 'bestiary' && <BestiaryTab monsters={filteredMonsters} materials={materials} />}
|
||||
{tab === 'items' && <ItemsTab items={filteredItems} />}
|
||||
{tab === 'craft' && <CraftTab recipes={filteredRecipes} materials={materials} />}
|
||||
{tab === 'forge' && <ForgeTab />}
|
||||
{tab === 'shop' && <ShopTab items={items} />}
|
||||
{tab === 'shop' && <ShopTab items={filteredItems} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user