Add activity page

pull/3985/head
Kamran Ahmed 2 years ago
parent 476557db80
commit 44949709d1
  1. 2
      src/components/Activity/ActivityCounters.tsx
  2. 8
      src/components/Activity/ActivityPage.tsx
  3. 6
      src/components/Activity/EmptyActivity.tsx
  4. 52
      src/components/Activity/ResourceProgress.tsx
  5. 4
      src/components/Navigation/Navigation.astro
  6. 30
      src/lib/date.ts

@ -47,7 +47,7 @@ export function ActivityCounters(props: ActivityCountersType) {
/> />
<ActivityCounter <ActivityCounter
text={'Learning Streak'} text={'Visit Streak'}
count={`${streak?.count || 0}d`} count={`${streak?.count || 0}d`}
/> />
</div> </div>

@ -20,6 +20,7 @@ type ActivityResponse = {
done: number; done: number;
total: number; total: number;
skipped: number; skipped: number;
updatedAt: string;
}[]; }[];
bestPractices: { bestPractices: {
title: string; title: string;
@ -28,10 +29,13 @@ type ActivityResponse = {
done: number; done: number;
skipped: number; skipped: number;
total: number; total: number;
updatedAt: string;
}[]; }[];
}; };
streak: { streak: {
count: number; count: number;
firstVisitAt: Date | null;
lastVisitAt: Date | null;
}; };
activity: { activity: {
type: 'done' | 'learning' | 'pending' | 'skipped'; type: 'done' | 'learning' | 'pending' | 'skipped';
@ -52,7 +56,7 @@ export function ActivityPage() {
async function loadActivity() { async function loadActivity() {
const { error, response } = await httpGet<ActivityResponse>( const { error, response } = await httpGet<ActivityResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-activity` `${import.meta.env.PUBLIC_API_URL}/v1-get-user-stats`
); );
if (!response || error) { if (!response || error) {
@ -105,6 +109,7 @@ export function ActivityPage() {
skippedCount={roadmap.skipped || 0} skippedCount={roadmap.skipped || 0}
resourceId={roadmap.id} resourceId={roadmap.id}
resourceType={'roadmap'} resourceType={'roadmap'}
updatedAt={roadmap.updatedAt}
title={roadmap.title} title={roadmap.title}
onCleared={() => { onCleared={() => {
pageLoadingMessage.set('Updating activity'); pageLoadingMessage.set('Updating activity');
@ -124,6 +129,7 @@ export function ActivityPage() {
skippedCount={bestPractice.skipped || 0} skippedCount={bestPractice.skipped || 0}
resourceType={'best-practice'} resourceType={'best-practice'}
title={bestPractice.title} title={bestPractice.title}
updatedAt={bestPractice.updatedAt}
onCleared={() => { onCleared={() => {
pageLoadingMessage.set('Updating activity'); pageLoadingMessage.set('Updating activity');
loadActivity().finally(() => { loadActivity().finally(() => {

@ -7,10 +7,10 @@ export function EmptyActivity() {
<img <img
alt="no roadmaps" alt="no roadmaps"
src={CheckIcon} src={CheckIcon}
class="mb-2 h-[120px] w-[120px] opacity-10" class="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
/> />
<h2 class="text-xl font-bold">No Progress</h2> <h2 class="text-lg sm:text-xl font-bold">No Progress</h2>
<p className="my-2 max-w-[400px] text-gray-500"> <p className="my-1 sm:my-2 max-w-[400px] text-gray-500 text-sm sm:text-base">
Progress will appear here as you start tracking your{' '} Progress will appear here as you start tracking your{' '}
<a href="/roadmaps" class="mt-4 text-blue-500 hover:underline"> <a href="/roadmaps" class="mt-4 text-blue-500 hover:underline">
Roadmaps Roadmaps

@ -1,10 +1,12 @@
import { useEffect, useState } from 'preact/hooks'; import { useState } from 'preact/hooks';
import { httpPost } from '../../lib/http'; import { httpPost } from '../../lib/http';
import { getRelativeTimeString } from '../../lib/date';
type ResourceProgressType = { type ResourceProgressType = {
resourceType: 'roadmap' | 'best-practice'; resourceType: 'roadmap' | 'best-practice';
resourceId: string; resourceId: string;
title: string; title: string;
updatedAt: string;
totalCount: number; totalCount: number;
doneCount: number; doneCount: number;
learningCount: number; learningCount: number;
@ -17,6 +19,7 @@ export function ResourceProgress(props: ResourceProgressType) {
const [isConfirming, setIsConfirming] = useState(false); const [isConfirming, setIsConfirming] = useState(false);
const { const {
updatedAt,
resourceType, resourceType,
resourceId, resourceId,
title, title,
@ -71,16 +74,30 @@ export function ResourceProgress(props: ResourceProgressType) {
width: `${progressPercentage}%`, width: `${progressPercentage}%`,
}} }}
></span> ></span>
<span className="relative flex-1 cursor-pointer">{title}</span> <span className="relative flex-1 cursor-pointer truncate">
<span className="cursor-pointer text-sm text-gray-400"> {title}
5 hours ago </span>
<span className="ml-1 cursor-pointer text-sm text-gray-400">
{getRelativeTimeString(updatedAt)}
</span> </span>
</a> </a>
<p className="items-start sm:space-between flex flex-row rounded-b-md border border-t-0 px-2 py-2 text-xs text-gray-500"> <p className="sm:space-between flex flex-row items-start rounded-b-md border border-t-0 px-2 py-2 text-xs text-gray-500">
<span className="flex-1 gap-1 flex"> <span className="hidden flex-1 gap-1 sm:flex">
<span>{doneCount} done</span> &bull; {doneCount > 0 && (
{ learningCount > 0 && <><span>{learningCount} in progress</span> &bull;</> } <>
{ skippedCount > 0 && <><span>{skippedCount} skipped</span> &bull;</> } <span>{doneCount} done</span> &bull;
</>
)}
{learningCount > 0 && (
<>
<span>{learningCount} in progress</span> &bull;
</>
)}
{skippedCount > 0 && (
<>
<span>{skippedCount} skipped</span> &bull;
</>
)}
<span>{totalCount} total</span> <span>{totalCount} total</span>
</span> </span>
{!isConfirming && ( {!isConfirming && (
@ -101,10 +118,19 @@ export function ResourceProgress(props: ResourceProgressType) {
{isConfirming && ( {isConfirming && (
<span> <span>
<span className='hidden sm:inline'>Are you sure?{' '}</span> Are you sure?{' '}
<span className='inline sm:hidden'>Sure?{' '}</span> <button
<button onClick={clearProgress} className="ml-1 mr-1 underline text-red-500 hover:text-red-800">Yes</button>{' '} onClick={clearProgress}
<button onClick={() => setIsConfirming(false)} className="underline text-red-500 hover:text-red-800">No</button> className="ml-1 mr-1 text-red-500 underline hover:text-red-800"
>
Yes
</button>{' '}
<button
onClick={() => setIsConfirming(false)}
className="text-red-500 underline hover:text-red-800"
>
No
</button>
</span> </span>
)} )}
</p> </p>

@ -26,8 +26,8 @@ import AccountDropdown from './AccountDropdown.astro';
<a href='/videos' class='text-gray-400 hover:text-white'>Videos</a> <a href='/videos' class='text-gray-400 hover:text-white'>Videos</a>
</li> </li>
<li> <li>
<kbd data-command-menu class="hidden md:flex items-center text-gray-400 border border-gray-800 rounded-md px-2.5 py-1 text-sm hover:bg-gray-800 hover:cursor-pointer"> <kbd data-command-menu class="hidden sm:flex items-center text-gray-400 border border-gray-800 rounded-md px-2.5 py-1 text-sm hover:bg-gray-800 hover:cursor-pointer">
<!-- <Icon icon='search' class='h-3 w-3 mr-2' /> --> <Icon icon='search' class='h-3 w-3 mr-2' />
<kbd class='font-sans mr-1'>⌘</kbd><kbd class='font-sans'>K</kbd> <kbd class='font-sans mr-1'>⌘</kbd><kbd class='font-sans'>K</kbd>
</kbd> </kbd>
</li> </li>

@ -0,0 +1,30 @@
export function getRelativeTimeString(date: string): string {
if (!Intl?.RelativeTimeFormat) {
return date;
}
const rtf = new Intl.RelativeTimeFormat('en', {
numeric: 'auto',
style: 'narrow',
});
const currentDate = new Date();
const targetDate = new Date(date);
const diffInMilliseconds = currentDate.getTime() - targetDate.getTime();
const diffInMinutes = Math.round(diffInMilliseconds / (1000 * 60));
const diffInHours = Math.round(diffInMilliseconds / (1000 * 60 * 60));
const diffInDays = Math.round(diffInMilliseconds / (1000 * 60 * 60 * 24));
let relativeTime;
if (diffInMinutes < 60) {
relativeTime = rtf.format(-diffInMinutes, 'minute');
} else if (diffInHours < 24) {
relativeTime = rtf.format(-diffInHours, 'hour');
} else {
relativeTime = rtf.format(-diffInDays, 'day');
}
return relativeTime;
}
Loading…
Cancel
Save