mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-20 08:02:35 +02:00
Show resource progress on best practices
This commit is contained in:
@@ -1,20 +1,10 @@
|
|||||||
---
|
---
|
||||||
|
import ResourceProgressStats from './ResourceProgressStats.astro';
|
||||||
export interface Props {
|
export interface Props {
|
||||||
bestPracticeId: string;
|
bestPracticeId: string;
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class='mt-4 sm:mt-7 border-0 sm:border rounded-md mb-0 sm:-mb-[65px]'>
|
<div class='mt-4 sm:mt-7 border-0 sm:border rounded-md mb-0 sm:-mb-[65px]'>
|
||||||
<!-- Desktop: Roadmap Resources - Alert -->
|
<ResourceProgressStats />
|
||||||
<div class='hidden sm:flex justify-between px-2 bg-white items-center rounded-md p-1.5'>
|
|
||||||
<p class='text-sm'>
|
|
||||||
<span class='text-yellow-900 bg-yellow-200 py-0.5 px-1 text-xs rounded-sm font-medium uppercase mr-0.5'>Tip</span>
|
|
||||||
Click the best practices for details and resources
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile - Roadmap resources alert -->
|
|
||||||
<p class='block sm:hidden text-sm border border-yellow-500 text-yellow-700 rounded-md py-1.5 px-2 bg-white relative'>
|
|
||||||
Click the best practices for details and resources
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
57
src/components/ResourceProgressStats.astro
Normal file
57
src/components/ResourceProgressStats.astro
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
isSecondaryBanner?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isSecondaryBanner = false } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-progress-nums-container
|
||||||
|
class:list={[
|
||||||
|
'hidden sm:flex justify-between px-2 bg-white items-center py-1.5 relative striped-loader bg-white',
|
||||||
|
{
|
||||||
|
'rounded-bl-md rounded-br-md': isSecondaryBanner,
|
||||||
|
'rounded-md': !isSecondaryBanner,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class='flex text-sm opacity-0 transition-opacity duration-300'
|
||||||
|
data-progress-nums
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class='mr-2.5 rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900'
|
||||||
|
>
|
||||||
|
<span data-progress-percentage>0</span>% Done
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span><span data-progress-done>0</span> completed</span><span
|
||||||
|
class='mx-1.5 text-gray-400'>·</span
|
||||||
|
>
|
||||||
|
<span><span data-progress-learning>0</span> in progress</span><span
|
||||||
|
class='mx-1.5 text-gray-400'>·</span
|
||||||
|
>
|
||||||
|
<span><span data-progress-skipped>0</span> skipped</span><span
|
||||||
|
class='mx-1.5 text-gray-400'>·</span
|
||||||
|
>
|
||||||
|
<span><span data-progress-total>0</span> Total</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
data-progress-nums-container
|
||||||
|
class='relative block rounded-md border bg-white px-2 py-1.5 text-sm text-sm text-gray-700 sm:hidden striped-loader bg-white -mb-2'
|
||||||
|
>
|
||||||
|
<span data-progress-nums class='opacity-0 transition-opacity duration-300'>
|
||||||
|
<span
|
||||||
|
class='mr-2.5 rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900'
|
||||||
|
>
|
||||||
|
<span data-progress-percentage>0</span>% Done
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<span data-progress-done>0</span> of <span data-progress-total>0</span> Done
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
@@ -2,6 +2,7 @@
|
|||||||
import { ClearProgress } from './Activity/ClearProgress';
|
import { ClearProgress } from './Activity/ClearProgress';
|
||||||
import AstroIcon from './AstroIcon.astro';
|
import AstroIcon from './AstroIcon.astro';
|
||||||
import Icon from './AstroIcon.astro';
|
import Icon from './AstroIcon.astro';
|
||||||
|
import ResourceProgressStats from './ResourceProgressStats.astro';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
roadmapId: string;
|
roadmapId: string;
|
||||||
@@ -43,32 +44,5 @@ const roadmapTitle =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Desktop: Roadmap Resources - Alert -->
|
<ResourceProgressStats isSecondaryBanner={hasTNSBanner} />
|
||||||
<div id="progress-nums-container"
|
|
||||||
class:list={[
|
|
||||||
'hidden sm:flex justify-between px-2 bg-white items-center py-1.5 relative striped-loader bg-white',
|
|
||||||
{
|
|
||||||
'rounded-bl-md rounded-br-md': hasTNSBanner,
|
|
||||||
'rounded-md': !hasTNSBanner,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<p class='text-sm flex opacity-0 transition-opacity duration-300' id="progress-nums">
|
|
||||||
<span class="font-medium py-0.5 rounded-sm text-xs uppercase bg-yellow-200 px-1 text-yellow-900 mr-2.5">
|
|
||||||
<span class="progress-percentage">0</span>% Done
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span><span class="progress-done">0</span> completed</span><span class="mx-1.5 text-gray-400">·</span>
|
|
||||||
<span><span class="progress-learning">0</span> learning</span><span class="mx-1.5 text-gray-400">·</span>
|
|
||||||
<span><span class="progress-skipped">0</span> skipped</span><span class="mx-1.5 text-gray-400">·</span>
|
|
||||||
<span><span class="progress-total">0</span> Total</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile - Roadmap resources alert -->
|
|
||||||
<p
|
|
||||||
class='relative block rounded-md border border-yellow-500 bg-white px-2 py-1.5 text-sm text-yellow-700 sm:hidden'
|
|
||||||
>
|
|
||||||
Track your progress and learn about the topics by clicking the roadmap items.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
@@ -10,6 +10,7 @@ import { httpGet } from '../../lib/http';
|
|||||||
import { isLoggedIn } from '../../lib/jwt';
|
import { isLoggedIn } from '../../lib/jwt';
|
||||||
import {
|
import {
|
||||||
isTopicDone,
|
isTopicDone,
|
||||||
|
refreshProgressCounters,
|
||||||
renderTopicProgress,
|
renderTopicProgress,
|
||||||
ResourceType,
|
ResourceType,
|
||||||
updateResourceProgress as updateResourceProgressApi,
|
updateResourceProgress as updateResourceProgressApi,
|
||||||
@@ -87,6 +88,7 @@ export function TopicDetail() {
|
|||||||
topicId,
|
topicId,
|
||||||
done.includes(topicId) ? 'done' : 'pending'
|
done.includes(topicId) ? 'done' : 'pending'
|
||||||
);
|
);
|
||||||
|
refreshProgressCounters();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
|
@@ -67,7 +67,7 @@ export async function updateResourceProgress(
|
|||||||
resourceId,
|
resourceId,
|
||||||
response.done,
|
response.done,
|
||||||
response.learning,
|
response.learning,
|
||||||
response.skipped,
|
response.skipped
|
||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -76,7 +76,7 @@ export async function updateResourceProgress(
|
|||||||
export async function getResourceProgress(
|
export async function getResourceProgress(
|
||||||
resourceType: 'roadmap' | 'best-practice',
|
resourceType: 'roadmap' | 'best-practice',
|
||||||
resourceId: string
|
resourceId: string
|
||||||
): Promise<{ done: string[]; learning: string[], skipped: string[] }> {
|
): Promise<{ done: string[]; learning: string[]; skipped: string[] }> {
|
||||||
// No need to load progress if user is not logged in
|
// No need to load progress if user is not logged in
|
||||||
if (!Cookies.get(TOKEN_COOKIE_NAME)) {
|
if (!Cookies.get(TOKEN_COOKIE_NAME)) {
|
||||||
return {
|
return {
|
||||||
@@ -129,7 +129,7 @@ async function loadFreshProgress(
|
|||||||
resourceId,
|
resourceId,
|
||||||
response?.done || [],
|
response?.done || [],
|
||||||
response?.learning || [],
|
response?.learning || [],
|
||||||
response?.skipped || [],
|
response?.skipped || []
|
||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -140,7 +140,7 @@ export function setResourceProgress(
|
|||||||
resourceId: string,
|
resourceId: string,
|
||||||
done: string[],
|
done: string[],
|
||||||
learning: string[],
|
learning: string[],
|
||||||
skipped: string [],
|
skipped: string[]
|
||||||
): void {
|
): void {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
`${resourceType}-${resourceId}-progress`,
|
`${resourceType}-${resourceId}-progress`,
|
||||||
@@ -209,8 +209,11 @@ export async function renderResourceProgress(
|
|||||||
resourceType: ResourceType,
|
resourceType: ResourceType,
|
||||||
resourceId: string
|
resourceId: string
|
||||||
) {
|
) {
|
||||||
const { done = [], learning = [], skipped = [] } =
|
const {
|
||||||
(await getResourceProgress(resourceType, resourceId)) || {};
|
done = [],
|
||||||
|
learning = [],
|
||||||
|
skipped = [],
|
||||||
|
} = (await getResourceProgress(resourceType, resourceId)) || {};
|
||||||
|
|
||||||
done.forEach((topicId) => {
|
done.forEach((topicId) => {
|
||||||
renderTopicProgress(topicId, 'done');
|
renderTopicProgress(topicId, 'done');
|
||||||
@@ -228,9 +231,11 @@ export async function renderResourceProgress(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function refreshProgressCounters() {
|
export function refreshProgressCounters() {
|
||||||
const progressNumsContainer = document.getElementById('progress-nums-container');
|
const progressNumsContainers = document.querySelectorAll(
|
||||||
const progressNums = document.getElementById('progress-nums');
|
'[data-progress-nums-container]'
|
||||||
if (!progressNumsContainer || !progressNums) {
|
);
|
||||||
|
const progressNums = document.querySelectorAll('[data-progress-nums]');
|
||||||
|
if (progressNumsContainers.length === 0 || progressNums.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,43 +246,80 @@ export function refreshProgressCounters() {
|
|||||||
const roadmapSwitchers = document.querySelectorAll(
|
const roadmapSwitchers = document.querySelectorAll(
|
||||||
'[data-group-id^="json:"]'
|
'[data-group-id^="json:"]'
|
||||||
).length;
|
).length;
|
||||||
|
const checkBoxes = document.querySelectorAll(
|
||||||
|
'[data-group-id^="check:"]'
|
||||||
|
).length;
|
||||||
|
|
||||||
const totalItems = totalClickable - externalLinks - roadmapSwitchers;
|
const totalCheckBoxesDone = document.querySelectorAll(
|
||||||
const totalDone = document.querySelectorAll('.clickable-group.done').length;
|
'[data-group-id^="check:"].done'
|
||||||
|
).length;
|
||||||
|
const totalCheckBoxesLearning = document.querySelectorAll(
|
||||||
|
'[data-group-id^="check:"].learning'
|
||||||
|
).length;
|
||||||
|
const totalCheckBoxesSkipped = document.querySelectorAll(
|
||||||
|
'[data-group-id^="check:"].skipped'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const totalItems =
|
||||||
|
totalClickable - externalLinks - roadmapSwitchers - checkBoxes;
|
||||||
|
|
||||||
|
const totalDone =
|
||||||
|
document.querySelectorAll('.clickable-group.done').length -
|
||||||
|
totalCheckBoxesDone;
|
||||||
const totalLearning = document.querySelectorAll(
|
const totalLearning = document.querySelectorAll(
|
||||||
'.clickable-group.learning'
|
'.clickable-group.learning'
|
||||||
).length;
|
).length - totalCheckBoxesLearning;
|
||||||
const totalSkipped = document.querySelectorAll(
|
const totalSkipped = document.querySelectorAll(
|
||||||
'.clickable-group.skipped'
|
'.clickable-group.skipped'
|
||||||
).length;
|
).length - totalCheckBoxesSkipped;
|
||||||
|
|
||||||
const doneCountEl = document.querySelector('.progress-done');
|
const doneCountEls = document.querySelectorAll('[data-progress-done]');
|
||||||
if (doneCountEl) {
|
if (doneCountEls.length > 0) {
|
||||||
doneCountEl.innerHTML = `${totalDone}`;
|
doneCountEls.forEach(
|
||||||
|
(doneCountEl) => (doneCountEl.innerHTML = `${totalDone}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const learningCountEl = document.querySelector('.progress-learning');
|
const learningCountEls = document.querySelectorAll(
|
||||||
if (learningCountEl) {
|
'[data-progress-learning]'
|
||||||
learningCountEl.innerHTML = `${totalLearning}`;
|
);
|
||||||
|
if (learningCountEls.length > 0) {
|
||||||
|
learningCountEls.forEach(
|
||||||
|
(learningCountEl) => (learningCountEl.innerHTML = `${totalLearning}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const skippedCountEl = document.querySelector('.progress-skipped');
|
const skippedCountEls = document.querySelectorAll('[data-progress-skipped]');
|
||||||
if (skippedCountEl) {
|
if (skippedCountEls.length > 0) {
|
||||||
skippedCountEl.innerHTML = `${totalSkipped}`;
|
skippedCountEls.forEach(
|
||||||
|
(skippedCountEl) => (skippedCountEl.innerHTML = `${totalSkipped}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalCountEl = document.querySelector('.progress-total');
|
const totalCountEls = document.querySelectorAll('[data-progress-total]');
|
||||||
if (totalCountEl) {
|
if (totalCountEls.length > 0) {
|
||||||
totalCountEl.innerHTML = `${totalItems}`;
|
totalCountEls.forEach(
|
||||||
|
(totalCountEl) => (totalCountEl.innerHTML = `${totalItems}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const progressPercentage = Math.round(((totalDone + totalSkipped) / totalItems) * 100);
|
const progressPercentage = Math.round(
|
||||||
const progressPercentageEl = document.querySelector('.progress-percentage');
|
((totalDone + totalSkipped) / totalItems) * 100
|
||||||
if (progressPercentageEl) {
|
);
|
||||||
progressPercentageEl.innerHTML = `${progressPercentage}`;
|
const progressPercentageEls = document.querySelectorAll(
|
||||||
|
'[data-progress-percentage]'
|
||||||
|
);
|
||||||
|
if (progressPercentageEls.length > 0) {
|
||||||
|
progressPercentageEls.forEach(
|
||||||
|
(progressPercentageEl) =>
|
||||||
|
(progressPercentageEl.innerHTML = `${progressPercentage}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progressNumsContainers.forEach((progressNumsContainer) =>
|
||||||
progressNumsContainer.classList.remove('striped-loader')
|
progressNumsContainer.classList.remove('striped-loader')
|
||||||
progressNums.classList.remove('opacity-0');
|
);
|
||||||
progressNums.classList.remove('opacity-100');
|
progressNums.forEach((progressNum) => {
|
||||||
|
progressNum.classList.remove('opacity-0');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user