diff --git a/client/dev-dist/sw.js b/client/dev-dist/sw.js index ee427d5..c42ebe1 100644 --- a/client/dev-dist/sw.js +++ b/client/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-86c9b217'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.itc0gcirm3o" + "revision": "0.cqa50r5rbt" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/client/src/components/screens/Home/ClaimMissionAnimation.tsx b/client/src/components/screens/Home/ClaimMissionAnimation.tsx new file mode 100644 index 0000000..ee42a00 --- /dev/null +++ b/client/src/components/screens/Home/ClaimMissionAnimation.tsx @@ -0,0 +1,187 @@ +import { useEffect, useState, useMemo } from "react" +import { motion } from "framer-motion" +import Particles, { initParticlesEngine } from "@tsparticles/react" +import type { Engine, Container, IOptions, RecursivePartial } from "@tsparticles/engine" +import { MoveDirection } from "@tsparticles/engine" +import { loadFull } from "tsparticles" +import coinIcon from "../../../assets/icons/CoinIcon.webp"; + +interface Mission { + id: string + title: string + description: string + difficulty: 'Easy' | 'Mid' | 'Hard' + reward: number + completed: boolean + claimed?: boolean // Añadimos esta propiedad para el estado de reclamado +} + +interface ClaimMissionAnimationProps { + mission: Mission; + onClose: () => void; +} + +export function ClaimMissionAnimation({ mission, onClose }: ClaimMissionAnimationProps): JSX.Element | null { + const [engineLoaded, setEngineLoaded] = useState(false) + + useEffect(() => { + initParticlesEngine(async (engine: Engine) => { + await loadFull(engine) + }).then(() => setEngineLoaded(true)) + }, []) + + // Effect to automatically close after 4 seconds + useEffect(() => { + const timer = setTimeout(onClose, 4000) + return () => clearTimeout(timer) + }, [onClose]) + + // Play success sound effect + useEffect(() => { + const audio = new Audio("/purchase-success.mp3") + audio.volume = 0.5 + audio.play().catch((err) => console.log("Audio play failed:", err)) + return () => { + audio.pause() + audio.currentTime = 0 + } + }, []) + + const particlesLoaded = async (container?: Container): Promise => { + console.log("Claim particles loaded", container) + } + + const options = useMemo>( + () => ({ + fullScreen: { enable: false }, + fpsLimit: 60, + particles: { + number: { value: 100, density: { enable: true, area: 800 } }, + color: { + value: ["#FF8F3F", "#FF5722", "#5BB3DB", "#E6DCC7", "#26453D"], + }, + shape: { type: "circle" }, + opacity: { + value: 0.8, + random: true, + animation: { enable: true, speed: 1, minimumValue: 0.1, sync: false }, + }, + size: { value: 10, random: true, animation: { enable: false } }, + links: { enable: false }, + move: { + enable: true, + speed: 6, + direction: MoveDirection.none, + random: true, + straight: false, + outModes: { default: "out" }, + }, + collisions: { enable: false }, + }, + interactivity: { + detectsOn: "canvas", + events: { + onHover: { enable: false }, + onClick: { enable: false }, + resize: { enable: true }, + }, + }, + detectRetina: true, + }), + [] + ) + + if (!engineLoaded) return null + + return ( + + {/* Particle background */} + + + {/* Confirmation card */} + e.stopPropagation()} + > + {/* Coin icon with glow animation */} + + {/* Coin icon */} + Coin Icon + + + + + {/* Mission completed title */} +

+ Mission Completed! +

+ + {/* Reward amount */} +
+ + +{mission.reward} + + Coin Icon +
+ + {/* Success message */} + {/* + Coins added to your wallet! + */} +
+
+ ) +} + +export default ClaimMissionAnimation \ No newline at end of file diff --git a/client/src/components/screens/Home/DailyMissionsModal.tsx b/client/src/components/screens/Home/DailyMissionsModal.tsx new file mode 100644 index 0000000..ee82743 --- /dev/null +++ b/client/src/components/screens/Home/DailyMissionsModal.tsx @@ -0,0 +1,261 @@ +import { motion, AnimatePresence } from "framer-motion" +import { useState } from "react" +import GolemTalkIcon from "../../../assets/icons/GolemTalkIcon.webp" +import { ClaimMissionAnimation } from "./ClaimMissionAnimation" +import coinIcon from "../../../assets/icons/CoinIcon.webp"; + +interface Mission { + id: string + title: string + description: string + difficulty: 'Easy' | 'Mid' | 'Hard' + reward: number + completed: boolean + claimed?: boolean +} + +interface DailyMissionsModalProps { + /** Player's address for context */ + playerAddress: string + /** Callback to close the modal */ + onClose: () => void +} + +export function DailyMissionsModal({ onClose }: DailyMissionsModalProps) { + const [showCelebration, setShowCelebration] = useState(false) + const [claimedMission, setClaimedMission] = useState(null) + const [missions, setMissions] = useState([ + { + id: "mission_1", + title: "First Steps", + description: "Complete 3 runs in any level", + difficulty: "Easy", + reward: 100, + completed: false, + claimed: false + }, + { + id: "mission_2", + title: "Speed Runner", + description: "Finish a run in under 2 minutes", + difficulty: "Mid", + reward: 250, + completed: false, + claimed: false + }, + { + id: "mission_3", + title: "Coin Master", + description: "Collect 1000 coins in a single run", + difficulty: "Hard", + reward: 500, + completed: true, + claimed: false + } + ]) + + const getDifficultyStyle = (difficulty: Mission['difficulty']) => { + switch (difficulty) { + case 'Easy': + return 'bg-green-500 text-white' + case 'Mid': + return 'bg-orange-500 text-white' + case 'Hard': + return 'bg-red-500 text-white' + default: + return 'bg-gray-500 text-white' + } + } + + const handleClaimReward = (mission: Mission) => { + setClaimedMission(mission) + setShowCelebration(true) + + setMissions(prevMissions => + prevMissions.map(m => + m.id === mission.id ? { ...m, claimed: true } : m + ) + ) + + console.log(`Claiming reward for mission: ${mission.id}, reward: ${mission.reward}`) + } + + const handleCloseCelebration = () => { + setShowCelebration(false) + setClaimedMission(null) + } + + return ( + <> + + {/* Container principal con flex column */} + e.stopPropagation()} + > + {/* Imagen del golem - ahora dentro del flujo normal */} + + + {/* Card del modal con las misiones */} +
+ {/* Título del modal */} +
+

Daily Missions

+

Complete missions to earn rewards!

+
+ + {/* Lista de misiones */} +
+ {missions.map((mission: Mission, index: number) => ( + + {/* Header: Dificultad, Recompensa y Estado */} +
+
+ {/* Etiqueta de dificultad */} + + {mission.difficulty} + + + {/* Recompensa */} +
+ + {mission.reward} + + Coin Icon +
+
+ + {/* Indicador de completado */} + {mission.completed && ( +
+ +
+ )} +
+ + {/* Título y descripción */} +
+

+ {mission.title} +

+

+ {mission.description} +

+
+ + {/* Botón de reclamar */} + {mission.completed && !mission.claimed && ( +
+ handleClaimReward(mission)} + > + Claim Reward + +
+ )} + + {/* Overlay para misiones completadas */} + {mission.completed && ( +
+ )} + + ))} +
+ + {/* Botón para cerrar */} + + Close + +
+ + + + {/* Animación de celebración */} + + {showCelebration && claimedMission && ( + + )} + + + ) +} \ No newline at end of file diff --git a/client/src/components/screens/Home/HomeScreen.tsx b/client/src/components/screens/Home/HomeScreen.tsx index f8704b8..4e97b7e 100644 --- a/client/src/components/screens/Home/HomeScreen.tsx +++ b/client/src/components/screens/Home/HomeScreen.tsx @@ -8,7 +8,8 @@ import BackgroundParticles from "../../shared/BackgroundParticles"; import { characters, getGolemVisualDataById } from "../../../constants/characters"; import { TopBar } from "../../layout/TopBar"; import bannerImg from "../../../assets/icons/banner.webp"; -import { GolemTalkModal } from "./GolemTalkModal"; +//import { GolemTalkModal } from "./GolemTalkModal"; +import { DailyMissionsModal } from "./DailyMissionsModal"; import useAppStore from "../../../zustand/store"; import toast, { Toaster } from 'react-hot-toast'; @@ -277,12 +278,20 @@ export const HomeScreen = memo(function HomeScreen({
- + {/* {showTalkModal && (
)} +
*/} + + + {showTalkModal && ( +
+ +
+ )}
({ server: { port: 3001, - // https: { - // key: fs.readFileSync("mkcert+1-key.pem"), // Path to private key file - // cert: fs.readFileSync("mkcert+1.pem"), // Path to certificate file - // }, + https: { + key: fs.readFileSync("mkcert+1-key.pem"), // Path to private key file + cert: fs.readFileSync("mkcert+1.pem"), // Path to certificate file + }, }, plugins: [ react(),