619 lines
20 KiB
TypeScript
619 lines
20 KiB
TypeScript
// Client-side storage utilities for tracking data
|
|
// Now uses API calls to persist data in SQLite database
|
|
|
|
export interface UsageEntry {
|
|
date: string; // ISO date string YYYY-MM-DD
|
|
count: number;
|
|
substance: 'nicotine' | 'weed';
|
|
}
|
|
|
|
export interface UserPreferences {
|
|
substance: 'nicotine' | 'weed';
|
|
trackingStartDate: string | null;
|
|
hasCompletedSetup: boolean;
|
|
dailyGoal: number | null;
|
|
quitPlan: QuitPlan | null;
|
|
userName: string | null;
|
|
userAge: number | null;
|
|
religion: 'christian' | 'secular' | null;
|
|
lastNicotineUsageTime?: string | null; // ISO timestamp of last usage
|
|
lastWeedUsageTime?: string | null; // ISO timestamp of last usage
|
|
}
|
|
|
|
export interface QuitPlan {
|
|
startDate: string;
|
|
endDate: string;
|
|
weeklyTargets: number[];
|
|
baselineAverage: number;
|
|
}
|
|
|
|
// ============ NEW FEATURE INTERFACES ============
|
|
|
|
export interface Achievement {
|
|
badgeId: string;
|
|
unlockedAt: string;
|
|
substance: 'nicotine' | 'weed' | 'both';
|
|
}
|
|
|
|
export interface ReminderSettings {
|
|
enabled: boolean;
|
|
reminderTime: string; // HH:MM format
|
|
frequency: 'daily' | 'hourly';
|
|
}
|
|
|
|
export interface SavingsConfig {
|
|
costPerUnit: number;
|
|
unitsPerDay: number;
|
|
savingsGoal: number | null;
|
|
goalName: string | null;
|
|
currency: string;
|
|
substance: 'nicotine' | 'weed';
|
|
}
|
|
|
|
export interface BadgeDefinition {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
howToUnlock: string;
|
|
icon: string;
|
|
}
|
|
|
|
export interface HealthMilestone {
|
|
id: string;
|
|
timeMinutes: number;
|
|
title: string;
|
|
description: string;
|
|
icon: string;
|
|
}
|
|
|
|
// ============ BADGE DEFINITIONS ============
|
|
|
|
export const BADGE_DEFINITIONS: BadgeDefinition[] = [
|
|
{ id: 'first_day', name: 'First Step', description: 'Logged your first usage', howToUnlock: 'Log your usage for the first time', icon: 'Footprints' },
|
|
{ id: 'streak_3', name: 'Hat Trick', description: '3 days substance-free', howToUnlock: 'Go 3 consecutive days without using a tracked substance', icon: 'Flame' },
|
|
{ id: 'streak_7', name: 'Week Warrior', description: 'Tracked for one week', howToUnlock: 'Track your usage for 7 days', icon: 'Shield' },
|
|
{ id: 'fighter', name: 'Fighter', description: '7 days substance-free', howToUnlock: 'Go 7 consecutive days without using any substance', icon: 'Swords' },
|
|
{ id: 'one_month', name: 'Monthly Master', description: 'One month tracked with 50% reduction', howToUnlock: 'Track for 30 days and reduce your usage by at least 50%', icon: 'Crown' },
|
|
{ id: 'goal_crusher', name: 'Goal Crusher', description: 'One month substance-free', howToUnlock: 'Go 30 consecutive days without using any substance', icon: 'Trophy' },
|
|
];
|
|
|
|
// ============ HEALTH MILESTONES ============
|
|
|
|
export const HEALTH_MILESTONES: HealthMilestone[] = [
|
|
{ id: '20min', timeMinutes: 20, title: 'Blood Pressure Normalizes', description: 'Your heart rate and blood pressure begin to drop', icon: 'Heart' },
|
|
{ id: '8hr', timeMinutes: 480, title: 'Oxygen Levels Rise', description: 'Carbon monoxide levels drop, oxygen levels increase', icon: 'Wind' },
|
|
{ id: '24hr', timeMinutes: 1440, title: 'Heart Attack Risk Drops', description: 'Your risk of heart attack begins to decrease', icon: 'HeartPulse' },
|
|
{ id: '48hr', timeMinutes: 2880, title: 'Senses Sharpen', description: 'Taste and smell begin to improve', icon: 'Eye' },
|
|
{ id: '72hr', timeMinutes: 4320, title: 'Breathing Easier', description: 'Bronchial tubes relax, energy levels increase', icon: 'Wind' },
|
|
{ id: '2wk', timeMinutes: 20160, title: 'Circulation Improves', description: 'Blood circulation significantly improves', icon: 'Activity' },
|
|
{ id: '1mo', timeMinutes: 43200, title: 'Lung Function Improves', description: 'Lung capacity increases up to 30%', icon: 'TrendingUp' },
|
|
{ id: '3mo', timeMinutes: 129600, title: 'Cilia Regenerate', description: 'Lungs begin to heal, coughing decreases', icon: 'Sparkles' },
|
|
{ id: '1yr', timeMinutes: 525600, title: 'Heart Disease Risk Halved', description: 'Risk of coronary heart disease cut in half', icon: 'HeartHandshake' },
|
|
];
|
|
|
|
const defaultPreferences: UserPreferences = {
|
|
substance: 'nicotine',
|
|
trackingStartDate: null,
|
|
hasCompletedSetup: false,
|
|
dailyGoal: null,
|
|
quitPlan: null,
|
|
userName: null,
|
|
userAge: null,
|
|
religion: null,
|
|
};
|
|
|
|
// Cache for preferences and usage data to avoid excessive API calls
|
|
let preferencesCache: UserPreferences | null = null;
|
|
let usageDataCache: UsageEntry[] | null = null;
|
|
let achievementsCache: Achievement[] | null = null;
|
|
let reminderSettingsCache: ReminderSettings | null = null;
|
|
let savingsConfigCache: SavingsConfig | null = null;
|
|
|
|
export function clearCache(): void {
|
|
preferencesCache = null;
|
|
usageDataCache = null;
|
|
achievementsCache = null;
|
|
reminderSettingsCache = null;
|
|
savingsConfigCache = null;
|
|
}
|
|
|
|
// These functions are kept for backwards compatibility but no longer used
|
|
export function setCurrentUserId(_userId: string): void {
|
|
// No-op - user ID is now managed by session
|
|
}
|
|
|
|
export function getCurrentUserId(): string | null {
|
|
return null;
|
|
}
|
|
|
|
// Async API functions
|
|
export async function fetchPreferences(): Promise<UserPreferences> {
|
|
if (preferencesCache) return preferencesCache;
|
|
try {
|
|
const response = await fetch('/api/preferences');
|
|
if (!response.ok) {
|
|
console.error('Failed to fetch preferences');
|
|
return defaultPreferences;
|
|
}
|
|
const data = await response.json() as UserPreferences;
|
|
preferencesCache = data;
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching preferences:', error);
|
|
return defaultPreferences;
|
|
}
|
|
}
|
|
|
|
export async function savePreferencesAsync(preferences: UserPreferences): Promise<void> {
|
|
try {
|
|
const response = await fetch('/api/preferences', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(preferences),
|
|
});
|
|
if (response.ok) {
|
|
preferencesCache = preferences;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving preferences:', error);
|
|
}
|
|
}
|
|
|
|
export async function fetchUsageData(): Promise<UsageEntry[]> {
|
|
if (usageDataCache) return usageDataCache;
|
|
try {
|
|
const response = await fetch('/api/usage');
|
|
if (!response.ok) {
|
|
console.error('Failed to fetch usage data');
|
|
return [];
|
|
}
|
|
const data = await response.json() as UsageEntry[];
|
|
usageDataCache = data;
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching usage data:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function saveUsageEntryAsync(entry: UsageEntry): Promise<void> {
|
|
try {
|
|
await fetch('/api/usage', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(entry),
|
|
});
|
|
usageDataCache = null; // Invalidate cache
|
|
} catch (error) {
|
|
console.error('Error saving usage entry:', error);
|
|
}
|
|
}
|
|
|
|
export async function setUsageForDateAsync(
|
|
date: string,
|
|
count: number,
|
|
substance: 'nicotine' | 'weed'
|
|
): Promise<void> {
|
|
try {
|
|
await fetch('/api/usage', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ date, count, substance }),
|
|
});
|
|
usageDataCache = null; // Invalidate cache
|
|
} catch (error) {
|
|
console.error('Error setting usage for date:', error);
|
|
}
|
|
}
|
|
|
|
export async function clearDayDataAsync(
|
|
date: string,
|
|
substance: 'nicotine' | 'weed'
|
|
): Promise<void> {
|
|
try {
|
|
await fetch(`/api/usage?date=${date}&substance=${substance}`, {
|
|
method: 'DELETE',
|
|
});
|
|
usageDataCache = null; // Invalidate cache
|
|
} catch (error) {
|
|
console.error('Error clearing day data:', error);
|
|
}
|
|
}
|
|
|
|
// ============ ACHIEVEMENTS FUNCTIONS ============
|
|
|
|
export async function fetchAchievements(): Promise<Achievement[]> {
|
|
if (achievementsCache) return achievementsCache;
|
|
try {
|
|
const response = await fetch('/api/achievements');
|
|
if (!response.ok) return [];
|
|
const data = await response.json() as Achievement[];
|
|
achievementsCache = data;
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching achievements:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function unlockAchievement(
|
|
badgeId: string,
|
|
substance: 'nicotine' | 'weed' | 'both'
|
|
): Promise<{ achievement: Achievement | null; isNew: boolean }> {
|
|
try {
|
|
const response = await fetch('/api/achievements', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ badgeId, substance }),
|
|
});
|
|
if (response.ok) {
|
|
const data = await response.json() as { badgeId: string; unlockedAt: string; substance: 'nicotine' | 'weed' | 'both'; alreadyUnlocked?: boolean };
|
|
achievementsCache = null; // Invalidate cache
|
|
return {
|
|
achievement: {
|
|
badgeId: data.badgeId,
|
|
unlockedAt: data.unlockedAt,
|
|
substance: data.substance,
|
|
},
|
|
isNew: !data.alreadyUnlocked,
|
|
};
|
|
}
|
|
return { achievement: null, isNew: false };
|
|
} catch (error) {
|
|
console.error('Error unlocking achievement:', error);
|
|
return { achievement: null, isNew: false };
|
|
}
|
|
}
|
|
|
|
export function getAchievements(): Achievement[] {
|
|
return achievementsCache || [];
|
|
}
|
|
|
|
// ============ REMINDERS FUNCTIONS ============
|
|
|
|
export async function fetchReminderSettings(): Promise<ReminderSettings> {
|
|
if (reminderSettingsCache) return reminderSettingsCache;
|
|
try {
|
|
const response = await fetch('/api/reminders');
|
|
if (!response.ok) return { enabled: false, reminderTime: '09:00', frequency: 'daily' };
|
|
const data = await response.json() as ReminderSettings;
|
|
reminderSettingsCache = data;
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching reminder settings:', error);
|
|
return { enabled: false, reminderTime: '09:00', frequency: 'daily' };
|
|
}
|
|
}
|
|
|
|
export async function saveReminderSettings(settings: ReminderSettings): Promise<void> {
|
|
try {
|
|
const response = await fetch('/api/reminders', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(settings),
|
|
});
|
|
if (response.ok) {
|
|
reminderSettingsCache = settings;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving reminder settings:', error);
|
|
}
|
|
}
|
|
|
|
export function getReminderSettings(): ReminderSettings {
|
|
return reminderSettingsCache || { enabled: false, reminderTime: '09:00', frequency: 'daily' };
|
|
}
|
|
|
|
// ============ SAVINGS FUNCTIONS ============
|
|
|
|
export async function fetchSavingsConfig(): Promise<SavingsConfig | null> {
|
|
if (savingsConfigCache) return savingsConfigCache;
|
|
try {
|
|
const response = await fetch('/api/savings');
|
|
if (!response.ok) return null;
|
|
const data = await response.json() as SavingsConfig | null;
|
|
savingsConfigCache = data;
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching savings config:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function saveSavingsConfig(config: SavingsConfig): Promise<void> {
|
|
try {
|
|
const response = await fetch('/api/savings', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(config),
|
|
});
|
|
if (response.ok) {
|
|
savingsConfigCache = config;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving savings config:', error);
|
|
}
|
|
}
|
|
|
|
export function getSavingsConfig(): SavingsConfig | null {
|
|
return savingsConfigCache;
|
|
}
|
|
|
|
// ============ CALCULATION HELPERS ============
|
|
|
|
export function calculateStreak(
|
|
usageData: UsageEntry[],
|
|
substance: 'nicotine' | 'weed'
|
|
): number {
|
|
let streak = 0;
|
|
const today = new Date();
|
|
const substanceData = usageData.filter((e) => e.substance === substance);
|
|
|
|
for (let i = 0; i <= 365; i++) {
|
|
const checkDate = new Date(today);
|
|
checkDate.setDate(checkDate.getDate() - i);
|
|
const dateStr = checkDate.toISOString().split('T')[0];
|
|
const dayUsage = substanceData.find((e) => e.date === dateStr)?.count ?? -1;
|
|
|
|
if (dayUsage === 0) {
|
|
streak++;
|
|
} else if (dayUsage > 0) {
|
|
break;
|
|
}
|
|
// If dayUsage === -1 (no entry), we continue but don't count it as a streak day
|
|
}
|
|
return streak;
|
|
}
|
|
|
|
export function calculateTotalSaved(
|
|
savingsConfig: SavingsConfig | null,
|
|
usageData: UsageEntry[],
|
|
startDate: string | null
|
|
): number {
|
|
if (!savingsConfig || !startDate) return 0;
|
|
|
|
const start = new Date(startDate);
|
|
const today = new Date();
|
|
const daysSinceStart = Math.floor(
|
|
(today.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)
|
|
);
|
|
|
|
if (daysSinceStart <= 0) return 0;
|
|
|
|
// Expected spending if they continued at baseline (unitsPerDay is now unitsPerWeek)
|
|
const weeksSinceStart = daysSinceStart / 7;
|
|
const expectedSpend =
|
|
weeksSinceStart * savingsConfig.costPerUnit * savingsConfig.unitsPerDay;
|
|
|
|
// Actual usage converted to cost (assuming ~20 puffs/hits per unit)
|
|
const relevantUsage = usageData.filter(
|
|
(e) => e.substance === savingsConfig.substance && new Date(e.date) >= start
|
|
);
|
|
const actualUnits = relevantUsage.reduce((sum, e) => sum + e.count, 0);
|
|
const unitsPerPack = 20; // Average puffs per pack/unit
|
|
const actualSpend = (actualUnits / unitsPerPack) * savingsConfig.costPerUnit;
|
|
|
|
return Math.max(0, expectedSpend - actualSpend);
|
|
}
|
|
|
|
|
|
|
|
export function checkBadgeEligibility(
|
|
badgeId: string,
|
|
usageData: UsageEntry[],
|
|
preferences: UserPreferences,
|
|
substance: 'nicotine' | 'weed'
|
|
): boolean {
|
|
const streak = calculateStreak(usageData, substance);
|
|
const nicotineStreak = calculateStreak(usageData, 'nicotine');
|
|
const weedStreak = calculateStreak(usageData, 'weed');
|
|
const totalDays = new Set(
|
|
usageData.filter((e) => e.substance === substance).map((e) => e.date)
|
|
).size;
|
|
|
|
// Check if user has tracked for at least 30 days and reduced usage by 50%
|
|
const checkMonthlyReduction = (): boolean => {
|
|
if (!preferences.trackingStartDate) return false;
|
|
const startDate = new Date(preferences.trackingStartDate);
|
|
const today = new Date();
|
|
const daysSinceStart = Math.floor(
|
|
(today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)
|
|
);
|
|
if (daysSinceStart < 30) return false;
|
|
|
|
// Get first week's average
|
|
const firstWeekData = usageData.filter((e) => {
|
|
const entryDate = new Date(e.date);
|
|
const daysSinceEntry = Math.floor(
|
|
(entryDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)
|
|
);
|
|
return e.substance === substance && daysSinceEntry >= 0 && daysSinceEntry < 7;
|
|
});
|
|
const firstWeekTotal = firstWeekData.reduce((sum, e) => sum + e.count, 0);
|
|
const firstWeekAvg = firstWeekData.length > 0 ? firstWeekTotal / 7 : 0;
|
|
|
|
// Get last week's average
|
|
const lastWeekData = usageData.filter((e) => {
|
|
const entryDate = new Date(e.date);
|
|
const daysAgo = Math.floor(
|
|
(today.getTime() - entryDate.getTime()) / (1000 * 60 * 60 * 24)
|
|
);
|
|
return e.substance === substance && daysAgo >= 0 && daysAgo < 7;
|
|
});
|
|
const lastWeekTotal = lastWeekData.reduce((sum, e) => sum + e.count, 0);
|
|
const lastWeekAvg = lastWeekTotal / 7;
|
|
|
|
// Check if reduced by at least 50%
|
|
if (firstWeekAvg <= 0) return lastWeekAvg === 0;
|
|
return lastWeekAvg <= firstWeekAvg * 0.5;
|
|
};
|
|
|
|
switch (badgeId) {
|
|
case 'first_day':
|
|
// Log usage for the first time
|
|
return totalDays >= 1;
|
|
case 'streak_3':
|
|
// 3 days off a tracked substance
|
|
return streak >= 3;
|
|
case 'streak_7':
|
|
// Track usage for one week (7 days of entries)
|
|
return totalDays >= 7;
|
|
case 'fighter':
|
|
// 7 days off ANY substance (both nicotine AND weed)
|
|
return nicotineStreak >= 7 && weedStreak >= 7;
|
|
case 'one_month':
|
|
// Track one month and reduce usage by 50%
|
|
return checkMonthlyReduction();
|
|
case 'goal_crusher':
|
|
// One month substance free (both substances)
|
|
return nicotineStreak >= 30 && weedStreak >= 30;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Synchronous functions that use cache (for backwards compatibility)
|
|
// These should be replaced with async versions in components
|
|
|
|
export function getPreferences(_userId?: string): UserPreferences {
|
|
return preferencesCache || defaultPreferences;
|
|
}
|
|
|
|
export function getUsageData(_userId?: string): UsageEntry[] {
|
|
return usageDataCache || [];
|
|
}
|
|
|
|
export function savePreferences(preferences: UserPreferences, _userId?: string): void {
|
|
preferencesCache = preferences;
|
|
savePreferencesAsync(preferences);
|
|
}
|
|
|
|
export function saveUsageEntry(entry: UsageEntry, _userId?: string): void {
|
|
saveUsageEntryAsync(entry);
|
|
}
|
|
|
|
export function setUsageForDate(
|
|
date: string,
|
|
count: number,
|
|
substance: 'nicotine' | 'weed',
|
|
_userId?: string
|
|
): void {
|
|
setUsageForDateAsync(date, count, substance);
|
|
}
|
|
|
|
export function getUsageForDate(
|
|
date: string,
|
|
substance: 'nicotine' | 'weed',
|
|
_userId?: string
|
|
): number {
|
|
const data = usageDataCache || [];
|
|
const entry = data.find((e) => e.date === date && e.substance === substance);
|
|
return entry?.count ?? 0;
|
|
}
|
|
|
|
export function clearDayData(
|
|
date: string,
|
|
substance: 'nicotine' | 'weed',
|
|
_userId?: string
|
|
): void {
|
|
clearDayDataAsync(date, substance);
|
|
}
|
|
|
|
const LAST_PROMPT_KEY = 'quittraq_last_prompt_date';
|
|
|
|
export function shouldShowUsagePrompt(): boolean {
|
|
if (typeof window === 'undefined') return false;
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const lastPromptDate = localStorage.getItem(LAST_PROMPT_KEY);
|
|
|
|
return lastPromptDate !== today;
|
|
}
|
|
|
|
export function markPromptShown(): void {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
localStorage.setItem(LAST_PROMPT_KEY, today);
|
|
}
|
|
|
|
export function getWeeklyData(substance: 'nicotine' | 'weed', _userId?: string): UsageEntry[] {
|
|
const data = usageDataCache || [];
|
|
const today = new Date();
|
|
const weekAgo = new Date(today);
|
|
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
|
|
return data.filter((entry) => {
|
|
const entryDate = new Date(entry.date);
|
|
return entry.substance === substance && entryDate >= weekAgo && entryDate <= today;
|
|
});
|
|
}
|
|
|
|
export function calculateWeeklyAverage(substance: 'nicotine' | 'weed', _userId?: string): number {
|
|
const weeklyData = getWeeklyData(substance);
|
|
if (weeklyData.length === 0) return 0;
|
|
|
|
const total = weeklyData.reduce((sum, entry) => sum + entry.count, 0);
|
|
return Math.round(total / weeklyData.length);
|
|
}
|
|
|
|
export function hasOneWeekOfData(substance: 'nicotine' | 'weed', _userId?: string): boolean {
|
|
const prefs = preferencesCache;
|
|
if (!prefs?.trackingStartDate) return false;
|
|
|
|
const startDate = new Date(prefs.trackingStartDate);
|
|
const today = new Date();
|
|
const daysDiff = Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
|
|
return daysDiff >= 7;
|
|
}
|
|
|
|
export function generateQuitPlan(substance: 'nicotine' | 'weed', _userId?: string): QuitPlan {
|
|
const baseline = calculateWeeklyAverage(substance);
|
|
const today = new Date();
|
|
const startDate = today.toISOString().split('T')[0];
|
|
|
|
// 4-week reduction plan with 25% weekly reduction
|
|
const endDate = new Date(today);
|
|
endDate.setDate(endDate.getDate() + 28);
|
|
|
|
// Gradual reduction: each week reduce by 25%
|
|
const weeklyTargets: number[] = [];
|
|
let current = baseline;
|
|
for (let i = 0; i < 4; i++) {
|
|
current = Math.max(0, Math.round(current * 0.75));
|
|
weeklyTargets.push(current);
|
|
}
|
|
|
|
return {
|
|
startDate,
|
|
endDate: endDate.toISOString().split('T')[0],
|
|
weeklyTargets,
|
|
baselineAverage: baseline,
|
|
};
|
|
}
|
|
|
|
export function getCurrentWeekTarget(_userId?: string): number | null {
|
|
const prefs = preferencesCache;
|
|
if (!prefs?.quitPlan) return null;
|
|
|
|
const startDate = new Date(prefs.quitPlan.startDate);
|
|
const today = new Date();
|
|
const weekNumber = Math.floor((today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24 * 7));
|
|
|
|
if (weekNumber >= prefs.quitPlan.weeklyTargets.length) {
|
|
return 0; // Goal achieved
|
|
}
|
|
|
|
return prefs.quitPlan.weeklyTargets[weekNumber];
|
|
}
|
|
|
|
export async function clearAllDataAsync(): Promise<void> {
|
|
// This would need a dedicated API endpoint
|
|
console.warn('clearAllData not implemented for API-based storage');
|
|
}
|
|
|
|
export function clearAllData(_userId?: string): void {
|
|
clearAllDataAsync();
|
|
}
|