diff --git a/src/App.tsx b/src/App.tsx
index 0297334..e1c9279 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,12 +6,20 @@ import { Sidebar } from './components/Sidebar';
import { useHabits } from './hooks/useHabits';
import { useWeek } from './hooks/useWeek';
import { ThemeProvider, useThemeContext } from './contexts/ThemeContext';
+import { AuthProvider, useAuth } from './contexts/AuthContext';
+import { Login } from './components/Login';
+import { SignUp } from './components/SignUp';
+import { SettingsView } from './components/SettingsView';
+
+const DAYS_OF_WEEK = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
function HabitTrackerContent() {
const { theme, isDark, toggleDarkMode, defaultView, habitSort } = useThemeContext();
const [newHabit, setNewHabit] = useState('');
const [activeView, setActiveView] = useState<'habits' | 'calendar' | 'settings'>(defaultView);
const [currentMonth, setCurrentMonth] = useState(new Date());
+ const { user, loading, signOut } = useAuth();
+ const [authView, setAuthView] = useState<'login' | 'signup'>('login');
const {
habits,
@@ -19,8 +27,7 @@ function HabitTrackerContent() {
addHabit: addHabitApi,
toggleHabit,
updateHabit,
- deleteHabit,
- updateStreak
+ deleteHabit
} = useHabits();
const {
@@ -30,8 +37,6 @@ function HabitTrackerContent() {
changeWeek
} = useWeek();
- const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
-
useEffect(() => {
fetchHabits();
setCurrentWeek(getCurrentWeekDates());
@@ -122,11 +127,10 @@ function HabitTrackerContent() {
Keep up the good work! Consistency is key.
@@ -147,163 +151,17 @@ function HabitTrackerContent() {
/>
);
- const renderSettingsView = () => {
- 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();
- }
- };
+ if (loading) {
+ return Loading...
;
+ }
- return (
-
-
Settings
-
-
- Dark Mode
-
-
-
-
- Show Streaks
-
-
-
-
-
-
Daily Reminder
-
Get notified at 10:00 AM daily
-
-
-
-
-
-
-
Default View
-
Choose your starting page
-
-
-
-
-
-
-
-
-
-
Sort Habits
-
Choose how to order your habits
-
-
-
-
-
-
-
-
+ if (!user) {
+ return authView === 'login' ? (
+ setAuthView('signup')} />
+ ) : (
+ setAuthView('login')} />
);
- };
+ }
return (
@@ -312,7 +170,7 @@ function HabitTrackerContent() {
{activeView === 'habits' && renderHabitsView()}
{activeView === 'calendar' && renderCalendarView()}
- {activeView === 'settings' && renderSettingsView()}
+ {activeView === 'settings' && }
@@ -321,8 +179,10 @@ 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 91749dc..e2c3857 100644
--- a/src/components/HabitList.tsx
+++ b/src/components/HabitList.tsx
@@ -1,7 +1,8 @@
-import React, { useEffect } from 'react';
+import React from 'react';
import { Trash2 } from 'lucide-react';
import { Habit } from '../types';
import { useThemeContext } from '../contexts/ThemeContext';
+import { calculateStreak } from '../utils/streakCalculator';
interface HabitListProps {
habits: Habit[];
@@ -10,83 +11,9 @@ interface HabitListProps {
onToggleHabit: (id: number, date: string) => void;
onUpdateHabit: (id: number, name: string) => void;
onDeleteHabit: (id: number) => void;
- onUpdateStreak: (id: number, newStreak: number) => Promise;
+ onUpdateStreak?: (id: number, newStreak: number) => Promise;
}
-const calculateStreak = (completedDates: string[]): { currentStreak: number; bestStreak: number } => {
- if (!completedDates || completedDates.length === 0) {
- return { currentStreak: 0, bestStreak: 0 };
- }
-
- // Get today at midnight in UTC
- const today = new Date();
- const utcToday = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()));
-
- const todayStr = utcToday.toISOString().split('T')[0];
-
- // Sort dates in descending order (most recent first)
- const sortedDates = [...completedDates]
- .filter(date => !isNaN(new Date(date).getTime()))
- .sort((a, b) => new Date(b).getTime() - new Date(a).getTime());
-
- let currentStreak = 0;
- let bestStreak = 0;
- let tempStreak = 0;
-
- // Check if today is completed to maintain streak
- const hasTodayCompleted = sortedDates.includes(todayStr);
-
- // Initialize current streak based on today's completion
- if (hasTodayCompleted) {
- currentStreak = 1;
- let checkDate = new Date(utcToday);
-
- // Check previous days
- while (true) {
- checkDate.setUTCDate(checkDate.getUTCDate() - 1);
- const dateStr = checkDate.toISOString().split('T')[0];
-
- if (sortedDates.includes(dateStr)) {
- currentStreak++;
- } else {
- break;
- }
- }
- }
-
- // Calculate best streak
- for (let i = 0; i < sortedDates.length; i++) {
- const currentDate = new Date(sortedDates[i]);
- currentDate.setHours(0, 0, 0, 0); // Normalize time part for comparison
-
- if (i === 0) {
- tempStreak = 1;
- } else {
- const prevDate = new Date(sortedDates[i - 1]);
- const diffDays = Math.floor(
- (prevDate.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24)
- );
-
- if (diffDays === 1) {
- tempStreak++;
- } else if (diffDays === 0) {
- // Same day, skip
- continue;
- } else {
- // Reset streak on gap
- bestStreak = Math.max(bestStreak, tempStreak);
- tempStreak = 1;
- }
- }
- }
-
- // Final check for best streak
- bestStreak = Math.max(bestStreak, tempStreak);
- bestStreak = Math.max(bestStreak, currentStreak);
-
- return { currentStreak, bestStreak };
-};
-
export function HabitList({
habits,
currentWeek,
@@ -94,17 +21,15 @@ export function HabitList({
onToggleHabit,
onUpdateHabit,
onDeleteHabit,
- onUpdateStreak,
}: HabitListProps) {
const { showStreaks } = useThemeContext();
-
- useEffect(() => {
- console.log('Current week dates:',
- currentWeek.map(date =>
- `${new Date(date).toLocaleDateString()} (${daysOfWeek[new Date(date).getDay() === 0 ? 6 : new Date(date).getDay() - 1]})`
- )
- );
- }, []);
+
+ // Helper function to get day name
+ const getDayName = (dateStr: string) => {
+ const date = new Date(dateStr);
+ const dayIndex = date.getDay() === 0 ? 6 : date.getDay() - 1;
+ return daysOfWeek[dayIndex];
+ };
return (
@@ -112,13 +37,11 @@ export function HabitList({
| Habit |
- {currentWeek.map((dateStr, index) => {
+ {currentWeek.map((dateStr) => {
const date = new Date(dateStr);
- const dayIndex = date.getDay() === 0 ? 6 : date.getDay() - 1;
-
return (
- {daysOfWeek[dayIndex]}
+ {getDayName(dateStr)}
{date.getDate()}
@@ -155,12 +78,6 @@ export function HabitList({
checked={habit.completedDates.includes(date)}
onChange={() => {
onToggleHabit(habit.id, date);
- const newCompletedDates = habit.completedDates.includes(date)
- ? habit.completedDates.filter(d => d !== date)
- : [...habit.completedDates, date];
-
- const { bestStreak } = calculateStreak(newCompletedDates);
- onUpdateStreak(habit.id, bestStreak);
}}
aria-label={`Mark ${habit.name} as completed for ${date}`}
className="sr-only"
diff --git a/src/components/Login.tsx b/src/components/Login.tsx
new file mode 100644
index 0000000..62eeda2
--- /dev/null
+++ b/src/components/Login.tsx
@@ -0,0 +1,67 @@
+import { useState } from 'react';
+import { useAuth } from '../contexts/AuthContext';
+import { useThemeContext } from '../contexts/ThemeContext';
+
+export function Login({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const { signIn } = useAuth();
+ const { theme } = useThemeContext();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ try {
+ setError('');
+ await signIn(email, password);
+ } catch (error) {
+ setError('Failed to sign in');
+ }
+ };
+
+ return (
+
+
+ Log In
+ {error && {error} }
+
+
+ Don't have an account?{' '}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx
new file mode 100644
index 0000000..328e3b2
--- /dev/null
+++ b/src/components/SettingsView.tsx
@@ -0,0 +1,120 @@
+import React from 'react';
+import { Sun, Moon } from 'lucide-react';
+import { useThemeContext } from '../contexts/ThemeContext';
+
+export function SettingsView() {
+ 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
+
+ {/* Theme Toggle */}
+
+ Theme
+
+
+
+ {/* Streaks Toggle */}
+
+ Streaks
+
+
+
+ {/* Daily Reminder Toggle */}
+
+ Notifications
+
+
+
+ {/* Default View */}
+
+ Default View
+
+
+
+ {/* Habit Sort */}
+
+ Sort Habits By
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index f653652..c7921e5 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,6 +1,7 @@
import React from 'react';
-import { Plus, CalendarIcon, SettingsIcon } from 'lucide-react';
+import { Plus, CalendarIcon, SettingsIcon, LogOut } from 'lucide-react';
import { useThemeContext } from '../contexts/ThemeContext';
+import { useAuth } from '../contexts/AuthContext';
type View = 'habits' | 'calendar' | 'settings';
@@ -11,13 +12,14 @@ interface SidebarProps {
export const Sidebar: React.FC = ({ activeView, setActiveView }) => {
const { theme } = useThemeContext();
+ const { signOut } = useAuth();
return (
- |