Files
discord-rpg-concept/web/src/pages/AdminQuests.tsx

153 lines
5.2 KiB
TypeScript

import React from "react";
import { QuestForm } from "../components/quest-form";
import { QuestTable } from "../components/quest-table";
import { SectionHeader } from "../components/section-header";
import { toast } from "sonner";
interface QuestListItem {
id: number;
name: string;
description: string | null;
triggerEvent: string;
requirements: { target?: number };
rewards: { xp?: number; balance?: number };
}
export function AdminQuests() {
const [quests, setQuests] = React.useState<QuestListItem[]>([]);
const [isInitialLoading, setIsInitialLoading] = React.useState(true);
const [isRefreshing, setIsRefreshing] = React.useState(false);
const [lastCreatedQuestId, setLastCreatedQuestId] = React.useState<number | null>(null);
const [editingQuest, setEditingQuest] = React.useState<QuestListItem | null>(null);
const [isFormModeEdit, setIsFormModeEdit] = React.useState(false);
const formRef = React.useRef<HTMLDivElement>(null);
const fetchQuests = React.useCallback(async (isRefresh = false) => {
if (isRefresh) {
setIsRefreshing(true);
} else {
setIsInitialLoading(true);
}
try {
const response = await fetch("/api/quests");
if (!response.ok) {
throw new Error("Failed to fetch quests");
}
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
setQuests(data.data);
}
} catch (error) {
console.error("Error fetching quests:", error);
toast.error("Failed to load quests", {
description: error instanceof Error ? error.message : "Unknown error",
});
} finally {
setIsInitialLoading(false);
setIsRefreshing(false);
}
}, []);
React.useEffect(() => {
fetchQuests(false);
}, [fetchQuests]);
React.useEffect(() => {
if (lastCreatedQuestId !== null) {
const element = document.getElementById(`quest-row-${lastCreatedQuestId}`);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "center" });
element.classList.add("bg-primary/10");
setTimeout(() => {
element.classList.remove("bg-primary/10");
}, 2000);
}
setLastCreatedQuestId(null);
}
}, [lastCreatedQuestId, quests]);
const handleQuestCreated = () => {
fetchQuests(true);
toast.success("Quest list updated", {
description: "The quest inventory has been refreshed.",
});
};
const handleDeleteQuest = async (id: number) => {
try {
const response = await fetch(`/api/quests/${id}`, {
method: "DELETE",
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || "Failed to delete quest");
}
setQuests((prev) => prev.filter((q) => q.id !== id));
toast.success("Quest deleted", {
description: `Quest #${id} has been successfully deleted.`,
});
} catch (error) {
console.error("Error deleting quest:", error);
toast.error("Failed to delete quest", {
description: error instanceof Error ? error.message : "Unknown error",
});
}
};
const handleEditQuest = (id: number) => {
const quest = quests.find(q => q.id === id);
if (quest) {
setEditingQuest(quest);
setIsFormModeEdit(true);
formRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
}
};
const handleQuestUpdated = () => {
fetchQuests(true);
setEditingQuest(null);
setIsFormModeEdit(false);
toast.success("Quest list updated", {
description: "The quest inventory has been refreshed.",
});
};
const handleFormCancel = () => {
setEditingQuest(null);
setIsFormModeEdit(false);
};
return (
<main className="pt-8 px-8 pb-12 max-w-7xl mx-auto space-y-12">
<SectionHeader
badge="Quest Management"
title="Quests"
description="Create and manage quests for the Aurora RPG students."
/>
<div className="animate-in fade-in slide-in-from-bottom-4 duration-700">
<QuestTable
quests={quests}
isInitialLoading={isInitialLoading}
isRefreshing={isRefreshing}
onRefresh={() => fetchQuests(true)}
onDelete={handleDeleteQuest}
onEdit={handleEditQuest}
/>
</div>
<div className="animate-in fade-in slide-up duration-700" ref={formRef}>
<QuestForm
initialData={editingQuest || undefined}
onUpdate={handleQuestUpdated}
onCancel={handleFormCancel}
/>
</div>
</main>
);
}
export default AdminQuests;