diff --git a/src/components/ActionResourceButton.tsx b/src/components/ActionResourceButton.tsx new file mode 100644 index 0000000..43645f1 --- /dev/null +++ b/src/components/ActionResourceButton.tsx @@ -0,0 +1,63 @@ +import { LucideIcon } from 'lucide-react'; +import { Pet, Resources } from '../types/Pet'; +import { isActionActive, formatResourceName, getResourceFromAction } from '../utils/petUtils'; + +const colorClassMap = { + amber: 'bg-amber-900/30 hover:bg-amber-800/50 border-amber-500/50', + emerald: 'bg-emerald-900/30 hover:bg-emerald-800/50 border-emerald-500/50', + red: 'bg-red-900/30 hover:bg-red-800/50 border-red-500/50', + green: 'bg-green-900/30 hover:bg-green-800/50 border-green-500/50', + blue: 'bg-blue-900/30 hover:bg-blue-800/50 border-blue-500/50', + purple: 'bg-purple-900/30 hover:bg-purple-800/50 border-purple-500/50', +} as const; + +const getActionVerb = (actionType: 'gather' | 'explore' | 'battle'): string => { + const verbs = { + gather: 'Gathering', + explore: 'Exploring', + battle: 'Battling', + }; + return verbs[actionType]; +}; + +type ButtonColor = keyof typeof colorClassMap; + +interface ActionResourceButtonProps { + pet: Pet; + icon: LucideIcon; + label: string; + actionType: 'gather' | 'explore' | 'battle'; + color: ButtonColor; + onActionClick: () => void; + onActionComplete: (updatedPet: Pet) => void; + onResourcesUpdate: (resources: Resources) => void; +} + +export default function ActionResourceButton({ + pet, + icon: Icon, + label, + actionType, + color, + onActionClick +}: ActionResourceButtonProps) { + const isActive = isActionActive(pet.petGatherAction, actionType); + const currentResource = getResourceFromAction(pet.petGatherAction); + + return ( + + + + {isActive && currentResource + ? `${getActionVerb(actionType)} ${formatResourceName(currentResource)}` + : label} + + + ); +} diff --git a/src/components/GatherResourcesButton.tsx b/src/components/GatherResourcesButton.tsx deleted file mode 100755 index f867988..0000000 --- a/src/components/GatherResourcesButton.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { useState, useEffect } from 'react'; -import { FeatherIcon as GatherIcon } from 'lucide-react'; -import ResourceSelectionModal from './modal/ResourceSelectionModal'; -import { Pet, Resources } from '../types/Pet'; -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 = { - wisdom: 'GATHERING_WISDOM', - gold: 'GATHERING_GOLD', - food: 'GATHERING_FOOD' -}; - -export default function GatherResourcesButton({ pet, onGatherStart, onGatherComplete, onResourcesUpdate }: GatherResourcesButtonProps) { - const [isModalOpen, setIsModalOpen] = useState(false); - const [isGathering, setIsGathering] = useState(isGatheringAction(pet.petGatherAction)); - - // Initialize gathering check if pet is already gathering - useEffect(() => { - if (isGatheringAction(pet.petGatherAction)) { - setIsGathering(true); - getPetGatheredResources(pet.id).then(resources => { - onResourcesUpdate(resources); - }); - onGatherStart(); - } - }, []); - - useEffect(() => { - let interval: number; - - if (isGathering) { - interval = setInterval(async () => { - try { - const resources = await getPetGatheredResources(pet.id); - onResourcesUpdate(resources); - } catch (error) { - console.error('Failed to check gathered resources:', error); - } - }, 10000); - } - - return () => { - if (interval) { - clearInterval(interval); - } - }; - }, [isGathering, pet.id]); - - useEffect(() => { - setIsGathering(isGatheringAction(pet.petGatherAction)); - }, [pet.petGatherAction]); - - const handleGatherStart = async (resourceType: string) => { - if (resourceType === 'stop') { - try { - await updatePetAction(pet.id, { gatherAction: '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]; - const updatedPet = await updatePetAction(pet.id, { gatherAction: petAction }); - onGatherComplete(updatedPet); - } catch (error) { - console.error('Failed to gather resources:', error); - } finally { - setIsModalOpen(false); - } - }; - - const currentResource = getResourceFromAction(pet.petGatherAction); - - return ( - - setIsModalOpen(true)} - className={`flex items-center justify-center space-x-2 - bg-amber-900/30 hover:bg-amber-800/50 - border-2 border-amber-500/50 rounded-lg p-4 - transition-all duration-300 transform hover:scale-105 w-full`} - > - - - {isGathering && currentResource - ? `Gathering ${formatResourceName(currentResource)}...` - : 'Gather Resources'} - - - - setIsModalOpen(false)} - onGather={handleGatherStart} - pet={pet} - isGathering={isGathering} - /> - - ); -} \ No newline at end of file diff --git a/src/components/InteractionMenu.tsx b/src/components/InteractionMenu.tsx index c813a92..5828780 100755 --- a/src/components/InteractionMenu.tsx +++ b/src/components/InteractionMenu.tsx @@ -1,11 +1,13 @@ -import { Pizza, PlayCircle, Moon } from 'lucide-react'; -import GatherResourcesButton from './GatherResourcesButton'; +import { Pizza, PlayCircle, Moon, Compass, Sword, FeatherIcon } from 'lucide-react'; import CollectResourcesButton from './CollectResourcesButton'; import { Pet, Resources } from '../types/Pet'; import { useState, useEffect } from 'react'; -import { updatePetAction } from '../services/api/api'; +import { updatePetAction, getPetGatheredResources } from '../services/api/api'; import { PetBasicAction } from '../types/PetUpdateActionRequest'; import ActionButton from './button/ActionButton'; +import ActionResourceButton from './ActionResourceButton'; +import ResourceSelectionModal from './modal/ResourceSelectionModal'; +import { PetAction } from '../types/PetUpdateActionRequest'; interface InteractionMenuProps { pet: Pet; @@ -16,6 +18,8 @@ interface 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); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedActionType, setSelectedActionType] = useState<'gather' | 'explore' | 'battle' | null>(null); useEffect(() => { const updateCooldown = () => { @@ -40,6 +44,26 @@ export default function InteractionMenu({ pet, onPetUpdate }: InteractionMenuPro return () => clearInterval(interval); }, [pet.basicActionCooldown]); + useEffect(() => { + const fetchGatheredResources = async () => { + if (pet.petGatherAction === 'IDLE') { + return; + } + + try { + const resources = await getPetGatheredResources(pet.id); + setGatheredResources(resources); + } catch (error) { + console.error('Failed to fetch gathered resources:', error); + } + }; + + fetchGatheredResources(); + const interval = setInterval(fetchGatheredResources, 10000); // Poll every 10 seconds + + return () => clearInterval(interval); + }, [pet.id, pet.petGatherAction]); + const formatCooldownTime = (ms: number) => { const minutes = Math.floor(ms / 60000); const seconds = Math.floor((ms % 60000) / 1000); @@ -73,6 +97,45 @@ export default function InteractionMenu({ pet, onPetUpdate }: InteractionMenuPro }; } + const handleActionStart = async (actionType: 'gather' | 'explore' | 'battle') => { + if (actionType === 'gather') { + setSelectedActionType(actionType); + setIsModalOpen(true); + return; + } + + try { + const action: PetAction = actionType === 'explore' ? 'EXPLORING' : 'BATTLE'; + const updatedPet = await updatePetAction(pet.id, { gatherAction: action }); + onPetUpdate(updatedPet); + } catch (error) { + console.error('Failed to start action:', error); + } + }; + + const handleResourceSelect = async (resourceType: string) => { + if (resourceType === 'stop') { + try { + const updatedPet = await updatePetAction(pet.id, { gatherAction: 'IDLE' }); + onPetUpdate(updatedPet); + } catch (error) { + console.error('Failed to stop action:', error); + } + setIsModalOpen(false); + return; + } + + try { + const action: PetAction = `GATHERING_${resourceType.toUpperCase()}` as PetAction; + const updatedPet = await updatePetAction(pet.id, { gatherAction: action }); + onPetUpdate(updatedPet); + } catch (error) { + console.error('Failed to start gathering:', error); + } finally { + setIsModalOpen(false); + } + }; + return ( {remainingCooldown !== null && ( @@ -103,19 +166,54 @@ export default function InteractionMenu({ pet, onPetUpdate }: InteractionMenuPro disabled={remainingCooldown !== null} /> - - + console.log('Gathering started')} - onGatherComplete={handleGatherComplete} + icon={FeatherIcon} + label="Gather" + actionType="gather" + color="amber" + onActionClick={() => handleActionStart('gather')} + onActionComplete={handleGatherComplete} onResourcesUpdate={handleResourcesUpdate} /> + handleActionStart('explore')} + onActionComplete={handleGatherComplete} + onResourcesUpdate={handleResourcesUpdate} + /> + handleActionStart('battle')} + onActionComplete={handleGatherComplete} + onResourcesUpdate={handleResourcesUpdate} + /> + + + + + setIsModalOpen(false)} + onGather={handleResourceSelect} + pet={pet} + isGathering={pet.petGatherAction !== 'IDLE'} + /> ); } \ No newline at end of file diff --git a/src/components/PetDisplay.tsx b/src/components/PetDisplay.tsx index 2e4731e..34e3e65 100755 --- a/src/components/PetDisplay.tsx +++ b/src/components/PetDisplay.tsx @@ -1,5 +1,5 @@ import { Pet } from '../types/Pet'; -import { Brain, Dumbbell, Heart, Sparkles, Coins, Pizza, Trash2 } from 'lucide-react'; +import { Brain, Dumbbell, Heart, Sparkles, Coins, Pizza, Trash2, Trophy } from 'lucide-react'; import { PET_CLASSES } from '../data/petClasses'; interface PetDisplayProps { @@ -7,16 +7,16 @@ interface PetDisplayProps { } export default function PetDisplay({ pet }: PetDisplayProps) { - const StatBar = ({ value, label, icon: Icon }: { value: number; label: string; icon: any }) => ( + const StatBar = ({ value, maxValue, label, icon: Icon }: { value: number; maxValue: number; label: string; icon: any }) => ( - {value}% + {value}/{maxValue} ); @@ -29,19 +29,42 @@ export default function PetDisplay({ pet }: PetDisplayProps) { ); return ( - + {PET_CLASSES[pet.class].emoji} - {pet.name} + + + {pet.name} + + + + + Level {pet.level} + {PET_CLASSES[pet.class].name} - - - + + + diff --git a/src/utils/petUtils.ts b/src/utils/petUtils.ts index 0add37c..57782c0 100644 --- a/src/utils/petUtils.ts +++ b/src/utils/petUtils.ts @@ -4,11 +4,31 @@ export function isGatheringAction(action: PetGatherAction): boolean { return action.startsWith('GATHERING_'); } -export function getResourceFromAction(action: PetGatherAction): string | null { - if (!isGatheringAction(action)) return null; - return action.replace('GATHERING_', '').toLowerCase(); +export function getResourceFromAction(action: string): string | null { + if (!action) return null; + + const patterns = ['GATHERING_', 'EXPLORING_', 'BATTLE_']; + for (const pattern of patterns) { + if (action.startsWith(pattern)) { + return action.replace(pattern, '').toLowerCase(); + } + + } + return null; } export function formatResourceName(resource: string): string { return resource.charAt(0).toUpperCase() + resource.slice(1); +} + +export function isActionActive(action: string, actionType: string): boolean { + if (!action) return false; + + const actionMap = { + 'gather': 'GATHERING_', + 'explore': 'EXPLORING_', + 'battle': 'BATTLE_' + }; + + return action.startsWith(actionMap[actionType] || ''); } \ No newline at end of file
{PET_CLASSES[pet.class].name}