diff --git a/src/App.tsx b/src/App.tsx index 4d06592..3a38d6b 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,12 @@ import { useState, useEffect } from 'react'; -import ClassSelection from './components/ClassSelection'; import PetDisplay from './components/PetDisplay'; import InteractionMenu from './components/InteractionMenu'; -import NameModal from './components/modal/NameModal'; -import ConfirmationModal from './components/modal/ConfirmationModal'; -import { Pet, PetClassInfo } from './types/Pet'; -import { fetchPets, createPet } from './services/api/api'; +import PetRegister from './components/PetRegister'; +import { Pet } from './types/Pet'; +import { fetchPets } from './services/api/api'; export default function App() { const [pet, setPet] = useState(null); - const [showConfirmation, setShowConfirmation] = useState(false); - const [showNameModal, setShowNameModal] = useState(false); - const [petName, setPetName] = useState(''); - const [selectedClass, setSelectedClass] = useState<{ key: string; info: PetClassInfo } | null>(null); useEffect(() => { const loadPets = async () => { @@ -29,78 +23,6 @@ export default function App() { loadPets(); }, []); - const handleClassSelect = (classKey: string, classInfo: PetClassInfo) => { - setSelectedClass({ key: classKey, info: classInfo }); - setShowConfirmation(true); - }; - - const handleConfirm = () => { - if (!selectedClass) return; - setShowConfirmation(false); - setShowNameModal(true); - setPetName(''); - }; - - const handleNameSubmit = async () => { - if (!selectedClass || !petName.trim()) return; - - try { - const newPet = await createPet({ - name: petName.trim(), - class: selectedClass.key, - }); - setPet(newPet); - setShowNameModal(false); - } catch (error) { - console.error('Error creating pet:', error); - } - }; - - const handleFeed = () => { - if (!pet) return; - const updatedPet = { - ...pet, - stats: { - ...pet.stats, - strength: Math.min(100, pet.stats.strength + 5) - }, - resources: { - ...pet.resources, - food: Math.max(0, pet.resources.food - 10) - } - }; - handlePetUpdate(updatedPet); - }; - - const handlePlay = () => { - if (!pet) return; - const updatedPet = { - ...pet, - stats: { - ...pet.stats, - charisma: Math.min(100, pet.stats.charisma + 5) - }, - resources: { - ...pet.resources, - wisdom: pet.resources.wisdom + 5 - } - }; - handlePetUpdate(updatedPet); - }; - - const handleSleep = () => { - if (!pet) return; - const updatedPet = { - ...pet, - stats: { - intelligence: Math.min(100, pet.stats.intelligence + 5), - strength: Math.min(100, pet.stats.strength + 2), - charisma: Math.min(100, pet.stats.charisma + 2) - } - }; - handlePetUpdate(updatedPet); - }; - const handleCustomize = () => { console.log('Customize pet'); }; @@ -110,29 +32,7 @@ export default function App() { }; if (!pet) { - return ( -
- {showNameModal && selectedClass ? ( - - ) : showConfirmation && selectedClass ? ( - - ) : ( - - )} -
- ); + return ; } return ( @@ -142,9 +42,6 @@ export default function App() { diff --git a/src/components/GatherResourcesButton.tsx b/src/components/GatherResourcesButton.tsx index 5b7b7fb..f867988 100755 --- a/src/components/GatherResourcesButton.tsx +++ b/src/components/GatherResourcesButton.tsx @@ -1,34 +1,35 @@ import { useState, useEffect } from 'react'; import { FeatherIcon as GatherIcon } from 'lucide-react'; import ResourceSelectionModal from './modal/ResourceSelectionModal'; -import CollectResourcesButton from './CollectResourcesButton'; import { Pet, Resources } from '../types/Pet'; -import { updatePetAction, getPetGatheredResources, putPetCollectResources } from '../services/api/api'; -import { PetAction } from '../types/PetUpdateActionRequest'; +import { updatePetAction, getPetGatheredResources } from '../services/api/api'; +import { PetGatherAction } from '../types/PetUpdateActionRequest'; import { isGatheringAction, formatResourceName, getResourceFromAction } from '../utils/petUtils'; interface GatherResourcesButtonProps { pet: Pet; onGatherStart: () => void; onGatherComplete: (updatedPet: Pet) => void; + onResourcesUpdate: (resources: Resources) => void; } -const resourceToActionMap: Record = { +const resourceToActionMap: Record = { wisdom: 'GATHERING_WISDOM', gold: 'GATHERING_GOLD', food: 'GATHERING_FOOD' }; -export default function GatherResourcesButton({ pet, onGatherStart, onGatherComplete }: GatherResourcesButtonProps) { +export default function GatherResourcesButton({ pet, onGatherStart, onGatherComplete, onResourcesUpdate }: GatherResourcesButtonProps) { const [isModalOpen, setIsModalOpen] = useState(false); - const [isGathering, setIsGathering] = useState(isGatheringAction(pet.petAction)); - const [gatheredResources, setGatheredResources] = useState({ wisdom: 0, gold: 0, food: 0, junk: 0 }); + const [isGathering, setIsGathering] = useState(isGatheringAction(pet.petGatherAction)); // Initialize gathering check if pet is already gathering useEffect(() => { - if (isGatheringAction(pet.petAction)) { + if (isGatheringAction(pet.petGatherAction)) { setIsGathering(true); - const resources = getPetGatheredResources(pet.id).then(setGatheredResources); + getPetGatheredResources(pet.id).then(resources => { + onResourcesUpdate(resources); + }); onGatherStart(); } }, []); @@ -40,7 +41,7 @@ export default function GatherResourcesButton({ pet, onGatherStart, onGatherComp interval = setInterval(async () => { try { const resources = await getPetGatheredResources(pet.id); - setGatheredResources(resources); + onResourcesUpdate(resources); } catch (error) { console.error('Failed to check gathered resources:', error); } @@ -55,13 +56,13 @@ export default function GatherResourcesButton({ pet, onGatherStart, onGatherComp }, [isGathering, pet.id]); useEffect(() => { - setIsGathering(isGatheringAction(pet.petAction)); - }, [pet.petAction]); + setIsGathering(isGatheringAction(pet.petGatherAction)); + }, [pet.petGatherAction]); const handleGatherStart = async (resourceType: string) => { if (resourceType === 'stop') { try { - await updatePetAction(pet.id, { petActionGather: 'IDLE' }); + await updatePetAction(pet.id, { gatherAction: 'IDLE' }); setIsGathering(false); onGatherComplete(pet); } catch (error) { @@ -76,7 +77,7 @@ export default function GatherResourcesButton({ pet, onGatherStart, onGatherComp try { const petAction = resourceToActionMap[resourceType]; - const updatedPet = await updatePetAction(pet.id, { petActionGather: petAction }); + const updatedPet = await updatePetAction(pet.id, { gatherAction: petAction }); onGatherComplete(updatedPet); } catch (error) { console.error('Failed to gather resources:', error); @@ -85,17 +86,7 @@ export default function GatherResourcesButton({ pet, onGatherStart, onGatherComp } }; - const handleCollect = async () => { - try { - const updatedPet = await putPetCollectResources(pet.id); - setGatheredResources({ wisdom: 0, gold: 0, food: 0, junk: 0 }); - onGatherComplete(updatedPet); - } catch (error) { - console.error('Failed to collect resources:', error); - } - }; - - const currentResource = getResourceFromAction(pet.petAction); + const currentResource = getResourceFromAction(pet.petGatherAction); return (
@@ -114,12 +105,6 @@ export default function GatherResourcesButton({ pet, onGatherStart, onGatherComp - - setIsModalOpen(false)} diff --git a/src/components/InteractionMenu.tsx b/src/components/InteractionMenu.tsx index 9e3e690..c813a92 100755 --- a/src/components/InteractionMenu.tsx +++ b/src/components/InteractionMenu.tsx @@ -1,45 +1,119 @@ -import { Pizza, PlayCircle, Moon, Paintbrush } from 'lucide-react'; +import { Pizza, PlayCircle, Moon } from 'lucide-react'; import GatherResourcesButton from './GatherResourcesButton'; -import { Pet } from '../types/Pet'; -import { useState } from 'react'; +import CollectResourcesButton from './CollectResourcesButton'; +import { Pet, Resources } from '../types/Pet'; +import { useState, useEffect } from 'react'; +import { updatePetAction } from '../services/api/api'; +import { PetBasicAction } from '../types/PetUpdateActionRequest'; +import ActionButton from './button/ActionButton'; interface InteractionMenuProps { pet: Pet; onPetUpdate: (updatedPet: Pet) => void; - onFeed: () => void; - onPlay: () => void; - onSleep: () => void; onCustomize: () => void; } -export default function InteractionMenu({ pet, onPetUpdate, onFeed, onPlay, onSleep, onCustomize }: InteractionMenuProps) { +export default function InteractionMenu({ pet, onPetUpdate }: InteractionMenuProps) { + const [gatheredResources, setGatheredResources] = useState({ wisdom: 0, gold: 0, food: 0, junk: 0 }); + const [remainingCooldown, setRemainingCooldown] = useState(null); + + useEffect(() => { + const updateCooldown = () => { + if (!pet.basicActionCooldown) { + setRemainingCooldown(null); + return; + } + + const cooldownTime = new Date(pet.basicActionCooldown).getTime(); + const now = new Date().getTime(); + const remaining = Math.max(0, cooldownTime - now); + + if (remaining === 0) { + setRemainingCooldown(null); + } else { + setRemainingCooldown(remaining); + } + }; + + updateCooldown(); + const interval = setInterval(updateCooldown, 1000); + return () => clearInterval(interval); + }, [pet.basicActionCooldown]); + + const formatCooldownTime = (ms: number) => { + const minutes = Math.floor(ms / 60000); + const seconds = Math.floor((ms % 60000) / 1000); + + if (minutes > 0) { + return `${minutes} minute${minutes > 1 ? 's' : ''} remaining`; + } + return `${seconds} second${seconds > 1 ? 's' : ''} remaining`; + }; + const handleGatherComplete = (updatedPet: Pet) => { onPetUpdate(updatedPet); }; - const ActionButton = ({ icon: Icon, label, onClick, color }: { icon: any; label: string; onClick: () => void; color: string }) => ( - - ); + const handleResourcesUpdate = (resources: Resources) => { + setGatheredResources(resources); + }; + + const handleCollect = () => { + setGatheredResources({ wisdom: 0, gold: 0, food: 0, junk: 0 }); + }; + + function performBasicAction(basicAction: PetBasicAction): () => void { + return async () => { + try { + const updatedPet = await updatePetAction(pet.id, { basicAction: basicAction }); + onPetUpdate(updatedPet); + } catch (error) { + console.error('Failed to perform basic action:', error); + } + }; + } return (
- - - - {/* */} + {remainingCooldown !== null && ( +
+ Cooldown: {formatCooldownTime(remainingCooldown)} +
+ )} + + + + +
console.log('Gathering started')} onGatherComplete={handleGatherComplete} + onResourcesUpdate={handleResourcesUpdate} + /> +
diff --git a/src/components/PetRegister.tsx b/src/components/PetRegister.tsx new file mode 100644 index 0000000..5a11652 --- /dev/null +++ b/src/components/PetRegister.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react'; +import ClassSelection from './ClassSelection'; +import NameModal from './modal/NameModal'; +import ConfirmationModal from './modal/ConfirmationModal'; +import { PetClassInfo } from '../types/Pet'; +import { createPet } from '../services/api/api'; + +interface PetRegisterProps { + onPetCreated: (newPet: any) => void; +} + +export default function PetRegister({ onPetCreated }: PetRegisterProps) { + const [showConfirmation, setShowConfirmation] = useState(false); + const [showNameModal, setShowNameModal] = useState(false); + const [petName, setPetName] = useState(''); + const [selectedClass, setSelectedClass] = useState<{ key: string; info: PetClassInfo } | null>(null); + + const handleClassSelect = (classKey: string, classInfo: PetClassInfo) => { + setSelectedClass({ key: classKey, info: classInfo }); + setShowConfirmation(true); + }; + + const handleConfirm = () => { + if (!selectedClass) return; + setShowConfirmation(false); + setShowNameModal(true); + setPetName(''); + }; + + const handleNameSubmit = async () => { + if (!selectedClass || !petName.trim()) return; + + try { + const newPet = await createPet({ + name: petName.trim(), + class: selectedClass.key, + }); + onPetCreated(newPet); + } catch (error) { + console.error('Error creating pet:', error); + } + }; + + return ( +
+ {showNameModal && selectedClass ? ( + + ) : showConfirmation && selectedClass ? ( + + ) : ( + + )} +
+ ); +} diff --git a/src/components/button/ActionButton.tsx b/src/components/button/ActionButton.tsx new file mode 100644 index 0000000..d9f2557 --- /dev/null +++ b/src/components/button/ActionButton.tsx @@ -0,0 +1,25 @@ +import { LucideIcon } from 'lucide-react'; + +interface ActionButtonProps { + icon: LucideIcon; + label: string; + onClick: () => void; + color: string; + disabled?: boolean; +} + +export default function ActionButton({ icon: Icon, label, onClick, color, disabled }: ActionButtonProps) { + return ( + + ); +} diff --git a/src/components/modal/ResourceSelectionModal.tsx b/src/components/modal/ResourceSelectionModal.tsx index 1243d50..29be339 100755 --- a/src/components/modal/ResourceSelectionModal.tsx +++ b/src/components/modal/ResourceSelectionModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Brain, Coins, Pizza, X, Loader2, StopCircle } from 'lucide-react'; +import { Brain, Coins, Pizza, X, StopCircle } from 'lucide-react'; import { Pet } from '../../types/Pet'; import { formatResourceName, getResourceFromAction } from '../../utils/petUtils'; @@ -68,7 +68,7 @@ export default function ResourceSelectionModal({ return Math.floor(baseStat * multiplier); }; - const currentResource = getResourceFromAction(pet.petAction); + const currentResource = getResourceFromAction(pet.petGatherAction); return (
diff --git a/src/services/api/api.ts b/src/services/api/api.ts index 12fa3eb..58e258e 100644 --- a/src/services/api/api.ts +++ b/src/services/api/api.ts @@ -28,7 +28,7 @@ export async function createPet(data: PetCreationRequest): Promise { export async function updatePetAction(petId: string, data: PetUpdateActionRequest): Promise { try { - const response = await api.put(`/api/v1/pet/${petId}/action/gather`, data); + const response = await api.put(`/api/v1/pet/${petId}/action`, data); return response.data; } catch (error: any) { console.error('Failed to update pet action:', error.message); diff --git a/src/services/api/examples.ts b/src/services/api/examples.ts deleted file mode 100755 index 7aa2238..0000000 --- a/src/services/api/examples.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Example usage of the API service -import { ApiService } from './index'; -import { Pet } from '../../types/Pet'; - -// Get API service instance -const api = ApiService.getInstance(); - -// Example functions using the API service -export async function getPet(id: string) { - try { - const response = await api.get(`/pets/${id}`); - return response.data; - } catch (error: any) { - if (api.isNetworkError(error)) { - console.error('Network error occurred'); - } else if (api.isTimeoutError(error)) { - console.error('Request timed out'); - } else if (api.isServerError(error)) { - console.error('Server error occurred'); - } - throw error; - } -} - -export async function updatePet(id: string, data: Partial) { - try { - const response = await api.put(`/pets/${id}`, data); - return response.data; - } catch (error: any) { - console.error('Failed to update pet:', error.message); - throw error; - } -} - -export async function gatherResources(id: string, resourceType: string) { - try { - const response = await api.post<{ success: boolean }>(`/pets/${id}/gather`, { - resourceType, - }); - return response.data; - } catch (error: any) { - console.error('Failed to gather resources:', error.message); - throw error; - } -} \ No newline at end of file diff --git a/src/types/Pet.ts b/src/types/Pet.ts index 1eb26ce..d17e261 100755 --- a/src/types/Pet.ts +++ b/src/types/Pet.ts @@ -1,4 +1,4 @@ -import { PetAction } from "./PetUpdateActionRequest"; +import { PetBasicAction, PetGatherAction } from "./PetUpdateActionRequest"; export type PetClass = 'FOREST_SPIRIT' | 'OCEAN_GUARDIAN' | 'FIRE_ELEMENTAL' | 'MYTHICAL_BEAST' | 'SHADOW_WALKER' | 'CYBER_PET' | 'BIO_MECHANICAL'; @@ -6,6 +6,9 @@ export interface PetStats { intelligence: number; strength: number; charisma: number; + maxIntelligence: number; + maxStrength: number; + maxCharisma: number; } export interface Resources { @@ -22,7 +25,9 @@ export interface Pet { stats: PetStats; resources: Resources; level: number; - petAction: PetAction; + petBasicAction: PetBasicAction; + basicActionCooldown: Date; + petGatherAction: PetGatherAction; } export interface PetClassInfo { diff --git a/src/types/PetUpdateActionRequest.ts b/src/types/PetUpdateActionRequest.ts index 7c635b7..ca5d305 100644 --- a/src/types/PetUpdateActionRequest.ts +++ b/src/types/PetUpdateActionRequest.ts @@ -1,5 +1,7 @@ -export type PetAction = 'IDLE' | 'GATHERING_WISDOM' | 'GATHERING_GOLD' | 'GATHERING_FOOD'; +export type PetBasicAction = 'UNKNOWN' | 'FEED' | 'PLAY' | 'SLEEP'; +export type PetGatherAction = 'IDLE' | 'GATHERING_WISDOM' | 'GATHERING_GOLD' | 'GATHERING_FOOD'; export interface PetUpdateActionRequest { - petActionGather: PetAction; -} \ No newline at end of file + basicAction?: PetBasicAction; + gatherAction?: PetGatherAction; +} diff --git a/src/utils/petUtils.ts b/src/utils/petUtils.ts index d7bad78..0add37c 100644 --- a/src/utils/petUtils.ts +++ b/src/utils/petUtils.ts @@ -1,10 +1,10 @@ -import { PetAction } from '../types/PetUpdateActionRequest'; +import { PetGatherAction } from '../types/PetUpdateActionRequest'; -export function isGatheringAction(action: PetAction): boolean { +export function isGatheringAction(action: PetGatherAction): boolean { return action.startsWith('GATHERING_'); } -export function getResourceFromAction(action: PetAction): string | null { +export function getResourceFromAction(action: PetGatherAction): string | null { if (!isGatheringAction(action)) return null; return action.replace('GATHERING_', '').toLowerCase(); }