diff --git a/habits.db b/habits.db index d7563ff..46d2ff3 100644 Binary files a/habits.db and b/habits.db differ diff --git a/src/App.tsx b/src/App.tsx index 66e3f91..0297334 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,9 +8,9 @@ import { useWeek } from './hooks/useWeek'; import { ThemeProvider, useThemeContext } from './contexts/ThemeContext'; function HabitTrackerContent() { - const { theme, isDark, toggleDarkMode } = useThemeContext(); + const { theme, isDark, toggleDarkMode, defaultView, habitSort } = useThemeContext(); const [newHabit, setNewHabit] = useState(''); - const [activeView, setActiveView] = useState<'habits' | 'calendar' | 'settings'>('habits'); + const [activeView, setActiveView] = useState<'habits' | 'calendar' | 'settings'>(defaultView); const [currentMonth, setCurrentMonth] = useState(new Date()); const { @@ -63,6 +63,14 @@ function HabitTrackerContent() { setCurrentWeek(getCurrentWeekDates()); }; + const getSortedHabits = () => { + if (habitSort === 'alphabetical') { + return [...habits].sort((a, b) => a.name.localeCompare(b.name)); + } + // Default to dateCreated (assuming habits are already in creation order) + return habits; + }; + const renderHabitsView = () => (
@@ -112,7 +120,7 @@ function HabitTrackerContent() {
new Date(year, month + 1, 0).getDate()} getCompletedHabitsForDate={getCompletedHabitsForDate} + onToggleHabit={async (habitId, date) => { + await toggleHabit(habitId, date); + await fetchHabits(); + }} /> ); const renderSettingsView = () => { - const { theme, isDark, showStreaks, toggleDarkMode, toggleStreaks } = useThemeContext(); + const { + theme, + isDark, + showStreaks, + dailyReminder, + defaultView, + habitSort, + toggleDarkMode, + toggleStreaks, + toggleDailyReminder, + setDefaultView, + setHabitSort + } = useThemeContext(); + const handleReminderToggle = () => { + if (!dailyReminder && Notification.permission === 'default') { + Notification.requestPermission().then(permission => { + if (permission === 'granted') { + toggleDailyReminder(); + } + }); + } else { + toggleDailyReminder(); + } + }; + return (

Settings

@@ -164,8 +200,6 @@ function HabitTrackerContent() { relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ease-in-out ${showStreaks ? 'bg-[#2ecc71]' : 'bg-gray-200 dark:bg-gray-700'} - focus:outline-none focus:ring-2 focus:ring-[#2ecc71] focus:ring-offset-2 - dark:focus:ring-offset-gray-800 `} >
+ +
+
+ Daily Reminder +

Get notified at 10:00 AM daily

+
+ +
+ +
+
+ Default View +

Choose your starting page

+
+
+ + +
+
+ +
+
+ Sort Habits +

Choose how to order your habits

+
+
+ + +
+
); diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx index 56657f2..285e83d 100644 --- a/src/components/Calendar.tsx +++ b/src/components/Calendar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { ChevronLeft, ChevronRight, Check } from 'lucide-react'; import { useThemeContext } from '../contexts/ThemeContext'; import { Habit } from '../types'; @@ -9,6 +9,7 @@ interface CalendarProps { onChangeMonth: (direction: 'prev' | 'next') => void; getDaysInMonth: (year: number, month: number) => number; getCompletedHabitsForDate: (date: string) => Habit[]; + onToggleHabit: (habitId: number, date: string) => void; } export const Calendar: React.FC = ({ @@ -16,7 +17,8 @@ export const Calendar: React.FC = ({ habits, onChangeMonth, getDaysInMonth, - getCompletedHabitsForDate + getCompletedHabitsForDate, + onToggleHabit }) => { const { theme } = useThemeContext(); @@ -35,6 +37,11 @@ export const Calendar: React.FC = ({ // Get today's date in YYYY-MM-DD format const today = formatDate(new Date()); + const handleToggleHabit = async (e: React.MouseEvent, habitId: number, date: string) => { + e.stopPropagation(); + await onToggleHabit(habitId, date); + }; + return (
@@ -151,6 +158,7 @@ export const Calendar: React.FC = ({ `} >
= ({ ${theme.calendar.tooltip.shadow} border backdrop-blur-sm - pointer-events-none `}> {completedHabits.length > 0 && (
@@ -167,8 +174,17 @@ export const Calendar: React.FC = ({
    {completedHabits.map(habit => ( -
  • - {habit.name} +
  • + {habit.name} +
  • ))}
@@ -181,8 +197,17 @@ export const Calendar: React.FC = ({
    {incompleteHabits.map(habit => ( -
  • - {habit.name} +
  • + {habit.name} +
  • ))}
diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx index 13eb285..7c2898a 100644 --- a/src/contexts/ThemeContext.tsx +++ b/src/contexts/ThemeContext.tsx @@ -1,12 +1,20 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; import { Theme, useTheme } from '../styles/theme'; +type HabitSortOption = 'dateCreated' | 'alphabetical'; + interface ThemeContextType { theme: Theme; isDark: boolean; showStreaks: boolean; + dailyReminder: boolean; + defaultView: 'habits' | 'calendar'; + habitSort: HabitSortOption; toggleDarkMode: () => void; toggleStreaks: () => void; + toggleDailyReminder: () => void; + setDefaultView: (view: 'habits' | 'calendar') => void; + setHabitSort: (sort: HabitSortOption) => void; } const ThemeContext = createContext(undefined); @@ -14,6 +22,13 @@ const ThemeContext = createContext(undefined); export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [isDark, setIsDark] = useState(() => localStorage.getItem('darkMode') === 'true'); const [showStreaks, setShowStreaks] = useState(() => localStorage.getItem('showStreaks') !== 'false'); + const [dailyReminder, setDailyReminder] = useState(() => localStorage.getItem('dailyReminder') === 'true'); + const [defaultView, setDefaultView] = useState<'habits' | 'calendar'>(() => + (localStorage.getItem('defaultView') as 'habits' | 'calendar') || 'habits' + ); + const [habitSort, setHabitSort] = useState(() => + (localStorage.getItem('habitSort') as HabitSortOption) || 'dateCreated' + ); const theme = useTheme(isDark); useEffect(() => { @@ -29,11 +44,97 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre localStorage.setItem('showStreaks', showStreaks.toString()); }, [showStreaks]); + useEffect(() => { + if (dailyReminder) { + // Request notification permission if not granted + if (Notification.permission !== 'granted') { + Notification.requestPermission(); + } + + // Schedule notification for 10am + const now = new Date(); + const scheduledTime = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + 10, // 10am + 0, + 0 + ); + + // If it's past 10am, schedule for tomorrow + if (now > scheduledTime) { + scheduledTime.setDate(scheduledTime.getDate() + 1); + } + + const timeUntilNotification = scheduledTime.getTime() - now.getTime(); + + const timer = setTimeout(() => { + if (Notification.permission === 'granted') { + new Notification('Habit Tracker Reminder', { + body: "Don't forget to track your habits for today!", + icon: '/favicon.ico' // Add your app's icon path here + }); + + // Schedule next day's notification + const nextDay = new Date(scheduledTime); + nextDay.setDate(nextDay.getDate() + 1); + const nextTimeUntilNotification = nextDay.getTime() - new Date().getTime(); + setTimeout(() => { + if (dailyReminder) { + new Notification('Habit Tracker Reminder', { + body: "Don't forget to track your habits for today!", + icon: '/favicon.ico' + }); + } + }, nextTimeUntilNotification); + } + }, timeUntilNotification); + + return () => clearTimeout(timer); + } + }, [dailyReminder]); + const toggleDarkMode = () => setIsDark(!isDark); const toggleStreaks = () => setShowStreaks(!showStreaks); + const toggleDailyReminder = () => { + if (!dailyReminder && Notification.permission !== 'granted') { + Notification.requestPermission().then(permission => { + if (permission === 'granted') { + setDailyReminder(true); + localStorage.setItem('dailyReminder', 'true'); + } + }); + } else { + setDailyReminder(!dailyReminder); + localStorage.setItem('dailyReminder', (!dailyReminder).toString()); + } + }; + + const handleSetDefaultView = (view: 'habits' | 'calendar') => { + setDefaultView(view); + localStorage.setItem('defaultView', view); + }; + + const handleSetHabitSort = (sort: HabitSortOption) => { + setHabitSort(sort); + localStorage.setItem('habitSort', sort); + }; return ( - + {children} );