From 95a369849a35d2c391e0251bdf8730f633096ebb Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Fri, 16 Aug 2024 04:30:30 +0600 Subject: [PATCH] feat: implement user streak --- .../AccountStreak/AccountStreak.tsx | 190 ++++++++++++++++++ src/components/Navigation/Navigation.astro | 9 +- 2 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 src/components/AccountStreak/AccountStreak.tsx diff --git a/src/components/AccountStreak/AccountStreak.tsx b/src/components/AccountStreak/AccountStreak.tsx new file mode 100644 index 000000000..b269011f5 --- /dev/null +++ b/src/components/AccountStreak/AccountStreak.tsx @@ -0,0 +1,190 @@ +import { useEffect, useRef, useState } from 'react'; +import { isLoggedIn } from '../../lib/jwt'; +import { httpGet } from '../../lib/http'; +import { useToast } from '../../hooks/use-toast'; +import { ChevronDown, Flame, X } from 'lucide-react'; +import { useOutsideClick } from '../../hooks/use-outside-click'; +import { cn } from '../../lib/classname'; + +type StreakResponse = { + count: number; + longestCount: number; + previousCount?: number | null; + firstVisitAt: Date; + lastVisitAt: Date; +}; + +type AccountStreakProps = {}; + +export function AccountStreak(props: AccountStreakProps) { + const toast = useToast(); + const dropdownRef = useRef(null); + + const [isLoading, setIsLoading] = useState(true); + const [accountStreak, setAccountStreak] = useState({ + count: 0, + longestCount: 0, + firstVisitAt: new Date(), + lastVisitAt: new Date(), + }); + const [showDropdown, setShowDropdown] = useState(false); + + const loadAccountStreak = async () => { + if (!isLoggedIn()) { + return; + } + + setIsLoading(true); + const { response, error } = await httpGet( + `${import.meta.env.PUBLIC_API_URL}/v1-streak`, + ); + + if (error || !response) { + toast.error(error?.message || 'Failed to load account streak'); + setIsLoading(false); + return; + } + + setAccountStreak(response); + setIsLoading(false); + }; + + useOutsideClick(dropdownRef, () => { + setShowDropdown(false); + }); + + useEffect(() => { + loadAccountStreak().finally(() => {}); + }, []); + + if (!isLoggedIn() || isLoading) { + return null; + } + + let { count: currentCount } = accountStreak; + const previousCount = + accountStreak?.previousCount || accountStreak?.count || 0; + + // Adding one to show the current day + const currentCircleCount = Math.min(currentCount, 5) + 1; + // Adding one day to show the streak they broke + const leftCircleCount = Math.min(5 - currentCircleCount, previousCount) + 1; + const remainingCount = Math.max(0, 10 - leftCircleCount - currentCircleCount); + + return ( +
+ + + {showDropdown && ( +
+
+
+

+ Current Streak + + {accountStreak?.count || 0} + +

+

+ Longest Streak + + {accountStreak?.longestCount || 0} + +

+
+ +
+
+ {[...Array(leftCircleCount)].map((_, index) => { + const isLast = index === leftCircleCount - 1; + const dayCount = + previousCount - leftCircleCount + index + 1 + 1; + + return ( +
+
+ {isLast ? ( + + ) : ( + + )} +
+ {dayCount} +
+ ); + })} + {[...Array(currentCircleCount)].map((_, index) => { + const dayCount = + currentCount - currentCircleCount + index + 1 + 1; + const isLast = index === currentCircleCount - 1; + + return ( +
+
+ {!isLast && ( + + )} +
+ {dayCount} + {isLast && ( + + )} +
+ ); + })} + + {[...Array(remainingCount)].map((_, index) => { + const dayCount = currentCount + index + 1 + 1; + + return ( +
+
+ {dayCount} +
+ ); + })} +
+
+
+
+ )} +
+ ); +} diff --git a/src/components/Navigation/Navigation.astro b/src/components/Navigation/Navigation.astro index 88af9075e..fa7229652 100644 --- a/src/components/Navigation/Navigation.astro +++ b/src/components/Navigation/Navigation.astro @@ -4,6 +4,7 @@ import Icon from '../AstroIcon.astro'; import { NavigationDropdown } from '../NavigationDropdown'; import { AccountDropdown } from './AccountDropdown'; import NewIndicator from './NewIndicator.astro'; +import { AccountStreak } from '../AccountStreak/AccountStreak'; ---
@@ -40,10 +41,7 @@ import NewIndicator from './NewIndicator.astro'; Start Here - + Teams AI @@ -68,7 +66,8 @@ import NewIndicator from './NewIndicator.astro'; -
  • +
  • +