generated from nhcarrigan/template
feat: unify toast styles and add quest/milestone toast notifications
- Merge .codex-toast and .achievement-toast into a single .game-toast class - Fix storyToast inner class names and replace <button> wrapper with <div> - Add QuestCompleteToast and QuestFailedToast components - Add MilestoneToast for prestige, transcendence, and apotheosis events - Move shared toast container to gameLayout so all toasts stack in one column - Wire quest detection in GameContext to store full Quest objects for toast names - Trigger prestige toast from both auto-prestige and manual prestige panel
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
type GameState,
|
||||
type LoginBonusResult,
|
||||
type NumberFormat,
|
||||
type Quest,
|
||||
type TranscendenceResponse,
|
||||
isStoryChapterUnlocked,
|
||||
} from "@elysium/types";
|
||||
@@ -334,6 +335,61 @@ interface GameContextValue {
|
||||
*/
|
||||
dismissAchievement: (id: string)=> void;
|
||||
|
||||
/**
|
||||
* Queue of newly completed quests (for toast notifications).
|
||||
*/
|
||||
completedQuestToasts: Array<Quest>;
|
||||
|
||||
/**
|
||||
* Remove a quest from the completed toast queue.
|
||||
*/
|
||||
dismissCompletedQuest: (id: string)=> void;
|
||||
|
||||
/**
|
||||
* Queue of newly failed quests (for toast notifications).
|
||||
*/
|
||||
failedQuestToasts: Array<Quest>;
|
||||
|
||||
/**
|
||||
* Remove a quest from the failed toast queue.
|
||||
*/
|
||||
dismissFailedQuest: (id: string)=> void;
|
||||
|
||||
/**
|
||||
* Whether the prestige milestone toast is currently showing.
|
||||
*/
|
||||
showPrestigeToast: boolean;
|
||||
|
||||
/**
|
||||
* Trigger the prestige milestone toast (called from prestigePanel on manual prestige).
|
||||
*/
|
||||
triggerPrestigeToast: ()=> void;
|
||||
|
||||
/**
|
||||
* Dismiss the prestige milestone toast.
|
||||
*/
|
||||
dismissPrestigeToast: ()=> void;
|
||||
|
||||
/**
|
||||
* Whether the transcendence milestone toast is currently showing.
|
||||
*/
|
||||
showTranscendenceToast: boolean;
|
||||
|
||||
/**
|
||||
* Dismiss the transcendence milestone toast.
|
||||
*/
|
||||
dismissTranscendenceToast: ()=> void;
|
||||
|
||||
/**
|
||||
* Whether the apotheosis milestone toast is currently showing.
|
||||
*/
|
||||
showApotheosisToast: boolean;
|
||||
|
||||
/**
|
||||
* Dismiss the apotheosis milestone toast.
|
||||
*/
|
||||
dismissApotheosisToast: ()=> void;
|
||||
|
||||
/**
|
||||
* The player's chosen number display format.
|
||||
*/
|
||||
@@ -514,6 +570,15 @@ export const GameProvider = ({
|
||||
const [ unlockedAchievements, setUnlockedAchievements ] = useState<
|
||||
Array<Achievement>
|
||||
>([]);
|
||||
const [ completedQuestToasts, setCompletedQuestToasts ] = useState<
|
||||
Array<Quest>
|
||||
>([]);
|
||||
const [ failedQuestToasts, setFailedQuestToasts ] = useState<Array<Quest>>(
|
||||
[],
|
||||
);
|
||||
const [ showPrestigeToast, setShowPrestigeToast ] = useState(false);
|
||||
const [ showTranscendenceToast, setShowTranscendenceToast ] = useState(false);
|
||||
const [ showApotheosisToast, setShowApotheosisToast ] = useState(false);
|
||||
const [ lastSavedAt, setLastSavedAt ] = useState<number | null>(null);
|
||||
const [ isSyncing, setIsSyncing ] = useState(false);
|
||||
const [ syncError, setSyncError ] = useState<string | null>(null);
|
||||
@@ -530,8 +595,8 @@ export const GameProvider = ({
|
||||
const isSyncingReference = useRef(false);
|
||||
const rafReference = useRef<number | null>(null);
|
||||
const unlockedAchievementsReference = useRef<Array<Achievement>>([]);
|
||||
const newlyCompletedQuestsCountReference = useRef(0);
|
||||
const newlyFailedQuestsCountReference = useRef(0);
|
||||
const newlyCompletedQuestsReference = useRef<Array<Quest>>([]);
|
||||
const newlyFailedQuestsReference = useRef<Array<Quest>>([]);
|
||||
const signatureReference = useRef<string | null>(
|
||||
localStorage.getItem("elysium_save_signature"),
|
||||
);
|
||||
@@ -949,17 +1014,17 @@ export const GameProvider = ({
|
||||
);
|
||||
|
||||
// Detect newly completed quests
|
||||
newlyCompletedQuestsCountReference.current = next.quests.filter(
|
||||
newlyCompletedQuestsReference.current = next.quests.filter(
|
||||
(q, index) => {
|
||||
return (
|
||||
previous.quests[index]?.status === "active"
|
||||
&& q.status === "completed"
|
||||
);
|
||||
},
|
||||
).length;
|
||||
);
|
||||
|
||||
// Detect newly failed quests
|
||||
newlyFailedQuestsCountReference.current = next.quests.filter(
|
||||
newlyFailedQuestsReference.current = next.quests.filter(
|
||||
(q, index) => {
|
||||
const previousFailedAt = previous.quests[index]?.lastFailedAt;
|
||||
return (
|
||||
@@ -967,7 +1032,7 @@ export const GameProvider = ({
|
||||
&& q.lastFailedAt !== previousFailedAt
|
||||
);
|
||||
},
|
||||
).length;
|
||||
);
|
||||
|
||||
return next;
|
||||
});
|
||||
@@ -987,24 +1052,30 @@ export const GameProvider = ({
|
||||
unlockedAchievementsReference.current = [];
|
||||
}
|
||||
|
||||
if (newlyCompletedQuestsCountReference.current > 0) {
|
||||
if (newlyCompletedQuestsReference.current.length > 0) {
|
||||
setCompletedQuestToasts((previous) => {
|
||||
return [ ...previous, ...newlyCompletedQuestsReference.current ];
|
||||
});
|
||||
if (enableSoundsReference.current) {
|
||||
playSound("questCompleted");
|
||||
}
|
||||
if (enableNotificationsReference.current) {
|
||||
sendNotification("📜 Quest Complete!", "A quest has been completed.");
|
||||
}
|
||||
newlyCompletedQuestsCountReference.current = 0;
|
||||
newlyCompletedQuestsReference.current = [];
|
||||
}
|
||||
|
||||
if (newlyFailedQuestsCountReference.current > 0) {
|
||||
if (newlyFailedQuestsReference.current.length > 0) {
|
||||
setFailedQuestToasts((previous) => {
|
||||
return [ ...previous, ...newlyFailedQuestsReference.current ];
|
||||
});
|
||||
if (enableSoundsReference.current) {
|
||||
playSound("questFailed");
|
||||
}
|
||||
if (enableNotificationsReference.current) {
|
||||
sendNotification("💀 Quest Failed!", "A quest has failed.");
|
||||
}
|
||||
newlyFailedQuestsCountReference.current = 0;
|
||||
newlyFailedQuestsReference.current = [];
|
||||
}
|
||||
|
||||
// Auto-save every 30 seconds (skip if a force sync is in-flight to avoid signature collisions)
|
||||
@@ -1054,6 +1125,7 @@ export const GameProvider = ({
|
||||
isAutoPrestigingReference.current = true;
|
||||
void prestigeApi({}).
|
||||
then(async() => {
|
||||
setShowPrestigeToast(true);
|
||||
if (enableSoundsReference.current) {
|
||||
playSound("prestige");
|
||||
}
|
||||
@@ -1443,6 +1515,7 @@ export const GameProvider = ({
|
||||
|
||||
const transcend = useCallback(async() => {
|
||||
const result = await transcendApi({});
|
||||
setShowTranscendenceToast(true);
|
||||
if (enableSoundsReference.current) {
|
||||
playSound("transcendence");
|
||||
}
|
||||
@@ -1455,6 +1528,7 @@ export const GameProvider = ({
|
||||
|
||||
const apotheosis = useCallback(async() => {
|
||||
const result = await achieveApotheosisApi({});
|
||||
setShowApotheosisToast(true);
|
||||
if (enableSoundsReference.current) {
|
||||
playSound("apotheosis");
|
||||
}
|
||||
@@ -1733,6 +1807,38 @@ export const GameProvider = ({
|
||||
setBattleResult(null);
|
||||
}, []);
|
||||
|
||||
const dismissCompletedQuest = useCallback((id: string) => {
|
||||
setCompletedQuestToasts((previous) => {
|
||||
return previous.filter((q) => {
|
||||
return q.id !== id;
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const dismissFailedQuest = useCallback((id: string) => {
|
||||
setFailedQuestToasts((previous) => {
|
||||
return previous.filter((q) => {
|
||||
return q.id !== id;
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const triggerPrestigeToast = useCallback(() => {
|
||||
setShowPrestigeToast(true);
|
||||
}, []);
|
||||
|
||||
const dismissPrestigeToast = useCallback(() => {
|
||||
setShowPrestigeToast(false);
|
||||
}, []);
|
||||
|
||||
const dismissTranscendenceToast = useCallback(() => {
|
||||
setShowTranscendenceToast(false);
|
||||
}, []);
|
||||
|
||||
const dismissApotheosisToast = useCallback(() => {
|
||||
setShowApotheosisToast(false);
|
||||
}, []);
|
||||
|
||||
const dismissAchievement = useCallback((id: string) => {
|
||||
setUnlockedAchievements((previous) => {
|
||||
return previous.filter((a) => {
|
||||
@@ -1829,18 +1935,25 @@ export const GameProvider = ({
|
||||
challengeBoss,
|
||||
collectExploration,
|
||||
completeChapter,
|
||||
completedQuestToasts,
|
||||
craftRecipe,
|
||||
currentSchemaVersion,
|
||||
dismissAchievement,
|
||||
dismissApotheosisToast,
|
||||
dismissBattle,
|
||||
dismissCodexEntry,
|
||||
dismissCompletedQuest,
|
||||
dismissFailedQuest,
|
||||
dismissLoginBonus,
|
||||
dismissOfflineGold,
|
||||
dismissPrestigeToast,
|
||||
dismissStoryChapter,
|
||||
dismissTranscendenceToast,
|
||||
enableNotifications,
|
||||
enableSounds,
|
||||
equipItem,
|
||||
error,
|
||||
failedQuestToasts,
|
||||
forceSync,
|
||||
formatNumber,
|
||||
handleClick,
|
||||
@@ -1860,6 +1973,9 @@ export const GameProvider = ({
|
||||
setEnableNotifications,
|
||||
setEnableSounds,
|
||||
setNumberFormat,
|
||||
showApotheosisToast,
|
||||
showPrestigeToast,
|
||||
showTranscendenceToast,
|
||||
startExploration,
|
||||
startQuest,
|
||||
state,
|
||||
@@ -1868,6 +1984,7 @@ export const GameProvider = ({
|
||||
toggleAutoPrestige,
|
||||
toggleAutoQuest,
|
||||
transcend,
|
||||
triggerPrestigeToast,
|
||||
unlockedAchievements,
|
||||
unlockedCodexEntryIds,
|
||||
unlockedStoryChapterIds,
|
||||
@@ -1875,6 +1992,8 @@ export const GameProvider = ({
|
||||
}, [
|
||||
apotheosis,
|
||||
battleResult,
|
||||
completedQuestToasts,
|
||||
failedQuestToasts,
|
||||
formatNumber,
|
||||
buyAdventurer,
|
||||
buyEchoUpgrade,
|
||||
@@ -1887,11 +2006,16 @@ export const GameProvider = ({
|
||||
craftRecipe,
|
||||
currentSchemaVersion,
|
||||
dismissAchievement,
|
||||
dismissApotheosisToast,
|
||||
dismissBattle,
|
||||
dismissCodexEntry,
|
||||
dismissCompletedQuest,
|
||||
dismissFailedQuest,
|
||||
dismissLoginBonus,
|
||||
dismissOfflineGold,
|
||||
dismissPrestigeToast,
|
||||
dismissStoryChapter,
|
||||
dismissTranscendenceToast,
|
||||
enableNotifications,
|
||||
enableSounds,
|
||||
equipItem,
|
||||
@@ -1914,6 +2038,9 @@ export const GameProvider = ({
|
||||
setEnableNotifications,
|
||||
setEnableSounds,
|
||||
setNumberFormat,
|
||||
showApotheosisToast,
|
||||
showPrestigeToast,
|
||||
showTranscendenceToast,
|
||||
startExploration,
|
||||
startQuest,
|
||||
state,
|
||||
@@ -1922,6 +2049,7 @@ export const GameProvider = ({
|
||||
toggleAutoPrestige,
|
||||
toggleAutoQuest,
|
||||
transcend,
|
||||
triggerPrestigeToast,
|
||||
unlockedAchievements,
|
||||
unlockedCodexEntryIds,
|
||||
unlockedStoryChapterIds,
|
||||
|
||||
Reference in New Issue
Block a user