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 }) => (
-
+ {/* */}
console.log('Gathering started')}
- onGatherComplete={() => console.log('Gathering completed')}
+ onGatherComplete={handleGatherComplete}
/>
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