added demo quiz reset option. Added timer for next available daily quiz

This commit is contained in:
masongga
2026-05-24 07:48:51 -06:00
parent 4998576e64
commit 1a151b5c7c
6 changed files with 199 additions and 7 deletions

View File

@@ -0,0 +1,67 @@
"use client";
import { useEffect, useRef, useState } from "react";
import {
formatDailyQuizCountdown,
getMillisecondsUntilNextDailyQuiz,
getNextDailyQuizAvailableAt,
} from "@/lib/daily-quiz-storage";
type DailyQuizCountdownProps = {
onAvailable?: () => void;
};
export function DailyQuizCountdown({ onAvailable }: DailyQuizCountdownProps) {
const [remainingMs, setRemainingMs] = useState(() =>
getMillisecondsUntilNextDailyQuiz(),
);
const hasNotifiedRef = useRef(false);
useEffect(() => {
const tick = () => {
const nextRemainingMs = getMillisecondsUntilNextDailyQuiz();
setRemainingMs(nextRemainingMs);
if (nextRemainingMs === 0) {
if (!hasNotifiedRef.current) {
hasNotifiedRef.current = true;
onAvailable?.();
}
return;
}
hasNotifiedRef.current = false;
};
tick();
const intervalId = window.setInterval(tick, 1000);
return () => {
window.clearInterval(intervalId);
};
}, [onAvailable]);
const nextAvailableAt = getNextDailyQuizAvailableAt();
const nextAvailableLabel = nextAvailableAt.toLocaleString(undefined, {
weekday: "short",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});
return (
<div className="space-y-1 pt-1">
<p className="text-sm text-muted-foreground">
Next check-in available in{" "}
<span className="font-medium tabular-nums text-foreground">
{formatDailyQuizCountdown(remainingMs)}
</span>
</p>
<p className="text-xs text-muted-foreground">
Opens at midnight ({nextAvailableLabel})
</p>
</div>
);
}

View File

@@ -5,6 +5,7 @@ import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { WellnessSlider } from "@/components/daily-quiz/wellness-slider";
import { DailyQuizCountdown } from "@/components/daily-quiz/daily-quiz-countdown";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -28,6 +29,7 @@ import {
getCachedQuizTabData,
prefetchQuizTabData,
} from "@/lib/app-tab-data-cache";
import { HABIT_PET_DATA_UPDATED_EVENT } from "@/lib/app-events";
import { saveDailyEntry } from "@/lib/daily-quiz-storage";
import { JOURNAL_MAX_LENGTH } from "@/lib/journal-safety";
import { routes } from "@/lib/routes";
@@ -63,7 +65,7 @@ export function DailyQuizForm() {
async function loadDailyCheckIn() {
try {
const data = await prefetchQuizTabData();
const data = await prefetchQuizTabData({ force: true });
if (cancelled) {
return;
@@ -89,8 +91,15 @@ export function DailyQuizForm() {
void loadDailyCheckIn();
const handleDataUpdated = () => {
void loadDailyCheckIn();
};
window.addEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated);
return () => {
cancelled = true;
window.removeEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated);
};
}, [applyQuizData]);
@@ -161,6 +170,11 @@ export function DailyQuizForm() {
</Link>{" "}
whenever you want to see how they are doing.
</p>
<DailyQuizCountdown
onAvailable={() => {
void prefetchQuizTabData({ force: true }).then(applyQuizData);
}}
/>
</div>
</CardContent>
</Card>

View File

@@ -25,6 +25,7 @@ import {
} from "@/lib/daily-reminders";
import { getDailyReminderStatus } from "@/lib/daily-reminder-status";
import { getTodayDateKey } from "@/lib/habits-storage";
import { resetTodaysDailyQuiz } from "@/lib/daily-quiz-storage";
import {
reminderDeliveryOptions,
usesSystemReminders,
@@ -56,6 +57,8 @@ export function ProfilePreferencesForm({ email }: ProfilePreferencesFormProps) {
const [notificationMessage, setNotificationMessage] = useState<string | null>(
null,
);
const [quizResetMessage, setQuizResetMessage] = useState<string | null>(null);
const [isResettingQuiz, setIsResettingQuiz] = useState(false);
useEffect(() => {
setNotificationPermission(getNotificationPermissionState());
@@ -209,6 +212,27 @@ export function ProfilePreferencesForm({ email }: ProfilePreferencesFormProps) {
);
};
const handleResetDailyQuiz = async () => {
setQuizResetMessage(null);
setError(null);
setIsResettingQuiz(true);
try {
await resetTodaysDailyQuiz();
setQuizResetMessage(
"Today's quiz was reset. Open the Quiz tab to take it again.",
);
} catch (resetError) {
setError(
resetError instanceof Error
? resetError.message
: "Could not reset today's quiz.",
);
} finally {
setIsResettingQuiz(false);
}
};
if (!isReady) {
return (
<p className="text-sm text-muted-foreground">Loading profile...</p>
@@ -365,6 +389,33 @@ export function ProfilePreferencesForm({ email }: ProfilePreferencesFormProps) {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Demo tools</CardTitle>
<CardDescription>
Shortcuts for testing flows without waiting for the daily reset.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-xs text-muted-foreground">
Reset clears today&apos;s wellness quiz entry so you can complete it
again immediately.
</p>
<Button
type="button"
variant="outline"
className="w-full"
disabled={isSaving || isResettingQuiz}
onClick={() => void handleResetDailyQuiz()}
>
{isResettingQuiz ? "Resetting..." : "Reset today's quiz"}
</Button>
{quizResetMessage ? (
<p className="text-xs text-emerald-600">{quizResetMessage}</p>
) : null}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Account settings</CardTitle>