forked from syntaxbullet/aurorabot
153 lines
5.2 KiB
TypeScript
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;
|