Compare commits

...

3 Commits

9 changed files with 241 additions and 49 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Idle Pet</title>
</head>
<body>
<div id="root"></div>

View File

@ -48,7 +48,10 @@ export default function App() {
<AnimatedBackground />
<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">
<PetDisplay pet={pet} />
<PetDisplay
pet={pet}
onPetUpdate={handlePetUpdate} // Add this prop
/>
<InteractionMenu
pet={pet}
onPetUpdate={handlePetUpdate}

View File

@ -1,12 +1,16 @@
import { Pet } from '../types/Pet';
import { Brain, Dumbbell, Heart, Sparkles, Coins, Pizza, Trash2, Trophy } from 'lucide-react';
import { PET_CLASSES } from '../data/petClasses';
import { useState } from 'react';
import InventoryModal from './modal/InventoryModal';
import SkillTreeModal from './modal/SkillTreeModal';
interface PetDisplayProps {
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 }) => (
<div className="flex items-center space-x-2">
<Icon className="w-5 h-5" />
@ -28,6 +32,9 @@ export default function PetDisplay({ pet }: PetDisplayProps) {
</div>
);
const [showInventoryModal, setShowInventoryModal] = useState(false);
const [showSkillTreeModal, setShowSkillTreeModal] = useState(false);
return (
<div className="bg-gray-900 p-6 rounded-xl shadow-xl">
<div className="text-center mb-8">
@ -67,12 +74,42 @@ export default function PetDisplay({ pet }: PetDisplayProps) {
/>
</div>
<div className="grid grid-cols-2 gap-4">
<ResourceCounter value={pet.resources.wisdom} label="Wisdom" icon={Sparkles} />
<ResourceCounter value={pet.resources.gold} label="Gold" icon={Coins} />
<ResourceCounter value={pet.resources.food} label="Food" icon={Pizza} />
<ResourceCounter value={pet.resources.junk} label="Junk" icon={Trash2} />
<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.gold} label="Gold" icon={Coins} />
<ResourceCounter value={pet.resources.food} label="Food" icon={Pizza} />
<ResourceCounter value={pet.resources.junk} label="Junk" icon={Trash2} />
</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>
);
}

View 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>
);
}

View 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>
);
}

View File

@ -1,57 +1,53 @@
import { ApiService } from './index';
import { Pet, Resources } from '../../types/Pet';
import { InvItemInteraction, Pet, Resources } from '../../types/Pet';
import { PetCreationRequest } from '../../types/PetCreationRequest';
import { PetUpdateActionRequest } from '../../types/PetUpdateActionRequest';
import { PetSkill, Skill } from '../../types/Skills';
// Get API service instance
const api = ApiService.getInstance();
export async function fetchPets(): Promise<Pet[]> {
try {
const response = await api.get<Pet[]>('/api/v1/pet');
return response.data;
} catch (error: any) {
console.error('Failed to fetch pets:', error.message);
throw error;
}
const response = await api.get<Pet[]>('/api/v1/pet');
return response.data;
}
export async function createPet(data: PetCreationRequest): Promise<Pet> {
try {
const response = await api.post<Pet>('/api/v1/pet', data);
return response.data;
} catch (error: any) {
console.error('Failed to create pet:', error.message);
throw error;
}
const response = await api.post<Pet>('/api/v1/pet', data);
return response.data;
}
export async function updatePetAction(petId: string, data: PetUpdateActionRequest): Promise<Pet> {
try {
const response = await api.put<Pet>(`/api/v1/pet/${petId}/action`, data);
return response.data;
} catch (error: any) {
console.error('Failed to update pet action:', error.message);
throw error;
}
const response = await api.put<Pet>(`/api/v1/pet/${petId}/action`, data);
return response.data;
}
export async function getPetGatheredResources(petId: string): Promise<Resources> {
try {
const response = await api.get<Resources>(`/api/v1/pet/${petId}/resources/gathered`);
return response.data;
} catch (error: any) {
console.error('Failed to fetch pet gathered resources:', error.message);
throw error;
}
const response = await api.get<Resources>(`/api/v1/pet/${petId}/resources/gathered`);
return response.data;
}
export async function putPetCollectResources(petId: string): Promise<Pet> {
try {
const response = await api.put<Pet>(`/api/v1/pet/${petId}/resources/collect`);
return response.data;
} catch (error: any) {
console.error('Failed to collect pet gathered resources:', error.message);
throw error;
}
const response = await api.put<Pet>(`/api/v1/pet/${petId}/resources/collect`);
return response.data;
}
export async function getAllSkills(): Promise<Skill[]> {
const response = await api.get<Skill[]>(`/api/v1/skill`);
return response.data;
}
export async function getPetSkills(petId: string): Promise<PetSkill[]> {
const response = await api.get<PetSkill[]>(`/api/v1/skill/${petId}/pet`);
return response.data;
}
export async function postAllocatePetSkill(petId: string, skillId: number): Promise<PetSkill> {
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;
}

View File

@ -25,9 +25,20 @@ export interface Pet {
stats: PetStats;
resources: Resources;
level: number;
petBasicAction: PetBasicAction;
basicActionCooldown: Date;
experience: number;
health: number;
maxHealth: number;
petGatherAction: PetGatherAction;
basicActionCooldown: Date;
petBasicAction: PetBasicAction;
skillPoints: number;
inventory: Inventory;
}
export type InvItemInteraction = 'EQUIP' | 'UNEQUIP' | 'USE' | 'DROP';
export interface Inventory {
items: number[];
capacity: number;
}
export interface PetClassInfo {

View File

@ -1,6 +1,4 @@
import { PetClass } from "./Pet";
export interface PetCreationRequest {
name: string;
class: PetClass;
class: string;
}

26
src/types/Skills.ts Normal file
View File

@ -0,0 +1,26 @@
export interface Skill {
id: number;
name: string;
description: string;
type: string;
pointsCost: number;
icon: string;
skillsIdRequired: number | null;
effects: SkillEffect[];
}
export interface SkillEffect {
id: number;
skillId: number;
tier: string;
effect: string;
value: number;
}
export interface PetSkill {
id: number;
petId: string;
skillId: number;
skill: Skill;
currentTier: string;
}