diff --git a/src/components/Activity/ActivityCounters.tsx b/src/components/Activity/ActivityCounters.tsx new file mode 100644 index 000000000..f647761ca --- /dev/null +++ b/src/components/Activity/ActivityCounters.tsx @@ -0,0 +1,56 @@ +type ActivityCountersType = { + done: { + today: number; + total: number; + }; + learning: { + today: number; + total: number; + }; + streak: { + count: number; + }; +}; + +type ActivityCounterType = { + text: string; + count: string; +}; + +function ActivityCounter(props: ActivityCounterType) { + const { text, count } = props; + + return ( +
+

+ {count} +

+

{text}

+
+ ); +} + +export function ActivityCounters(props: ActivityCountersType) { + const { done, learning, streak } = props; + + return ( +
+
+ + + + + +
+
+ ); +} diff --git a/src/components/Activity/ActivityPage.tsx b/src/components/Activity/ActivityPage.tsx new file mode 100644 index 000000000..72f0ff9cf --- /dev/null +++ b/src/components/Activity/ActivityPage.tsx @@ -0,0 +1,141 @@ +import { useEffect, useState } from 'preact/hooks'; +import { httpGet } from '../../lib/http'; +import { ActivityCounters } from './ActivityCounters'; +import { ResourceProgress } from './ResourceProgress'; +import { pageLoadingMessage } from '../../stores/page'; +import { EmptyActivity } from './EmptyActivity'; + +type ActivityResponse = { + done: { + today: number; + total: number; + }; + learning: { + today: number; + total: number; + roadmaps: { + title: string; + id: string; + learning: number; + done: number; + total: number; + skipped: number; + }[]; + bestPractices: { + title: string; + id: string; + learning: number; + done: number; + skipped: number; + total: number; + }[]; + }; + streak: { + count: number; + }; + activity: { + type: 'done' | 'learning' | 'pending' | 'skipped'; + createdAt: Date; + metadata: { + resourceId?: string; + resourceType?: 'roadmap' | 'best-practice'; + topicId?: string; + topicLabel?: string; + resourceTitle?: string; + }; + }[]; +}; + +export function ActivityPage() { + const [activity, setActivity] = useState(); + const [isLoading, setIsLoading] = useState(true); + + async function loadActivity() { + const { error, response } = await httpGet( + `${import.meta.env.PUBLIC_API_URL}/v1-get-user-activity` + ); + + if (!response || error) { + console.error('Error loading activity'); + console.error(error); + + return; + } + + setActivity(response); + } + + useEffect(() => { + loadActivity().finally(() => { + pageLoadingMessage.set(''); + setIsLoading(false); + }); + }, []); + + const learningRoadmaps = activity?.learning.roadmaps || []; + const learningBestPractices = activity?.learning.bestPractices || []; + + if (isLoading) { + return null; + } + + return ( + <> + + +
+ {learningRoadmaps.length === 0 && + learningBestPractices.length === 0 && } + + {(learningRoadmaps.length > 0 || learningBestPractices.length > 0) && ( + <> +

+ Continue Following +

+
+ {learningRoadmaps.map((roadmap) => ( + { + pageLoadingMessage.set('Updating activity'); + loadActivity().finally(() => { + pageLoadingMessage.set(''); + }); + }} + /> + ))} + + {learningBestPractices.map((bestPractice) => ( + { + pageLoadingMessage.set('Updating activity'); + loadActivity().finally(() => { + pageLoadingMessage.set(''); + }); + }} + /> + ))} +
+ + )} +
+ + ); +} diff --git a/src/components/Activity/EmptyActivity.tsx b/src/components/Activity/EmptyActivity.tsx new file mode 100644 index 000000000..0fd9f33ee --- /dev/null +++ b/src/components/Activity/EmptyActivity.tsx @@ -0,0 +1,27 @@ +import CheckIcon from '../../icons/roadmap.svg'; + +export function EmptyActivity() { + return ( +
+
+ no roadmaps +

No Progress

+

+ Progress will appear here as you start tracking your{' '} + + Roadmaps + {' '} + or{' '} + + Best Practices + {' '} + progress. +

+
+
+ ); +} diff --git a/src/components/Activity/ResourceProgress.tsx b/src/components/Activity/ResourceProgress.tsx new file mode 100644 index 000000000..77729e8d8 --- /dev/null +++ b/src/components/Activity/ResourceProgress.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from 'preact/hooks'; +import { httpPost } from '../../lib/http'; + +type ResourceProgressType = { + resourceType: 'roadmap' | 'best-practice'; + resourceId: string; + title: string; + totalCount: number; + doneCount: number; + learningCount: number; + skippedCount: number; + onCleared: () => void; +}; + +export function ResourceProgress(props: ResourceProgressType) { + const [isClearing, setIsClearing] = useState(false); + const [isConfirming, setIsConfirming] = useState(false); + + const { + resourceType, + resourceId, + title, + totalCount, + learningCount, + doneCount, + skippedCount, + onCleared, + } = props; + + async function clearProgress() { + setIsClearing(true); + const { error, response } = await httpPost( + `${import.meta.env.PUBLIC_API_URL}/v1-clear-resource-progress`, + { + resourceId, + resourceType, + } + ); + + if (error || !response) { + alert('Error clearing progress. Please try again.'); + console.error(error); + setIsClearing(false); + return; + } + + localStorage.removeItem(`${resourceType}-${resourceId}-progress`); + console.log(`${resourceType}-${resourceId}-progress`); + setIsClearing(false); + setIsConfirming(false); + onCleared(); + } + + const url = + resourceType === 'roadmap' + ? `/${resourceId}` + : `/best-practices/${resourceId}`; + + const totalMarked = doneCount + skippedCount; + const progressPercentage = Math.round((totalMarked / totalCount) * 100); + + return ( +
+ + + {title} + + 5 hours ago + + +

+ + {doneCount} done • + { learningCount > 0 && <>{learningCount} in progress • } + { skippedCount > 0 && <>{skippedCount} skipped • } + {totalCount} total + + {!isConfirming && ( + + )} + + {isConfirming && ( + + Are you sure?{' '} + Sure?{' '} + {' '} + + + )} +

+
+ ); +} diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index ba8b04c5f..fb74de0f6 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -21,7 +21,7 @@ type PageType = { const defaultPages: PageType[] = [ { url: '/', title: 'Home', group: 'Pages', icon: HomeIcon }, { - url: '/account/update-profile', + url: '/account', title: 'Account', group: 'Pages', icon: UserIcon, diff --git a/src/pages/account/index.astro b/src/pages/account/index.astro index 04b353125..2f5125154 100644 --- a/src/pages/account/index.astro +++ b/src/pages/account/index.astro @@ -1,41 +1,11 @@ --- import AccountSidebar from '../../components/AccountSidebar.astro'; +import { ActivityPage } from '../../components/Activity/ActivityPage'; import AccountLayout from '../../layouts/AccountLayout.astro'; --- - + -
-
-
-

30

-

Topics Completed

-
-
-

20

-

Currently Learning

-
-
-

2d

-

Learning Streak

-
-
-
- -
-

Continue Learning

- -
+