generated from nhcarrigan/template
feat: debug panel with force unlocks and hard reset (#65)
## Summary - Adds a new **Debug** tab to the game UI with two self-service tools for players with broken save state - **Force Unlocks**: scans the player's save and grants any zones, quests, bosses, and exploration areas they've earned but that are still locked — shows a breakdown of what was unlocked (or reports nothing needed fixing) - **Hard Reset**: wipes progress back to a fresh save (preserving lifetime stats), guarded behind a confirmation modal to prevent accidental clicks ## Files added - `apps/api/src/routes/debug.ts` — two POST endpoints (`/force-unlocks`, `/hard-reset`) - `apps/web/src/components/game/debugPanel.tsx` — the Debug tab UI - `apps/web/src/components/ui/confirmationModal.tsx` — reusable confirmation modal ## Files modified - `apps/api/src/index.ts` — registers the debug router - `packages/types/src/interfaces/api.ts` — adds `ForceUnlocksResponse` type - `packages/types/src/index.ts` — exports the new type - `apps/web/src/api/client.ts` — adds `forceUnlocks()` and `debugHardReset()` API calls - `apps/web/src/context/gameContext.tsx` — wires both functions into game context - `apps/web/src/components/game/gameLayout.tsx` — adds the Debug tab - `apps/web/src/styles.css` — styles for action buttons, cards, result messages, and confirmation modal ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #65 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #65.
This commit is contained in:
@@ -42,6 +42,8 @@ import {
|
||||
challengeBoss as challengeBossApi,
|
||||
collectExploration as collectExplorationApi,
|
||||
craftRecipe as craftRecipeApi,
|
||||
debugHardReset as debugHardResetApi,
|
||||
forceUnlocks as forceUnlocksApi,
|
||||
loadGame,
|
||||
prestige as prestigeApi,
|
||||
resetProgress as resetProgressApi,
|
||||
@@ -546,6 +548,24 @@ interface GameContextValue {
|
||||
*/
|
||||
resetProgress: ()=> Promise<void>;
|
||||
|
||||
/**
|
||||
* Force-unlock any zones, quests, and bosses the player has earned but that
|
||||
* are still incorrectly locked due to a state bug.
|
||||
* @returns Counts of what was corrected.
|
||||
*/
|
||||
forceUnlocks: ()=> Promise<{
|
||||
bossesUnlocked: number;
|
||||
explorationUnlocked: number;
|
||||
questsUnlocked: number;
|
||||
zonesUnlocked: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Completely wipe the player's progress back to a brand-new save via the
|
||||
* debug endpoint.
|
||||
*/
|
||||
debugHardReset: ()=> Promise<void>;
|
||||
|
||||
/**
|
||||
* Last auto-boss fight result — null until the first auto fight completes or
|
||||
* when auto-boss is toggled off.
|
||||
@@ -2025,6 +2045,61 @@ export const GameProvider = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const forceUnlocks = useCallback(async() => {
|
||||
try {
|
||||
const data = await forceUnlocksApi();
|
||||
setState(data.state);
|
||||
if (data.signature !== undefined) {
|
||||
signatureReference.current = data.signature;
|
||||
localStorage.setItem("elysium_save_signature", data.signature);
|
||||
}
|
||||
return {
|
||||
bossesUnlocked: data.bossesUnlocked,
|
||||
explorationUnlocked: data.explorationUnlocked,
|
||||
questsUnlocked: data.questsUnlocked,
|
||||
zonesUnlocked: data.zonesUnlocked,
|
||||
};
|
||||
} catch (error_: unknown) {
|
||||
setError(
|
||||
error_ instanceof Error
|
||||
? error_.message
|
||||
: "Failed to force unlocks",
|
||||
);
|
||||
return {
|
||||
bossesUnlocked: 0,
|
||||
explorationUnlocked: 0,
|
||||
questsUnlocked: 0,
|
||||
zonesUnlocked: 0,
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const debugHardReset = useCallback(async() => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await debugHardResetApi();
|
||||
setState(data.state);
|
||||
setLastSavedAt(data.state.player.lastSavedAt);
|
||||
setSchemaOutdated(false);
|
||||
setOfflineGold(0);
|
||||
setOfflineEssence(0);
|
||||
setLoginBonus(null);
|
||||
if (data.signature !== undefined) {
|
||||
signatureReference.current = data.signature;
|
||||
localStorage.setItem("elysium_save_signature", data.signature);
|
||||
}
|
||||
} catch (error_: unknown) {
|
||||
setError(
|
||||
error_ instanceof Error
|
||||
? error_.message
|
||||
: "Failed to reset progress",
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const dismissLoginBonus = useCallback(() => {
|
||||
setLoginBonus(null);
|
||||
}, []);
|
||||
@@ -2054,6 +2129,7 @@ export const GameProvider = ({
|
||||
completedQuestToasts,
|
||||
craftRecipe,
|
||||
currentSchemaVersion,
|
||||
debugHardReset,
|
||||
dismissAchievement,
|
||||
dismissApotheosisToast,
|
||||
dismissBattle,
|
||||
@@ -2072,6 +2148,7 @@ export const GameProvider = ({
|
||||
failedQuestToasts,
|
||||
flushBossLoreToasts,
|
||||
forceSync,
|
||||
forceUnlocks,
|
||||
formatNumber,
|
||||
handleClick,
|
||||
isLoading,
|
||||
@@ -2125,6 +2202,7 @@ export const GameProvider = ({
|
||||
completeChapter,
|
||||
craftRecipe,
|
||||
currentSchemaVersion,
|
||||
debugHardReset,
|
||||
dismissAchievement,
|
||||
dismissApotheosisToast,
|
||||
dismissBattle,
|
||||
@@ -2142,6 +2220,7 @@ export const GameProvider = ({
|
||||
error,
|
||||
flushBossLoreToasts,
|
||||
forceSync,
|
||||
forceUnlocks,
|
||||
handleClick,
|
||||
isLoading,
|
||||
isSyncing,
|
||||
|
||||
Reference in New Issue
Block a user