From 3f8aa30fc6e8e35784265a6b0b90d67afed6e743 Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Wed, 2 Aug 2023 19:58:29 +0100 Subject: [PATCH] Progress modal --- .../UserProgress/UserProgressModal.tsx | 204 +++++++++--------- 1 file changed, 108 insertions(+), 96 deletions(-) diff --git a/src/components/UserProgress/UserProgressModal.tsx b/src/components/UserProgress/UserProgressModal.tsx index 6646d9597..52f79ef27 100644 --- a/src/components/UserProgress/UserProgressModal.tsx +++ b/src/components/UserProgress/UserProgressModal.tsx @@ -1,15 +1,15 @@ import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { wireframeJSONToSVG } from 'roadmap-renderer'; -import { Spinner } from '../ReactIcons/Spinner'; import '../FrameRenderer/FrameRenderer.css'; import { useOutsideClick } from '../../hooks/use-outside-click'; import { useKeydown } from '../../hooks/use-keydown'; import { httpGet } from '../../lib/http'; -import { ResourceType, renderTopicProgress } from '../../lib/resource-progress'; +import { renderTopicProgress, ResourceType } from '../../lib/resource-progress'; import CloseIcon from '../../icons/close.svg'; import { useToast } from '../../hooks/use-toast'; import { deleteUrlParam, getUrlParams } from '../../lib/browser'; import { useAuth } from '../../hooks/use-auth'; +import { Spinner } from '../ReactIcons/Spinner'; export type ProgressMapProps = { resourceId: string; @@ -30,24 +30,29 @@ type UserProgressResponse = { }; export function UserProgressModal(props: ProgressMapProps) { + const { s: userId } = getUrlParams(); const { resourceId, resourceType } = props; - const containerEl = useRef(null); + + const resourceSvgEl = useRef(null); const popupBodyEl = useRef(null); - const { uid: userId } = getUrlParams(); - const currentUser = useAuth(); + const currentUser = useAuth(); if (!userId || currentUser?.id === userId) { - deleteUrlParam('uid'); + deleteUrlParam('s'); return null; } - const [showModal, setShowModal] = useState(userId ? true : false); + const [showModal, setShowModal] = useState(!!userId); + const [resourceSvg, setResourceSvg] = useState(null); + const [progressResponse, setProgressResponse] = + useState(); - const [userResponse, setUserResponse] = useState(); const [isLoading, setIsLoading] = useState(true); const toast = useToast(); - let resourceJsonUrl = 'https://roadmap.sh'; + let resourceJsonUrl = import.meta.env.DEV + ? 'http://localhost:3000' + : 'https://roadmap.sh'; if (resourceType === 'roadmap') { resourceJsonUrl += `/${resourceId}.json`; } else { @@ -58,33 +63,31 @@ export function UserProgressModal(props: ProgressMapProps) { userId: string, resourceType: string, resourceId: string - ) { + ): Promise { const { error, response } = await httpGet( `${ import.meta.env.PUBLIC_API_URL }/v1-get-user-progress/${userId}?resourceType=${resourceType}&resourceId=${resourceId}` ); + if (error || !response) { toast.error(error?.message || 'Failed to get member progress'); - return; + + return undefined; } - setUserResponse(response); return response; } - async function renderResource(jsonUrl: string) { - const res = await fetch(jsonUrl); - const json = await res.json(); - const svg = await wireframeJSONToSVG(json, { + async function getRoadmapSVG(jsonUrl: string): Promise { + const { error, response: roadmapJson } = await httpGet(jsonUrl); + return await wireframeJSONToSVG(roadmapJson, { fontURL: '/fonts/balsamiq.woff2', }); - - containerEl.current?.replaceChildren(svg); } function onClose() { - deleteUrlParam('uid'); + deleteUrlParam('s'); setShowModal(false); } @@ -97,30 +100,18 @@ export function UserProgressModal(props: ProgressMapProps) { }); useEffect(() => { - if ( - !containerEl.current || - !resourceJsonUrl || - !resourceId || - !resourceType - ) { + if (!resourceJsonUrl || !resourceId || !resourceType) { return; } setIsLoading(true); Promise.all([ - renderResource(resourceJsonUrl), + getRoadmapSVG(resourceJsonUrl), getUserProgress(userId, resourceType, resourceId), ]) - .then(([_, user = {}]) => { - const { progress } = user; - const { done, learning, skipped } = progress || { - done: [], - learning: [], - skipped: [], - }; - done?.forEach((id: string) => renderTopicProgress(id, 'done')); - learning?.forEach((id: string) => renderTopicProgress(id, 'learning')); - skipped?.forEach((id: string) => renderTopicProgress(id, 'skipped')); + .then(([svg, user]) => { + setResourceSvg(svg); + setProgressResponse(user); }) .catch((err) => { console.error(err); @@ -131,78 +122,107 @@ export function UserProgressModal(props: ProgressMapProps) { }); }, [userId]); - async function handleClick(e: MouseEvent) { - const targetGroup = (e.target as HTMLElement)?.closest('g'); - if (!targetGroup) { - return; - } - const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : ''; - if (!groupId) { + useEffect(() => { + console.log(resourceSvg, progressResponse, popupBodyEl); + if (!resourceSvg || !progressResponse?.user || !popupBodyEl?.current) { return; } - e.preventDefault(); - e.stopPropagation(); - return; - } + const { progress } = progressResponse; + const { done, learning, skipped } = progress || { + done: [], + learning: [], + skipped: [], + }; + + const popupBody = popupBodyEl?.current; + + done?.forEach((id: string) => renderTopicProgress(id, 'done', popupBody)); + learning?.forEach((id: string) => + renderTopicProgress(id, 'learning', popupBody) + ); + skipped?.forEach((id: string) => + renderTopicProgress(id, 'skipped', popupBody) + ); + }, [progressResponse, resourceSvg, popupBodyEl]); + + // Disable clicks on the progress SVG useEffect(() => { - if (!containerEl.current) { - return; + function handleClick(e: any) { + const closestSvg = e.target.closest('#user-progress-modal'); + if (!closestSvg) { + return; + } + + e.preventDefault(); + e.stopPropagation(); } - containerEl.current.addEventListener('click', handleClick); + + document?.addEventListener('click', handleClick); + document?.addEventListener('touchstart', handleClick); + document?.addEventListener('contextmenu', handleClick); return () => { - containerEl.current?.removeEventListener('click', handleClick); + document?.addEventListener('click', handleClick); }; - }, [containerEl.current]); + }, []); + + const user = progressResponse?.user; + const progress = progressResponse?.progress; - const user = useMemo(() => userResponse, [userResponse]); - const userProgressTotal = user?.progress?.total || 0; - const userDone = user?.progress?.done?.length || 0; + const userProgressTotal = progress?.total || 0; + const userDone = progress?.done?.length || 0; const progressPercentage = Math.round((userDone / userProgressTotal) * 100) || 0; - const userLearning = user?.progress?.learning?.length || 0; - const userSkipped = user?.progress?.skipped?.length || 0; + const userLearning = progress?.learning?.length || 0; + const userSkipped = progress?.skipped?.length || 0; if (!showModal) { return null; } + const loadingMessage = isLoading ? ( +
+
+
+
+ + + Loading user progress... + +
+
+
+
+ ) : null; + return ( -
+