import React, { useState, useEffect } from 'react'; import { useGame } from '../../contexts/GameContext'; import { GAME_API_URL } from '../../config'; import { GameModal } from './GameModal'; import { GameButton } from '../common/GameButton'; import { getAssetPath } from '../../utils/assetPath'; import './DialogModal.css'; interface DialogModalProps { npcId: string; npcData: any; onClose: () => void; onTrade?: () => void; } interface Topic { id: string; title: { [key: string]: string } | string; text: { [key: string]: string } | string; } interface Quest { quest_id: string; title: { [key: string]: string } | string; description: { [key: string]: string } | string; giver_id: string; objectives: any[]; repeatable?: boolean; type?: 'individual' | 'global'; // Logic for frontend state status?: 'available' | 'active' | 'completed' | 'can_turn_in'; } export const DialogModal: React.FC = ({ npcId, npcData, onClose, onTrade }) => { const { token, locale, actions } = useGame(); const [dialogData, setDialogData] = useState(null); const [currentText, setCurrentText] = useState(""); const [quests, setQuests] = useState([]); const [viewState, setViewState] = useState<'greeting' | 'topic' | 'quest_preview'>('greeting'); const [selectedQuest, setSelectedQuest] = useState(null); // Fetch dialog and quests useEffect(() => { const fetchData = async () => { if (!token || !npcId) return; try { // 1. Fetch Dialog const dialogRes = await fetch(`${GAME_API_URL}/npcs/${npcId}/dialog`, { headers: { 'Authorization': `Bearer ${token}` } }); const dialog = await dialogRes.json(); setDialogData(dialog); // Initial greeting const greeting = getLocalized(dialog.greeting) || "Hello."; setCurrentText(greeting); // 2. Fetch Available Quests (Starts) const availRes = await fetch(`${GAME_API_URL}/quests/available`, { headers: { 'Authorization': `Bearer ${token}` } }); const availableQuests = await availRes.json(); // 3. Fetch Active Quests (Turn-ins) const activeRes = await fetch(`${GAME_API_URL}/quests/active`, { headers: { 'Authorization': `Bearer ${token}` } }); const activeQuests = await activeRes.json(); // Filter and Merge for this NPC const npcQuests: Quest[] = []; // Add available quests from this NPC if (Array.isArray(availableQuests)) { availableQuests.forEach((q: any) => { if (q.giver_id === npcId) { npcQuests.push({ ...q, status: 'available' }); } }); } // Add active quests from this NPC if (Array.isArray(activeQuests)) { activeQuests.forEach((q: any) => { if (q.giver_id === npcId && q.status === 'active') { npcQuests.push({ ...q, status: 'active' }); } }); } setQuests(npcQuests); } catch (e) { console.error("Error fetching NPC data", e); } }; fetchData(); }, [npcId, token, locale]); const getLocalized = (obj: any) => { if (typeof obj === 'string') return obj; return obj?.[locale] || obj?.['en'] || ""; }; const handleTopicClick = (topic: Topic) => { const text = getLocalized(topic.text) || "..."; setCurrentText(text); setViewState('topic'); }; const handleQuestClick = (quest: Quest) => { setSelectedQuest(quest); const desc = getLocalized(quest.description); if (quest.status === 'active') { setCurrentText(desc + "\n\n(Quest in progress...)"); } else { setCurrentText(desc); } setViewState('quest_preview'); }; const acceptQuest = async () => { if (!selectedQuest) return; try { const res = await fetch(`${GAME_API_URL}/quests/accept/${selectedQuest.quest_id}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); // Refresh or update state setCurrentText("Quest accepted! Good luck."); if (data.quest) { actions.handleQuestUpdate(data.quest); } setTimeout(() => { setViewState('greeting'); // Remove from available, add to active locally (simplification) setQuests(prev => prev.map(q => q.quest_id === selectedQuest.quest_id ? { ...q, status: 'active' } : q)); setSelectedQuest(null); resetToGreeting(); }, 1500); } else { const err = await res.json(); alert(err.detail); } } catch (e) { console.error(e); } }; const handInQuest = async () => { if (!selectedQuest) return; try { const res = await fetch(`${GAME_API_URL}/quests/hand_in/${selectedQuest.quest_id}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); const result = await res.json(); if (res.ok) { if (result.quest_update) { actions.handleQuestUpdate(result.quest_update); } // Refresh game data to update inventory/stats actions.fetchGameData(); if (result.is_completed) { let msg = getLocalized(result.completion_text) || "Thank you!"; if (result.rewards && result.rewards.length > 0) { msg += "\n\nRewards:\n" + result.rewards.join('\n'); } setCurrentText(msg); // Remove from list setQuests(prev => prev.filter(q => q.quest_id !== selectedQuest.quest_id)); } else { setCurrentText(`Progress updated.\n${result.items_deducted?.join('\n')}`); } setTimeout(() => { resetToGreeting(); }, 2000); } else { alert(result.detail); } } catch (e) { console.error(e); } }; const resetToGreeting = () => { if (!dialogData) return; const greeting = getLocalized(dialogData.greeting) || "Hello."; setCurrentText(greeting); setViewState('greeting'); setSelectedQuest(null); }; if (!dialogData) return null; const npcName = getLocalized(npcData?.name) || "Unknown"; return (
{npcName}

{currentText}

{/* BACK BUTTON */} {(viewState === 'topic' || viewState === 'quest_preview') && ( ← Back )} {/* NPC TOPICS */} {viewState === 'greeting' && dialogData.topics?.map((topic: Topic) => ( handleTopicClick(topic)}> 💬 {getLocalized(topic.title)} ))} {/* QUESTS */} {viewState === 'greeting' && quests.map(q => ( handleQuestClick(q)} variant={q.status === 'active' ? 'warning' : 'info'} > {q.status === 'available' ? '❗' : '❓'} {getLocalized(q.title)} ))} {/* CONFIRM QUEST ACTION */} {viewState === 'quest_preview' && selectedQuest?.status === 'available' && (
Accept Quest
)} {viewState === 'quest_preview' && selectedQuest?.status === 'active' && (
{/* If it's pure kill quest, 'Complete' makes more sense than 'Hand In' */} {selectedQuest.objectives?.some((o: any) => o.type === 'kill_count') && !selectedQuest.objectives?.some((o: any) => o.type === 'item_delivery') ? "Complete Quest" : "Hand In Items"}
)} {/* TRADE - Only show in greeting */} {viewState === 'greeting' && npcData.trade?.enabled && ( 💰 Trade )} {/* EXIT - Span full width */} {viewState === 'greeting' && ( Goodbye )}
); };