This commit is contained in:
2025-12-08 12:19:53 +00:00
parent 9eeb88e9ea
commit 81674f8094
5 changed files with 324 additions and 86 deletions
+23
View File
@@ -11,12 +11,27 @@ export interface Achievement {
secret?: boolean;
}
// Track AI message count in localStorage
const AI_MESSAGE_COUNT_KEY = 'ai-message-count';
export const incrementAiMessageCount = (): number => {
const current = parseInt(localStorage.getItem(AI_MESSAGE_COUNT_KEY) || '0', 10);
const newCount = current + 1;
localStorage.setItem(AI_MESSAGE_COUNT_KEY, newCount.toString());
return newCount;
};
export const getAiMessageCount = (): number => {
return parseInt(localStorage.getItem(AI_MESSAGE_COUNT_KEY) || '0', 10);
};
interface AchievementsContextType {
achievements: Achievement[];
unlockAchievement: (id: string) => void;
getUnlockedCount: () => number;
getTotalCount: () => number;
timeOnSite: number;
checkAiAchievements: () => void;
}
const defaultAchievements: Achievement[] = [
@@ -214,6 +229,13 @@ export const AchievementsProvider = ({ children }: { children: ReactNode }) => {
return achievements.length;
}, [achievements]);
// Check AI message achievements
const checkAiAchievements = useCallback(() => {
const count = getAiMessageCount();
if (count >= 5) unlockAchievement('ai_conversation');
if (count >= 20) unlockAchievement('ai_long_chat');
}, [unlockAchievement]);
// Save achievements
useEffect(() => {
localStorage.setItem('achievements', JSON.stringify(achievements));
@@ -227,6 +249,7 @@ export const AchievementsProvider = ({ children }: { children: ReactNode }) => {
getUnlockedCount,
getTotalCount,
timeOnSite,
checkAiAchievements,
}}
>
{children}
+64 -8
View File
@@ -7,7 +7,7 @@ interface SettingsContextType {
setCrtEnabled: (enabled: boolean) => void;
soundEnabled: boolean;
setSoundEnabled: (enabled: boolean) => void;
cryptoConsent: boolean;
cryptoConsent: boolean | null; // null = never asked this session
setCryptoConsent: (consent: boolean) => void;
playSound: (type: SoundType) => void;
hashrate: number;
@@ -16,6 +16,8 @@ interface SettingsContextType {
setTotalHashes: (hashes: number) => void;
acceptedHashes: number;
setAcceptedHashes: (hashes: number) => void;
audioBlocked: boolean;
resetAudioContext: () => void;
}
const SettingsContext = createContext<SettingsContextType | undefined>(undefined);
@@ -31,11 +33,30 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
return saved !== null ? JSON.parse(saved) : true;
});
const [cryptoConsent, setCryptoConsent] = useState(() => {
// Crypto consent: null = needs prompt, true = accepted, false = declined
// On session start, if user previously declined, we reset to null to re-prompt
const [cryptoConsent, setCryptoConsentState] = useState<boolean | null>(() => {
const saved = localStorage.getItem('cryptoConsent');
return saved !== null ? JSON.parse(saved) : false;
if (saved === null) return null; // Never set
const parsed = JSON.parse(saved);
// If declined (false), return null to re-prompt on new session
// We use sessionStorage to track if we've already shown the prompt this session
const sessionPrompted = sessionStorage.getItem('cryptoConsentPrompted');
if (parsed === false && !sessionPrompted) {
return null; // Re-prompt
}
return parsed;
});
const setCryptoConsent = (consent: boolean) => {
setCryptoConsentState(consent);
localStorage.setItem('cryptoConsent', JSON.stringify(consent));
sessionStorage.setItem('cryptoConsentPrompted', 'true');
};
const [audioBlocked, setAudioBlocked] = useState(false);
const [audioFailCount, setAudioFailCount] = useState(0);
const [hashrate, setHashrate] = useState(0);
const [totalHashes, setTotalHashes] = useState(0);
const [acceptedHashes, setAcceptedHashes] = useState(0);
@@ -81,9 +102,15 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
localStorage.setItem('soundEnabled', JSON.stringify(soundEnabled));
}, [soundEnabled]);
useEffect(() => {
localStorage.setItem('cryptoConsent', JSON.stringify(cryptoConsent));
}, [cryptoConsent]);
// Reset audio context to try again after user interaction
const resetAudioContext = useCallback(() => {
if (audioContextRef.current) {
audioContextRef.current.close();
audioContextRef.current = null;
}
setAudioBlocked(false);
setAudioFailCount(0);
}, []);
const playSound = useCallback((type: SoundType) => {
// Use ref to ensure we always have the latest value
@@ -91,6 +118,21 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
try {
const audioContext = getAudioContext();
// Check if context is blocked
if (audioContext.state === 'suspended') {
audioContext.resume().catch(() => {
setAudioFailCount(prev => {
const newCount = prev + 1;
if (newCount >= 3) {
setAudioBlocked(true);
}
return newCount;
});
});
return;
}
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
@@ -142,11 +184,23 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
oscillator.stop(now + 0.15);
break;
}
// Reset fail count on success
if (audioFailCount > 0) {
setAudioFailCount(0);
setAudioBlocked(false);
}
} catch (e) {
// Silently fail if audio context has issues
console.warn('Audio playback failed:', e);
setAudioFailCount(prev => {
const newCount = prev + 1;
if (newCount >= 3) {
setAudioBlocked(true);
}
return newCount;
});
}
}, [getAudioContext]);
}, [getAudioContext, audioFailCount]);
return (
<SettingsContext.Provider
@@ -164,6 +218,8 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
setTotalHashes,
acceptedHashes,
setAcceptedHashes,
audioBlocked,
resetAudioContext,
}}
>
{children}