Feature/achievement handling (#2)
* Add NewAchievement type to ScoreBreakdown * Show toast on achievement unlock after game completion * Show toast on achievement unlock after versus win
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import { toast } from 'sonner';
|
||||
import { getMovieDetails } from '@/api/movies';
|
||||
import type {
|
||||
ChainLink,
|
||||
@@ -210,6 +211,16 @@ export const useGameStore = create<GameState>((set, get) => ({
|
||||
completeGameSession(sessionId, newChain, hintsUsed)
|
||||
.then((backendScore) => {
|
||||
set({ score: backendScore });
|
||||
|
||||
// Show toast for each newly earned achievement
|
||||
if (backendScore.newAchievements?.length) {
|
||||
for (const achievement of backendScore.newAchievements) {
|
||||
toast.success(`Achievement Unlocked: ${achievement.name}`, {
|
||||
description: achievement.description,
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
set({ validationError: 'Failed to calculate score.' });
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { create } from 'zustand';
|
||||
import { toast } from 'sonner';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import { createSocket } from '@/lib/create-socket';
|
||||
import type { ScoreBreakdown } from '@/types';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import type { ScoreBreakdown, NewAchievement } from '@/types';
|
||||
|
||||
export type LobbyState =
|
||||
| 'idle'
|
||||
@@ -117,7 +119,14 @@ export const useVersusStore = create<VersusState>((set, get) => ({
|
||||
set({ opponentFinished: true });
|
||||
});
|
||||
|
||||
socket.on('match-finished', (data) => {
|
||||
socket.on('match-finished', (data: {
|
||||
winnerId: string | null;
|
||||
player1: { id: string; username: string };
|
||||
player2: { id: string; username: string };
|
||||
player1Score: ScoreBreakdown | null;
|
||||
player2Score: ScoreBreakdown | null;
|
||||
newAchievements?: NewAchievement[];
|
||||
}) => {
|
||||
set({
|
||||
lobbyState: 'finished',
|
||||
matchResult: {
|
||||
@@ -128,6 +137,17 @@ export const useVersusStore = create<VersusState>((set, get) => ({
|
||||
player2Score: data.player2Score,
|
||||
},
|
||||
});
|
||||
|
||||
// Show achievement toasts if the current user is the winner
|
||||
const currentUserId = useAuthStore.getState().user?.id;
|
||||
if (data.newAchievements?.length && currentUserId === data.winnerId) {
|
||||
for (const achievement of data.newAchievements) {
|
||||
toast.success(`Achievement Unlocked: ${achievement.name}`, {
|
||||
description: achievement.description,
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('match-cancelled', () => {
|
||||
|
||||
@@ -29,6 +29,13 @@ export interface PresetMoviePair extends MoviePair {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface NewAchievement {
|
||||
key: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface ScoreBreakdown {
|
||||
baseScore: number;
|
||||
chainLength: number;
|
||||
@@ -41,6 +48,7 @@ export interface ScoreBreakdown {
|
||||
elapsedSeconds: number;
|
||||
totalScore: number;
|
||||
par?: number;
|
||||
newAchievements?: NewAchievement[];
|
||||
}
|
||||
|
||||
/** Number of actor-movie pair "links" in a chain (excludes starting movie) */
|
||||
|
||||
Reference in New Issue
Block a user