feat: add inventory and skill tree modals, enhance pet display with update handling
This commit is contained in:
parent
a624ba1e73
commit
243c50a1a0
@ -48,7 +48,10 @@ export default function App() {
|
|||||||
<AnimatedBackground />
|
<AnimatedBackground />
|
||||||
<div className="relative z-10 min-h-screen backdrop-blur-sm bg-gray-900/20 text-white p-4">
|
<div className="relative z-10 min-h-screen backdrop-blur-sm bg-gray-900/20 text-white p-4">
|
||||||
<div className="max-w-4xl mx-auto grid gap-8">
|
<div className="max-w-4xl mx-auto grid gap-8">
|
||||||
<PetDisplay pet={pet} />
|
<PetDisplay
|
||||||
|
pet={pet}
|
||||||
|
onPetUpdate={handlePetUpdate} // Add this prop
|
||||||
|
/>
|
||||||
<InteractionMenu
|
<InteractionMenu
|
||||||
pet={pet}
|
pet={pet}
|
||||||
onPetUpdate={handlePetUpdate}
|
onPetUpdate={handlePetUpdate}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { Pet } from '../types/Pet';
|
import { Pet } from '../types/Pet';
|
||||||
import { Brain, Dumbbell, Heart, Sparkles, Coins, Pizza, Trash2, Trophy } from 'lucide-react';
|
import { Brain, Dumbbell, Heart, Sparkles, Coins, Pizza, Trash2, Trophy } from 'lucide-react';
|
||||||
import { PET_CLASSES } from '../data/petClasses';
|
import { PET_CLASSES } from '../data/petClasses';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import InventoryModal from './modal/InventoryModal';
|
||||||
|
import SkillTreeModal from './modal/SkillTreeModal';
|
||||||
|
|
||||||
interface PetDisplayProps {
|
interface PetDisplayProps {
|
||||||
pet: Pet;
|
pet: Pet;
|
||||||
|
onPetUpdate: (updatedPet: Pet) => void; // Add this prop
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PetDisplay({ pet }: PetDisplayProps) {
|
export default function PetDisplay({ pet, onPetUpdate }: PetDisplayProps) {
|
||||||
const StatBar = ({ value, maxValue, label, icon: Icon }: { value: number; maxValue: number; label: string; icon: any }) => (
|
const StatBar = ({ value, maxValue, label, icon: Icon }: { value: number; maxValue: number; label: string; icon: any }) => (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Icon className="w-5 h-5" />
|
<Icon className="w-5 h-5" />
|
||||||
@ -28,6 +32,9 @@ export default function PetDisplay({ pet }: PetDisplayProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [showInventoryModal, setShowInventoryModal] = useState(false);
|
||||||
|
const [showSkillTreeModal, setShowSkillTreeModal] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-900 p-6 rounded-xl shadow-xl">
|
<div className="bg-gray-900 p-6 rounded-xl shadow-xl">
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
@ -67,12 +74,42 @@ export default function PetDisplay({ pet }: PetDisplayProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="flex items-start mb-4 space-x-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4 flex-1">
|
||||||
<ResourceCounter value={pet.resources.wisdom} label="Wisdom" icon={Sparkles} />
|
<ResourceCounter value={pet.resources.wisdom} label="Wisdom" icon={Sparkles} />
|
||||||
<ResourceCounter value={pet.resources.gold} label="Gold" icon={Coins} />
|
<ResourceCounter value={pet.resources.gold} label="Gold" icon={Coins} />
|
||||||
<ResourceCounter value={pet.resources.food} label="Food" icon={Pizza} />
|
<ResourceCounter value={pet.resources.food} label="Food" icon={Pizza} />
|
||||||
<ResourceCounter value={pet.resources.junk} label="Junk" icon={Trash2} />
|
<ResourceCounter value={pet.resources.junk} label="Junk" icon={Trash2} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col space-y-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowInventoryModal(true)}
|
||||||
|
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded"
|
||||||
|
>
|
||||||
|
Inventory
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowSkillTreeModal(true)}
|
||||||
|
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded"
|
||||||
|
>
|
||||||
|
Skill Tree
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showInventoryModal && (
|
||||||
|
<InventoryModal
|
||||||
|
inventory={pet.inventory}
|
||||||
|
petId={pet.id}
|
||||||
|
onClose={() => setShowInventoryModal(false)}
|
||||||
|
onPetUpdate={onPetUpdate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showSkillTreeModal && (
|
||||||
|
<SkillTreeModal
|
||||||
|
onClose={() => setShowSkillTreeModal(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
95
src/components/modal/InventoryModal.tsx
Normal file
95
src/components/modal/InventoryModal.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Inventory, InvItemInteraction, Pet } from '../../types/Pet';
|
||||||
|
import { putPetItemInteract } from '../../services/api/api';
|
||||||
|
|
||||||
|
interface InventoryModalProps {
|
||||||
|
inventory: Inventory;
|
||||||
|
petId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
onPetUpdate: (updatedPet: Pet) => void; // Add this prop
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InventoryModal({ inventory, petId, onClose, onPetUpdate }: InventoryModalProps) {
|
||||||
|
const capacity = inventory.capacity; // Expected to be 20 for 4 rows x 5 cols
|
||||||
|
const items = inventory.items;
|
||||||
|
const gridSlots = Array.from({ length: capacity }, (_, i) => items[i]);
|
||||||
|
const [selectedItemIndex, setSelectedItemIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const handleItemClick = (index: number) => {
|
||||||
|
if (selectedItemIndex === index || gridSlots[index] === undefined) {
|
||||||
|
setSelectedItemIndex(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setSelectedItemIndex(index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInteraction = async (interaction: InvItemInteraction) => {
|
||||||
|
if (selectedItemIndex === null || gridSlots[selectedItemIndex] === undefined) return;
|
||||||
|
try {
|
||||||
|
const updatedPet = await putPetItemInteract(petId, gridSlots[selectedItemIndex], interaction);
|
||||||
|
onPetUpdate(updatedPet);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Item interaction failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
||||||
|
<div className="bg-gray-800 p-6 rounded-xl max-w-2xl w-full">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-2xl font-bold">Inventory</h2>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-5 grid-rows-4 gap-4">
|
||||||
|
{gridSlots.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleItemClick(index)}
|
||||||
|
className={`border rounded p-4 flex items-center justify-center h-16
|
||||||
|
${item !== undefined ? 'bg-gray-700 cursor-pointer' : 'bg-gray-800 text-gray-500'}
|
||||||
|
${selectedItemIndex === index ? 'border-blue-500 animate-pulse' : 'border-gray-600'}`}
|
||||||
|
>
|
||||||
|
{item !== undefined ? <span>{item}</span> : <span className="text-gray-500">Empty</span>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 grid grid-cols-4 gap-4">
|
||||||
|
<button
|
||||||
|
disabled={selectedItemIndex === null}
|
||||||
|
onClick={() => handleInteraction('USE')}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded disabled:opacity-50"
|
||||||
|
>
|
||||||
|
Use
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
disabled={selectedItemIndex === null}
|
||||||
|
onClick={() => handleInteraction('DROP')}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded disabled:opacity-50"
|
||||||
|
>
|
||||||
|
Drop
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
disabled={selectedItemIndex === null}
|
||||||
|
onClick={() => handleInteraction('EQUIP')}
|
||||||
|
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded disabled:opacity-50"
|
||||||
|
>
|
||||||
|
Equip
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
disabled={selectedItemIndex === null}
|
||||||
|
onClick={() => handleInteraction('UNEQUIP')}
|
||||||
|
className="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded disabled:opacity-50"
|
||||||
|
>
|
||||||
|
Unequip
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
26
src/components/modal/SkillTreeModal.tsx
Normal file
26
src/components/modal/SkillTreeModal.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface SkillTreeModalProps {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SkillTreeModal({ onClose }: SkillTreeModalProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50"
|
||||||
|
>
|
||||||
|
<div className="bg-gray-800 p-6 rounded-xl max-w-2xl w-full">
|
||||||
|
<h2 className="text-2xl font-bold mb-4">Skill Tree</h2>
|
||||||
|
<p className="text-gray-400 mb-6">Skill tree modal under development.</p>
|
||||||
|
<div className="mt-6 flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { ApiService } from './index';
|
import { ApiService } from './index';
|
||||||
import { Pet, Resources } from '../../types/Pet';
|
import { InvItemInteraction, Pet, Resources } from '../../types/Pet';
|
||||||
import { PetCreationRequest } from '../../types/PetCreationRequest';
|
import { PetCreationRequest } from '../../types/PetCreationRequest';
|
||||||
import { PetUpdateActionRequest } from '../../types/PetUpdateActionRequest';
|
import { PetUpdateActionRequest } from '../../types/PetUpdateActionRequest';
|
||||||
import { PetSkill, Skill } from '../../types/Skills';
|
import { PetSkill, Skill } from '../../types/Skills';
|
||||||
@ -42,7 +42,12 @@ export async function getPetSkills(petId: string): Promise<PetSkill[]> {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postAllocatePetSkill(petId: string, skillId: int): Promise<PetSkill> {
|
export async function postAllocatePetSkill(petId: string, skillId: number): Promise<PetSkill> {
|
||||||
const response = await api.get<PetSkill>(`/api/v1/skill/${petId}/allocate/${skillId}`);
|
const response = await api.post<PetSkill>(`/api/v1/skill/${petId}/allocate/${skillId}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putPetItemInteract(petId: string, itemId: number, inter: InvItemInteraction): Promise<Pet> {
|
||||||
|
const response = await api.put<Pet>(`/api/v1/inventory/${petId}/${itemId}/${inter.toLowerCase()}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
@ -35,6 +35,7 @@ export interface Pet {
|
|||||||
inventory: Inventory;
|
inventory: Inventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InvItemInteraction = 'EQUIP' | 'UNEQUIP' | 'USE' | 'DROP';
|
||||||
export interface Inventory {
|
export interface Inventory {
|
||||||
items: number[];
|
items: number[];
|
||||||
capacity: number;
|
capacity: number;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user