computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
226 lines
5.6 KiB
226 lines
5.6 KiB
import Cookies from 'js-cookie'; |
|
import { httpGet, httpPost } from './http'; |
|
import { TOKEN_COOKIE_NAME } from './jwt'; |
|
import Element = astroHTML.JSX.Element; |
|
|
|
export type ResourceType = 'roadmap' | 'best-practice'; |
|
export type ResourceProgressType = 'done' | 'learning' | 'pending' | 'skipped'; |
|
|
|
type TopicMeta = { |
|
topicId: string; |
|
resourceType: ResourceType; |
|
resourceId: string; |
|
}; |
|
|
|
export async function isTopicDone(topic: TopicMeta): Promise<boolean> { |
|
const { topicId, resourceType, resourceId } = topic; |
|
const { done = [] } = |
|
(await getResourceProgress(resourceType, resourceId)) || {}; |
|
|
|
return done?.includes(topicId); |
|
} |
|
|
|
export async function getTopicStatus( |
|
topic: TopicMeta |
|
): Promise<ResourceProgressType> { |
|
const { topicId, resourceType, resourceId } = topic; |
|
const progressResult = await getResourceProgress(resourceType, resourceId); |
|
|
|
if (progressResult?.done?.includes(topicId)) { |
|
return 'done'; |
|
} |
|
|
|
if (progressResult?.learning?.includes(topicId)) { |
|
return 'learning'; |
|
} |
|
|
|
if (progressResult?.skipped?.includes(topicId)) { |
|
return 'skipped'; |
|
} |
|
|
|
return 'pending'; |
|
} |
|
|
|
export async function updateResourceProgress( |
|
topic: TopicMeta, |
|
progressType: ResourceProgressType |
|
) { |
|
const { topicId, resourceType, resourceId } = topic; |
|
|
|
const { response, error } = await httpPost<{ |
|
done: string[]; |
|
learning: string[]; |
|
skipped: string[]; |
|
}>(`${import.meta.env.PUBLIC_API_URL}/v1-update-resource-progress`, { |
|
topicId, |
|
resourceType, |
|
resourceId, |
|
progress: progressType, |
|
}); |
|
|
|
if (error || !response?.done || !response?.learning) { |
|
throw new Error(error?.message || 'Something went wrong'); |
|
} |
|
|
|
setResourceProgress( |
|
resourceType, |
|
resourceId, |
|
response.done, |
|
response.learning, |
|
response.skipped, |
|
); |
|
|
|
return response; |
|
} |
|
|
|
export async function getResourceProgress( |
|
resourceType: 'roadmap' | 'best-practice', |
|
resourceId: string |
|
): Promise<{ done: string[]; learning: string[], skipped: string[] }> { |
|
// No need to load progress if user is not logged in |
|
if (!Cookies.get(TOKEN_COOKIE_NAME)) { |
|
return { |
|
done: [], |
|
learning: [], |
|
skipped: [], |
|
}; |
|
} |
|
|
|
const progressKey = `${resourceType}-${resourceId}-progress`; |
|
|
|
const rawProgress = localStorage.getItem(progressKey); |
|
const progress = JSON.parse(rawProgress || 'null'); |
|
|
|
const progressTimestamp = progress?.timestamp; |
|
const diff = new Date().getTime() - parseInt(progressTimestamp || '0', 10); |
|
const isProgressExpired = diff > 15 * 60 * 1000; // 15 minutes |
|
|
|
if (!progress || isProgressExpired) { |
|
return loadFreshProgress(resourceType, resourceId); |
|
} |
|
|
|
return progress; |
|
} |
|
|
|
async function loadFreshProgress( |
|
resourceType: ResourceType, |
|
resourceId: string |
|
) { |
|
const { response, error } = await httpGet<{ |
|
done: string[]; |
|
learning: string[]; |
|
skipped: string[]; |
|
}>(`${import.meta.env.PUBLIC_API_URL}/v1-get-user-resource-progress`, { |
|
resourceType, |
|
resourceId, |
|
}); |
|
|
|
if (error || !response) { |
|
console.error(error); |
|
return { |
|
done: [], |
|
learning: [], |
|
skipped: [], |
|
}; |
|
} |
|
|
|
setResourceProgress( |
|
resourceType, |
|
resourceId, |
|
response?.done || [], |
|
response?.learning || [], |
|
response?.skipped || [], |
|
); |
|
|
|
return response; |
|
} |
|
|
|
export function setResourceProgress( |
|
resourceType: 'roadmap' | 'best-practice', |
|
resourceId: string, |
|
done: string[], |
|
learning: string[], |
|
skipped: string [], |
|
): void { |
|
localStorage.setItem( |
|
`${resourceType}-${resourceId}-progress`, |
|
JSON.stringify({ |
|
done, |
|
learning, |
|
skipped, |
|
timestamp: new Date().getTime(), |
|
}) |
|
); |
|
} |
|
|
|
export function renderTopicProgress( |
|
topicId: string, |
|
topicProgress: ResourceProgressType |
|
) { |
|
const isLearning = topicProgress === 'learning'; |
|
const isSkipped = topicProgress === 'skipped'; |
|
const isDone = topicProgress === 'done'; |
|
const matchingElements: Element[] = []; |
|
|
|
// Elements having sort order in the beginning of the group id |
|
document |
|
.querySelectorAll(`[data-group-id$="-${topicId}"]`) |
|
.forEach((element: unknown) => { |
|
const foundGroupId = |
|
(element as HTMLOrSVGElement)?.dataset?.groupId || ''; |
|
const validGroupRegex = new RegExp(`^\\d+-${topicId}$`); |
|
|
|
if (validGroupRegex.test(foundGroupId)) { |
|
matchingElements.push(element); |
|
} |
|
}); |
|
|
|
// Elements with exact match of the topic id |
|
document |
|
.querySelectorAll(`[data-group-id="${topicId}"]`) |
|
.forEach((element) => { |
|
matchingElements.push(element); |
|
}); |
|
|
|
// Matching "check:XXXX" box of the topic |
|
document |
|
.querySelectorAll(`[data-group-id="check:${topicId}"]`) |
|
.forEach((element) => { |
|
matchingElements.push(element); |
|
}); |
|
|
|
matchingElements.forEach((element) => { |
|
if (isDone) { |
|
element.classList.add('done'); |
|
element.classList.remove('learning', 'skipped'); |
|
} else if (isLearning) { |
|
element.classList.add('learning'); |
|
element.classList.remove('done', 'skipped'); |
|
} else if (isSkipped) { |
|
element.classList.add('skipped'); |
|
element.classList.remove('done', 'learning'); |
|
} else { |
|
element.classList.remove('done', 'skipped', 'learning'); |
|
} |
|
}); |
|
} |
|
|
|
export async function renderResourceProgress( |
|
resourceType: ResourceType, |
|
resourceId: string |
|
) { |
|
const { done = [], learning = [], skipped = [] } = |
|
(await getResourceProgress(resourceType, resourceId)) || {}; |
|
|
|
done.forEach((topicId) => { |
|
renderTopicProgress(topicId, 'done'); |
|
}); |
|
|
|
learning.forEach((topicId) => { |
|
renderTopicProgress(topicId, 'learning'); |
|
}); |
|
|
|
skipped.forEach((topicId) => { |
|
renderTopicProgress(topicId, 'skipped'); |
|
}); |
|
}
|
|
|