feat: arc quests accept from arc panel + side quests only in available
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 35s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 35s
Arc panel: boutons Accepter/Réclamer directement sur chaque quête d'arc,
progress affiché (3/5), arcs lockés avec 🔒 et opacity réduite.
Quêtes disponibles: seulement les secondaires (pas les arcs).
Quêtes d'arc abandonnées: ré-acceptables depuis le panel arc.
Zone locking respecté dans getArcs (zoneUnlocked flag).
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { questApi } from '../api/endpoints';
|
||||
import { Scroll, CheckCircle, Circle, Trophy, ChevronDown, ChevronRight, Star, Coins, Swords } from 'lucide-react';
|
||||
import { Scroll, CheckCircle, Circle, Trophy, ChevronDown, ChevronRight, Star, Coins, Swords, Lock } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const OBJ_LABELS: Record<string, string> = {
|
||||
@@ -116,44 +116,90 @@ function QuestCard({ pq, mode }: { pq: any; mode: 'active' | 'available' | 'comp
|
||||
);
|
||||
}
|
||||
|
||||
function ArcQuestRow({ q }: { q: any }) {
|
||||
const qc = useQueryClient();
|
||||
|
||||
const acceptMut = useMutation({
|
||||
mutationFn: () => questApi.accept(q.id),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['questArcs'] });
|
||||
qc.invalidateQueries({ queryKey: ['questsActive'] });
|
||||
},
|
||||
});
|
||||
|
||||
const claimMut = useMutation({
|
||||
mutationFn: () => questApi.claim(q.playerQuestId),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['questArcs'] });
|
||||
qc.invalidateQueries({ queryKey: ['questsActive'] });
|
||||
qc.invalidateQueries({ queryKey: ['character'] });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, padding: '4px 0', borderBottom: '1px solid #1a2030' }}>
|
||||
{q.playerStatus === 'claimed'
|
||||
? <CheckCircle size={12} color="#3ddc84" />
|
||||
: q.playerStatus === 'completed'
|
||||
? <Trophy size={12} color="#f4c94e" />
|
||||
: q.playerStatus === 'active'
|
||||
? <Swords size={12} color="#5ba4f5" />
|
||||
: <Circle size={11} color="#3a4560" />
|
||||
}
|
||||
<div style={{ flex: 1 }}>
|
||||
<span style={{
|
||||
color: q.playerStatus === 'claimed' ? '#3ddc84' : q.playerStatus === 'active' ? '#dce4f0' : '#6b7a99',
|
||||
}}>{q.name}</span>
|
||||
{q.playerStatus === 'active' && (
|
||||
<span style={{ fontSize: 10, color: '#5ba4f5', marginLeft: 6 }}>{q.progress}/{q.objectiveCount}</span>
|
||||
)}
|
||||
</div>
|
||||
<span style={{ fontSize: 10, color: '#6b7a99' }}>{q.rewardXp} XP</span>
|
||||
{q.minLevel > 1 && !q.levelOk && <span style={{ fontSize: 9, color: '#e84040' }}>Niv.{q.minLevel}</span>}
|
||||
|
||||
{/* Actions */}
|
||||
{q.canAccept && (
|
||||
<button className="btn btn-ghost" style={{ fontSize: 10, padding: '0.1rem 0.4rem' }}
|
||||
disabled={acceptMut.isPending} onClick={() => acceptMut.mutate()}>
|
||||
{acceptMut.isPending ? '...' : '+ Accepter'}
|
||||
</button>
|
||||
)}
|
||||
{q.playerStatus === 'completed' && (
|
||||
<button className="btn btn-gold" style={{ fontSize: 10, padding: '0.1rem 0.4rem' }}
|
||||
disabled={claimMut.isPending} onClick={() => claimMut.mutate()}>
|
||||
{claimMut.isPending ? '...' : '🎁 Réclamer'}
|
||||
</button>
|
||||
)}
|
||||
{acceptMut.isError && <span style={{ color: '#e84040', fontSize: 9 }}>{(acceptMut.error as Error).message}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ArcSection({ arc }: { arc: any }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { completed, total } = arc.progress;
|
||||
const locked = !arc.zoneUnlocked;
|
||||
|
||||
return (
|
||||
<div className="card" style={{ padding: '0.75rem 1rem', marginBottom: '0.5rem' }}>
|
||||
<div className={`card ${locked ? '' : arc.completed ? '' : 'card-gold'}`} style={{ padding: '0.75rem 1rem', marginBottom: '0.5rem', opacity: locked ? 0.4 : 1 }}>
|
||||
<div
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', marginBottom: open ? 8 : 0 }}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', marginBottom: open && !locked ? 8 : 0 }}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? <ChevronDown size={14} color="#6b7a99" /> : <ChevronRight size={14} color="#6b7a99" />}
|
||||
<Scroll size={14} color={arc.completed ? '#3ddc84' : '#f4c94e'} />
|
||||
<span style={{ fontWeight: 700, fontSize: 14, color: arc.completed ? '#3ddc84' : '#f4c94e', flex: 1 }}>{arc.name}</span>
|
||||
{locked ? <Lock size={14} color="#6b7a99" /> : open ? <ChevronDown size={14} color="#6b7a99" /> : <ChevronRight size={14} color="#6b7a99" />}
|
||||
<Scroll size={14} color={arc.completed ? '#3ddc84' : locked ? '#6b7a99' : '#f4c94e'} />
|
||||
<span style={{ fontWeight: 700, fontSize: 14, color: arc.completed ? '#3ddc84' : locked ? '#6b7a99' : '#f4c94e', flex: 1 }}>
|
||||
{arc.name}
|
||||
</span>
|
||||
<span style={{ fontSize: 11, color: '#6b7a99' }}>{completed}/{total}</span>
|
||||
{arc.completed && <CheckCircle size={14} color="#3ddc84" />}
|
||||
{locked && <span style={{ fontSize: 10, color: '#6b7a99' }}>🔒 Complétez l'arc précédent</span>}
|
||||
</div>
|
||||
{open && (
|
||||
{open && !locked && (
|
||||
<>
|
||||
<p style={{ fontSize: 11, color: '#6b7a99', margin: '0 0 8px', paddingLeft: 28 }}>{arc.description}</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, paddingLeft: 12 }}>
|
||||
{arc.quests.map((q: any) => (
|
||||
<div key={q.id} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, padding: '3px 0' }}>
|
||||
{q.playerStatus === 'claimed'
|
||||
? <CheckCircle size={12} color="#3ddc84" />
|
||||
: q.playerStatus === 'completed'
|
||||
? <Trophy size={12} color="#f4c94e" />
|
||||
: q.playerStatus === 'active'
|
||||
? <Swords size={12} color="#5ba4f5" />
|
||||
: <Circle size={11} color="#3a4560" />
|
||||
}
|
||||
<span style={{
|
||||
color: q.playerStatus === 'claimed' ? '#3ddc84' : q.playerStatus === 'active' ? '#dce4f0' : '#6b7a99',
|
||||
flex: 1,
|
||||
}}>{q.name}</span>
|
||||
<span style={{ fontSize: 10, color: '#6b7a99' }}>{q.rewardXp} XP</span>
|
||||
{q.minLevel > 1 && <span style={{ fontSize: 10, color: '#9ca3af' }}>Niv.{q.minLevel}+</span>}
|
||||
</div>
|
||||
))}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', paddingLeft: 12 }}>
|
||||
{arc.quests.map((q: any) => <ArcQuestRow key={q.id} q={q} />)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user