Fix usage timestamp persistence and clean up debug code
This commit is contained in:
parent
711b5d838a
commit
7ee0aff52f
@ -71,22 +71,25 @@ export async function POST(request: NextRequest) {
|
|||||||
lastWeedUsageTime?: string;
|
lastWeedUsageTime?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validation
|
// Validation & Normalization
|
||||||
if (body.substance && !['nicotine', 'weed'].includes(body.substance)) {
|
if (body.substance && !['nicotine', 'weed'].includes(body.substance)) {
|
||||||
return NextResponse.json({ error: 'Invalid substance' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid substance' }, { status: 400 });
|
||||||
}
|
}
|
||||||
if (body.trackingStartDate && !/^\d{4}-\d{2}-\d{2}$/.test(body.trackingStartDate)) {
|
if (body.trackingStartDate && !/^\d{4}-\d{2}-\d{2}$/.test(body.trackingStartDate)) {
|
||||||
return NextResponse.json({ error: 'Invalid trackingStartDate format' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid trackingStartDate format' }, { status: 400 });
|
||||||
}
|
}
|
||||||
if (body.dailyGoal !== undefined && (typeof body.dailyGoal !== 'number' || body.dailyGoal < 0)) {
|
|
||||||
|
// Loose type checking for numbers (allow strings that parse to numbers)
|
||||||
|
const dailyGoal = Number(body.dailyGoal);
|
||||||
|
if (body.dailyGoal !== undefined && body.dailyGoal !== null && (isNaN(dailyGoal) || dailyGoal < 0)) {
|
||||||
return NextResponse.json({ error: 'Invalid dailyGoal' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid dailyGoal' }, { status: 400 });
|
||||||
}
|
}
|
||||||
if (body.userName && (typeof body.userName !== 'string' || body.userName.length > 100)) {
|
|
||||||
return NextResponse.json({ error: 'Invalid userName' }, { status: 400 });
|
const userAge = Number(body.userAge);
|
||||||
}
|
if (body.userAge !== undefined && body.userAge !== null && (isNaN(userAge) || userAge < 0 || userAge > 120)) {
|
||||||
if (body.userAge !== undefined && (typeof body.userAge !== 'number' || body.userAge < 0 || body.userAge > 120)) {
|
|
||||||
return NextResponse.json({ error: 'Invalid userAge' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid userAge' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.religion && !['christian', 'secular'].includes(body.religion)) {
|
if (body.religion && !['christian', 'secular'].includes(body.religion)) {
|
||||||
return NextResponse.json({ error: 'Invalid religion' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid religion' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -218,8 +218,17 @@ export function Dashboard({ user }: DashboardProps) {
|
|||||||
...preferences,
|
...preferences,
|
||||||
[substance === 'nicotine' ? 'lastNicotineUsageTime' : 'lastWeedUsageTime']: now,
|
[substance === 'nicotine' ? 'lastNicotineUsageTime' : 'lastWeedUsageTime']: now,
|
||||||
};
|
};
|
||||||
await savePreferencesAsync(latestPrefs);
|
|
||||||
setPreferences(latestPrefs);
|
// Force specific fields to be present to avoid partial update issues
|
||||||
|
// This ensures that even if preferences is stale, we explicitly set the usage time
|
||||||
|
const payload: UserPreferences = {
|
||||||
|
...latestPrefs,
|
||||||
|
lastNicotineUsageTime: substance === 'nicotine' ? now : (latestPrefs.lastNicotineUsageTime ?? null),
|
||||||
|
lastWeedUsageTime: substance === 'weed' ? now : (latestPrefs.lastWeedUsageTime ?? null),
|
||||||
|
};
|
||||||
|
|
||||||
|
await savePreferencesAsync(payload);
|
||||||
|
setPreferences(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveLoggingSubstance(null);
|
setActiveLoggingSubstance(null);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
Cigarette,
|
Cigarette,
|
||||||
Leaf
|
Leaf
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import { getTodayString, getLocalDateString } from '@/lib/date-utils';
|
||||||
|
|
||||||
interface HealthTimelineCardProps {
|
interface HealthTimelineCardProps {
|
||||||
usageData: UsageEntry[];
|
usageData: UsageEntry[];
|
||||||
@ -225,29 +226,57 @@ function HealthTimelineCardComponent({
|
|||||||
// Calculate last usage timestamps only when data changes
|
// Calculate last usage timestamps only when data changes
|
||||||
const lastUsageTimes = useMemo(() => {
|
const lastUsageTimes = useMemo(() => {
|
||||||
const getTimestamp = (substance: 'nicotine' | 'weed') => {
|
const getTimestamp = (substance: 'nicotine' | 'weed') => {
|
||||||
// 1. Check for stored timestamp first
|
let lastTime = 0;
|
||||||
const stored = substance === 'nicotine' ? preferences?.lastNicotineUsageTime : preferences?.lastWeedUsageTime;
|
|
||||||
if (stored) return new Date(stored).getTime();
|
|
||||||
|
|
||||||
// 2. Fallback to usage data
|
// 1. Check for stored timestamp
|
||||||
|
const stored = substance === 'nicotine' ? preferences?.lastNicotineUsageTime : preferences?.lastWeedUsageTime;
|
||||||
|
if (stored) {
|
||||||
|
lastTime = new Date(stored).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check usage data (usually more up-to-date for "just logged")
|
||||||
const lastEntry = usageData
|
const lastEntry = usageData
|
||||||
.filter(e => e.substance === substance && e.count > 0)
|
.filter(e => e.substance === substance && e.count > 0)
|
||||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())[0];
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())[0];
|
||||||
|
|
||||||
if (lastEntry) {
|
if (lastEntry) {
|
||||||
|
const todayStr = getTodayString();
|
||||||
|
|
||||||
|
// Calculate local midnight for today
|
||||||
|
const now = new Date();
|
||||||
|
const localMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
||||||
|
|
||||||
|
// If usage recorded today
|
||||||
|
if (lastEntry.date === todayStr) {
|
||||||
|
// Check if the stored timestamp belongs to today (is after midnight)
|
||||||
|
if (lastTime >= localMidnight) {
|
||||||
|
return lastTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: If we have usage "Today" but no valid timestamp,
|
||||||
|
// we must assume it just happened or we missed the timestamp.
|
||||||
|
// Returning Date.now() resets the timer to 0.
|
||||||
|
return Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
const d = new Date(lastEntry.date);
|
const d = new Date(lastEntry.date);
|
||||||
d.setHours(23, 59, 59, 999);
|
d.setHours(23, 59, 59, 999);
|
||||||
return d.getTime();
|
const entryTime = d.getTime();
|
||||||
|
|
||||||
|
// Take the more recent of the two
|
||||||
|
lastTime = Math.max(lastTime, entryTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Fallback to start date
|
|
||||||
if (preferences?.trackingStartDate) {
|
|
||||||
|
// 3. Fallback to start date if no usage found
|
||||||
|
if (lastTime === 0 && preferences?.trackingStartDate) {
|
||||||
const d = new Date(preferences.trackingStartDate);
|
const d = new Date(preferences.trackingStartDate);
|
||||||
d.setHours(0, 0, 0, 0);
|
d.setHours(0, 0, 0, 0);
|
||||||
return d.getTime();
|
lastTime = d.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return lastTime || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -302,6 +331,7 @@ function HealthTimelineCardComponent({
|
|||||||
<TimelineColumn substance="nicotine" minutesFree={nicotineMinutes} theme={theme} />
|
<TimelineColumn substance="nicotine" minutesFree={nicotineMinutes} theme={theme} />
|
||||||
<TimelineColumn substance="weed" minutesFree={weedMinutes} theme={theme} />
|
<TimelineColumn substance="weed" minutesFree={weedMinutes} theme={theme} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -74,17 +74,29 @@ export async function upsertPreferencesD1(userId: string, data: Partial<UserPref
|
|||||||
if (data.userName !== undefined) { updates.push('userName = ?'); values.push(data.userName); }
|
if (data.userName !== undefined) { updates.push('userName = ?'); values.push(data.userName); }
|
||||||
if (data.userAge !== undefined) { updates.push('userAge = ?'); values.push(data.userAge); }
|
if (data.userAge !== undefined) { updates.push('userAge = ?'); values.push(data.userAge); }
|
||||||
if (data.religion !== undefined) { updates.push('religion = ?'); values.push(data.religion); }
|
if (data.religion !== undefined) { updates.push('religion = ?'); values.push(data.religion); }
|
||||||
if (data.lastNicotineUsageTime !== undefined) { updates.push('lastNicotineUsageTime = ?'); values.push(data.lastNicotineUsageTime); }
|
|
||||||
if (data.lastWeedUsageTime !== undefined) { updates.push('lastWeedUsageTime = ?'); values.push(data.lastWeedUsageTime); }
|
// Explicit checks for usage times
|
||||||
|
if (data.lastNicotineUsageTime !== undefined) {
|
||||||
|
updates.push('lastNicotineUsageTime = ?');
|
||||||
|
values.push(data.lastNicotineUsageTime);
|
||||||
|
}
|
||||||
|
if (data.lastWeedUsageTime !== undefined) {
|
||||||
|
updates.push('lastWeedUsageTime = ?');
|
||||||
|
values.push(data.lastWeedUsageTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (data.quitPlanJson !== undefined) { updates.push('quitPlanJson = ?'); values.push(data.quitPlanJson); }
|
if (data.quitPlanJson !== undefined) { updates.push('quitPlanJson = ?'); values.push(data.quitPlanJson); }
|
||||||
|
|
||||||
updates.push('updatedAt = ?');
|
updates.push('updatedAt = ?');
|
||||||
values.push(now);
|
values.push(now);
|
||||||
values.push(userId);
|
values.push(userId);
|
||||||
|
|
||||||
await db.prepare(
|
if (updates.length > 1) { // At least updatedAt is always there
|
||||||
`UPDATE UserPreferences SET ${updates.join(', ')} WHERE userId = ?`
|
await db.prepare(
|
||||||
).bind(...values).run();
|
`UPDATE UserPreferences SET ${updates.join(', ')} WHERE userId = ?`
|
||||||
|
).bind(...values).run();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Insert
|
// Insert
|
||||||
await db.prepare(
|
await db.prepare(
|
||||||
|
|||||||
@ -181,6 +181,9 @@ export async function savePreferencesAsync(preferences: UserPreferences): Promis
|
|||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
preferencesCache = preferences;
|
preferencesCache = preferences;
|
||||||
|
} else {
|
||||||
|
const err = await response.json();
|
||||||
|
console.error('[Storage] Error saving preferences:', response.status, err);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving preferences:', error);
|
console.error('Error saving preferences:', error);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user