From 41fdaf226eded461c477a06353fe12c707543f21 Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 23 Jul 2024 18:57:29 +0200 Subject: [PATCH 1/7] Installed tanstack query and used it in admin/periodId --- .../applicantoverview/ApplicantsOverview.tsx | 4 +- lib/api/periodApi.ts | 8 ++++ package-lock.json | 25 ++++++++++ package.json | 1 + pages/_app.tsx | 23 +++++++--- pages/admin/[period-id]/index.tsx | 46 ++++++++----------- 6 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 lib/api/periodApi.ts diff --git a/components/applicantoverview/ApplicantsOverview.tsx b/components/applicantoverview/ApplicantsOverview.tsx index a93689f0..5647d3b1 100644 --- a/components/applicantoverview/ApplicantsOverview.tsx +++ b/components/applicantoverview/ApplicantsOverview.tsx @@ -10,8 +10,8 @@ import ApplicantTable from "./ApplicantTable"; import ApplicantOverviewSkeleton from "./ApplicantOverviewSkeleton"; interface Props { - period: periodType | null; - committees?: string[] | null; + period?: periodType; + committees?: string[]; committee?: string; includePreferences: boolean; } diff --git a/lib/api/periodApi.ts b/lib/api/periodApi.ts new file mode 100644 index 00000000..18a347a1 --- /dev/null +++ b/lib/api/periodApi.ts @@ -0,0 +1,8 @@ +import { QueryFunctionContext } from '@tanstack/react-query'; + +export const fetchPeriodById = async (context: QueryFunctionContext) => { + const id = context.queryKey[1]; + return fetch(`/api/periods/${id}`).then(res => + res.json() + ); +} diff --git a/package-lock.json b/package-lock.json index 79c8d9e9..899cebb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@heroicons/react": "^2.1.3", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "github:tailwindcss/typography", + "@tanstack/react-query": "^5.51.11", "@types/mongodb": "^4.0.7", "mongodb": "^6.1.0", "next": "^12.3.4", @@ -1734,6 +1735,30 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/@tanstack/query-core": { + "version": "5.51.9", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.9.tgz", + "integrity": "sha512-HsAwaY5J19MD18ykZDS3aVVh+bAt0i7m6uQlFC2b77DLV9djo+xEN7MWQAQQTR8IM+7r/zbozTQ7P0xr0bHuew==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.51.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.11.tgz", + "integrity": "sha512-4Kq2x0XpDlpvSnaLG+8pHNH60zEc3mBvb3B2tOMDjcPCi/o+Du3p/9qpPLwJOTliVxxPJAP27fuIhLrsRdCr7A==", + "dependencies": { + "@tanstack/query-core": "5.51.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", diff --git a/package.json b/package.json index 693f5999..e69364f2 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@heroicons/react": "^2.1.3", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "github:tailwindcss/typography", + "@tanstack/react-query": "^5.51.11", "@types/mongodb": "^4.0.7", "mongodb": "^6.1.0", "next": "^12.3.4", diff --git a/pages/_app.tsx b/pages/_app.tsx index b3015108..65ef4fe1 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -8,6 +8,15 @@ import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; import LoadingPage from "../components/LoadingPage"; import Signature from "../lib/utils/Signature"; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const queryClient = new QueryClient({ + defaultOptions: { // TODO: go over default options + queries: { + staleTime: 1000 * 60 * 10, + }, + }, +}); const SessionHandler: React.FC<{ children: React.ReactNode }> = ({ children, @@ -70,12 +79,14 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: any) {
- - -
- -
-
+ + + +
+ +
+
+
diff --git a/pages/admin/[period-id]/index.tsx b/pages/admin/[period-id]/index.tsx index 63901159..7d2e91f1 100644 --- a/pages/admin/[period-id]/index.tsx +++ b/pages/admin/[period-id]/index.tsx @@ -1,44 +1,34 @@ import { useSession } from "next-auth/react"; import { useEffect, useState } from "react"; import router from "next/router"; -import { applicantType, periodType } from "../../../lib/types/types"; +import { periodType } from "../../../lib/types/types"; import NotFound from "../../404"; import ApplicantsOverview from "../../../components/applicantoverview/ApplicantsOverview"; +import { useQuery } from '@tanstack/react-query'; +import { fetchPeriodById } from "../../../lib/api/periodApi"; +import LoadingPage from "../../../components/LoadingPage"; +import ErrorPage from "../../../components/ErrorPage"; const Admin = () => { const { data: session } = useSession(); const periodId = router.query["period-id"]; - const [period, setPeriod] = useState(null); - const [committees, setCommittees] = useState(null); - useEffect(() => { - const fetchPeriod = async () => { - if (!session || session.user?.role !== "admin") { - return; - } - if (periodId === undefined) return; + const [period, setPeriod] = useState(); + const [committees, setCommittees] = useState(); - try { - const response = await fetch(`/api/periods/${periodId}`); - const data = await response.json(); - if (response.ok) { - setPeriod(data.period); - setCommittees(data.period.committees); - } else { - throw new Error(data.error || "Unknown error"); - } - } catch (error) { - console.error("Error checking period:", error); - } finally { - } - }; + const { data, isError, isLoading } = useQuery({ + queryKey: ['periods', periodId], + queryFn: fetchPeriodById, + }); - fetchPeriod(); - }, [session?.user?.owId, periodId]); + useEffect(() => { + setPeriod(data?.period) + setCommittees(data?.period.committees) + }, [data, session?.user?.owId]); - if (!session || session.user?.role !== "admin") { - return ; - } + if (!session || session.user?.role !== "admin") return ; + if (isLoading) return ; + if (isError) return ; return ( Date: Tue, 23 Jul 2024 18:57:37 +0200 Subject: [PATCH 2/7] added error page component --- components/ErrorPage.tsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 components/ErrorPage.tsx diff --git a/components/ErrorPage.tsx b/components/ErrorPage.tsx new file mode 100644 index 00000000..786cef3a --- /dev/null +++ b/components/ErrorPage.tsx @@ -0,0 +1,23 @@ +import Image from "next/image"; +import { useTheme } from "../lib/hooks/useTheme"; + +const ErrorPage = () => { + const theme = useTheme(); + + const onlineLogoSrc = + theme === "dark" ? "/Online_hvit.svg" : "/Online_bla.svg"; + + return ( +
+ Online logo +
Det har skjedd en feil :(
+
+ ); +}; + +export default ErrorPage; From e0d672bbf67ee301c0a5eff0784f86bd4ba7996e Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 23 Jul 2024 19:14:58 +0200 Subject: [PATCH 3/7] Use tanstack fetching in committee selection page --- pages/admin/[period-id]/index.tsx | 2 +- pages/committee/[period-id]/index.tsx | 64 ++++++++++++--------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/pages/admin/[period-id]/index.tsx b/pages/admin/[period-id]/index.tsx index 7d2e91f1..3d299f3c 100644 --- a/pages/admin/[period-id]/index.tsx +++ b/pages/admin/[period-id]/index.tsx @@ -26,7 +26,7 @@ const Admin = () => { setCommittees(data?.period.committees) }, [data, session?.user?.owId]); - if (!session || session.user?.role !== "admin") return ; + if (session?.user?.role !== "admin") return ; if (isLoading) return ; if (isError) return ; diff --git a/pages/committee/[period-id]/index.tsx b/pages/committee/[period-id]/index.tsx index 73637161..2f5abea9 100644 --- a/pages/committee/[period-id]/index.tsx +++ b/pages/committee/[period-id]/index.tsx @@ -3,51 +3,43 @@ import { useEffect, useState } from "react"; import { useSession } from "next-auth/react"; import LoadingPage from "../../../components/LoadingPage"; import CommitteeCard from "../../../components/committee/CommitteeCard"; +import { useQuery } from "@tanstack/react-query"; +import { fetchPeriodById } from "../../../lib/api/periodApi"; +import ErrorPage from "../../../components/ErrorPage"; +import NotFound from "../../404"; const ChooseCommittee = () => { const { data: session } = useSession(); const router = useRouter(); const periodId = router.query["period-id"]; - const [committees, setCommittees] = useState(null); - const [loading, setLoading] = useState(true); + + const [committees, setCommittees] = useState(); + + const { data, isError, isLoading } = useQuery({ + queryKey: ['periods', periodId], + queryFn: fetchPeriodById, + }); useEffect(() => { - const fetchPeriod = async () => { - if (!session || !periodId) return; - - try { - const res = await fetch(`/api/periods/${periodId}`); - const data = await res.json(); - - if (data.period) { - const userCommittees = session!.user!.committees; - const periodCommittees = data.period.committees; - - if (data.period.optionalCommittees != null) { - periodCommittees.push(...data.period.optionalCommittees); - } - - const filteredCommittees = periodCommittees.filter( - (committee: string) => - userCommittees?.includes(committee.toLowerCase()) - ); - setCommittees(filteredCommittees); - } - } catch (error) { - console.error("Failed to fetch interview periods:", error); - } finally { - setLoading(false); - } - }; - fetchPeriod(); - }, [periodId, session]); - - if (loading) { - return ; - } + if(!data) return; + + const userCommittees = session!.user!.committees; + const periodCommittees = [...data.period?.committees, ...data.period?.optionalCommittees]; + + const matchingCommittees = periodCommittees.filter( + (committee: string) => + userCommittees?.includes(committee.toLowerCase()) + ); + setCommittees(matchingCommittees); + + }, [data, session]) + + if (session?.user?.committees?.length === 0) return ; + if (isLoading) return ; + if (isError) return ; return ( -
+

Velg komite

{committees?.map((committee) => CommitteeCard({ From 647ecfec410e00ed9424ce7edf95c7a387b8f24f Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 23 Jul 2024 19:18:17 +0200 Subject: [PATCH 4/7] revert removal of null type. will remove this later --- components/applicantoverview/ApplicantsOverview.tsx | 4 ++-- pages/admin/[period-id]/index.tsx | 4 ++-- pages/committee/[period-id]/index.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/applicantoverview/ApplicantsOverview.tsx b/components/applicantoverview/ApplicantsOverview.tsx index 5647d3b1..1c40ef39 100644 --- a/components/applicantoverview/ApplicantsOverview.tsx +++ b/components/applicantoverview/ApplicantsOverview.tsx @@ -10,8 +10,8 @@ import ApplicantTable from "./ApplicantTable"; import ApplicantOverviewSkeleton from "./ApplicantOverviewSkeleton"; interface Props { - period?: periodType; - committees?: string[]; + period?: periodType | null; + committees?: string[] | null; committee?: string; includePreferences: boolean; } diff --git a/pages/admin/[period-id]/index.tsx b/pages/admin/[period-id]/index.tsx index 3d299f3c..c06d9923 100644 --- a/pages/admin/[period-id]/index.tsx +++ b/pages/admin/[period-id]/index.tsx @@ -13,8 +13,8 @@ const Admin = () => { const { data: session } = useSession(); const periodId = router.query["period-id"]; - const [period, setPeriod] = useState(); - const [committees, setCommittees] = useState(); + const [period, setPeriod] = useState(null); + const [committees, setCommittees] = useState(null); const { data, isError, isLoading } = useQuery({ queryKey: ['periods', periodId], diff --git a/pages/committee/[period-id]/index.tsx b/pages/committee/[period-id]/index.tsx index 2f5abea9..4f4cf4df 100644 --- a/pages/committee/[period-id]/index.tsx +++ b/pages/committee/[period-id]/index.tsx @@ -13,7 +13,7 @@ const ChooseCommittee = () => { const router = useRouter(); const periodId = router.query["period-id"]; - const [committees, setCommittees] = useState(); + const [committees, setCommittees] = useState(null); const { data, isError, isLoading } = useQuery({ queryKey: ['periods', periodId], From c453217c4b7dd073073038f31aaafd2f13dc1e9c Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 23 Jul 2024 19:32:19 +0200 Subject: [PATCH 5/7] added queryfunction for getting applicant --- lib/api/applicantApi.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 lib/api/applicantApi.ts diff --git a/lib/api/applicantApi.ts b/lib/api/applicantApi.ts new file mode 100644 index 00000000..d15f34f8 --- /dev/null +++ b/lib/api/applicantApi.ts @@ -0,0 +1,9 @@ +import { QueryFunctionContext } from '@tanstack/react-query'; + +export const fetchApplicantByPeriodAndId = async (context: QueryFunctionContext) => { + const periodId = context.queryKey[1]; + const applicantId = context.queryKey[2]; + return fetch(`/api/applicants/${periodId}/${applicantId}`).then(res => + res.json() + ); +} From 3cc169f31e8d14c5dc28cc03b750ace4050d1191 Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 23 Jul 2024 19:32:37 +0200 Subject: [PATCH 6/7] refactored fetching of period and applicant on application page --- pages/application/[period-id].tsx | 98 +++++++++---------------------- 1 file changed, 27 insertions(+), 71 deletions(-) diff --git a/pages/application/[period-id].tsx b/pages/application/[period-id].tsx index 9a469f51..3e60d47e 100644 --- a/pages/application/[period-id].tsx +++ b/pages/application/[period-id].tsx @@ -16,6 +16,10 @@ import ApplicantCard from "../../components/applicantoverview/ApplicantCard"; import LoadingPage from "../../components/LoadingPage"; import { formatDateNorwegian } from "../../lib/utils/dateUtils"; import PageTitle from "../../components/PageTitle"; +import { useQuery } from "@tanstack/react-query"; +import { fetchPeriodById } from "../../lib/api/periodApi"; +import { fetchApplicantByPeriodAndId } from "../../lib/api/applicantApi"; +import ErrorPage from "../../components/ErrorPage"; interface FetchedApplicationData { exists: boolean; @@ -26,14 +30,12 @@ const Application: NextPage = () => { const { data: session } = useSession(); const router = useRouter(); const periodId = router.query["period-id"] as string; + const applicantId = session?.user?.owId; const [hasAlreadySubmitted, setHasAlreadySubmitted] = useState(true); const [periodExists, setPeriodExists] = useState(false); - const [fetchedApplicationData, setFetchedApplicationData] = - useState(null); const [activeTab, setActiveTab] = useState(0); - const [isLoading, setIsLoading] = useState(true); const [applicationData, setApplicationData] = useState< DeepPartial >({ @@ -53,53 +55,31 @@ const Application: NextPage = () => { const [period, setPeriod] = useState(); const [isApplicationPeriodOver, setIsApplicationPeriodOver] = useState(false); + const { data: periodData, isError: periodIsError, isLoading: periodIsLoading } = useQuery({ + queryKey: ['periods', periodId], + queryFn: fetchPeriodById, + }); + + const { data: applicantData, isError: applicantIsError, isLoading: applicantIsLoading } = useQuery({ + queryKey: ['applicants', periodId, applicantId], + queryFn: fetchApplicantByPeriodAndId, + }); + useEffect(() => { - if (!period) { - return; - } + if (!periodData) return; + + setPeriod(periodData.period); + setPeriodExists(periodData.exists); const currentDate = new Date().toISOString(); - if (new Date(period.applicationPeriod.end) < new Date(currentDate)) { + if (new Date(periodData.period.applicationPeriod.end) < new Date(currentDate)) { setIsApplicationPeriodOver(true); } - }, [period]); + }, [periodData]); useEffect(() => { - const checkPeriodAndApplicationStatus = async () => { - if (!periodId || !session?.user?.owId) return; - - try { - const periodResponse = await fetch(`/api/periods/${periodId}`); - const periodData = await periodResponse.json(); - if (periodResponse.ok) { - setPeriod(periodData.period); - setPeriodExists(periodData.exists); - fetchApplicationData(); - } else { - throw new Error(periodData.error || "Unknown error"); - } - } catch (error) { - console.error("Error checking period:", error); - } - - try { - const applicationResponse = await fetch( - `/api/applicants/${periodId}/${session.user.owId}` - ); - const applicationData = await applicationResponse.json(); - - if (!applicationResponse.ok) { - throw new Error(applicationData.error || "Unknown error"); - } - } catch (error) { - console.error("Error checking application status:", error); - } finally { - setIsLoading(false); - } - }; - - checkPeriodAndApplicationStatus(); - }, [session?.user?.owId, periodId]); + setHasAlreadySubmitted(applicantData?.exists); + }, [applicantData]); const handleSubmitApplication = async () => { if (!validateApplication(applicationData)) return; @@ -135,31 +115,6 @@ const Application: NextPage = () => { } else { toast.error("Det skjedde en feil, vennligst prøv igjen"); } - } finally { - fetchApplicationData(); - } - }; - - const fetchApplicationData = async () => { - if (!session?.user?.owId || !periodId) return; - - try { - const response = await fetch( - `/api/applicants/${periodId}/${session.user.owId}` - ); - const data = await response.json(); - if (!data.exists) { - setHasAlreadySubmitted(false); - } else { - setFetchedApplicationData(data); - } - - if (!response.ok) { - throw new Error(data.error || "Unknown error"); - } - } catch (error) { - console.error("Error fetching application data:", error); - toast.error("Failed to fetch application data."); } }; @@ -190,7 +145,8 @@ const Application: NextPage = () => { } }; - if (isLoading) return ; + if (periodIsLoading || applicantIsLoading) return ; + if (periodIsError || applicantIsError) return ; if (!periodExists) { return ( @@ -224,10 +180,10 @@ const Application: NextPage = () => { onClick={handleDeleteApplication} /> )} - {fetchedApplicationData && ( + {applicantData && (
From 04b42ed80866fc709247d840069401542bed1cbb Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 23 Jul 2024 19:36:59 +0200 Subject: [PATCH 7/7] refactored periodfetching in committee/periodId/committee --- .../[period-id]/[committee]/index.tsx | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/pages/committee/[period-id]/[committee]/index.tsx b/pages/committee/[period-id]/[committee]/index.tsx index 60b74059..56b51831 100644 --- a/pages/committee/[period-id]/[committee]/index.tsx +++ b/pages/committee/[period-id]/[committee]/index.tsx @@ -20,6 +20,9 @@ import { changeDisplayName } from "../../../../lib/utils/toString"; import Custom404 from "../../../404"; import PageTitle from "../../../../components/PageTitle"; import Link from "next/link"; +import { useQuery } from "@tanstack/react-query"; +import { fetchPeriodById } from "../../../../lib/api/periodApi"; +import ErrorPage from "../../../../components/ErrorPage"; const CommitteeApplicantOverView: NextPage = () => { const { data: session } = useSession(); @@ -38,21 +41,14 @@ const CommitteeApplicantOverView: NextPage = () => { const [singleCommitteeInPeriod, setSingleCommitteeInPeriod] = useState(true); - useEffect(() => { - if (!session || !periodId) return; - - const fetchPeriod = async () => { - try { - const res = await fetch(`/api/periods/${periodId}`); - const data = await res.json(); - setPeriod(data.period); - } catch (error) { - console.error("Failed to fetch interview periods:", error); - } - }; + const { data: periodData, isError: periodIsError, isLoading: periodIsLoading } = useQuery({ + queryKey: ['periods', periodId], + queryFn: fetchPeriodById, + }); - fetchPeriod(); - }, [periodId]); + useEffect(() => { + setPeriod(periodData?.period); + }, [periodData]); useEffect(() => { const userCommittees = session?.user?.committees?.map(c => c.toLowerCase()) || []; @@ -120,13 +116,9 @@ const CommitteeApplicantOverView: NextPage = () => { checkAccess(); }, [period]); - if (loading) { - return ; - } - - if (!session || !hasAccess) { - return ; - } + if (loading || periodIsLoading) return ; + if (!hasAccess) return ; + if (periodIsError) return ; const interviewPeriodEnd = period?.interviewPeriod.end ? new Date(period.interviewPeriod.end)