Show progress on homepage

feat/faves
Kamran Ahmed 2 years ago
parent cd8a1e13ac
commit e7022764d2
  1. 4
      src/components/Activity/EmptyActivity.tsx
  2. 20
      src/components/HeroSection/CheckIcon.tsx
  3. 23
      src/components/HeroSection/EmptyProgress.tsx
  4. 92
      src/components/HeroSection/FavoriteRoadmaps.tsx
  5. 2
      src/components/HeroSection/HeroSection.astro
  6. 45
      src/components/HeroSection/ProgressList.tsx
  7. 8
      src/data/roadmaps/cpp/content/115-compilers/100-stages.md
  8. 48
      src/lib/home-progress.ts
  9. 2
      src/pages/index.astro

@ -1,4 +1,4 @@
import CheckIcon from '../../icons/roadmap.svg'; import RoadmapIcon from '../../icons/roadmap.svg';
export function EmptyActivity() { export function EmptyActivity() {
return ( return (
@ -6,7 +6,7 @@ export function EmptyActivity() {
<div class="flex flex-col items-center p-7 text-center"> <div class="flex flex-col items-center p-7 text-center">
<img <img
alt="no roadmaps" alt="no roadmaps"
src={CheckIcon} src={RoadmapIcon}
class="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10" class="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
/> />
<h2 class="text-lg sm:text-xl font-bold">No Progress</h2> <h2 class="text-lg sm:text-xl font-bold">No Progress</h2>

@ -0,0 +1,20 @@
type CheckIconProps = {
additionalClasses?: string;
};
export function CheckIcon(props: CheckIconProps) {
const { additionalClasses = 'mr-2 top-[0.5px] w-[20px] h-[20px]' } = props;
return (
<svg
className={`relative ${additionalClasses}]`}
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"></path>
</svg>
);
}

@ -0,0 +1,23 @@
import { CheckIcon } from './CheckIcon';
type EmptyProgressProps = {
title?: string;
message?: string;
};
export function EmptyProgress(props: EmptyProgressProps) {
const {
title = 'Start learning ..',
message = 'Your progress and favorite roadmaps will appear here',
} = props;
return (
<div className="relative flex min-h-full flex-col items-center justify-center">
<h2 className={'mb-1 flex items-center text-2xl text-gray-200'}>
<CheckIcon />
Start learning ..
</h2>
<p className={'text-gray-400'}>{message}</p>
</div>
);
}

@ -1,20 +1,100 @@
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useState } from 'preact/hooks';
import { EmptyProgress } from './EmptyProgress';
import { httpGet } from '../../lib/http';
import { ProgressList } from './ProgressList';
export type UserProgressResponse = {
resourceId: string;
resourceType: 'roadmap' | 'best-practice';
resourceTitle: string;
done: number;
learning: number;
skipped: number;
total: number;
updatedAt: Date;
}[];
function renderProgress(progressList: UserProgressResponse) {
progressList.forEach((progress) => {
const href =
progress.resourceType === 'best-practice'
? `/best-practices/${progress.resourceId}`
: `/${progress.resourceId}`;
const element = document.querySelector(`a[href="${href}"]`);
if (!element) {
return;
}
const totalDone = progress.done + progress.skipped;
const percentageDone = (totalDone / progress.total) * 100;
const progressBar: HTMLElement = element.querySelector('[data-progress]')!;
progressBar.style.width = `${percentageDone}%`;
});
}
export function FavoriteRoadmaps() { export function FavoriteRoadmaps() {
const [isPreparing, setIsPreparing] = useState(true); const [isPreparing, setIsPreparing] = useState(true);
useEffect(() => { const [isLoading, setIsLoading] = useState(false);
const [progress, setProgress] = useState<UserProgressResponse>([]);
const [containerOpacity, setContainerOpacity] = useState(0);
function showProgressContainer() {
const heroEl = document.getElementById('hero-text')!; const heroEl = document.getElementById('hero-text')!;
heroEl.classList.add('opacity-0') if (!heroEl) {
return;
}
heroEl.classList.add('opacity-0');
setTimeout(() => {
heroEl.parentElement?.removeChild(heroEl);
setIsPreparing(false); setIsPreparing(false);
setTimeout(() => {
setContainerOpacity(100);
}, 50);
}, 300);
}
async function loadProgress() {
setIsLoading(true);
const { response: progressList, error } =
await httpGet<UserProgressResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-all-progress`
);
if (error || !progressList) {
return;
}
setProgress(progressList);
renderProgress(progressList);
}
useEffect(() => {
showProgressContainer();
loadProgress().finally(() => {
setIsLoading(false);
}); });
}, []);
if (isPreparing) { if (isPreparing) {
return null; return null;
} }
return null; const hasProgress = progress.length > 0;
// return ( return (
// <div class="min-h-full border-t border-t-[#1e293c] bg-gray-900"></div> <div
// ); class={`min-h-auto flex bg-gradient-to-b transition-opacity duration-500 sm:min-h-[280px] opacity-${containerOpacity} ${hasProgress && `border-t border-t-[#1e293c]`}`}
>
<div className="container min-h-full">
{!isLoading && progress.length == 0 && <EmptyProgress />}
{isLoading && <EmptyProgress title="Loading progress .." />}
{!isLoading && progress.length > 0 && (
<ProgressList progress={progress} />
)}
</div>
</div>
);
} }

@ -2,7 +2,7 @@
import { FavoriteRoadmaps } from './FavoriteRoadmaps'; import { FavoriteRoadmaps } from './FavoriteRoadmaps';
--- ---
<div class='relative min-h-[281px] border-b border-b-[#1e293c]'> <div class='relative min-h-auto sm:min-h-[281px] border-b border-b-[#1e293c]'>
<div <div
class='container px-6 py-6 pb-14 text-left sm:px-0 sm:py-20 sm:text-center transition-opacity duration-300' class='container px-6 py-6 pb-14 text-left sm:px-0 sm:py-20 sm:text-center transition-opacity duration-300'
id='hero-text' id='hero-text'

@ -0,0 +1,45 @@
import type { UserProgressResponse } from './FavoriteRoadmaps';
import { CheckIcon } from './CheckIcon';
type ProgressListProps = {
progress: UserProgressResponse;
};
export function ProgressList(props: ProgressListProps) {
const { progress } = props;
return (
<div className="relative py-7">
<p className="mb-4 flex items-center text-center text-sm text-gray-400">
<CheckIcon additionalClasses={'mr-1.5 w-[14px] h-[14px]'} />
Your favorite roadmaps and tracked progress.
</p>
<div className="grid grid-cols-3 gap-2">
{progress.map((resource) => {
const url =
resource.resourceType === 'roadmap'
? `/${resource.resourceId}`
: `/best-practices/${resource.resourceId}`;
const percentageDone =
((resource.skipped + resource.done) / resource.total) * 100;
return (
<a
href={url}
className="relative flex flex-col rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300 overflow-hidden"
>
<span className='relative z-20'>{resource.resourceTitle}</span>
<span
class="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]"
style={{ width: `${percentageDone}%` }}
></span>
</a>
);
})}
</div>
</div>
);
}

@ -39,10 +39,10 @@ The third stage is converting the compiler's intermediate representation into as
**Code Example (x86 Assembly):** **Code Example (x86 Assembly):**
```assembly ```
mov eax, 10 mov eax, 10
mov ebx, 20 mov ebx, 20
add eax, ebx add eax, ebx
``` ```
## Linking ## Linking

@ -1,48 +0,0 @@
import { httpGet } from './http';
import { isLoggedIn } from './jwt';
type UserProgressResponse = {
resourceId: string;
resourceType: 'roadmap' | 'best-practice';
done: number;
learning: number;
skipped: number;
total: number;
updatedAt: Date;
}[];
async function renderProgress() {
if (!isLoggedIn()) {
return;
}
const { response: progressList, error } = await httpGet<UserProgressResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-all-progress`
);
if (error || !progressList) {
return;
}
progressList.forEach((progress) => {
const href =
progress.resourceType === 'best-practice'
? `/best-practices/${progress.resourceId}`
: `/${progress.resourceId}`;
const element = document.querySelector(`a[href="${href}"]`);
if (!element) {
return;
}
const totalDone = progress.done + progress.skipped;
const percentageDone = (totalDone / progress.total) * 100;
const progressBar: HTMLElement = element.querySelector('[data-progress]')!;
progressBar.style.width = `${percentageDone}%`;
});
}
// on DOM load
window.addEventListener('DOMContentLoaded', () => {
window.setTimeout(renderProgress, 0);
});

@ -63,6 +63,4 @@ const videos = await getAllVideos();
<FeaturedVideos heading='Videos' videos={videos.slice(0, 7)} /> <FeaturedVideos heading='Videos' videos={videos.slice(0, 7)} />
</div> </div>
</div> </div>
<script src='../lib/home-progress.ts'></script>
</BaseLayout> </BaseLayout>

Loading…
Cancel
Save