diff --git a/src/components/ui/date-picker.tsx b/src/components/ui/date-picker.tsx new file mode 100644 index 0000000..50fb74f --- /dev/null +++ b/src/components/ui/date-picker.tsx @@ -0,0 +1,152 @@ +import * as React from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { motion } from "framer-motion"; + +import { cn } from "@/lib/utils"; + +interface DatePickerProps extends React.HTMLAttributes { + preselectedDate?: Date; + onDateChange?: (date: Date) => void; +} + +const DatePicker = React.forwardRef( + ({ className, preselectedDate, onDateChange, ...props }, ref) => { + const [currentDate, setCurrentDate] = React.useState(new Date()); + const [selectedDate, setSelectedDate] = React.useState(preselectedDate || new Date()); + + const daysInMonth = (month: number, year: number) => { + return new Date(year, month + 1, 0).getDate(); + }; + + const startDayOfMonth = (month: number, year: number) => { + return new Date(year, month, 1).getDay(); + }; + + const handlePrevMonth = () => { + setCurrentDate( + new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1) + ); + }; + + const handleNextMonth = () => { + setCurrentDate( + new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1) + ); + }; + + const handleDateClick = (day: number) => { + const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + setSelectedDate(newDate); + if (onDateChange) { + onDateChange(newDate); + } + }; + + const renderDays = () => { + const days = []; + const daysInCurrentMonth = daysInMonth( + currentDate.getMonth(), + currentDate.getFullYear() + ); + const startDay = startDayOfMonth( + currentDate.getMonth(), + currentDate.getFullYear() + ); + + for (let i = 0; i < startDay; i++) { + days.push(
); + } + + for (let day = 1; day <= daysInCurrentMonth; day++) { + days.push( + handleDateClick(day)} + > + {day} + + ); + } + + return days; + }; + + return ( +
+
+ + + + + {currentDate.toLocaleString("default", { month: "long" })}{" "} + {currentDate.getFullYear()} + + + + +
+
+ {["S", "M", "T", "W", "T", "F", "S"].map((date, index) => ( + + {date} + + ))} + {renderDays()} +
+
+ ); + } +); + +DatePicker.displayName = "DatePicker"; + +export default DatePicker; diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index c66b0c2..f1c9758 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useAuth } from '../utils/auth'; -import { updateStreak, getUserProfile } from '../utils/firebase'; +import { updateStreak, getUserProfile, updateStreakStart } from '../utils/firebase'; import { cn } from '@/lib/utils'; import { ArrowRight, @@ -10,6 +10,9 @@ import { Trophy, TrendingUp, MessageCircle, + Map, + HeartPulse, + CheckIcon HeartPulse, BookOpen } from 'lucide-react'; @@ -22,6 +25,7 @@ import CommunityMap from '@/components/CommunityMap'; import { motion } from 'framer-motion'; import { toast } from 'sonner'; import VerseSlideshow from '@/components/VerseSlideshow'; +import DatePicker from '@/components/ui/date-picker'; const formatDate = (date: Date) => { return new Intl.DateTimeFormat('en-US', { @@ -36,6 +40,8 @@ const Dashboard: React.FC = () => { const [streak, setStreak] = useState(0); const [lastCheckIn, setLastCheckIn] = useState(null); const [isCheckedInToday, setIsCheckedInToday] = useState(false); + const [isCheckInSide, setIsCheckInSide] = useState(true); + const [selectedDate, setSelectedDate] = useState(new Date()); const featuredMeditations = [ { @@ -78,9 +84,10 @@ const Dashboard: React.FC = () => { const handleCheckIn = async () => { if (!currentUser) return; - + console.log(currentUser.uid) try { const result = await updateStreak(currentUser.uid); + console.log(result) if (result.success) { const updatedProfile = await getUserProfile(currentUser.uid); @@ -109,6 +116,44 @@ const Dashboard: React.FC = () => { } }; + const handleStreakSet = async () => { + if (!currentUser) return; + + try { + const result = await updateStreakStart(currentUser.uid, selectedDate); + + if (result.success) { + // Refresh user data + const updatedProfile = await getUserProfile(currentUser.uid); + + if (updatedProfile) { + setStreak(updatedProfile.streakDays || 0); + } + + if (result.message === 'Streak start updated successfully') { + toast.success("Streak start updated!", { + description: `Your streak has been reset to start from ${formatDate(selectedDate)}.`, + }); + } + } + + if (!result.success && result.message === 'Invalid Date') { + toast.error("Invalid date selected", { + description: "Please select a valid date to start your streak.", + }); + } + + } catch (error) { + console.error('Error updating streak:', error); + toast.error("Failed to set streak start date", { + description: "Please try again later.", + }); + } finally { + setIsCheckInSide(true); + } + }; + + return ( { "border-primary/20 h-full", streak > 0 && "bg-gradient-to-br from-primary/10 to-transparent" )}> - - - - Your Current Streak - - - {isCheckedInToday - ? 'You\'ve checked in today. Great job!' - : 'Remember to check in daily to maintain your streak'} - - - -
-
-
- {streak} -
-
- consecutive {streak === 1 ? 'day' : 'days'} + {/* Current Streak Card - Check In Side */} + {isCheckInSide ? ( + + + + + Your Current Streak + + + {isCheckedInToday + ? 'You\'ve checked in today. Great job!' + : 'Remember to check in daily to maintain your streak'} + + + +
+
+
+ {streak} +
+
+ consecutive {streak === 1 ? 'day' : 'days'} +
+
-
-
- - - + + + + ) : ( + // Current Streak Card - Set Streak Start Date Side + - {isCheckedInToday ? 'Already Checked In Today' : 'Check In for Today'} - {!isCheckedInToday && } - - + + + + Set Your Streak + + + Change Your Streak Start Date + + + +
+ {/* calculate the streak start date to display in DatePicker */} + +
+
+ + + + +
+ )} diff --git a/src/utils/firebase.ts b/src/utils/firebase.ts index ee7c175..21e5997 100644 --- a/src/utils/firebase.ts +++ b/src/utils/firebase.ts @@ -137,6 +137,7 @@ export const register = async ( role: 'member', // Default role joinedAt: Timestamp.now(), streakDays: 0, + streakStartDate: Timestamp.now(), lastCheckIn: Timestamp.now() }); @@ -291,7 +292,38 @@ export const updateStreak = async (userId: string) => { } }; -// Log relapse - used for analytics +export const updateStreakStart = async (userId: string, startDate: Date) => { + try { + const userRef = doc(db, 'users', userId); + const userDoc = await getDoc(userRef); + + if (userDoc.exists()) { + const userData = userDoc.data(); + const lastCheckIn = userData.lastCheckIn?.toDate() || new Date(0); + + const diffInMilliseconds = lastCheckIn.getTime() - startDate.getTime(); + const diffInDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24)); + + if (diffInDays < 0) { + return { success: false, message: 'Invalid Date' }; + } + + await updateDoc(userRef, { + streakDays: diffInDays > 0 ? diffInDays : 0, // Ensure streakDays is not negative + streakStartDate: Timestamp.fromDate(startDate) + }); + + return { success: true, message: 'Streak start updated successfully' }; + } + + return { success: false, message: 'User not found' }; + } catch (error) { + console.error('Error setting streak:', error); + return { success: false, message: error.message }; + } +}; + +// Log relapse with multiple triggers (used for analytics) interface LogRelapseResult { success: boolean; message?: string;