Add streaks

feat/streak
Kamran Ahmed 7 months ago
parent f461af7c2d
commit 80d69dd051
  1. 45
      src/components/AccountStreak/AccountStreak.tsx
  2. 24
      src/components/AccountStreak/StreakDay.tsx

@ -5,6 +5,12 @@ import { useToast } from '../../hooks/use-toast';
import { Flame, X } from 'lucide-react'; import { Flame, X } from 'lucide-react';
import { useOutsideClick } from '../../hooks/use-outside-click'; import { useOutsideClick } from '../../hooks/use-outside-click';
import { StreakDay } from './StreakDay'; import { StreakDay } from './StreakDay';
import {
navigationDropdownOpen,
roadmapsDropdownOpen,
} from '../../stores/page.ts';
import { useStore } from '@nanostores/react';
import { cn } from '../../lib/classname.ts';
type StreakResponse = { type StreakResponse = {
count: number; count: number;
@ -29,6 +35,15 @@ export function AccountStreak(props: AccountStreakProps) {
}); });
const [showDropdown, setShowDropdown] = useState(false); const [showDropdown, setShowDropdown] = useState(false);
const $roadmapsDropdownOpen = useStore(roadmapsDropdownOpen);
const $navigationDropdownOpen = useStore(navigationDropdownOpen);
useEffect(() => {
if ($roadmapsDropdownOpen || $navigationDropdownOpen) {
setShowDropdown(false);
}
}, [$roadmapsDropdownOpen, $navigationDropdownOpen]);
const loadAccountStreak = async () => { const loadAccountStreak = async () => {
if (!isLoggedIn()) { if (!isLoggedIn()) {
return; return;
@ -76,7 +91,12 @@ export function AccountStreak(props: AccountStreakProps) {
return ( return (
<div className="relative z-[90] animate-fade-in"> <div className="relative z-[90] animate-fade-in">
<button <button
className="flex items-center justify-center rounded-lg p-1.5 px-2 text-purple-400 hover:bg-purple-100/10 focus:outline-none" className={cn(
'flex items-center justify-center rounded-lg p-1.5 px-2 text-purple-400 hover:bg-purple-100/10 focus:outline-none',
{
'bg-purple-100/10': showDropdown,
},
)}
onClick={() => setShowDropdown(true)} onClick={() => setShowDropdown(true)}
> >
<Flame className="size-5" /> <Flame className="size-5" />
@ -90,15 +110,15 @@ export function AccountStreak(props: AccountStreakProps) {
ref={dropdownRef} ref={dropdownRef}
className="absolute right-0 top-full z-50 w-[320px] translate-y-1 rounded-lg bg-slate-800 shadow-xl" className="absolute right-0 top-full z-50 w-[320px] translate-y-1 rounded-lg bg-slate-800 shadow-xl"
> >
<div className="px-3 py-2"> <div className="px-4 py-2.5">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2 text-sm text-slate-500">
<p className="text-sm text-slate-400"> <p>
Current Streak Current Streak
<span className="ml-2 font-medium text-white"> <span className="ml-2 font-medium text-white">
{accountStreak?.count || 0} {accountStreak?.count || 0}
</span> </span>
</p> </p>
<p className="text-sm text-slate-400"> <p>
Longest Streak Longest Streak
<span className="ml-2 font-medium text-white"> <span className="ml-2 font-medium text-white">
{accountStreak?.longestCount || 0} {accountStreak?.longestCount || 0}
@ -106,12 +126,13 @@ export function AccountStreak(props: AccountStreakProps) {
</p> </p>
</div> </div>
<div className="mt-8"> <div className="mb-5 mt-8">
<div className="grid grid-cols-10 gap-1"> <div className="grid grid-cols-10 gap-1">
{Array.from({ length: totalCircles }).map((_, index) => { {Array.from({ length: totalCircles }).map((_, index) => {
let dayCount, let dayCount,
icon, icon,
isPreviousStreakDay, isPreviousStreakDay,
isBrokenStreakDay,
isCurrentStreakDay, isCurrentStreakDay,
isRemainingStreakDay, isRemainingStreakDay,
isToday; isToday;
@ -120,9 +141,10 @@ export function AccountStreak(props: AccountStreakProps) {
// Previous streak days // Previous streak days
dayCount = previousCount - leftCircleCount + index + 1 + 1; dayCount = previousCount - leftCircleCount + index + 1 + 1;
isPreviousStreakDay = true; isPreviousStreakDay = true;
icon = isBrokenStreakDay = index === leftCircleCount - 1;
index === leftCircleCount - 1 ? (
<X className="size-3.5 text-white" /> icon = isBrokenStreakDay ? (
<X className="opacit size-3.5 text-white" />
) : ( ) : (
<Flame className="size-3.5 text-white" /> <Flame className="size-3.5 text-white" />
); );
@ -147,6 +169,7 @@ export function AccountStreak(props: AccountStreakProps) {
key={`streak-${index}`} key={`streak-${index}`}
dayCount={dayCount} dayCount={dayCount}
icon={icon} icon={icon}
isBrokenStreakDay={isBrokenStreakDay}
isPreviousStreakDay={isPreviousStreakDay} isPreviousStreakDay={isPreviousStreakDay}
isCurrentStreakDay={isCurrentStreakDay} isCurrentStreakDay={isCurrentStreakDay}
isRemainingStreakDay={isRemainingStreakDay} isRemainingStreakDay={isRemainingStreakDay}
@ -156,6 +179,10 @@ export function AccountStreak(props: AccountStreakProps) {
})} })}
</div> </div>
</div> </div>
<p className="text-center text-xs text-slate-500">
Visit every day to keep your streak alive!
</p>
</div> </div>
</div> </div>
)} )}

@ -6,6 +6,7 @@ type StreakDayProps = {
isToday?: boolean; isToday?: boolean;
isCurrentStreakDay?: boolean; isCurrentStreakDay?: boolean;
isPreviousStreakDay?: boolean; isPreviousStreakDay?: boolean;
isBrokenStreakDay?: boolean;
isRemainingStreakDay?: boolean; isRemainingStreakDay?: boolean;
dayCount: number; dayCount: number;
icon?: ReactNode; icon?: ReactNode;
@ -15,6 +16,7 @@ export function StreakDay(props: StreakDayProps) {
const { const {
isCurrentStreakDay, isCurrentStreakDay,
isPreviousStreakDay, isPreviousStreakDay,
isBrokenStreakDay,
isRemainingStreakDay, isRemainingStreakDay,
dayCount, dayCount,
icon, icon,
@ -29,22 +31,20 @@ export function StreakDay(props: StreakDayProps) {
)} )}
> >
<div <div
className={cn( className={cn('flex size-6 items-center justify-center rounded-full', {
'flex size-6 items-center justify-center rounded-full', 'bg-red-500': isPreviousStreakDay,
isPreviousStreakDay && 'bg-red-500', 'bg-purple-500': isCurrentStreakDay,
isCurrentStreakDay && 'bg-purple-500', 'bg-slate-700': isRemainingStreakDay,
isRemainingStreakDay && 'bg-slate-700', 'border-2 border-dashed border-slate-500 bg-transparent': isToday,
isToday && 'border-2 border-dashed border-slate-500 bg-transparent', })}
)}
> >
{isToday ? null : icon} {isToday ? null : icon}
</div> </div>
<span <span
className={cn( className={cn('text-sm', {
'text-sm', 'text-slate-500': isPreviousStreakDay,
isPreviousStreakDay && 'text-slate-400', 'text-slate-100': isCurrentStreakDay || isRemainingStreakDay,
(isCurrentStreakDay || isRemainingStreakDay) && 'text-slate-100', })}
)}
> >
{dayCount} {dayCount}
</span> </span>

Loading…
Cancel
Save