Add progress loader

pull/3813/head
Kamran Ahmed 2 years ago
parent 20db0baec5
commit 3825968106
  1. 2
      src/components/FrameRenderer/FrameRenderer.astro
  2. 98
      src/components/FrameRenderer/renderer.ts
  3. 7
      src/components/Setting/UpdatePasswordForm.tsx
  4. 7
      src/components/Setting/UpdateProfileForm.tsx
  5. 6
      src/lib/http.ts
  6. 17
      src/lib/resource-progress.ts

@ -27,4 +27,4 @@ const { resourceId, resourceType, jsonUrl, dimensions = null } = Astro.props;
</div> </div>
</div> </div>
<script src='./renderer.js'></script> <script src='./renderer.ts'></script>

@ -1,9 +1,18 @@
import { wireframeJSONToSVG } from 'roadmap-renderer'; import { wireframeJSONToSVG } from 'roadmap-renderer';
import Cookies from 'js-cookie'; import {
import { TOKEN_COOKIE_NAME } from '../../lib/jwt.ts'; renderResourceProgress,
import { httpGet } from '../../lib/http.ts'; ResourceType,
} from '../../lib/resource-progress';
export class Renderer { export class Renderer {
resourceId: string;
resourceType: string;
jsonUrl: string;
loaderHTML: string | null;
containerId: string;
loaderId: string;
constructor() { constructor() {
this.resourceId = ''; this.resourceId = '';
this.resourceType = ''; this.resourceType = '';
@ -35,58 +44,33 @@ export class Renderer {
} }
// Clone it so we can use it later // Clone it so we can use it later
this.loaderHTML = this.loaderEl.innerHTML; this.loaderHTML = this.loaderEl!.innerHTML;
const dataset = this.containerEl.dataset; const dataset = this.containerEl.dataset;
this.resourceType = dataset.resourceType; this.resourceType = dataset.resourceType!;
this.resourceId = dataset.resourceId; this.resourceId = dataset.resourceId!;
this.jsonUrl = dataset.jsonUrl; this.jsonUrl = dataset.jsonUrl!;
return true; return true;
} }
async loadProgress() {
const token = Cookies.get(TOKEN_COOKIE_NAME);
if (!token) {
return;
}
const { response, error } = await httpGet(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-resource-progress`,
{
resourceId: this.resourceId,
resourceType: this.resourceType,
}
);
if (!response) {
console.error(error);
return;
}
const { done } = response;
done.forEach((topic) => {
const topicEl = document.querySelector(`[data-group-id$="-${topic}"]`);
if (topicEl) {
topicEl.classList.add('done');
}
});
}
/** /**
* @param { string } jsonUrl * @param { string } jsonUrl
* @returns {Promise<SVGElement>} * @returns {Promise<SVGElement>}
*/ */
jsonToSvg(jsonUrl) { jsonToSvg(jsonUrl: string) {
if (!jsonUrl) { if (!jsonUrl) {
console.error('jsonUrl not defined in frontmatter'); console.error('jsonUrl not defined in frontmatter');
return null; return null;
} }
this.containerEl.innerHTML = this.loaderHTML; if (!this.containerEl) {
return Promise.all([ return null;
fetch(jsonUrl) }
this.containerEl.innerHTML = this.loaderHTML!;
return fetch(jsonUrl)
.then((res) => { .then((res) => {
return res.json(); return res.json();
}) })
@ -96,9 +80,19 @@ export class Renderer {
}); });
}) })
.then((svg) => { .then((svg) => {
this.containerEl.replaceChildren(svg); this.containerEl?.replaceChildren(svg);
})
.then(() => {
return renderResourceProgress(
this.resourceType as ResourceType,
this.resourceId
);
}) })
.catch((error) => { .catch((error) => {
if (!this.containerEl) {
return;
}
const message = ` const message = `
<strong>There was an error.</strong><br> <strong>There was an error.</strong><br>
@ -106,12 +100,8 @@ export class Renderer {
${error.message} <br /> ${error.stack} ${error.message} <br /> ${error.stack}
`; `;
this.containerEl.innerHTML = `<div class="error py-5 text-center text-red-600 mx-auto">${message}</div>`; this.containerEl.innerHTML = `<div class="error py-5 text-center text-red-600 mx-auto">${message}</div>`;
}), });
this.loadProgress(),
]);
} }
onDOMLoaded() { onDOMLoaded() {
@ -129,16 +119,16 @@ export class Renderer {
} }
} }
switchRoadmap(newJsonUrl) { switchRoadmap(newJsonUrl: string) {
const newJsonFileSlug = newJsonUrl.split('/').pop().replace('.json', ''); const newJsonFileSlug = newJsonUrl.split('/').pop()?.replace('.json', '');
// Update the URL and attach the new roadmap type // Update the URL and attach the new roadmap type
if (window?.history?.pushState) { if (window?.history?.pushState) {
const url = new URL(window.location); const url = new URL(window.location.href);
const type = this.resourceType[0]; // r for roadmap, b for best-practices const type = this.resourceType[0]; // r for roadmap, b for best-practices
url.searchParams.delete(type); url.searchParams.delete(type);
url.searchParams.set(type, newJsonFileSlug); url.searchParams.set(type, newJsonFileSlug!);
window.history.pushState(null, '', url.toString()); window.history.pushState(null, '', url.toString());
} }
@ -154,13 +144,13 @@ export class Renderer {
label: `${newJsonFileSlug}`, label: `${newJsonFileSlug}`,
}); });
this.jsonToSvg(newJsonUrl).then(() => { this.jsonToSvg(newJsonUrl)?.then(() => {
this.containerEl.setAttribute('style', ''); this.containerEl?.setAttribute('style', '');
}); });
} }
handleSvgClick(e) { handleSvgClick(e: any) {
const targetGroup = e.target.closest('g') || {}; const targetGroup = e.target?.closest('g') || {};
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : ''; const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
if (!groupId) { if (!groupId) {
return; return;

@ -59,13 +59,6 @@ export default function UpdatePasswordForm() {
); );
if (error || !response) { if (error || !response) {
if (error?.status === 401) {
Cookies.remove(TOKEN_COOKIE_NAME);
window.location.reload();
return;
}
setIsLoading(false); setIsLoading(false);
setError(error?.message || 'Something went wrong'); setError(error?.message || 'Something went wrong');

@ -52,13 +52,6 @@ export function UpdateProfileForm() {
); );
if (error || !response) { if (error || !response) {
if (error?.status === 401) {
Cookies.remove(TOKEN_COOKIE_NAME);
window.location.reload();
return;
}
setIsLoading(false); setIsLoading(false);
setError(error?.message || 'Something went wrong'); setError(error?.message || 'Something went wrong');

@ -55,6 +55,12 @@ export async function httpCall<
}; };
} }
// Logout user if token is invalid
if (data.status === 401) {
Cookies.remove(TOKEN_COOKIE_NAME);
window.location.reload();
}
return { return {
response: undefined, response: undefined,
error: data as ErrorType, error: data as ErrorType,

@ -49,15 +49,19 @@ export async function getResourceProgress(
resourceType: 'roadmap' | 'best-practice', resourceType: 'roadmap' | 'best-practice',
resourceId: string resourceId: string
): Promise<string[]> { ): Promise<string[]> {
// No need to load progress if user is not logged in
if (!Cookies.get(TOKEN_COOKIE_NAME)) {
return [];
}
const progressKey = `${resourceType}-${resourceId}-progress`; const progressKey = `${resourceType}-${resourceId}-progress`;
const rawProgress = localStorage.getItem(progressKey); const rawProgress = localStorage.getItem(progressKey);
const progress = JSON.parse(rawProgress || 'null'); const progress = JSON.parse(rawProgress || 'null');
const progressTimestamp = progress?.timestamp; const progressTimestamp = progress?.timestamp;
const diff = new Date().getTime() - parseInt(progressTimestamp || '0', 10); const diff = new Date().getTime() - parseInt(progressTimestamp || '0', 10);
const isProgressExpired = diff > 10 * 60 * 1000; // 10 minutes const isProgressExpired = diff > 15 * 60 * 1000; // 15 minutes
console.log(progressKey);
if (!progress || isProgressExpired) { if (!progress || isProgressExpired) {
return loadFreshProgress(resourceType, resourceId); return loadFreshProgress(resourceType, resourceId);
@ -79,13 +83,6 @@ async function loadFreshProgress(
); );
if (error) { if (error) {
if (error.status === 401) {
Cookies.remove(TOKEN_COOKIE_NAME);
window.location.reload();
return [];
}
console.error(error); console.error(error);
return []; return [];
} }

Loading…
Cancel
Save