Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions src/components/ui/date-picker.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
preselectedDate?: Date;
onDateChange?: (date: Date) => void;
}

const DatePicker = React.forwardRef<HTMLDivElement, DatePickerProps>(
({ className, preselectedDate, onDateChange, ...props }, ref) => {
const [currentDate, setCurrentDate] = React.useState(new Date());
const [selectedDate, setSelectedDate] = React.useState<Date>(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(<div key={`empty-${i}`} />);
}

for (let day = 1; day <= daysInCurrentMonth; day++) {
days.push(
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.01 * day }}
key={day}
className={cn(
"grid-item place-self-center cursor-pointer rounded-lg w-[30px] h-[30px] flex items-center justify-center hover:bg-primary/30 transition-colors",
{
"border-0 text-primary-foreground bg-primary/80 hover:bg-primary/80":
selectedDate?.getDate() === day &&
selectedDate?.getMonth() === currentDate.getMonth() &&
selectedDate?.getFullYear() === currentDate.getFullYear(),
}, {
"hover:border-0 bg-primary/20":
new Date().getDate() === day &&
new Date().getMonth() === currentDate.getMonth() &&
new Date().getFullYear() === currentDate.getFullYear()
}
)}
onClick={() => handleDateClick(day)}
>
{day}
</motion.div>
);
}

return days;
};

return (
<div
ref={ref}
className={cn(
"flex flex-col items-center max-h-[300px] w-full sm:w-lg p-1 border-2 rounded-md text-xs",
className
)}
{...props}
>
<div className="flex flex-row items-center gap-2 mb-2">
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.01 }}
>
<ChevronLeft
onClick={handlePrevMonth}
className="cursor-pointer rounded-md border-2 hover:bg-primary/30 hover:border-0 transition-colors"
/>
</motion.div>
<motion.span
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.01 }}
className="flex text-lg font-bold text-center"
>
{currentDate.toLocaleString("default", { month: "long" })}{" "}
{currentDate.getFullYear()}
</motion.span>
<motion.div
initial={{ opacity: 0, x: 10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.01 }}
>
<ChevronRight
onClick={handleNextMonth}
className="cursor-pointer rounded-md border-2 hover:bg-primary/30 hover:border-0 transition-colors"
/>
</motion.div>
</div>
<div className="h-full w-full grid grid-cols-7 gap-1 p-2">
{["S", "M", "T", "W", "T", "F", "S"].map((date, index) => (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.01 * index }}
className="grid-item place-self-center"
key={`${date}-${index}`}
>
{date}
</motion.div>
))}
{renderDays()}
</div>
</div>
);
}
);

DatePicker.displayName = "DatePicker";

export default DatePicker;
169 changes: 135 additions & 34 deletions src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
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,
Calendar,
Trophy,
TrendingUp,
MessageCircle,
Map,
HeartPulse,
CheckIcon
HeartPulse,
BookOpen
} from 'lucide-react';
Expand All @@ -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', {
Expand All @@ -36,6 +40,8 @@ const Dashboard: React.FC = () => {
const [streak, setStreak] = useState(0);
const [lastCheckIn, setLastCheckIn] = useState<Date | null>(null);
const [isCheckedInToday, setIsCheckedInToday] = useState(false);
const [isCheckInSide, setIsCheckInSide] = useState(true);
const [selectedDate, setSelectedDate] = useState<Date>(new Date());

const featuredMeditations = [
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 (
<motion.div
className="container py-8 pb-16"
Expand Down Expand Up @@ -145,40 +190,96 @@ const Dashboard: React.FC = () => {
"border-primary/20 h-full",
streak > 0 && "bg-gradient-to-br from-primary/10 to-transparent"
)}>
<CardHeader className="pb-2">
<CardTitle className="flex items-center">
<Trophy className="h-5 w-5 mr-2 text-primary" />
Your Current Streak
</CardTitle>
<CardDescription>
{isCheckedInToday
? 'You\'ve checked in today. Great job!'
: 'Remember to check in daily to maintain your streak'}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-6">
<div className="text-center">
<div className="text-5xl font-bold mb-2 text-primary">
{streak}
</div>
<div className="text-muted-foreground">
consecutive {streak === 1 ? 'day' : 'days'}
{/* Current Streak Card - Check In Side */}
{isCheckInSide ? (
<motion.div
key="check-in-side"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<CardHeader className="pb-2">
<CardTitle className="flex items-center">
<Trophy className="h-5 w-5 mr-2 text-primary" />
Your Current Streak
</CardTitle>
<CardDescription>
{isCheckedInToday
? 'You\'ve checked in today. Great job!'
: 'Remember to check in daily to maintain your streak'}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-6">
<div className="text-center">
<div className="text-5xl font-bold mb-2 text-primary">
{streak}
</div>
<div className="text-muted-foreground">
consecutive {streak === 1 ? 'day' : 'days'}
</div>
</div>
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button
onClick={handleCheckIn}
disabled={isCheckedInToday}
variant={isCheckedInToday ? "outline" : "default"}
className="w-full"
</CardContent>
<CardFooter className="flex flex-col items-center space-y-2">
<Button
onClick={handleCheckIn}
disabled={isCheckedInToday}
variant={isCheckedInToday ? "outline" : "default"}
className="w-full"
>
{isCheckedInToday ? 'Already Checked In Today' : 'Check In for Today'}
{!isCheckedInToday && <Calendar className="ml-2 h-4 w-4" />}
</Button>
<Button
variant="outline"
className="w-full text-xs"
onClick={() => setIsCheckInSide(false)}
>
Edit Streak Start Date
</Button>
</CardFooter>
</motion.div>
) : (
// Current Streak Card - Set Streak Start Date Side
<motion.div
key="streak-start-side"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
{isCheckedInToday ? 'Already Checked In Today' : 'Check In for Today'}
{!isCheckedInToday && <Calendar className="ml-2 h-4 w-4" />}
</Button>
</CardFooter>
<CardHeader className="pb-2">
<CardTitle className="flex items-center">
<Trophy className="h-5 w-5 mr-2 text-primary" />
Set Your Streak
</CardTitle>
<CardDescription>
Change Your Streak Start Date
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center">
{/* calculate the streak start date to display in DatePicker */}
<DatePicker onDateChange={setSelectedDate} preselectedDate={new Date(Date.now() - streak * 24 * 60 * 60 * 1000)} />
</div>
</CardContent>
<CardFooter className="flex flex-col items-center space-y-2">
<Button
onClick={handleStreakSet}
className="w-full"
>
Set Streak Start Date <CheckIcon />
</Button>
<Button
variant="outline"
className="w-full text-xs"
onClick={() => setIsCheckInSide(true)}
>
Cancel
</Button>
</CardFooter>
</motion.div>
)}
</Card>
</motion.div>

Expand Down
Loading