mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-01-17 22:28:32 +01:00
Add activity page
This commit is contained in:
parent
476557db80
commit
44949709d1
@ -47,7 +47,7 @@ export function ActivityCounters(props: ActivityCountersType) {
|
||||
/>
|
||||
|
||||
<ActivityCounter
|
||||
text={'Learning Streak'}
|
||||
text={'Visit Streak'}
|
||||
count={`${streak?.count || 0}d`}
|
||||
/>
|
||||
</div>
|
||||
|
@ -20,6 +20,7 @@ type ActivityResponse = {
|
||||
done: number;
|
||||
total: number;
|
||||
skipped: number;
|
||||
updatedAt: string;
|
||||
}[];
|
||||
bestPractices: {
|
||||
title: string;
|
||||
@ -28,10 +29,13 @@ type ActivityResponse = {
|
||||
done: number;
|
||||
skipped: number;
|
||||
total: number;
|
||||
updatedAt: string;
|
||||
}[];
|
||||
};
|
||||
streak: {
|
||||
count: number;
|
||||
firstVisitAt: Date | null;
|
||||
lastVisitAt: Date | null;
|
||||
};
|
||||
activity: {
|
||||
type: 'done' | 'learning' | 'pending' | 'skipped';
|
||||
@ -52,7 +56,7 @@ export function ActivityPage() {
|
||||
|
||||
async function loadActivity() {
|
||||
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) {
|
||||
@ -105,6 +109,7 @@ export function ActivityPage() {
|
||||
skippedCount={roadmap.skipped || 0}
|
||||
resourceId={roadmap.id}
|
||||
resourceType={'roadmap'}
|
||||
updatedAt={roadmap.updatedAt}
|
||||
title={roadmap.title}
|
||||
onCleared={() => {
|
||||
pageLoadingMessage.set('Updating activity');
|
||||
@ -124,6 +129,7 @@ export function ActivityPage() {
|
||||
skippedCount={bestPractice.skipped || 0}
|
||||
resourceType={'best-practice'}
|
||||
title={bestPractice.title}
|
||||
updatedAt={bestPractice.updatedAt}
|
||||
onCleared={() => {
|
||||
pageLoadingMessage.set('Updating activity');
|
||||
loadActivity().finally(() => {
|
||||
|
@ -7,10 +7,10 @@ export function EmptyActivity() {
|
||||
<img
|
||||
alt="no roadmaps"
|
||||
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>
|
||||
<p className="my-2 max-w-[400px] text-gray-500">
|
||||
<h2 class="text-lg sm:text-xl font-bold">No Progress</h2>
|
||||
<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{' '}
|
||||
<a href="/roadmaps" class="mt-4 text-blue-500 hover:underline">
|
||||
Roadmaps
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { useState } from 'preact/hooks';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import { getRelativeTimeString } from '../../lib/date';
|
||||
|
||||
type ResourceProgressType = {
|
||||
resourceType: 'roadmap' | 'best-practice';
|
||||
resourceId: string;
|
||||
title: string;
|
||||
updatedAt: string;
|
||||
totalCount: number;
|
||||
doneCount: number;
|
||||
learningCount: number;
|
||||
@ -17,6 +19,7 @@ export function ResourceProgress(props: ResourceProgressType) {
|
||||
const [isConfirming, setIsConfirming] = useState(false);
|
||||
|
||||
const {
|
||||
updatedAt,
|
||||
resourceType,
|
||||
resourceId,
|
||||
title,
|
||||
@ -71,16 +74,30 @@ export function ResourceProgress(props: ResourceProgressType) {
|
||||
width: `${progressPercentage}%`,
|
||||
}}
|
||||
></span>
|
||||
<span className="relative flex-1 cursor-pointer">{title}</span>
|
||||
<span className="cursor-pointer text-sm text-gray-400">
|
||||
5 hours ago
|
||||
<span className="relative flex-1 cursor-pointer truncate">
|
||||
{title}
|
||||
</span>
|
||||
<span className="ml-1 cursor-pointer text-sm text-gray-400">
|
||||
{getRelativeTimeString(updatedAt)}
|
||||
</span>
|
||||
</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">
|
||||
<span className="flex-1 gap-1 flex">
|
||||
<span>{doneCount} done</span> •
|
||||
{ learningCount > 0 && <><span>{learningCount} in progress</span> •</> }
|
||||
{ skippedCount > 0 && <><span>{skippedCount} skipped</span> •</> }
|
||||
<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="hidden flex-1 gap-1 sm:flex">
|
||||
{doneCount > 0 && (
|
||||
<>
|
||||
<span>{doneCount} done</span> •
|
||||
</>
|
||||
)}
|
||||
{learningCount > 0 && (
|
||||
<>
|
||||
<span>{learningCount} in progress</span> •
|
||||
</>
|
||||
)}
|
||||
{skippedCount > 0 && (
|
||||
<>
|
||||
<span>{skippedCount} skipped</span> •
|
||||
</>
|
||||
)}
|
||||
<span>{totalCount} total</span>
|
||||
</span>
|
||||
{!isConfirming && (
|
||||
@ -101,10 +118,19 @@ export function ResourceProgress(props: ResourceProgressType) {
|
||||
|
||||
{isConfirming && (
|
||||
<span>
|
||||
<span className='hidden sm:inline'>Are you sure?{' '}</span>
|
||||
<span className='inline sm:hidden'>Sure?{' '}</span>
|
||||
<button onClick={clearProgress} className="ml-1 mr-1 underline text-red-500 hover:text-red-800">Yes</button>{' '}
|
||||
<button onClick={() => setIsConfirming(false)} className="underline text-red-500 hover:text-red-800">No</button>
|
||||
Are you sure?{' '}
|
||||
<button
|
||||
onClick={clearProgress}
|
||||
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>
|
||||
)}
|
||||
</p>
|
||||
|
@ -26,8 +26,8 @@ import AccountDropdown from './AccountDropdown.astro';
|
||||
<a href='/videos' class='text-gray-400 hover:text-white'>Videos</a>
|
||||
</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">
|
||||
<!-- <Icon icon='search' class='h-3 w-3 mr-2' /> -->
|
||||
<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' />
|
||||
<kbd class='font-sans mr-1'>⌘</kbd><kbd class='font-sans'>K</kbd>
|
||||
</kbd>
|
||||
</li>
|
||||
|
30
src/lib/date.ts
Normal file
30
src/lib/date.ts
Normal file
@ -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…
x
Reference in New Issue
Block a user