Improve achievements, move reminders to header, and reset health timeline on usage
This commit is contained in:
parent
54b7a294f5
commit
2491c79b0a
@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Achievement, BADGE_DEFINITIONS } from '@/lib/storage';
|
||||
import { Achievement, BADGE_DEFINITIONS, BadgeDefinition } from '@/lib/storage';
|
||||
import { useTheme } from '@/lib/theme-context';
|
||||
import {
|
||||
Trophy,
|
||||
@ -30,6 +30,7 @@ const iconMap: Record<string, React.ElementType> = {
|
||||
|
||||
export function AchievementsCard({ achievements, substance }: AchievementsCardProps) {
|
||||
const { theme } = useTheme();
|
||||
const [hoveredBadge, setHoveredBadge] = useState<string | null>(null);
|
||||
|
||||
const unlockedBadgeIds = useMemo(() => {
|
||||
return new Set(
|
||||
@ -44,7 +45,6 @@ export function AchievementsCard({ achievements, substance }: AchievementsCardPr
|
||||
? 'linear-gradient(135deg, rgba(124, 58, 237, 0.85) 0%, rgba(109, 40, 217, 0.9) 100%)'
|
||||
: 'linear-gradient(135deg, rgba(168, 85, 247, 0.2) 0%, rgba(139, 92, 246, 0.15) 100%)';
|
||||
|
||||
const substanceLabel = substance === 'nicotine' ? 'Nicotine' : 'Marijuana';
|
||||
const borderColor = 'border-purple-500/40';
|
||||
|
||||
return (
|
||||
@ -57,7 +57,7 @@ export function AchievementsCard({ achievements, substance }: AchievementsCardPr
|
||||
<CardHeader className="relative z-10 pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-white text-shadow-sm">
|
||||
<Trophy className="h-5 w-5 text-yellow-400" />
|
||||
<span>{substanceLabel} Achievements</span>
|
||||
<span>Achievements</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@ -71,23 +71,34 @@ export function AchievementsCard({ achievements, substance }: AchievementsCardPr
|
||||
a.badgeId === badge.id &&
|
||||
(a.substance === substance || a.substance === 'both')
|
||||
);
|
||||
const isHovered = hoveredBadge === badge.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={badge.id}
|
||||
className={`relative p-3 rounded-xl text-center transition-all duration-300 ${
|
||||
className={`relative p-3 rounded-xl text-center transition-all duration-300 cursor-pointer ${
|
||||
isUnlocked
|
||||
? 'bg-gradient-to-br from-yellow-500/30 to-amber-600/20 border border-yellow-500/50 hover:scale-105'
|
||||
: 'bg-white/5 border border-white/10 opacity-50'
|
||||
: 'bg-white/5 border border-white/10 hover:bg-white/10 hover:border-white/20'
|
||||
}`}
|
||||
title={
|
||||
isUnlocked
|
||||
? `Unlocked: ${new Date(unlockedAchievement!.unlockedAt).toLocaleDateString()}`
|
||||
: badge.description
|
||||
}
|
||||
onMouseEnter={() => setHoveredBadge(badge.id)}
|
||||
onMouseLeave={() => setHoveredBadge(null)}
|
||||
>
|
||||
{/* Hover tooltip */}
|
||||
{isHovered && (
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-20 w-48 p-2 bg-gray-900/95 border border-white/20 rounded-lg shadow-xl backdrop-blur-sm">
|
||||
<p className="text-xs text-white font-medium mb-1">{badge.name}</p>
|
||||
<p className="text-[10px] text-white/70">
|
||||
{isUnlocked
|
||||
? `Unlocked: ${new Date(unlockedAchievement!.unlockedAt).toLocaleDateString()}`
|
||||
: badge.howToUnlock}
|
||||
</p>
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900/95" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isUnlocked && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20 rounded-xl">
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20 rounded-xl pointer-events-none">
|
||||
<Lock className="h-4 w-4 text-white/40" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -11,16 +11,13 @@ import {
|
||||
markPromptShown,
|
||||
generateQuitPlan,
|
||||
fetchAchievements,
|
||||
fetchReminderSettings,
|
||||
fetchSavingsConfig,
|
||||
saveReminderSettings,
|
||||
saveSavingsConfig,
|
||||
unlockAchievement,
|
||||
checkBadgeEligibility,
|
||||
UserPreferences,
|
||||
UsageEntry,
|
||||
Achievement,
|
||||
ReminderSettings,
|
||||
SavingsConfig,
|
||||
BADGE_DEFINITIONS,
|
||||
BadgeDefinition,
|
||||
@ -35,7 +32,6 @@ import { AchievementsCard } from './AchievementsCard';
|
||||
import { CelebrationAnimation } from './CelebrationAnimation';
|
||||
import { HealthTimelineCard } from './HealthTimelineCard';
|
||||
import { SavingsTrackerCard } from './SavingsTrackerCard';
|
||||
import { ReminderSettingsCard } from './ReminderSettingsCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PlusCircle } from 'lucide-react';
|
||||
import { useTheme } from '@/lib/theme-context';
|
||||
@ -48,7 +44,6 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
const [preferences, setPreferences] = useState<UserPreferences | null>(null);
|
||||
const [usageData, setUsageData] = useState<UsageEntry[]>([]);
|
||||
const [achievements, setAchievements] = useState<Achievement[]>([]);
|
||||
const [reminderSettings, setReminderSettings] = useState<ReminderSettings>({ enabled: false, reminderTime: '09:00' });
|
||||
const [savingsConfig, setSavingsConfig] = useState<SavingsConfig | null>(null);
|
||||
const [showSetup, setShowSetup] = useState(false);
|
||||
const [showUsagePrompt, setShowUsagePrompt] = useState(false);
|
||||
@ -59,17 +54,15 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
const [prefs, usage, achvs, reminders, savings] = await Promise.all([
|
||||
const [prefs, usage, achvs, savings] = await Promise.all([
|
||||
fetchPreferences(),
|
||||
fetchUsageData(),
|
||||
fetchAchievements(),
|
||||
fetchReminderSettings(),
|
||||
fetchSavingsConfig(),
|
||||
]);
|
||||
setPreferences(prefs);
|
||||
setUsageData(usage);
|
||||
setAchievements(achvs);
|
||||
setReminderSettings(reminders);
|
||||
setSavingsConfig(savings);
|
||||
setRefreshKey(prev => prev + 1);
|
||||
return { prefs, usage, achvs };
|
||||
@ -176,11 +169,6 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
setRefreshKey(prev => prev + 1);
|
||||
};
|
||||
|
||||
const handleReminderSettingsChange = async (settings: ReminderSettings) => {
|
||||
setReminderSettings(settings);
|
||||
await saveReminderSettings(settings);
|
||||
};
|
||||
|
||||
const handleSavingsConfigChange = async (config: SavingsConfig) => {
|
||||
setSavingsConfig(config);
|
||||
await saveSavingsConfig(config);
|
||||
@ -271,12 +259,6 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
onSavingsConfigChange={handleSavingsConfigChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="opacity-0 animate-slide-in-right delay-600">
|
||||
<ReminderSettingsCard
|
||||
settings={reminderSettings}
|
||||
onSettingsChange={handleReminderSettingsChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -8,11 +8,21 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { User } from '@/lib/session';
|
||||
import { fetchPreferences } from '@/lib/storage';
|
||||
import { fetchPreferences, fetchReminderSettings, saveReminderSettings, ReminderSettings } from '@/lib/storage';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Cigarette, Leaf, LogOut, Home, ChevronDown, Sun, Moon } from 'lucide-react';
|
||||
import { Cigarette, Leaf, LogOut, Home, ChevronDown, Sun, Moon, Bell, BellOff, BellRing } from 'lucide-react';
|
||||
import { useTheme } from '@/lib/theme-context';
|
||||
|
||||
interface UserHeaderProps {
|
||||
@ -21,17 +31,43 @@ interface UserHeaderProps {
|
||||
|
||||
export function UserHeader({ user }: UserHeaderProps) {
|
||||
const [userName, setUserName] = useState<string | null>(null);
|
||||
const [reminderSettings, setReminderSettings] = useState<ReminderSettings>({ enabled: false, reminderTime: '09:00' });
|
||||
const [showReminderDialog, setShowReminderDialog] = useState(false);
|
||||
const [localTime, setLocalTime] = useState('09:00');
|
||||
const router = useRouter();
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { isSupported, permission, requestPermission } = useNotifications(reminderSettings);
|
||||
|
||||
useEffect(() => {
|
||||
const loadUserName = async () => {
|
||||
const prefs = await fetchPreferences();
|
||||
const loadData = async () => {
|
||||
const [prefs, reminders] = await Promise.all([
|
||||
fetchPreferences(),
|
||||
fetchReminderSettings(),
|
||||
]);
|
||||
setUserName(prefs.userName);
|
||||
setReminderSettings(reminders);
|
||||
setLocalTime(reminders.reminderTime);
|
||||
};
|
||||
loadUserName();
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const handleToggleReminders = async () => {
|
||||
if (!reminderSettings.enabled && permission !== 'granted') {
|
||||
const result = await requestPermission();
|
||||
if (result !== 'granted') return;
|
||||
}
|
||||
const newSettings = { ...reminderSettings, enabled: !reminderSettings.enabled };
|
||||
setReminderSettings(newSettings);
|
||||
await saveReminderSettings(newSettings);
|
||||
};
|
||||
|
||||
const handleTimeChange = async (newTime: string) => {
|
||||
setLocalTime(newTime);
|
||||
const newSettings = { ...reminderSettings, reminderTime: newTime };
|
||||
setReminderSettings(newSettings);
|
||||
await saveReminderSettings(newSettings);
|
||||
};
|
||||
|
||||
const initials = [user.firstName?.[0], user.lastName?.[0]]
|
||||
.filter(Boolean)
|
||||
.join('')
|
||||
@ -72,6 +108,22 @@ export function UserHeader({ user }: UserHeaderProps) {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<button
|
||||
onClick={() => setShowReminderDialog(true)}
|
||||
className={`p-2.5 sm:p-2 rounded-full transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-white/30 hover:scale-110 active:scale-95 ${
|
||||
reminderSettings.enabled
|
||||
? 'bg-indigo-500/30 hover:bg-indigo-500/40'
|
||||
: 'bg-white/10 hover:bg-white/20'
|
||||
}`}
|
||||
aria-label="Reminder settings"
|
||||
title={reminderSettings.enabled ? `Reminders on at ${reminderSettings.reminderTime}` : 'Reminders off'}
|
||||
>
|
||||
{reminderSettings.enabled ? (
|
||||
<BellRing className="h-5 w-5 text-indigo-300 transition-transform duration-300" />
|
||||
) : (
|
||||
<Bell className="h-5 w-5 text-white/70 transition-transform duration-300" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="p-2.5 sm:p-2 rounded-full bg-white/10 hover:bg-white/20 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-white/30 hover:scale-110 active:scale-95"
|
||||
@ -123,6 +175,100 @@ export function UserHeader({ user }: UserHeaderProps) {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Reminder Settings Dialog */}
|
||||
<Dialog open={showReminderDialog} onOpenChange={setShowReminderDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Bell className="h-5 w-5 text-indigo-400" />
|
||||
Daily Reminders
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
{/* Permission Status */}
|
||||
<div className="flex items-center justify-between p-3 bg-muted rounded-lg">
|
||||
<span className="text-sm text-muted-foreground">Notifications</span>
|
||||
<span className={`text-sm font-medium ${
|
||||
!isSupported ? 'text-red-400' :
|
||||
permission === 'granted' ? 'text-green-400' :
|
||||
permission === 'denied' ? 'text-red-400' : 'text-yellow-400'
|
||||
}`}>
|
||||
{!isSupported ? 'Not supported' :
|
||||
permission === 'granted' ? 'Enabled' :
|
||||
permission === 'denied' ? 'Blocked' : 'Not set'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Enable/Disable Toggle */}
|
||||
<div className="flex items-center justify-between p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
{reminderSettings.enabled ? (
|
||||
<BellRing className="h-4 w-4 text-indigo-400" />
|
||||
) : (
|
||||
<BellOff className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-sm">
|
||||
{reminderSettings.enabled ? 'Reminders On' : 'Reminders Off'}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleToggleReminders}
|
||||
disabled={!isSupported || permission === 'denied'}
|
||||
className={`relative w-12 h-6 rounded-full transition-all duration-300 ${
|
||||
reminderSettings.enabled ? 'bg-indigo-500' : 'bg-muted-foreground/30'
|
||||
} ${!isSupported || permission === 'denied' ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
>
|
||||
<div
|
||||
className={`absolute top-1 w-4 h-4 rounded-full bg-white transition-all duration-300 ${
|
||||
reminderSettings.enabled ? 'left-7' : 'left-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Time Picker */}
|
||||
{reminderSettings.enabled && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="reminderTime" className="text-sm">
|
||||
Reminder Time
|
||||
</Label>
|
||||
<Input
|
||||
id="reminderTime"
|
||||
type="time"
|
||||
value={localTime}
|
||||
onChange={(e) => handleTimeChange(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
You'll receive a reminder at this time each day
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Request Permission Button */}
|
||||
{isSupported && permission === 'default' && (
|
||||
<Button
|
||||
onClick={requestPermission}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
<Bell className="mr-2 h-4 w-4" />
|
||||
Enable Notifications
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Denied Message */}
|
||||
{permission === 'denied' && (
|
||||
<div className="p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
|
||||
<p className="text-xs text-red-400">
|
||||
Notifications are blocked. Please enable them in your browser settings to receive reminders.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ export interface BadgeDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
howToUnlock: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
@ -64,12 +65,12 @@ export interface HealthMilestone {
|
||||
// ============ BADGE DEFINITIONS ============
|
||||
|
||||
export const BADGE_DEFINITIONS: BadgeDefinition[] = [
|
||||
{ id: 'first_day', name: 'First Step', description: 'Tracked your first day', icon: 'Footprints' },
|
||||
{ id: 'streak_3', name: 'Hat Trick', description: '3-day streak substance-free', icon: 'Flame' },
|
||||
{ id: 'streak_7', name: 'Week Warrior', description: '7-day streak substance-free', icon: 'Shield' },
|
||||
{ id: 'two_weeks', name: 'Fortnight Fighter', description: '14-day streak substance-free', icon: 'Swords' },
|
||||
{ id: 'one_month', name: 'Monthly Master', description: '30-day streak substance-free', icon: 'Crown' },
|
||||
{ id: 'plan_completed', name: 'Goal Crusher', description: 'Completed your quit plan', icon: 'Trophy' },
|
||||
{ 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 ============
|
||||
@ -398,11 +399,19 @@ export function getMinutesSinceQuit(
|
||||
return 0;
|
||||
}
|
||||
|
||||
const lastUsageDate = new Date(substanceData[0].date);
|
||||
// Set to end of that day
|
||||
const now = new Date();
|
||||
const todayStr = now.toISOString().split('T')[0];
|
||||
const lastUsageDateStr = substanceData[0].date;
|
||||
|
||||
// If the last usage was today, reset to 0 (just used)
|
||||
if (lastUsageDateStr === todayStr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// For past days, count from the end of that day
|
||||
const lastUsageDate = new Date(lastUsageDateStr);
|
||||
lastUsageDate.setHours(23, 59, 59, 999);
|
||||
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - lastUsageDate.getTime();
|
||||
return Math.max(0, Math.floor(diffMs / (1000 * 60)));
|
||||
}
|
||||
@ -414,26 +423,68 @@ export function checkBadgeEligibility(
|
||||
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;
|
||||
const planCompleted =
|
||||
preferences.quitPlan !== null &&
|
||||
new Date() > new Date(preferences.quitPlan.endDate);
|
||||
|
||||
// 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':
|
||||
return streak >= 7;
|
||||
case 'two_weeks':
|
||||
return streak >= 14;
|
||||
// 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':
|
||||
return streak >= 30;
|
||||
case 'plan_completed':
|
||||
return planCompleted;
|
||||
// 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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user