Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/dev-dist/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ define(['./workbox-86c9b217'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.g8ftgsh6a18"
"revision": "0.vs5n5ncup7"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
Expand Down
18 changes: 13 additions & 5 deletions client/src/components/screens/Game/CoinsRewardCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ export interface ScoreRange {
}

// Predefined score ranges for coin rewards
// const SCORE_RANGES: ScoreRange[] = [
// { min: 0, max: 999, coins: 50, label: "Beginner" },
// { min: 1000, max: 2999, coins: 100, label: "Runner" },
// { min: 3000, max: 5999, coins: 250, label: "Speedster" },
// { min: 6000, max: 9999, coins: 500, label: "Champion" },
// { min: 10000, max: Infinity, coins: 1000, label: "Legend" }
// ];

const SCORE_RANGES: ScoreRange[] = [
{ min: 0, max: 999, coins: 50, label: "Beginner" },
{ min: 1000, max: 2999, coins: 100, label: "Runner" },
{ min: 3000, max: 5999, coins: 250, label: "Speedster" },
{ min: 6000, max: 9999, coins: 500, label: "Champion" },
{ min: 10000, max: Infinity, coins: 1000, label: "Legend" }
{ min: 0, max: 250, coins: 100, label: "Beginner" },
{ min: 251, max: 500, coins: 250, label: "Runner" },
{ min: 501, max: 1000, coins: 400, label: "Speedster" },
{ min: 1001, max: 2000, coins: 500, label: "Champion" },
{ min: 2000, max: Infinity, coins: 1000, label: "Legend" }
];

/**
Expand Down
198 changes: 162 additions & 36 deletions client/src/components/screens/Game/GameOverModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ interface GameOverModalProps {
isProcessingReward?: boolean;
rewardError?: string | null;
rewardTxStatus?: 'PENDING' | 'SUCCESS' | 'REJECTED' | null;

// Props for mission feedback
isMissionCompleting?: boolean;
missionError?: string | null;
completedMissions?: Array<{
mission: { id: number; description: string };
reason: string;
}>;
worldId?: number;
golemId?: number;
}

const GameOverModal: React.FC<GameOverModalProps> = ({
Expand All @@ -26,55 +36,50 @@ const GameOverModal: React.FC<GameOverModalProps> = ({
isProcessingReward = false,
rewardError = null,
rewardTxStatus = null,
isMissionCompleting = false,
missionError = null,
completedMissions = [],
}) => {
const isNewRecord = score > record;

// Add usePlayer hook to refresh data
const { refetch: refetchPlayer } = usePlayer();

// Calculate coin reward based on score
const coinReward = useCoinReward(score);
console.log('Coin Reward:', coinReward);

// Function to handle Restart click
// Calculated variables
const isProcessing = isProcessingReward || isMissionCompleting;
const hasErrors = rewardError || missionError;
const hasCompletedMissions = completedMissions.length > 0;



const handleRestartClick = async () => {
audioManager.playClickSound();

// Only fetch if the transaction was successful
if (rewardTxStatus === 'SUCCESS') {
try {
console.log('Refreshing player data before restart...');
await refetchPlayer();
console.log('Player data refreshed successfully');
} catch (err) {
console.error('Error refreshing player data:', err);
// Silently handle refresh error
}
}

// Call the restart handler from the parent component
onRestart();
};

// Function to handle Exit click
const handleExitClick = async () => {
audioManager.playClickSound();

// Only fetch if the transaction was successful
if (rewardTxStatus === 'SUCCESS') {
try {
console.log('Refreshing player data before exit...');
await refetchPlayer();
console.log('Player data refreshed successfully');
} catch (err) {
console.error('Error refreshing player data:', err);
// Silently handle refresh error
}
}

// Call the exit handler from the parent component
onExit();
};

// Determine the transaction status message
const getTransactionStatusMessage = () => {
if (rewardError) {
return `Error: ${rewardError}`;
Expand All @@ -96,16 +101,33 @@ const GameOverModal: React.FC<GameOverModalProps> = ({
return null;
};

// Determine the color of the transaction status
const getMissionStatusMessage = () => {
if (missionError) {
return `Mission Error: ${missionError}`;
}

if (isMissionCompleting) {
return 'Checking mission completion...';
}

return null;
};

const getStatusColor = () => {
if (rewardError) return 'bg-red-500 text-white';
if (rewardTxStatus === 'SUCCESS') return 'bg-green-500 text-white';
if (rewardTxStatus === 'REJECTED') return 'bg-orange-500 text-white';
return 'bg-blue-500 text-white';
};

const transactionStatusMessage = getTransactionStatusMessage();
const getMissionStatusColor = () => {
if (missionError) return 'bg-red-500 text-white';
if (hasCompletedMissions) return 'bg-green-500 text-white';
return 'bg-blue-500 text-white';
};

const transactionStatusMessage = getTransactionStatusMessage();
const missionStatusMessage = getMissionStatusMessage();

return (
<AnimatePresence>
Expand All @@ -117,7 +139,7 @@ const GameOverModal: React.FC<GameOverModalProps> = ({
exit={{ opacity: 0 }}
>
<motion.div
className="bg-surface rounded-xl border-4 border-primary w-full max-w-xs mx-4 p-6 flex flex-col items-center"
className="bg-surface rounded-xl border-4 border-primary w-full max-w-xs mx-4 p-6 flex flex-col items-center max-h-[90vh] overflow-y-auto"
initial={{ scale: 0.8, y: 50 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.8, y: 50 }}
Expand Down Expand Up @@ -179,7 +201,7 @@ const GameOverModal: React.FC<GameOverModalProps> = ({

{/* Tier Badge */}
<motion.div
className="bg-gradient-to-r from-yellow-400 to-yellow-600 text-dark text-center py-1 px-3 rounded-lg mb-3"
className="bg-gradient-to-r from-yellow-400 to-yellow-600 text-dark text-center py-1 px-3 rounded-[5px] mb-3"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
Expand All @@ -198,9 +220,9 @@ const GameOverModal: React.FC<GameOverModalProps> = ({
<div className="mb-1">
{coinReward.pointsToNextTier} points to {coinReward.nextTier?.label}
</div>
<div className="w-full bg-dark/20 rounded-full h-2">
<div className="w-full bg-dark/20 rounded-[5px] h-2">
<motion.div
className="bg-gradient-to-r from-yellow-400 to-yellow-600 h-2 rounded-full"
className="bg-gradient-to-r from-yellow-400 to-yellow-600 h-2 rounded-[5px]"
initial={{ width: 0 }}
animate={{ width: `${coinReward.percentage}%` }}
transition={{ delay: 0.7, duration: 0.8, ease: "easeOut" }}
Expand All @@ -212,7 +234,7 @@ const GameOverModal: React.FC<GameOverModalProps> = ({
{/* New Record Banner */}
{isNewRecord && (
<motion.div
className="bg-golem-gradient text-cream text-center py-2 rounded-lg mt-4 font-luckiest"
className="bg-golem-gradient text-cream text-center py-2 rounded-[5px] mt-4 font-luckiest"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
Expand All @@ -221,38 +243,142 @@ const GameOverModal: React.FC<GameOverModalProps> = ({
</motion.div>
)}

{/* Transaction Status Message */}
{/* Processing Status Messages */}
{transactionStatusMessage && (
<motion.div
className={`${getStatusColor()} text-center py-2 px-3 rounded-lg mt-3 font-luckiest text-sm`}
className={`${getStatusColor()} text-center py-2 px-3 rounded-[5px] mt-3 font-luckiest text-sm`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
{transactionStatusMessage}
</motion.div>
)}

{/* Mission Status */}
{missionStatusMessage && (
<motion.div
className={`${getMissionStatusColor()} text-center py-2 px-3 rounded-[5px] mt-2 font-luckiest text-sm`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
{missionStatusMessage}
</motion.div>
)}

{/* Mission Completion Success */}
{hasCompletedMissions && (
<motion.div
className="bg-green-50 border-2 border-green-200 rounded-[5px] p-3 mt-3"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.5, type: 'spring' }}
>
<div className="text-center mb-2">
<span className="font-luckiest text-green-800 text-sm">
🎉 {completedMissions.length} Mission{completedMissions.length > 1 ? 's' : ''} Completed!
</span>
</div>

<div className="space-y-2 max-h-24 overflow-y-auto">
{completedMissions.map((cm, index) => (
<motion.div
key={cm.mission.id}
className="bg-white rounded-[5px] p-2 border-green-100"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.6 + index * 0.1 }}
>
<div className="flex items-start justify-between gap-2">
<p className="font-rubik text-xs text-green-600 leading-relaxed flex-1">
{cm.reason}
</p>
<span className="text-green-500 font-bold text-sm flex-shrink-0">✓</span>
</div>
</motion.div>
))}
</div>

<motion.div
className="text-center mt-2 p-2 bg-yellow-100 rounded border border-yellow-200"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
>
<p className="text-xs text-yellow-700 font-luckiest">
💰 Check Daily Missions to claim rewards!
</p>
</motion.div>
</motion.div>
)}

{/* Error Messages */}
{hasErrors && !isProcessing && (
<motion.div
className="bg-red-50 border-2 border-red-200 rounded-lg p-3 mt-3"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<h3 className="font-luckiest text-red-800 text-sm mb-2 text-center">
⚠️ Processing Issues
</h3>
{rewardError && (
<p className="font-rubik text-xs text-red-700 mb-1">
Reward Error: {rewardError}
</p>
)}
{missionError && (
<p className="font-rubik text-xs text-red-700">
Mission Error: {missionError}
</p>
)}
</motion.div>
)}
</div>

{/* Buttons */}
<div className="flex w-full gap-4">
<motion.button
className="flex-1 bg-dark text-cream py-3 font-luckiest rounded-[5px]"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="flex-1 bg-dark text-cream py-3 font-luckiest rounded-[5px] disabled:opacity-50 disabled:cursor-not-allowed"
whileHover={!isProcessing ? { scale: 1.05 } : {}}
whileTap={!isProcessing ? { scale: 0.95 } : {}}
onClick={handleExitClick}
disabled={isProcessingReward && rewardTxStatus === 'PENDING'}
disabled={isProcessing}
>
EXIT
{isProcessing ? (
<div className="flex items-center justify-center gap-2">
<motion.div
className="w-3 h-3 border border-cream border-t-transparent"
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
/>
<span className="text-xs">WAIT</span>
</div>
) : (
'EXIT'
)}
</motion.button>
<motion.button
className="flex-1 btn-cr-yellow py-3 font-luckiest rounded-[5px]"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="flex-1 btn-cr-yellow py-3 font-luckiest rounded-[5px] disabled:opacity-50 disabled:cursor-not-allowed"
whileHover={!isProcessing ? { scale: 1.05 } : {}}
whileTap={!isProcessing ? { scale: 0.95 } : {}}
onClick={handleRestartClick}
disabled={isProcessingReward && rewardTxStatus === 'PENDING'}
disabled={isProcessing}
>
RESTART
{isProcessing ? (
<div className="flex items-center justify-center gap-2">
<motion.div
className="w-3 h-3 border border-dark border-t-transparent"
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
/>
<span className="text-xs">WAIT</span>
</div>
) : (
'RESTART'
)}
</motion.button>
</div>
</motion.div>
Expand Down
Loading