Show resource progress on best practices

feat/linkedin
Kamran Ahmed 2 years ago
parent 49cff0c22c
commit 2ca98bbb10
  1. 14
      src/components/BestPracticeHint.astro
  2. 57
      src/components/ResourceProgressStats.astro
  3. 30
      src/components/RoadmapHint.astro
  4. 2
      src/components/TopicDetail/TopicDetail.tsx
  5. 106
      src/lib/resource-progress.ts

@ -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>

@ -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'>&middot;</span
>
<span><span data-progress-learning>0</span> in progress</span><span
class='mx-1.5 text-gray-400'>&middot;</span
>
<span><span data-progress-skipped>0</span> skipped</span><span
class='mx-1.5 text-gray-400'>&middot;</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">&middot;</span>
<span><span class="progress-learning">0</span> learning</span><span class="mx-1.5 text-gray-400">&middot;</span>
<span><span class="progress-skipped">0</span> skipped</span><span class="mx-1.5 text-gray-400">&middot;</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 totalCheckBoxesDone = document.querySelectorAll(
'[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 totalItems = totalClickable - externalLinks - roadmapSwitchers; const totalDone =
const totalDone = document.querySelectorAll('.clickable-group.done').length; 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}`)
);
} }
progressNumsContainer.classList.remove('striped-loader') progressNumsContainers.forEach((progressNumsContainer) =>
progressNums.classList.remove('opacity-0'); progressNumsContainer.classList.remove('striped-loader')
progressNums.classList.remove('opacity-100'); );
progressNums.forEach((progressNum) => {
progressNum.classList.remove('opacity-0');
});
} }

Loading…
Cancel
Save