diff --git a/src/App.tsx b/src/App.tsx index 8ee2fc9..4d06592 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,9 @@ -import React, { useState, useEffect } from 'react'; +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'; @@ -56,7 +58,7 @@ export default function App() { const handleFeed = () => { if (!pet) return; - setPet({ + const updatedPet = { ...pet, stats: { ...pet.stats, @@ -66,12 +68,13 @@ export default function App() { ...pet.resources, food: Math.max(0, pet.resources.food - 10) } - }); + }; + handlePetUpdate(updatedPet); }; const handlePlay = () => { if (!pet) return; - setPet({ + const updatedPet = { ...pet, stats: { ...pet.stats, @@ -81,104 +84,50 @@ export default function App() { ...pet.resources, wisdom: pet.resources.wisdom + 5 } - }); + }; + handlePetUpdate(updatedPet); }; const handleSleep = () => { if (!pet) return; - setPet({ + 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'); }; + const handlePetUpdate = (updatedPet: Pet) => { + setPet(updatedPet); + }; + if (!pet) { return (
{showNameModal && selectedClass ? ( -
-
-

Name Your Pet

-

- Creating a new {selectedClass.info.name.toLowerCase()} pet -

-
-
- - setPetName(e.target.value)} - className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" - placeholder="Enter a name for your pet" - autoFocus - /> -
-
- - -
-
-
-
+ ) : showConfirmation && selectedClass ? ( -
-
-

Confirm Selection

-

Are you sure you want to choose {selectedClass.info.name}?

-
- {selectedClass.info.modifiers.map((modifier, index) => ( -
• {modifier}
- ))} -
-
- - -
-
-
+ ) : ( )} @@ -192,6 +141,7 @@ export default function App() { void; +} + +export default function CollectResourcesButton({ petId, resources, onCollect }: CollectResourcesButtonProps) { + const hasResources = Object.values(resources).some(value => value > 0); + + if (!hasResources) return null; + + const handleCollect = async () => { + try { + await putPetCollectResources(petId); + onCollect(); + } catch (error) { + console.error('Failed to collect resources:', error); + } + }; + + return ( + + ); +} diff --git a/src/components/GatherResourcesButton.tsx b/src/components/GatherResourcesButton.tsx index 5cbc5c8..5b7b7fb 100755 --- a/src/components/GatherResourcesButton.tsx +++ b/src/components/GatherResourcesButton.tsx @@ -1,14 +1,16 @@ -import React, { useState } from 'react'; +import { useState, useEffect } from 'react'; import { FeatherIcon as GatherIcon } from 'lucide-react'; -import ResourceSelectionModal from './ResourceSelectionModal'; -import { Pet } from '../types/Pet'; -import { updatePetAction } from '../services/api/api'; +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 { isGatheringAction, formatResourceName, getResourceFromAction } from '../utils/petUtils'; interface GatherResourcesButtonProps { pet: Pet; onGatherStart: () => void; - onGatherComplete: () => void; + onGatherComplete: (updatedPet: Pet) => void; } const resourceToActionMap: Record = { @@ -19,39 +21,105 @@ const resourceToActionMap: Record = { export default function GatherResourcesButton({ pet, onGatherStart, onGatherComplete }: GatherResourcesButtonProps) { const [isModalOpen, setIsModalOpen] = useState(false); - const [isGathering, setIsGathering] = useState(false); + const [isGathering, setIsGathering] = useState(isGatheringAction(pet.petAction)); + const [gatheredResources, setGatheredResources] = useState({ wisdom: 0, gold: 0, food: 0, junk: 0 }); + + // Initialize gathering check if pet is already gathering + useEffect(() => { + if (isGatheringAction(pet.petAction)) { + setIsGathering(true); + const resources = getPetGatheredResources(pet.id).then(setGatheredResources); + onGatherStart(); + } + }, []); + + useEffect(() => { + let interval: number; + + if (isGathering) { + interval = setInterval(async () => { + try { + const resources = await getPetGatheredResources(pet.id); + setGatheredResources(resources); + } catch (error) { + console.error('Failed to check gathered resources:', error); + } + }, 10000); + } + + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [isGathering, pet.id]); + + useEffect(() => { + setIsGathering(isGatheringAction(pet.petAction)); + }, [pet.petAction]); const handleGatherStart = async (resourceType: string) => { + if (resourceType === 'stop') { + try { + await updatePetAction(pet.id, { petActionGather: 'IDLE' }); + setIsGathering(false); + onGatherComplete(pet); + } catch (error) { + console.error('Failed to stop gathering:', error); + } + setIsModalOpen(false); + return; + } + setIsGathering(true); onGatherStart(); try { const petAction = resourceToActionMap[resourceType]; - await updatePetAction(pet.id, { petAction }); - onGatherComplete(); + const updatedPet = await updatePetAction(pet.id, { petActionGather: petAction }); + onGatherComplete(updatedPet); } catch (error) { console.error('Failed to gather resources:', error); } finally { - setIsGathering(false); setIsModalOpen(false); } }; + 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); + return ( - <> +
+ + setIsModalOpen(false)} @@ -59,6 +127,6 @@ export default function GatherResourcesButton({ pet, onGatherStart, onGatherComp pet={pet} isGathering={isGathering} /> - +
); } \ No newline at end of file diff --git a/src/components/InteractionMenu.tsx b/src/components/InteractionMenu.tsx index a77fb6a..9e3e690 100755 --- a/src/components/InteractionMenu.tsx +++ b/src/components/InteractionMenu.tsx @@ -1,17 +1,22 @@ -import React from 'react'; import { Pizza, PlayCircle, Moon, Paintbrush } from 'lucide-react'; import GatherResourcesButton from './GatherResourcesButton'; import { Pet } from '../types/Pet'; +import { useState } from 'react'; interface InteractionMenuProps { pet: Pet; + onPetUpdate: (updatedPet: Pet) => void; onFeed: () => void; onPlay: () => void; onSleep: () => void; onCustomize: () => void; } -export default function InteractionMenu({ pet, onFeed, onPlay, onSleep, onCustomize }: InteractionMenuProps) { +export default function InteractionMenu({ pet, onPetUpdate, onFeed, onPlay, onSleep, onCustomize }: InteractionMenuProps) { + const handleGatherComplete = (updatedPet: Pet) => { + onPetUpdate(updatedPet); + }; + const ActionButton = ({ icon: Icon, label, onClick, color }: { icon: any; label: string; onClick: () => void; color: string }) => (
diff --git a/src/components/PetDisplay.tsx b/src/components/PetDisplay.tsx index a0e1a00..2e4731e 100755 --- a/src/components/PetDisplay.tsx +++ b/src/components/PetDisplay.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Pet } from '../types/Pet'; import { Brain, Dumbbell, Heart, Sparkles, Coins, Pizza, Trash2 } from 'lucide-react'; import { PET_CLASSES } from '../data/petClasses'; diff --git a/src/components/modal/ConfirmationModal.tsx b/src/components/modal/ConfirmationModal.tsx new file mode 100644 index 0000000..42d7871 --- /dev/null +++ b/src/components/modal/ConfirmationModal.tsx @@ -0,0 +1,46 @@ +import { PetClassInfo } from '../../types/Pet'; + +interface ConfirmationModalProps { + selectedClass: { key: string; info: PetClassInfo }; + handleConfirm: () => void; + setShowConfirmation: (show: boolean) => void; + setSelectedClass: (selected: { key: string; info: PetClassInfo } | null) => void; +} + +export default function ConfirmationModal({ + selectedClass, + handleConfirm, + setShowConfirmation, + setSelectedClass, +}: ConfirmationModalProps) { + return ( +
+
+

Confirm Selection

+

Are you sure you want to choose {selectedClass.info.name}?

+
+ {selectedClass.info.modifiers.map((modifier, index) => ( +
• {modifier}
+ ))} +
+
+ + +
+
+
+ ); +} diff --git a/src/components/modal/NameModal.tsx b/src/components/modal/NameModal.tsx new file mode 100644 index 0000000..c1924cd --- /dev/null +++ b/src/components/modal/NameModal.tsx @@ -0,0 +1,68 @@ +import { PetClassInfo } from '../../types/Pet'; + +interface NameModalProps { + selectedClass: { key: string; info: PetClassInfo }; + petName: string; + setPetName: (name: string) => void; + handleNameSubmit: () => void; + setShowNameModal: (show: boolean) => void; + setSelectedClass: (selected: { key: string; info: PetClassInfo } | null) => void; +} + +export default function NameModal({ + selectedClass, + petName, + setPetName, + handleNameSubmit, + setShowNameModal, + setSelectedClass, +}: NameModalProps) { + return ( +
+
+

Name Your Pet

+

+ Creating a new {selectedClass.info.name.toLowerCase()} pet +

+
+
+ + setPetName(e.target.value)} + className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Enter a name for your pet" + autoFocus + /> +
+
+ + +
+
+
+
+ ); +} diff --git a/src/components/ResourceSelectionModal.tsx b/src/components/modal/ResourceSelectionModal.tsx similarity index 76% rename from src/components/ResourceSelectionModal.tsx rename to src/components/modal/ResourceSelectionModal.tsx index 32eaeb6..1243d50 100755 --- a/src/components/ResourceSelectionModal.tsx +++ b/src/components/modal/ResourceSelectionModal.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Brain, Coins, Pizza, X, Loader2 } from 'lucide-react'; -import { Pet } from '../types/Pet'; +import { Brain, Coins, Pizza, X, Loader2, StopCircle } from 'lucide-react'; +import { Pet } from '../../types/Pet'; +import { formatResourceName, getResourceFromAction } from '../../utils/petUtils'; interface ResourceSelectionModalProps { isOpen: boolean; @@ -67,19 +68,28 @@ export default function ResourceSelectionModal({ return Math.floor(baseStat * multiplier); }; + const currentResource = getResourceFromAction(pet.petAction); + return (

Gather Resources

+ {isGathering && currentResource && ( +
+

+ Currently gathering {formatResourceName(currentResource)} +

+
+ )} +
{resourceOptions.map((option) => ( )}
diff --git a/src/services/api/api.ts b/src/services/api/api.ts index 4bdda32..12fa3eb 100644 --- a/src/services/api/api.ts +++ b/src/services/api/api.ts @@ -1,5 +1,5 @@ import { ApiService } from './index'; -import { Pet } from '../../types/Pet'; +import { Pet, Resources } from '../../types/Pet'; import { PetCreationRequest } from '../../types/PetCreationRequest'; import { PetUpdateActionRequest } from '../../types/PetUpdateActionRequest'; @@ -28,10 +28,30 @@ 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`, data); + const response = await api.put(`/api/v1/pet/${petId}/action/gather`, data); return response.data; } catch (error: any) { console.error('Failed to update pet action:', error.message); throw error; } } + +export async function getPetGatheredResources(petId: string): Promise { + try { + const response = await api.get(`/api/v1/pet/${petId}/resources/gathered`); + return response.data; + } catch (error: any) { + console.error('Failed to fetch pet gathered resources:', error.message); + throw error; + } +} + +export async function putPetCollectResources(petId: string): Promise { + try { + const response = await api.put(`/api/v1/pet/${petId}/resources/collect`); + return response.data; + } catch (error: any) { + console.error('Failed to collect pet gathered resources:', error.message); + throw error; + } +} \ No newline at end of file diff --git a/src/types/Pet.ts b/src/types/Pet.ts index 3b3d6b1..1eb26ce 100755 --- a/src/types/Pet.ts +++ b/src/types/Pet.ts @@ -1,3 +1,5 @@ +import { PetAction } from "./PetUpdateActionRequest"; + export type PetClass = 'FOREST_SPIRIT' | 'OCEAN_GUARDIAN' | 'FIRE_ELEMENTAL' | 'MYTHICAL_BEAST' | 'SHADOW_WALKER' | 'CYBER_PET' | 'BIO_MECHANICAL'; export interface PetStats { @@ -20,6 +22,7 @@ export interface Pet { stats: PetStats; resources: Resources; level: number; + petAction: PetAction; } export interface PetClassInfo { diff --git a/src/types/PetCreationRequest.ts b/src/types/PetCreationRequest.ts index eda2301..66a7e20 100644 --- a/src/types/PetCreationRequest.ts +++ b/src/types/PetCreationRequest.ts @@ -1,6 +1,6 @@ import { PetClass } from "./Pet"; -export interface PetCrationRequest { +export interface PetCreationRequest { name: string; class: PetClass; } \ No newline at end of file diff --git a/src/types/PetUpdateActionRequest.ts b/src/types/PetUpdateActionRequest.ts index fa4ed6a..7c635b7 100644 --- a/src/types/PetUpdateActionRequest.ts +++ b/src/types/PetUpdateActionRequest.ts @@ -1,5 +1,5 @@ export type PetAction = 'IDLE' | 'GATHERING_WISDOM' | 'GATHERING_GOLD' | 'GATHERING_FOOD'; export interface PetUpdateActionRequest { - petAction: PetAction; + petActionGather: PetAction; } \ No newline at end of file diff --git a/src/utils/petUtils.ts b/src/utils/petUtils.ts new file mode 100644 index 0000000..d7bad78 --- /dev/null +++ b/src/utils/petUtils.ts @@ -0,0 +1,14 @@ +import { PetAction } from '../types/PetUpdateActionRequest'; + +export function isGatheringAction(action: PetAction): boolean { + return action.startsWith('GATHERING_'); +} + +export function getResourceFromAction(action: PetAction): string | null { + if (!isGatheringAction(action)) return null; + return action.replace('GATHERING_', '').toLowerCase(); +} + +export function formatResourceName(resource: string): string { + return resource.charAt(0).toUpperCase() + resource.slice(1); +} \ No newline at end of file