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 = () => (
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}
);