diff --git a/app/favicon.ico b/app/favicon.ico index 718d6fe..942c769 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/components/habits/daily-tasks-countdown.tsx b/components/habits/daily-tasks-countdown.tsx new file mode 100644 index 0000000..e480764 --- /dev/null +++ b/components/habits/daily-tasks-countdown.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; + +import { + formatCountdownDuration, + getMillisecondsUntilLocalMidnight, + getNextLocalMidnight, +} from "@/lib/date-keys"; +import { cn } from "@/lib/utils"; + +type DailyTasksCountdownProps = { + className?: string; + onDayChange?: () => void; +}; + +export function DailyTasksCountdown({ + className, + onDayChange, +}: DailyTasksCountdownProps) { + const [remainingMs, setRemainingMs] = useState(() => + getMillisecondsUntilLocalMidnight(), + ); + const hasNotifiedRef = useRef(false); + + useEffect(() => { + const tick = () => { + const nextRemainingMs = getMillisecondsUntilLocalMidnight(); + setRemainingMs(nextRemainingMs); + + if (nextRemainingMs === 0) { + if (!hasNotifiedRef.current) { + hasNotifiedRef.current = true; + onDayChange?.(); + } + return; + } + + hasNotifiedRef.current = false; + }; + + tick(); + const intervalId = window.setInterval(tick, 1000); + + return () => { + window.clearInterval(intervalId); + }; + }, [onDayChange]); + + const resetsAtLabel = getNextLocalMidnight().toLocaleTimeString(undefined, { + hour: "numeric", + minute: "2-digit", + }); + + return ( +
+

+ Time remaining today{" "} + + {formatCountdownDuration(remainingMs)} + +

+

+ Tasks reset at midnight ({resetsAtLabel}) +

+
+ ); +} diff --git a/components/habits/habit-tracker.tsx b/components/habits/habit-tracker.tsx index 46e92dd..a74ef98 100644 --- a/components/habits/habit-tracker.tsx +++ b/components/habits/habit-tracker.tsx @@ -3,6 +3,7 @@ import { Loader2 } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; +import { DailyTasksCountdown } from "@/components/habits/daily-tasks-countdown"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -41,6 +42,29 @@ const reasonLabels = { suggested: "Suggested for you", } as const; +function DailyTasksCardHeader({ + description, + onDayChange, +}: { + description: string; + onDayChange: () => void; +}) { + return ( + +
+
+ Today's tasks + {description} +
+ +
+
+ ); +} + function DailyTaskList({ tasks, getIsCompleted, @@ -424,13 +448,12 @@ export function HabitTracker({ mode = "daily" }: HabitTrackerProps) { return (
- - Today's tasks - - Personalized from your focus topics - {quizCompletedToday ? ", today's quiz," : ""} and custom habits. - - + {error ?

{error}

: null} {coinMessage ? ( @@ -572,12 +595,10 @@ export function HabitTracker({ mode = "daily" }: HabitTrackerProps) { return ( - - Today's tasks - - Personalized from your profile, daily quiz, journal, and custom habits. - - + {error ?

{error}

: null} {coinMessage ? ( diff --git a/lib/daily-quiz-storage.ts b/lib/daily-quiz-storage.ts index f4eac1c..62bb053 100644 --- a/lib/daily-quiz-storage.ts +++ b/lib/daily-quiz-storage.ts @@ -6,6 +6,7 @@ import { } from "@/lib/avatar-state"; import { notifyHabitPetDataUpdated } from "@/lib/app-events"; import { + formatCountdownDuration, getMillisecondsUntilLocalMidnight, getNextLocalMidnight, getTodayDateKey, @@ -36,12 +37,7 @@ export function getMillisecondsUntilNextDailyQuiz(from = new Date()) { } export function formatDailyQuizCountdown(milliseconds: number) { - const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000)); - const hours = Math.floor(totalSeconds / 3600); - const minutes = Math.floor((totalSeconds % 3600) / 60); - const seconds = totalSeconds % 60; - - return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + return formatCountdownDuration(milliseconds); } export async function resetTodaysDailyQuiz() { diff --git a/lib/date-keys.ts b/lib/date-keys.ts index 5593d29..7013847 100644 --- a/lib/date-keys.ts +++ b/lib/date-keys.ts @@ -17,3 +17,12 @@ export function getNextLocalMidnight(from = new Date()) { export function getMillisecondsUntilLocalMidnight(from = new Date()) { return Math.max(0, getNextLocalMidnight(from).getTime() - from.getTime()); } + +export function formatCountdownDuration(milliseconds: number) { + const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000)); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; +}