mirror of
https://github.com/harivansh-afk/Habit-Tracker.git
synced 2026-04-15 07:04:47 +00:00
user settings debugged
This commit is contained in:
parent
1cd3445bd6
commit
20e91dae4c
7 changed files with 282 additions and 224 deletions
16
src/App.tsx
16
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 (
|
||||
<AuthProvider>
|
||||
<ThemeProvider>
|
||||
<HabitTrackerContent />
|
||||
</ThemeProvider>
|
||||
<PreferencesProvider>
|
||||
<ThemeProvider>
|
||||
<HabitTrackerContent />
|
||||
</ThemeProvider>
|
||||
</PreferencesProvider>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -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({
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{habits.map((habit) => (
|
||||
{sortedHabits.map((habit) => (
|
||||
<tr key={habit.id} className="border-t dark:border-gray-700">
|
||||
<td className="px-4 py-2 dark:text-white">
|
||||
<input
|
||||
|
|
@ -106,6 +114,13 @@ export function HabitList({
|
|||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</td>
|
||||
{showStreaks && (
|
||||
<>
|
||||
<td className="px-4 py-2 text-center">
|
||||
{/* ... streak content ... */}
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className={`rounded-lg shadow p-6 ${theme.cardBackground}`}>
|
||||
<h2 className="text-xl font-semibold mb-6 dark:text-white">Settings</h2>
|
||||
|
||||
{/* Theme Toggle */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-medium mb-2 dark:text-white">Theme</h3>
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className={`flex items-center space-x-2 px-4 py-2 rounded-lg transition-colors duration-200
|
||||
${isDark
|
||||
? 'bg-gray-700 text-white hover:bg-gray-600'
|
||||
: 'bg-gray-100 text-gray-900 hover:bg-gray-200'}`}
|
||||
>
|
||||
{isDark ? <Moon className="h-5 w-5" /> : <Sun className="h-5 w-5" />}
|
||||
<span>{isDark ? 'Dark Mode' : 'Light Mode'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Streaks Toggle */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-medium mb-2 dark:text-white">Streaks</h3>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={showStreaks}
|
||||
onChange={toggleStreaks}
|
||||
/>
|
||||
<div className={`w-10 h-6 rounded-full transition-colors duration-200
|
||||
${showStreaks ? 'bg-green-500' : 'bg-gray-300'}`}>
|
||||
<div className={`absolute w-4 h-4 rounded-full bg-white transition-transform duration-200 transform
|
||||
${showStreaks ? 'translate-x-5' : 'translate-x-1'} top-1`} />
|
||||
</div>
|
||||
</div>
|
||||
<span className="ml-3 dark:text-white">Show Streaks</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Daily Reminder Toggle */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-medium mb-2 dark:text-white">Notifications</h3>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={dailyReminder}
|
||||
onChange={handleReminderToggle}
|
||||
/>
|
||||
<div className={`w-10 h-6 rounded-full transition-colors duration-200
|
||||
${dailyReminder ? 'bg-green-500' : 'bg-gray-300'}`}>
|
||||
<div className={`absolute w-4 h-4 rounded-full bg-white transition-transform duration-200 transform
|
||||
${dailyReminder ? 'translate-x-5' : 'translate-x-1'} top-1`} />
|
||||
</div>
|
||||
</div>
|
||||
<span className="ml-3 dark:text-white">Daily Reminder</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="max-w-2xl mx-auto space-y-8 p-4">
|
||||
{/* Default View */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-medium mb-2 dark:text-white">Default View</h3>
|
||||
<div className="space-y-2">
|
||||
<h3 className={`text-lg font-medium ${theme.text}`}>Default View</h3>
|
||||
<select
|
||||
value={defaultView}
|
||||
onChange={(e) => setDefaultView(e.target.value as 'habits' | 'calendar')}
|
||||
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 sm:text-sm rounded-md dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
value={preferences.default_view}
|
||||
onChange={(e) => updatePreferences({ default_view: e.target.value as 'habits' | 'calendar' })}
|
||||
className={`w-full p-2 rounded-lg border ${theme.input}`}
|
||||
>
|
||||
<option value="habits">Habits</option>
|
||||
<option value="calendar">Calendar</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Habit Sort */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-medium mb-2 dark:text-white">Sort Habits By</h3>
|
||||
{/* Sort Habits */}
|
||||
<div className="space-y-2">
|
||||
<h3 className={`text-lg font-medium ${theme.text}`}>Sort Habits By</h3>
|
||||
<select
|
||||
value={habitSort}
|
||||
onChange={(e) => setHabitSort(e.target.value as 'dateCreated' | 'alphabetical')}
|
||||
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 sm:text-sm rounded-md dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
value={preferences.habit_sort}
|
||||
onChange={(e) => handleSortChange(e.target.value as 'dateCreated' | 'alphabetical')}
|
||||
className={`w-full p-2 rounded-lg border ${theme.input}`}
|
||||
>
|
||||
<option value="dateCreated">Date Created</option>
|
||||
<option value="alphabetical">Alphabetical</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Show Streaks */}
|
||||
<div className="space-y-2">
|
||||
<h3 className={`text-lg font-medium ${theme.text}`}>Show Streaks</h3>
|
||||
<label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={preferences.show_streaks}
|
||||
onChange={(e) => handleStreaksChange(e.target.checked)}
|
||||
className="form-checkbox h-5 w-5 text-green-500"
|
||||
/>
|
||||
<span className={theme.text}>Enable streak counting</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Daily Reminder */}
|
||||
<div className="space-y-2">
|
||||
<h3 className={`text-lg font-medium ${theme.text}`}>Daily Reminder</h3>
|
||||
<label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={preferences.daily_reminder}
|
||||
onChange={(e) => updatePreferences({ daily_reminder: e.target.checked })}
|
||||
className="form-checkbox h-5 w-5 text-green-500"
|
||||
/>
|
||||
<span className={theme.text}>Enable daily reminders</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Theme */}
|
||||
<div className="space-y-2">
|
||||
<h3 className={`text-lg font-medium ${theme.text}`}>Theme</h3>
|
||||
<select
|
||||
value={preferences.theme}
|
||||
onChange={(e) => handleThemeChange(e.target.value as 'light' | 'dark')}
|
||||
className={`w-full p-2 rounded-lg border ${theme.input}`}
|
||||
>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
143
src/contexts/PreferencesContext.tsx
Normal file
143
src/contexts/PreferencesContext.tsx
Normal file
|
|
@ -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<void>;
|
||||
retryFetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
const PreferencesContext = createContext<PreferencesContextType | undefined>(undefined);
|
||||
|
||||
export function PreferencesProvider({ children }: { children: React.ReactNode }) {
|
||||
const [preferences, setPreferences] = useState<UserPreferences | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(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 (
|
||||
<PreferencesContext.Provider value={{
|
||||
preferences,
|
||||
loading,
|
||||
error,
|
||||
updatePreferences,
|
||||
retryFetch
|
||||
}}>
|
||||
{children}
|
||||
</PreferencesContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function usePreferences() {
|
||||
const context = useContext(PreferencesContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('usePreferences must be used within a PreferencesProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
|
@ -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<ThemeContextType | undefined>(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<HabitSortOption>(() =>
|
||||
(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 (
|
||||
<ThemeContext.Provider value={{
|
||||
theme,
|
||||
isDark,
|
||||
theme: currentTheme,
|
||||
showStreaks,
|
||||
dailyReminder,
|
||||
defaultView,
|
||||
habitSort,
|
||||
toggleDarkMode,
|
||||
toggleStreaks,
|
||||
toggleDailyReminder,
|
||||
setDefaultView: handleSetDefaultView,
|
||||
setHabitSort: handleSetHabitSort
|
||||
habitSort
|
||||
}}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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<Database>(supabaseUrl, supabaseAnonKey);
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
||||
auth: {
|
||||
persistSession: true,
|
||||
autoRefreshToken: true,
|
||||
}
|
||||
});
|
||||
13
src/types/preferences.ts
Normal file
13
src/types/preferences.ts
Normal file
|
|
@ -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<Omit<UserPreferences, 'id' | 'user_id' | 'created_at' | 'updated_at'>>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue