All files / src/redux achievementSlice.ts

85.1% Statements 40/47
56.25% Branches 9/16
100% Functions 9/9
83.72% Lines 36/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126  2x                 2x 14x     14x       2x 2x                         2x             2x                 8x     8x 1x 1x 1x       7x 7x 7x                         7x 18x       7x       2x 2x 2x               7x 7x 7x 7x 7x                 7x         1x 1x       1x 1x 1x 1x         2x   2x
// src/redux/achievementSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import {
    InitialAchievementMetrics,
    AchievementMetrics,
    AchievementDetails,
    AchievementMetricValue,
} from '../types';
 
// Helper function to serialize dates
const serializeValue = (value: AchievementMetricValue): string | number | boolean => {
    Iif (value instanceof Date) {
        return value.toISOString();
    }
    return value;
};
 
// Helper function to process metrics for storage
const processMetrics = (metrics: AchievementMetrics): Record<string, (string | number | boolean)[]> => {
    return Object.entries(metrics).reduce((acc, [key, values]) => ({
        ...acc,
        [key]: values.map(serializeValue)
    }), {});
};
 
export interface AchievementState {
    metrics: Record<string, (string | number | boolean)[]>;
    unlockedAchievements: string[];
    storageKey: string | null;
    pendingNotifications: AchievementDetails[];
}
 
const initialState: AchievementState = {
    metrics: {},
    unlockedAchievements: [],
    storageKey: null,
    pendingNotifications: [],
};
 
export const achievementSlice = createSlice({
    name: 'achievements',
    initialState,
    reducers: {
        initialize: (state, action: PayloadAction<{ 
            initialState?: InitialAchievementMetrics & { unlockedAchievements?: string[] }; 
            storageKey: string 
        }>) => {
            // Set storage key
            state.storageKey = action.payload.storageKey;
 
            // If initialState is undefined, reset to empty state
            if (action.payload.initialState === undefined) {
                state.metrics = {};
                state.unlockedAchievements = [];
                return;
            }
 
            // Load from storage first
            if (action.payload.storageKey) {
                const stored = localStorage.getItem(action.payload.storageKey);
                Iif (stored) {
                    try {
                        const parsed = JSON.parse(stored);
                        state.metrics = parsed.metrics || {};
                        state.unlockedAchievements = parsed.unlockedAchievements || [];
                        return;
                    } catch (error) {
                        console.error('Error parsing stored achievements:', error);
                    }
                }
            }
 
            // If no storage or parse error, use initial state
            const { unlockedAchievements, ...metrics } = action.payload.initialState;
            state.metrics = Object.entries(metrics).reduce((acc, [key, value]) => ({
                ...acc,
                [key]: Array.isArray(value) ? value.map(serializeValue) : [serializeValue(value as AchievementMetricValue)]
            }), {});
            state.unlockedAchievements = unlockedAchievements || [];
        },
 
        setMetrics: (state, action: PayloadAction<AchievementMetrics>) => {
            state.metrics = processMetrics(action.payload);
            if (state.storageKey !== null) {
                localStorage.setItem(state.storageKey, JSON.stringify({
                    metrics: state.metrics,
                    unlockedAchievements: state.unlockedAchievements
                }));
            }
        },
 
        unlockAchievement: (state, action: PayloadAction<AchievementDetails>) => {
            if (!state.unlockedAchievements.includes(action.payload.achievementId)) {
                state.unlockedAchievements.push(action.payload.achievementId);
                state.pendingNotifications.push(action.payload);
                if (state.storageKey !== null) {
                    localStorage.setItem(state.storageKey, JSON.stringify({
                        metrics: state.metrics,
                        unlockedAchievements: state.unlockedAchievements
                    }));
                }
            }
        },
 
        clearNotifications: (state) => {
            state.pendingNotifications = [];
        },
 
        resetAchievements: (state) => {
            // Remove from localStorage instead of setting empty state
            if (state.storageKey) {
                localStorage.removeItem(state.storageKey);
            }
 
            // Reset to empty state
            state.metrics = {};
            state.unlockedAchievements = [];
            state.pendingNotifications = [];
            state.storageKey = null;  // Clear the storage key to prevent re-initialization
        },
    },
});
 
export const { initialize, setMetrics, resetAchievements, unlockAchievement, clearNotifications } = achievementSlice.actions;
 
export default achievementSlice.reducer;