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
201 changes: 2 additions & 199 deletions frontend/app/streak/page.tsx
Original file line number Diff line number Diff line change
@@ -1,209 +1,11 @@
"use client";

import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import ShareOptionsSheet from "../../components/ShareOptionsSheet";
import ShareStreakCard from "@/components/ShareStreakCard";

export interface StreakData {
[date: string]: {
completed: boolean;
inStreak?: boolean;
missed?: boolean;
};
}

export interface DayData {
day: string;
completed: boolean;
}

interface StreakDayIndicatorProps {
status: "empty" | "completed" | "streak" | "missed";
isToday?: boolean;
inStreakRun?: boolean;
}

const StreakDayIndicator: React.FC<StreakDayIndicatorProps> = ({
status,
isToday = false,
inStreakRun = false,
}) => {
const baseClasses =
"flex items-center justify-center w-[24px] h-[24px] md:w-[28px] md:h-[28px] rounded-full z-10 shrink-0";

let statusClasses = "";
if (status === "empty") statusClasses = "bg-[#E6E6E6]/20";
else if (status === "completed") statusClasses = "bg-[#FACC15]";
else if (status === "streak")
statusClasses = "bg-[#FACC15] shadow-lg shadow-[#FACC15]/50";
else if (status === "missed") statusClasses = "bg-white/30";

const todayClasses = isToday
? "ring-2 ring-white ring-offset-2 ring-offset-[#050C16]"
: "";

return (
<div className="relative flex items-center justify-center w-[24px] h-[24px] md:w-[28px] md:h-[28px]">
{inStreakRun && status === "streak" && (
<div className="absolute w-[300%] h-[8px] bg-[#FACC15]/20 rounded-full z-0" />
)}
<div className={`${baseClasses} ${statusClasses} ${todayClasses}`}>
{status === "streak" && (
<span className="text-[10px]">🔥</span>
)}
</div>
</div>
);
};

interface StreakCalendarProps {
currentMonth: Date;
streakData: StreakData;
onMonthChange?: (date: Date) => void;
}

const StreakCalendar: React.FC<StreakCalendarProps> = ({
currentMonth,
streakData,
onMonthChange,
}) => {
const [selectedMonth, setSelectedMonth] = useState(currentMonth);

const weekDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December",
];

const getDaysInMonth = (date: Date) =>
new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();

const getFirstDayOfMonth = (date: Date) => {
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay();
return firstDay === 0 ? 6 : firstDay - 1;
};

const formatDateKey = (year: number, month: number, day: number) =>
`${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;

const isToday = (year: number, month: number, day: number) => {
const today = new Date();
return (
year === today.getFullYear() &&
month === today.getMonth() &&
day === today.getDate()
);
};

const handlePreviousMonth = () => {
const newMonth = new Date(selectedMonth.getFullYear(), selectedMonth.getMonth() - 1);
setSelectedMonth(newMonth);
onMonthChange?.(newMonth);
};

const handleNextMonth = () => {
const newMonth = new Date(selectedMonth.getFullYear(), selectedMonth.getMonth() + 1);
setSelectedMonth(newMonth);
onMonthChange?.(newMonth);
};

const renderCalendarDays = () => {
const daysInMonth = getDaysInMonth(selectedMonth);
const firstDayOfMonth = getFirstDayOfMonth(selectedMonth);
const year = selectedMonth.getFullYear();
const month = selectedMonth.getMonth();
const days = [];

for (let i = 0; i < firstDayOfMonth; i++) {
days.push(<div key={`empty-${i}`} className="aspect-square" />);
}

for (let day = 1; day <= daysInMonth; day++) {
const dateKey = formatDateKey(year, month, day);
const dayData = streakData[dateKey];
const today = isToday(year, month, day);

let status: "empty" | "completed" | "streak" | "missed" = "empty";
if (dayData?.missed) status = "missed";
else if (dayData?.completed)
status = dayData?.inStreak ? "streak" : "completed";

days.push(
<div
key={day}
className="aspect-square flex flex-col items-center justify-center gap-1"
>
<span
className={`text-xs font-nunito ${
today ? "text-white font-bold" : "text-[#E6E6E6]/60"
}`}
>
{day}
</span>
<StreakDayIndicator
status={status}
isToday={today}
inStreakRun={dayData?.inStreak}
/>
</div>
);
}

return days;
};

return (
<div className="w-full flex flex-col items-center">
<div className="bg-[#050C16] border border-[#FACC15]/20 w-full max-w-[566px] rounded-[16px] p-[16px] md:p-[24px]">
{/* Month Header */}
<div className="flex items-center justify-between mb-[20px]">
<button
onClick={handlePreviousMonth}
className="p-2 rounded-lg hover:bg-[#FACC15]/10 transition-colors cursor-pointer"
aria-label="Previous month"
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="text-[#FACC15]">
<path d="M12.5 5L7.5 10L12.5 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
<h2 className="text-base font-nunito font-bold text-white text-center uppercase tracking-widest">
{monthNames[selectedMonth.getMonth()].slice(0, 3).toUpperCase()}{" "}
{selectedMonth.getFullYear()}
</h2>
<button
onClick={handleNextMonth}
className="p-2 rounded-lg hover:bg-[#FACC15]/10 transition-colors cursor-pointer"
aria-label="Next month"
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="text-[#FACC15]">
<path d="M7.5 5L12.5 10L7.5 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>

{/* Divider */}
<div className="w-full h-px bg-[#FFFFFF]/10 mb-[16px]" />

{/* Weekday Labels */}
<div className="grid grid-cols-7 gap-1 mb-[12px]">
{weekDays.map((day) => (
<div key={day} className="aspect-square flex items-center justify-center">
<span className="text-xs font-nunito font-semibold text-[#E6E6E6]/50 uppercase">
{day}
</span>
</div>
))}
</div>

{/* Calendar Grid */}
<div className="grid grid-cols-7 gap-1">{renderCalendarDays()}</div>
</div>
</div>
);
};
import { StreakCalendar, StreakData } from "@/components/StreakCalendar";

interface StreakSummaryCardProps {
streakCount: number;
Expand Down Expand Up @@ -386,6 +188,7 @@ export default function StreakPage() {
Streak Calendar
</h2>
<StreakCalendar
variant="panel"
currentMonth={new Date()}
streakData={DEMO_STREAK_DATA}
/>
Expand Down
Loading
Loading