diff --git a/src/App.tsx b/src/App.tsx
index 85704ff..6e784b5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -11,6 +11,7 @@ import { Login } from './components/Login';
import { SignUp } from './components/SignUp';
import { SettingsView } from './components/SettingsView';
import { MobileNav } from './components/MobileNav';
+import { PreferencesProvider, usePreferences } from './contexts/PreferencesContext';
const DAYS_OF_WEEK = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
@@ -21,6 +22,7 @@ function HabitTrackerContent() {
const [currentMonth, setCurrentMonth] = useState(new Date());
const { user, loading, signOut } = useAuth();
const [authView, setAuthView] = useState<'login' | 'signup'>('login');
+ const { preferences } = usePreferences();
const {
habits,
@@ -43,6 +45,12 @@ function HabitTrackerContent() {
setCurrentWeek(getCurrentWeekDates());
}, []);
+ useEffect(() => {
+ if (preferences?.default_view) {
+ setActiveView(preferences.default_view);
+ }
+ }, [preferences?.default_view]);
+
const handleAddHabit = async (e: React.FormEvent) => {
e.preventDefault();
if (newHabit.trim()) {
@@ -196,9 +204,11 @@ function HabitTrackerContent() {
export default function HabitTracker() {
return (
-
-
-
+
+
+
+
+
);
}
\ No newline at end of file
diff --git a/src/components/HabitList.tsx b/src/components/HabitList.tsx
index e21e5eb..f786f9d 100644
--- a/src/components/HabitList.tsx
+++ b/src/components/HabitList.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import { Trash2 } from 'lucide-react';
import { Habit } from '../types';
import { useThemeContext } from '../contexts/ThemeContext';
@@ -22,7 +22,15 @@ export function HabitList({
onUpdateHabit,
onDeleteHabit,
}: HabitListProps) {
- const { showStreaks } = useThemeContext();
+ const { habitSort, showStreaks } = useThemeContext();
+
+ const sortedHabits = useMemo(() => {
+ if (habitSort === 'alphabetical') {
+ return [...habits].sort((a, b) => a.name.localeCompare(b.name));
+ }
+ // Default to dateCreated sort
+ return habits;
+ }, [habits, habitSort]);
// Helper function to get day name
const getDayName = (dateStr: string) => {
@@ -53,7 +61,7 @@ export function HabitList({
- {habits.map((habit) => (
+ {sortedHabits.map((habit) => (
|
|
+ {showStreaks && (
+ <>
+
+ {/* ... streak content ... */}
+ |
+ >
+ )}
))}
diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx
index 328e3b2..030bf63 100644
--- a/src/components/SettingsView.tsx
+++ b/src/components/SettingsView.tsx
@@ -1,120 +1,95 @@
import React from 'react';
import { Sun, Moon } from 'lucide-react';
+import { usePreferences } from '../contexts/PreferencesContext';
import { useThemeContext } from '../contexts/ThemeContext';
export function SettingsView() {
- const {
- theme,
- isDark,
- showStreaks,
- dailyReminder,
- defaultView,
- habitSort,
- toggleDarkMode,
- toggleStreaks,
- toggleDailyReminder,
- setDefaultView,
- setHabitSort
- } = useThemeContext();
+ const { preferences, updatePreferences } = usePreferences();
+ const { theme } = useThemeContext();
- const handleReminderToggle = () => {
- if (!dailyReminder && Notification.permission === 'default') {
- Notification.requestPermission().then(permission => {
- if (permission === 'granted') {
- toggleDailyReminder();
- }
- });
- } else {
- toggleDailyReminder();
- }
+ const handleThemeChange = async (newTheme: 'light' | 'dark') => {
+ await updatePreferences({ theme: newTheme });
+ // Theme will be updated automatically through ThemeContext
+ };
+
+ const handleSortChange = async (newSort: 'dateCreated' | 'alphabetical') => {
+ await updatePreferences({ habit_sort: newSort });
+ // Sort will be updated automatically through ThemeContext
+ };
+
+ const handleStreaksChange = async (showStreaks: boolean) => {
+ await updatePreferences({ show_streaks: showStreaks });
+ // Streaks visibility will be updated automatically through ThemeContext
};
return (
-
-
Settings
-
- {/* Theme Toggle */}
-
-
Theme
-
-
-
- {/* Streaks Toggle */}
-
-
Streaks
-
-
-
- {/* Daily Reminder Toggle */}
-
-
Notifications
-
-
-
+
{/* Default View */}
-
-
Default View
+
+
Default View
- {/* Habit Sort */}
-
-
Sort Habits By
+ {/* Sort Habits */}
+
+
Sort Habits By
+
+ {/* Show Streaks */}
+
+
Show Streaks
+
+
+
+ {/* Daily Reminder */}
+
+
Daily Reminder
+
+
+
+ {/* Theme */}
+
+
Theme
+
+
);
}
\ No newline at end of file
diff --git a/src/contexts/PreferencesContext.tsx b/src/contexts/PreferencesContext.tsx
new file mode 100644
index 0000000..d3085ce
--- /dev/null
+++ b/src/contexts/PreferencesContext.tsx
@@ -0,0 +1,143 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import { supabase } from '../lib/supabase';
+import { UserPreferences, PreferenceUpdate } from '../types/preferences';
+import { useAuth } from './AuthContext';
+
+interface PreferencesContextType {
+ preferences: UserPreferences | null;
+ loading: boolean;
+ error: string | null;
+ updatePreferences: (updates: PreferenceUpdate) => Promise
;
+ retryFetch: () => Promise;
+}
+
+const PreferencesContext = createContext(undefined);
+
+export function PreferencesProvider({ children }: { children: React.ReactNode }) {
+ const [preferences, setPreferences] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const { user } = useAuth();
+
+ const fetchPreferences = async () => {
+ if (!user) {
+ setLoading(false);
+ return;
+ }
+
+ try {
+ setLoading(true);
+ setError(null);
+
+ // Log the user and connection status
+ console.log('Current user:', user);
+ console.log('Supabase connection:', supabase);
+
+ // First, try to fetch existing preferences
+ const { data, error: fetchError } = await supabase
+ .from('user_preferences')
+ .select('*')
+ .eq('user_id', user.id)
+ .single();
+
+ if (fetchError) {
+ console.error('Fetch error:', fetchError);
+
+ // If the error is that no rows were found, create default preferences
+ if (fetchError.code === 'PGRST116') {
+ const defaultPreferences = {
+ user_id: user.id,
+ show_streaks: true,
+ daily_reminder: false,
+ default_view: 'habits' as const,
+ habit_sort: 'dateCreated' as const,
+ theme: 'light' as const
+ };
+
+ console.log('Creating default preferences:', defaultPreferences);
+
+ const { data: newData, error: insertError } = await supabase
+ .from('user_preferences')
+ .insert([defaultPreferences])
+ .select()
+ .single();
+
+ if (insertError) {
+ console.error('Insert error:', insertError);
+ throw insertError;
+ }
+
+ console.log('Created preferences:', newData);
+ setPreferences(newData);
+ return;
+ }
+
+ throw fetchError;
+ }
+
+ console.log('Fetched preferences:', data);
+ setPreferences(data);
+
+ } catch (err) {
+ console.error('Error in fetchPreferences:', err);
+ setError(err instanceof Error ? err.message : 'Failed to fetch preferences');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const updatePreferences = async (updates: PreferenceUpdate) => {
+ if (!user || !preferences) return;
+
+ try {
+ console.log('Updating preferences:', updates);
+
+ const { data, error } = await supabase
+ .from('user_preferences')
+ .update(updates)
+ .eq('user_id', user.id)
+ .select()
+ .single();
+
+ if (error) {
+ console.error('Update error:', error);
+ throw error;
+ }
+
+ console.log('Updated preferences:', data);
+ setPreferences(data);
+ } catch (err) {
+ console.error('Error in updatePreferences:', err);
+ setError(err instanceof Error ? err.message : 'Failed to update preferences');
+ throw err;
+ }
+ };
+
+ const retryFetch = async () => {
+ await fetchPreferences();
+ };
+
+ useEffect(() => {
+ fetchPreferences();
+ }, [user]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function usePreferences() {
+ const context = useContext(PreferencesContext);
+ if (context === undefined) {
+ throw new Error('usePreferences must be used within a PreferencesProvider');
+ }
+ return context;
+}
\ No newline at end of file
diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx
index 7c2898a..b8ea12d 100644
--- a/src/contexts/ThemeContext.tsx
+++ b/src/contexts/ThemeContext.tsx
@@ -1,149 +1,47 @@
-import React, { createContext, useContext, useState, useEffect } from 'react';
-import { Theme, useTheme } from '../styles/theme';
-
-type HabitSortOption = 'dateCreated' | 'alphabetical';
+import React, { createContext, useContext, useEffect } from 'react';
+import { lightTheme, darkTheme } from '../styles/theme';
+import { usePreferences } from './PreferencesContext';
interface ThemeContextType {
- theme: Theme;
- isDark: boolean;
+ theme: typeof lightTheme;
showStreaks: boolean;
- dailyReminder: boolean;
- defaultView: 'habits' | 'calendar';
- habitSort: HabitSortOption;
- toggleDarkMode: () => void;
- toggleStreaks: () => void;
- toggleDailyReminder: () => void;
- setDefaultView: (view: 'habits' | 'calendar') => void;
- setHabitSort: (sort: HabitSortOption) => void;
+ habitSort: 'dateCreated' | 'alphabetical';
}
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);
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const { preferences, updatePreferences } = usePreferences();
+
+ // Use preferences or fallback to defaults
+ const currentTheme = preferences?.theme === 'dark' ? darkTheme : lightTheme;
+ const showStreaks = preferences?.show_streaks ?? true;
+ const habitSort = preferences?.habit_sort ?? 'dateCreated';
useEffect(() => {
- if (isDark) {
+ // Apply theme to document
+ if (preferences?.theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
- localStorage.setItem('darkMode', isDark.toString());
- }, [isDark]);
-
- useEffect(() => {
- 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);
- };
+ }, [preferences?.theme]);
return (
{children}
);
-};
+}
-export const useThemeContext = () => {
+export function useThemeContext() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useThemeContext must be used within a ThemeProvider');
}
return context;
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts
index a4cd1ce..e7a5fcc 100644
--- a/src/lib/supabase.ts
+++ b/src/lib/supabase.ts
@@ -1,5 +1,4 @@
import { createClient } from '@supabase/supabase-js';
-import { Database } from '../types/supabase';
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
@@ -8,4 +7,9 @@ if (!supabaseUrl || !supabaseAnonKey) {
throw new Error('Missing Supabase environment variables');
}
-export const supabase = createClient(supabaseUrl, supabaseAnonKey);
\ No newline at end of file
+export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
+ auth: {
+ persistSession: true,
+ autoRefreshToken: true,
+ }
+});
\ No newline at end of file
diff --git a/src/types/preferences.ts b/src/types/preferences.ts
new file mode 100644
index 0000000..25e76da
--- /dev/null
+++ b/src/types/preferences.ts
@@ -0,0 +1,13 @@
+export interface UserPreferences {
+ id: string;
+ user_id: string;
+ show_streaks: boolean;
+ daily_reminder: boolean;
+ default_view: 'habits' | 'calendar';
+ habit_sort: 'dateCreated' | 'alphabetical';
+ theme: 'light' | 'dark';
+ created_at: string;
+ updated_at: string;
+}
+
+export type PreferenceUpdate = Partial>;
\ No newline at end of file