diff --git a/app/analytics/[questId]/page.tsx b/app/analytics/[questId]/page.tsx index d03b1f1e..cda3f7cd 100644 --- a/app/analytics/[questId]/page.tsx +++ b/app/analytics/[questId]/page.tsx @@ -30,10 +30,15 @@ import { import { numberWithCommas } from "@utils/numberService"; import { CDNImg } from "@components/cdn/image"; import { useMediaQuery } from "@mui/material"; -import AnalyticsSkeleton from "@components/skeletons/analyticsSkeleton"; import { QuestDefault } from "@constants/common"; import Typography from "@components/UI/typography/typography"; import { TEXT_TYPE } from "@constants/typography"; +import { + QuestHeaderSkeleton, + MetricCardSkeleton, + GraphSkeleton, + TasksSkeleton, +} from "@components/skeletons/allAnalysticQuestSkeleton"; type BoostQuestPageProps = { params: { @@ -45,7 +50,15 @@ export default function Page({ params }: BoostQuestPageProps) { const router = useRouter(); const { questId } = params; - const [loading, setLoading] = useState(true); + + const [isQuestLoading, setIsQuestLoading] = useState(true); + const [isGraphLoading, setIsGraphLoading] = useState(true); + const [isParticipationLoading, setIsParticipationLoading] = + useState(true); + const [isParticipantsLoading, setIsParticipantsLoading] = + useState(true); + const [isVisitorsLoading, setIsVisitorsLoading] = useState(true); + const [graphData, setGraphData] = useState< { _id: string; participants: number }[] >([]); @@ -55,10 +68,12 @@ export default function Page({ params }: BoostQuestPageProps) { const [uniqueVisitors, setUniqueVisitors] = useState(0); const isMobile = useMediaQuery("(max-width:768px)"); const [questData, setQuestData] = useState(QuestDefault); + const fetchGraphData = useCallback(async () => { + setIsGraphLoading(true); try { const res = await getQuestActivityData(parseInt(questId)); - if (!res) return; + if (!res) return []; const formattedData = res?.map( (data: { date: string; participants: number }) => { const dateString = data.date.split(" ")[0]; @@ -70,54 +85,72 @@ export default function Page({ params }: BoostQuestPageProps) { }; } ); - setGraphData(formattedData); + return formattedData; } catch (error) { console.log("Error while fetching graph data", error); + return []; + } finally { + setIsGraphLoading(false); } - }, []); + }, [questId]); const fetchQuestById = useCallback(async () => { + setIsQuestLoading(true); try { const res = await getQuestById(questId); if (!res || "error" in res) { - return; + return QuestDefault; } else { - setQuestData(res); + return res; } - } catch (error) { console.log("Error while fetching quest data", error); + return QuestDefault; + } finally { + setIsQuestLoading(false); } - }, []); + }, [questId]); const fetchQuestParticipation = useCallback(async () => { + setIsParticipationLoading(true); try { const res = await getQuestsParticipation(parseInt(questId)); - setQuestParticipationData(res); + return res; } catch (error) { console.log("Error while fetching quest data", error); + return undefined; + } finally { + setIsParticipationLoading(false); } - }, []); + }, [questId]); const fetchQuestParticipants = useCallback(async () => { + setIsParticipantsLoading(true); try { const res = (await getQuestParticipants( parseInt(questId) )) as QuestParticipantsDocument; - setQuestParticipants(Number(res.count)); + return Number(res.count); } catch (error) { console.log("Error while fetching quest data", error); + return 0; + } finally { + setIsParticipantsLoading(false); } - }, []); + }, [questId]); const fetchUniqueVisitorCount = useCallback(async () => { + setIsVisitorsLoading(true); try { const res = await getUniqueVisitorCount(parseInt(questId)); - setUniqueVisitors(res); + return res; } catch (error) { console.log("Error while fetching unique visitor count", error); + return undefined; + } finally { + setIsVisitorsLoading(false); } - }, []); + }, [questId]); const computePercentage = useCallback( (num: number) => { @@ -135,247 +168,316 @@ export default function Page({ params }: BoostQuestPageProps) { return tickItem; }, []); - const fetchPageData = useCallback(async () => { - setLoading(true); - await fetchQuestById(); - await fetchGraphData(); - await fetchQuestParticipation(); - await fetchQuestParticipants(); - await fetchUniqueVisitorCount(); - setLoading(false); - }, []); + const fetchAllData = useCallback(async () => { + const [quest, graph, participation, participants, visitors] = + await Promise.all([ + fetchQuestById(), + fetchGraphData(), + fetchQuestParticipation(), + fetchQuestParticipants(), + fetchUniqueVisitorCount(), + ]); + + setQuestData(quest); + setGraphData(graph); + setQuestParticipationData(participation); + setQuestParticipants(participants); + setUniqueVisitors(visitors); + }, [ + fetchQuestById, + fetchGraphData, + fetchQuestParticipation, + fetchQuestParticipants, + fetchUniqueVisitorCount, + ]); useEffect(() => { - fetchPageData(); - }, []); + fetchAllData(); + }, [fetchAllData]); return (
router.back()} />
- - {loading ? ( -
-
- -
-
- ) : ( - <> -
-
- {questData ? ( - <> -
-
- - {questData?.issuer} -
-
- - {questData?.name} - - - {questData?.expired ? "Finished" : "Ongoing"} - - - ) : null} -
-
-
-
-
- Unique users - - {uniqueVisitors && uniqueVisitors > 0 - ? numberWithCommas(uniqueVisitors) - : "NA"} +
+ {isQuestLoading ? ( + + ) : ( +
+ {questData ? ( + <> +
+
+ + + {questData?.issuer}
-
- - Users that finished the quest + + {questData?.name} + + + {questData?.expired ? "Finished" : "Ongoing"} + + + ) : null} +
+ )} + +
+
+ {isVisitorsLoading ? ( + + ) : ( +
+
+ + Unique users - - {questParticipants > 0 - ? numberWithCommas(questParticipants) + + {uniqueVisitors && uniqueVisitors > 0 + ? numberWithCommas(uniqueVisitors) : "NA"} - {uniqueVisitors && uniqueVisitors > 0 ? ( -
- - {uniqueVisitors > 0 - ? `${computePercentage(questParticipants)}%` - : "NA"} - - - of unique users - -
- ) : null}
-
-
+ )} -
-
- {graphData?.length > 0 ? ( - <> -
- - User Progress Visualization - - - Quests Completion over time - + {isParticipantsLoading ? ( + + ) : ( +
+ + Users that finished the quest + + + {questParticipants > 0 + ? numberWithCommas(questParticipants) + : "NA"} + + {uniqueVisitors && uniqueVisitors > 0 ? ( +
+ + {uniqueVisitors > 0 + ? `${computePercentage(questParticipants)}%` + : "NA"} + + + of unique users +
- + )} +
+
+
+ +
+ {isGraphLoading ? ( + + ) : ( +
+ {graphData?.length > 0 ? ( + <> +
+ - + + Quests Completion over time + +
+ + + + + + + + + + formatYAxis(value)} + /> + + + + + + + ) : ( +
+ + NA + +
+ )} +
+ )} +
+ +
+ {isParticipationLoading ? ( + + ) : ( +
+
+ + People who completed + + + Tasks + +
+ +
+ {questParticipationData && questParticipationData?.length > 0 ? ( + questParticipationData?.map( + (eachParticipation, index: number) => ( +
- - + - - - - - - formatYAxis(value)} - /> - - - - - - + {eachParticipation.name} + + + {numberWithCommas(eachParticipation.count)} + + {uniqueVisitors && uniqueVisitors > 0 ? ( +
+ + {uniqueVisitors > 0 + ? `${computePercentage( + eachParticipation.count + )}%` + : "NA"} + + + of unique users + +
+ ) : null} +
+
+ ) + ) ) : (
- NA + + NA +
)}
- -
-
-
- - People who completed - - Tasks -
- -
- {questParticipationData && - questParticipationData?.length > 0 ? ( - questParticipationData?.map( - (eachParticipation, index: number) => ( -
-
- - {eachParticipation.name} - - - {numberWithCommas(eachParticipation.count)} - - {uniqueVisitors && uniqueVisitors > 0 ? ( -
- - {uniqueVisitors > 0 - ? `${computePercentage( - eachParticipation.count - )}%` - : "NA"} - - - of unique users - -
- ) : null} -
-
- ) - ) - ) : ( -
- NA -
- )} -
-
-
- - )} + )} +
); } diff --git a/components/skeletons/allAnalysticQuestSkeleton.tsx b/components/skeletons/allAnalysticQuestSkeleton.tsx new file mode 100644 index 00000000..fac6c74a --- /dev/null +++ b/components/skeletons/allAnalysticQuestSkeleton.tsx @@ -0,0 +1,139 @@ +import { Skeleton } from "@mui/material"; +import analyticsStyles from "@styles/analytics.module.css"; + +export const QuestHeaderSkeleton = () => ( +
+
+ + +
+ + +
+); + +export const MetricCardSkeleton = () => ( +
+
+ + +
+ + +
+
+
+); + +export const GraphSkeleton = ({ isMobile }: { isMobile: boolean }) => ( +
+
+ + +
+ +
+); + +export const TasksSkeleton = () => ( +
+
+ + +
+
+ {[1, 2, 3].map((index) => ( +
+
+ + +
+ + +
+
+
+ ))} +
+
+);