760 lines
27 KiB
TypeScript
760 lines
27 KiB
TypeScript
"use client";
|
|
|
|
import type React from "react";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { useUser } from "@/context/user-context";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { Label } from "@/components/ui/label";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { Plus, Trash2, Sparkles, Loader2 } from "lucide-react";
|
|
import { APIKeyModal } from "@/components/api-key-modal";
|
|
import {
|
|
hasAPIKey,
|
|
generateQuestData,
|
|
getAIProvider,
|
|
} from "@/utils/ai-service";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { predefinedConsumables } from "@/data/items";
|
|
|
|
export function AddQuestForm() {
|
|
const { addCustomQuest } = useUser();
|
|
const { toast } = useToast();
|
|
const [open, setOpen] = useState(false);
|
|
const [apiKeyModalOpen, setApiKeyModalOpen] = useState(false);
|
|
const [isGeneratingWithAI, setIsGeneratingWithAI] = useState(false);
|
|
const [hasAIKey, setHasAIKey] = useState(false);
|
|
const [formData, setFormData] = useState({
|
|
title: "",
|
|
description: "",
|
|
difficulty: "C" as "S" | "A" | "B" | "C" | "D" | "E",
|
|
priority: "Medium" as "High" | "Medium" | "Low",
|
|
expiry: "One-time", // Changed default to One-time
|
|
expReward: 30,
|
|
statPointsReward: 1,
|
|
goldReward: 0,
|
|
strReward: 0,
|
|
agiReward: 0,
|
|
perReward: 0,
|
|
intReward: 0,
|
|
vitReward: 0,
|
|
itemRewards: [] as {
|
|
id?: string;
|
|
name: string;
|
|
type: string;
|
|
description: string;
|
|
}[],
|
|
});
|
|
|
|
// Check if AI API key exists on component mount
|
|
useEffect(() => {
|
|
setHasAIKey(hasAPIKey());
|
|
}, []);
|
|
|
|
const handleChange = (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[name]:
|
|
name.includes("Reward") && name !== "expReward"
|
|
? Number.parseInt(value) || 0
|
|
: value,
|
|
}));
|
|
};
|
|
|
|
const handleSelectChange = (name: string, value: string) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[name]: value,
|
|
}));
|
|
};
|
|
|
|
const addItemReward = () => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
itemRewards: [
|
|
...prev.itemRewards,
|
|
{
|
|
name: "",
|
|
type: "Material",
|
|
description: "",
|
|
},
|
|
],
|
|
}));
|
|
};
|
|
|
|
const removeItemReward = (index: number) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
itemRewards: prev.itemRewards.filter((_, i) => i !== index),
|
|
}));
|
|
};
|
|
|
|
const updateItemReward = (index: number, field: string, value: string) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
itemRewards: prev.itemRewards.map((item, i) =>
|
|
i === index ? { ...item, [field]: value } : item
|
|
),
|
|
}));
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
// Calculate reward string based on stat rewards
|
|
const rewardParts = [];
|
|
if (formData.goldReward > 0)
|
|
rewardParts.push(`${formData.goldReward} Gold`);
|
|
if (formData.strReward > 0)
|
|
rewardParts.push(`+${formData.strReward} Strength`);
|
|
if (formData.agiReward > 0)
|
|
rewardParts.push(`+${formData.agiReward} Agility`);
|
|
if (formData.perReward > 0)
|
|
rewardParts.push(`+${formData.perReward} Perception`);
|
|
if (formData.intReward > 0)
|
|
rewardParts.push(`+${formData.intReward} Intelligence`);
|
|
if (formData.vitReward > 0)
|
|
rewardParts.push(`+${formData.vitReward} Vitality`);
|
|
if (formData.itemRewards.length > 0) {
|
|
formData.itemRewards.forEach((item) => {
|
|
rewardParts.push(`${item.name} (${item.type})`);
|
|
});
|
|
}
|
|
|
|
const rewardString =
|
|
rewardParts.length > 0 ? rewardParts.join(", ") : "Experience only";
|
|
|
|
// Create the quest object
|
|
const newQuest = {
|
|
title: formData.title,
|
|
description: formData.description,
|
|
reward: rewardString,
|
|
difficulty: formData.difficulty,
|
|
priority: formData.priority,
|
|
expiry: formData.expiry,
|
|
expReward: Number.parseInt(formData.expReward.toString()),
|
|
statPointsReward: formData.statPointsReward,
|
|
goldReward: formData.goldReward,
|
|
statRewards: {
|
|
str: formData.strReward,
|
|
agi: formData.agiReward,
|
|
per: formData.perReward,
|
|
int: formData.intReward,
|
|
vit: formData.vitReward,
|
|
},
|
|
itemRewards: formData.itemRewards.map((item, index) => {
|
|
// Check if it's a predefined consumable (has an id property)
|
|
if (item.id && predefinedConsumables.some((p) => p.id === item.id)) {
|
|
// Find the predefined consumable to get all its properties
|
|
const predefined = predefinedConsumables.find(
|
|
(p) => p.id === item.id
|
|
);
|
|
if (predefined) {
|
|
return {
|
|
id: predefined.id,
|
|
name: predefined.name,
|
|
type: predefined.type,
|
|
rarity: predefined.rarity as
|
|
| "Common"
|
|
| "Uncommon"
|
|
| "Rare"
|
|
| "Epic"
|
|
| "Legendary",
|
|
description: predefined.description,
|
|
quantity: 1,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Default for custom items
|
|
return {
|
|
id: `custom-item-${Date.now()}-${index}`,
|
|
name: item.name,
|
|
type: item.type as any,
|
|
rarity: "Common" as
|
|
| "Common"
|
|
| "Uncommon"
|
|
| "Rare"
|
|
| "Epic"
|
|
| "Legendary",
|
|
description: item.description,
|
|
quantity: 1,
|
|
};
|
|
}),
|
|
};
|
|
|
|
// Add the quest
|
|
addCustomQuest(newQuest);
|
|
|
|
// Reset form and close dialog
|
|
setFormData({
|
|
title: "",
|
|
description: "",
|
|
difficulty: "C",
|
|
priority: "Medium",
|
|
expiry: "One-time", // Reset to One-time
|
|
expReward: 30,
|
|
statPointsReward: 1,
|
|
goldReward: 0,
|
|
strReward: 0,
|
|
agiReward: 0,
|
|
perReward: 0,
|
|
intReward: 0,
|
|
vitReward: 0,
|
|
itemRewards: [],
|
|
});
|
|
setOpen(false);
|
|
};
|
|
|
|
const processAIGeneratedItems = (items: any[]) => {
|
|
return items.map((item, index) => {
|
|
// First, check if the AI provided an ID for a consumable
|
|
if (item.type === "Consumable" && item.id) {
|
|
// Check if the ID matches any predefined consumable
|
|
const predefinedById = predefinedConsumables.find(
|
|
(p) => p.id === item.id
|
|
);
|
|
|
|
if (predefinedById) {
|
|
// Use the predefined consumable properties with matching ID
|
|
return {
|
|
id: predefinedById.id,
|
|
name: predefinedById.name,
|
|
type: predefinedById.type,
|
|
description: predefinedById.description,
|
|
rarity: predefinedById.rarity,
|
|
};
|
|
}
|
|
}
|
|
|
|
// If no ID match or ID not provided, fall back to name matching for consumables
|
|
if (item.type === "Consumable") {
|
|
// Try to find a matching predefined consumable by name (case-insensitive)
|
|
const predefined = predefinedConsumables.find(
|
|
(p) => p.name.toLowerCase() === item.name.toLowerCase()
|
|
);
|
|
|
|
if (predefined) {
|
|
// Use the predefined consumable properties
|
|
return {
|
|
id: predefined.id,
|
|
name: predefined.name,
|
|
type: predefined.type,
|
|
description: predefined.description,
|
|
rarity: predefined.rarity,
|
|
};
|
|
}
|
|
|
|
// Also try partial name matching for potions
|
|
const potionMatch = predefinedConsumables.find(
|
|
(p) =>
|
|
(item.name.toLowerCase().includes("health") &&
|
|
p.name.toLowerCase().includes("health")) ||
|
|
(item.name.toLowerCase().includes("mana") &&
|
|
p.name.toLowerCase().includes("mana")) ||
|
|
(item.name.toLowerCase().includes("focus") &&
|
|
p.name.toLowerCase().includes("focus"))
|
|
);
|
|
|
|
if (potionMatch) {
|
|
return {
|
|
id: potionMatch.id,
|
|
name: potionMatch.name,
|
|
type: potionMatch.type,
|
|
description: potionMatch.description,
|
|
rarity: potionMatch.rarity,
|
|
};
|
|
}
|
|
}
|
|
|
|
// For non-consumables or consumables that don't match predefined ones
|
|
return {
|
|
id: `custom-item-${Date.now()}-${index}`,
|
|
name: item.name,
|
|
type: item.type,
|
|
description: item.description || "",
|
|
rarity: "Common",
|
|
};
|
|
});
|
|
};
|
|
|
|
const handleGenerateWithAI = async () => {
|
|
// Check if description is empty
|
|
if (!formData.description.trim()) {
|
|
toast({
|
|
title: "Description Required",
|
|
description:
|
|
"Please enter a quest description before using AI generation.",
|
|
variant: "destructive",
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check if API key exists
|
|
if (!hasAIKey) {
|
|
setApiKeyModalOpen(true);
|
|
return;
|
|
}
|
|
|
|
// Generate quest data with AI
|
|
setIsGeneratingWithAI(true);
|
|
try {
|
|
const questData = await generateQuestData(formData.description);
|
|
const provider = getAIProvider() || "AI";
|
|
|
|
// Update form data with AI-generated data, but keep the current expiry
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
title: questData.title || prev.title,
|
|
description: questData.description || prev.description,
|
|
difficulty: questData.difficulty || prev.difficulty,
|
|
priority: questData.priority || prev.priority,
|
|
// Don't update expiry from AI
|
|
expReward: questData.expReward || prev.expReward,
|
|
statPointsReward: questData.statPointsReward || prev.statPointsReward,
|
|
goldReward: questData.goldReward || prev.goldReward,
|
|
strReward: questData.statRewards?.str || 0,
|
|
agiReward: questData.statRewards?.agi || 0,
|
|
perReward: questData.statRewards?.per || 0,
|
|
intReward: questData.statRewards?.int || 0,
|
|
vitReward: questData.statRewards?.vit || 0,
|
|
itemRewards: processAIGeneratedItems(questData.itemRewards || []),
|
|
}));
|
|
|
|
toast({
|
|
title: `${provider} Generation Complete`,
|
|
description:
|
|
"Quest details have been generated. Feel free to make any adjustments.",
|
|
});
|
|
} catch (error) {
|
|
toast({
|
|
title: "AI Generation Failed",
|
|
description:
|
|
error instanceof Error ? error.message : "An unknown error occurred",
|
|
variant: "destructive",
|
|
});
|
|
} finally {
|
|
setIsGeneratingWithAI(false);
|
|
}
|
|
};
|
|
|
|
const handleAPIKeySubmit = (provider: string) => {
|
|
setHasAIKey(true);
|
|
// Automatically trigger AI generation after API key is submitted
|
|
setTimeout(() => {
|
|
handleGenerateWithAI();
|
|
}, 500);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button className="bg-transparent border border-[#4cc9ff] hover:bg-[#4cc9ff]/10 text-[#4cc9ff]">
|
|
<Plus className="mr-2 h-4 w-4" /> Create Custom Quest
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent
|
|
className="bg-[#0a0e14] border-[#1e2a3a] text-[#e0f2ff] w-[90%] sm:max-w-md max-h-[90vh] overflow-y-auto animate-solo-modal"
|
|
style={
|
|
{
|
|
"--solo-expand-duration": "0.5s",
|
|
"--solo-expand-easing": "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
} as React.CSSProperties
|
|
}
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle className="text-[#4cc9ff]">
|
|
Create New Quest
|
|
</DialogTitle>
|
|
<DialogDescription className="text-[#8bacc1]">
|
|
Add a new quest to track your real-life progress
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="grid gap-4 py-4">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="title">Quest Title</Label>
|
|
<Input
|
|
id="title"
|
|
name="title"
|
|
value={formData.title}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a]"
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="description">Description</Label>
|
|
<Textarea
|
|
id="description"
|
|
name="description"
|
|
value={formData.description}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a] min-h-[80px]"
|
|
required
|
|
/>
|
|
</div>
|
|
{/* Moved AI button here, outside of the textarea */}
|
|
<Button
|
|
type="button"
|
|
className="bg-[#1e2a3a] hover:bg-[#2a3a4a] text-[#4cc9ff] w-full"
|
|
onClick={handleGenerateWithAI}
|
|
disabled={isGeneratingWithAI || !formData.description.trim()}
|
|
>
|
|
{isGeneratingWithAI ? (
|
|
<>
|
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
Generating with AI...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Sparkles className="h-4 w-4 mr-2" />
|
|
Enhance with AI
|
|
</>
|
|
)}
|
|
</Button>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="difficulty">Difficulty</Label>
|
|
<Select
|
|
value={formData.difficulty}
|
|
onValueChange={(value) =>
|
|
handleSelectChange("difficulty", value)
|
|
}
|
|
>
|
|
<SelectTrigger className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
<SelectValue placeholder="Select difficulty" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
<SelectItem value="S">S (Hardest)</SelectItem>
|
|
<SelectItem value="A">A</SelectItem>
|
|
<SelectItem value="B">B</SelectItem>
|
|
<SelectItem value="C">C (Medium)</SelectItem>
|
|
<SelectItem value="D">D</SelectItem>
|
|
<SelectItem value="E">E (Easiest)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="priority">Priority</Label>
|
|
<Select
|
|
value={formData.priority}
|
|
onValueChange={(value) =>
|
|
handleSelectChange("priority", value)
|
|
}
|
|
>
|
|
<SelectTrigger className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
<SelectValue placeholder="Select priority" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
<SelectItem value="High">High</SelectItem>
|
|
<SelectItem value="Medium">Medium</SelectItem>
|
|
<SelectItem value="Low">Low</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="expiry">Frequency</Label>
|
|
<Select
|
|
value={formData.expiry}
|
|
onValueChange={(value) => handleSelectChange("expiry", value)}
|
|
>
|
|
<SelectTrigger className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
<SelectValue placeholder="Select frequency" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
<SelectItem value="One-time">One-time</SelectItem>
|
|
<SelectItem value="Daily">Daily</SelectItem>
|
|
<SelectItem value="Weekly">Weekly</SelectItem>
|
|
<SelectItem value="Monthly">Monthly</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="expReward">EXP Reward</Label>
|
|
<Input
|
|
id="expReward"
|
|
name="expReward"
|
|
type="number"
|
|
min="1"
|
|
value={formData.expReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a]"
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="statPointsReward">Stat Points</Label>
|
|
<Input
|
|
id="statPointsReward"
|
|
name="statPointsReward"
|
|
type="number"
|
|
min="0"
|
|
value={formData.statPointsReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="goldReward">Gold Reward</Label>
|
|
<Input
|
|
id="goldReward"
|
|
name="goldReward"
|
|
type="number"
|
|
min="0"
|
|
value={formData.goldReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a]"
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Stat Rewards</Label>
|
|
<div className="grid grid-cols-5 gap-2">
|
|
<div>
|
|
<Label htmlFor="strReward" className="text-xs">
|
|
STR
|
|
</Label>
|
|
<Input
|
|
id="strReward"
|
|
name="strReward"
|
|
type="number"
|
|
min="0"
|
|
value={formData.strReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a] h-8"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="agiReward" className="text-xs">
|
|
AGI
|
|
</Label>
|
|
<Input
|
|
id="agiReward"
|
|
name="agiReward"
|
|
type="number"
|
|
min="0"
|
|
value={formData.agiReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a] h-8"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="perReward" className="text-xs">
|
|
PER
|
|
</Label>
|
|
<Input
|
|
id="perReward"
|
|
name="perReward"
|
|
type="number"
|
|
min="0"
|
|
value={formData.perReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a] h-8"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="intReward" className="text-xs">
|
|
INT
|
|
</Label>
|
|
<Input
|
|
id="intReward"
|
|
name="intReward"
|
|
type="number"
|
|
min="0"
|
|
value={formData.intReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a] h-8"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="vitReward" className="text-xs">
|
|
VIT
|
|
</Label>
|
|
<Input
|
|
id="vitReward"
|
|
name="vitReward"
|
|
type="number"
|
|
min="0"
|
|
value={formData.vitReward}
|
|
onChange={handleChange}
|
|
className="bg-[#0a0e14] border-[#1e2a3a] h-8"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label>Item Rewards</Label>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-7 border-[#4cc9ff]/50 hover:bg-[#4cc9ff]/10 text-[#4cc9ff]"
|
|
onClick={addItemReward}
|
|
>
|
|
<Plus className="h-3 w-3 mr-1" /> Add Item
|
|
</Button>
|
|
</div>
|
|
{formData.itemRewards.length > 0 ? (
|
|
<div className="space-y-3">
|
|
{formData.itemRewards.map((item, index) => (
|
|
<div
|
|
key={index}
|
|
className="grid gap-2 p-3 border border-[#1e2a3a] rounded-md relative"
|
|
>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="absolute top-2 right-2 h-6 w-6 text-red-400 hover:text-red-300 hover:bg-red-900/20"
|
|
onClick={() => removeItemReward(index)}
|
|
>
|
|
<Trash2 className="h-3 w-3" />
|
|
<span className="sr-only">Remove</span>
|
|
</Button>
|
|
<div>
|
|
<Label
|
|
htmlFor={`item-name-${index}`}
|
|
className="text-xs"
|
|
>
|
|
Item Name
|
|
</Label>
|
|
<Input
|
|
id={`item-name-${index}`}
|
|
value={item.name}
|
|
onChange={(e) =>
|
|
updateItemReward(index, "name", e.target.value)
|
|
}
|
|
className="bg-[#0a0e14] border-[#1e2a3a] h-8"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label
|
|
htmlFor={`item-type-${index}`}
|
|
className="text-xs"
|
|
>
|
|
Type
|
|
</Label>
|
|
<Select
|
|
value={item.type}
|
|
onValueChange={(value) =>
|
|
updateItemReward(index, "type", value)
|
|
}
|
|
>
|
|
<SelectTrigger className="bg-[#0a0e14] border-[#1e2a3a] h-8">
|
|
<SelectValue placeholder="Select type" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
<SelectItem value="Material">Material</SelectItem>
|
|
<SelectItem value="Consumable">
|
|
Consumable
|
|
</SelectItem>
|
|
<SelectItem value="Weapon">Weapon</SelectItem>
|
|
<SelectItem value="Armor">Armor</SelectItem>
|
|
<SelectItem value="Accessory">
|
|
Accessory
|
|
</SelectItem>
|
|
<SelectItem value="Rune">Rune</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Show preset consumables dropdown when Consumable type is selected */}
|
|
{item.type === "Consumable" && (
|
|
<div>
|
|
<Label
|
|
htmlFor={`item-preset-${index}`}
|
|
className="text-xs"
|
|
>
|
|
Preset Potions
|
|
</Label>
|
|
<Select
|
|
onValueChange={(presetId) => {
|
|
const preset = predefinedConsumables.find(
|
|
(p) => p.id === presetId
|
|
);
|
|
if (preset) {
|
|
updateItemReward(index, "name", preset.name);
|
|
updateItemReward(
|
|
index,
|
|
"description",
|
|
preset.description
|
|
);
|
|
// Store the ID for later use
|
|
updateItemReward(index, "id", presetId);
|
|
}
|
|
}}
|
|
>
|
|
<SelectTrigger className="bg-[#0a0e14] border-[#1e2a3a] h-8">
|
|
<SelectValue placeholder="Select a preset potion" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-[#0a0e14] border-[#1e2a3a]">
|
|
{predefinedConsumables.map((potion) => (
|
|
<SelectItem key={potion.id} value={potion.id}>
|
|
{potion.name} ({potion.rarity})
|
|
</SelectItem>
|
|
))}
|
|
<SelectItem value="custom">
|
|
Custom Consumable
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-3 text-[#8bacc1] text-sm border border-dashed border-[#1e2a3a] rounded-md">
|
|
No item rewards added
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button
|
|
type="submit"
|
|
className="bg-transparent border border-[#4cc9ff] hover:bg-[#4cc9ff]/10 text-[#4cc9ff]"
|
|
>
|
|
Create Quest
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* API Key Modal */}
|
|
<APIKeyModal
|
|
open={apiKeyModalOpen}
|
|
onOpenChange={setApiKeyModalOpen}
|
|
onKeySubmit={handleAPIKeySubmit}
|
|
/>
|
|
</>
|
|
);
|
|
}
|