From 131e5bb1e7a9ab4fdafe35df5b1f56888672dce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Gonz=C3=A1lez?= Date: Mon, 29 Apr 2024 16:20:05 +0200 Subject: [PATCH] ensures session on first render --- client/src/hooks/profile/index.ts | 40 +++++++------------ client/src/lib/react-query/index.ts | 13 ++++++ client/src/pages/_document.tsx | 7 ---- client/src/pages/analysis/chart.tsx | 10 ++++- client/src/pages/analysis/map.tsx | 11 ++++- client/src/pages/analysis/table.tsx | 12 +++++- client/src/pages/api/auth/[...nextauth].ts | 34 ++++++++++------ client/src/pages/data/index.tsx | 17 ++++++++ .../data/scenarios/[scenarioId]/edit.tsx | 19 ++++++++- .../interventions/[interventionId]/edit.tsx | 17 ++++++++ .../[scenarioId]/interventions/new.tsx | 17 ++++++++ client/src/pages/data/scenarios/index.tsx | 10 ++++- client/src/pages/data/scenarios/new.tsx | 17 ++++++++ client/src/pages/data/targets.tsx | 10 ++++- client/src/pages/eudr/index.tsx | 11 ++++- .../src/pages/eudr/suppliers/[supplierId].tsx | 10 ++++- client/src/pages/profile/index.tsx | 17 ++++++++ client/src/pages/profile/users.tsx | 17 ++++++++ client/src/types.d.ts | 12 ------ client/types/next-auth.d.ts | 22 ++++++++++ 20 files changed, 259 insertions(+), 64 deletions(-) create mode 100644 client/src/lib/react-query/index.ts create mode 100644 client/types/next-auth.d.ts diff --git a/client/src/hooks/profile/index.ts b/client/src/hooks/profile/index.ts index 592e09ddc..662f761cb 100644 --- a/client/src/hooks/profile/index.ts +++ b/client/src/hooks/profile/index.ts @@ -1,38 +1,28 @@ -import { useMemo } from 'react'; import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query'; +import { useSession } from 'next-auth/react'; import { apiRawServiceWithoutAuth, apiService } from 'services/api'; import { authService } from 'services/authentication'; import type { User, PasswordPayload, ErrorResponse, ProfilePayload } from 'types'; -import type { UseQueryResult, UseQueryOptions } from '@tanstack/react-query'; import type { AxiosPromise } from 'axios'; -type ResponseData = UseQueryResult; +export function useProfile() { + const { data: session } = useSession(); -const DEFAULT_QUERY_OPTIONS: UseQueryOptions = { - retry: false, - keepPreviousData: true, - refetchOnWindowFocus: false, -}; - -export function useProfile(): ResponseData { - const fetchProfile = useQuery( - ['profile'], - () => - apiService - .request({ - method: 'GET', - url: '/users/me', - }) - .then((response) => { - if (response.status === 200) return response.data.data; - return response; - }), - DEFAULT_QUERY_OPTIONS, + return useQuery(['profile', session.accessToken], () => + apiService + .request<{ + data: User & { + id: string; + type: 'users'; + }; + }>({ + method: 'GET', + url: '/users/me', + }) + .then(({ data }) => data?.data), ); - - return useMemo((): ResponseData => fetchProfile, [fetchProfile]); } export function useUpdateProfile() { diff --git a/client/src/lib/react-query/index.ts b/client/src/lib/react-query/index.ts new file mode 100644 index 000000000..0d0d43e6c --- /dev/null +++ b/client/src/lib/react-query/index.ts @@ -0,0 +1,13 @@ +import { QueryClient } from '@tanstack/react-query'; + +// todo: when we are able to use RSC, use cache's React +const getQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + }, + }, + }); + +export default getQueryClient; diff --git a/client/src/pages/_document.tsx b/client/src/pages/_document.tsx index ff9ec181e..f453c9d0f 100644 --- a/client/src/pages/_document.tsx +++ b/client/src/pages/_document.tsx @@ -1,13 +1,6 @@ import Document, { Html, Head, Main, NextScript } from 'next/document'; -import type { DocumentContext, DocumentInitialProps } from 'next/document'; - class MyDocument extends Document { - static async getInitialProps(ctx: DocumentContext): Promise { - const initialProps = await Document.getInitialProps(ctx); - return { ...initialProps }; - } - render(): JSX.Element { return ( diff --git a/client/src/pages/analysis/chart.tsx b/client/src/pages/analysis/chart.tsx index 5e42b3801..ab5cccb84 100644 --- a/client/src/pages/analysis/chart.tsx +++ b/client/src/pages/analysis/chart.tsx @@ -1,6 +1,8 @@ import { useMemo } from 'react'; import classNames from 'classnames'; +import { dehydrate } from '@tanstack/react-query'; +import { auth } from '@/pages/api/auth/[...nextauth]'; import { useAppSelector, useAppDispatch } from 'store/hooks'; import { setVisualizationMode } from 'store/features/analysis'; import { analysisFilters } from 'store/features/analysis/filters'; @@ -13,6 +15,7 @@ import AnalysisDynamicMetadata from 'containers/analysis-visualization/analysis- import Loading from 'components/loading'; import TitleTemplate from 'utils/titleTemplate'; import { tasksSSR } from 'services/ssr'; +import getQueryClient from '@/lib/react-query'; import type { ReactElement } from 'react'; import type { NextPageWithLayout } from 'pages/_app'; @@ -87,7 +90,12 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query } }, }; } - return { props: { query } }; + const session = await auth(req, res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { props: { query, session, dehydratedState: dehydrate(queryClient) } }; } catch (error) { if (error.response.status === 401) { return { diff --git a/client/src/pages/analysis/map.tsx b/client/src/pages/analysis/map.tsx index c9894974c..473ccffd0 100644 --- a/client/src/pages/analysis/map.tsx +++ b/client/src/pages/analysis/map.tsx @@ -1,5 +1,7 @@ import { MapProvider } from 'react-map-gl/maplibre'; +import { dehydrate } from '@tanstack/react-query'; +import { auth } from '@/pages/api/auth/[...nextauth]'; import useEffectOnce from 'hooks/once'; import { setVisualizationMode } from 'store/features/analysis'; import { useAppDispatch } from 'store/hooks'; @@ -8,6 +10,7 @@ import AnalysisLayout from 'layouts/analysis'; import AnalysisMap from 'containers/analysis-visualization/analysis-map'; import TitleTemplate from 'utils/titleTemplate'; import { tasksSSR } from 'services/ssr'; +import getQueryClient from '@/lib/react-query'; import type { NextPageWithLayout } from 'pages/_app'; import type { ReactElement } from 'react'; @@ -49,7 +52,13 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query } }, }; } - return { props: { query } }; + + const session = await auth(req, res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { props: { query, session, dehydratedState: dehydrate(queryClient) } }; } catch (error) { if (error.response.status === 401) { return { diff --git a/client/src/pages/analysis/table.tsx b/client/src/pages/analysis/table.tsx index dedece570..568d60cdf 100644 --- a/client/src/pages/analysis/table.tsx +++ b/client/src/pages/analysis/table.tsx @@ -1,3 +1,6 @@ +import { dehydrate } from '@tanstack/react-query'; + +import { auth } from '@/pages/api/auth/[...nextauth]'; import { useAppDispatch } from 'store/hooks'; import useEffectOnce from 'hooks/once'; import { setVisualizationMode } from 'store/features/analysis'; @@ -6,6 +9,7 @@ import AnalysisLayout from 'layouts/analysis'; import AnalysisTable from 'containers/analysis-visualization/analysis-table'; import TitleTemplate from 'utils/titleTemplate'; import { tasksSSR } from 'services/ssr'; +import getQueryClient from '@/lib/react-query'; import type { NextPageWithLayout } from 'pages/_app'; import type { ReactElement } from 'react'; @@ -44,7 +48,13 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query } }, }; } - return { props: { query } }; + + const session = await auth(req, res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { props: { query, session, dehydratedState: dehydrate(queryClient) } }; } catch (error) { if (error.response.status === 401) { return { diff --git a/client/src/pages/api/auth/[...nextauth].ts b/client/src/pages/api/auth/[...nextauth].ts index 2fae5e5b4..6199be292 100644 --- a/client/src/pages/api/auth/[...nextauth].ts +++ b/client/src/pages/api/auth/[...nextauth].ts @@ -1,16 +1,13 @@ -import NextAuth from 'next-auth'; +import NextAuth, { getServerSession } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; +import { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next'; import { authService } from 'services/authentication'; import getUserFullName from 'utils/user-full-name'; +import { User } from '@/types'; import type { NextAuthOptions } from 'next-auth'; -type CustomCredentials = Credential & { - password: string; - username: string; -}; - export const options: NextAuthOptions = { /** * Defining custom pages @@ -38,10 +35,13 @@ export const options: NextAuthOptions = { password: { label: 'Password', type: 'password' }, }, async authorize(credentials) { - const { username, password } = credentials as CustomCredentials; + const { username, password } = credentials; // Request to sign in - const signInRequest = await authService.request({ + const signInRequest = await authService.request<{ + user: User; + accessToken: string; + }>({ url: '/sign-in', method: 'POST', data: { username, password }, @@ -50,7 +50,6 @@ export const options: NextAuthOptions = { const { data, status } = signInRequest; - // Parsing session data if (data && status === 201) { return { ...data.user, @@ -71,8 +70,9 @@ export const options: NextAuthOptions = { const newToken = { ...token }; if (user) { - const { accessToken } = user; - newToken.accessToken = accessToken as string; + const { accessToken, ...rest } = user; + newToken.accessToken = accessToken; + newToken.user = rest; // If it's not expired, return the token, // if (Date.now() / 1000 + TIME_TO_REFRESH_TOKEN < (newToken?.exp as number)) { @@ -98,10 +98,20 @@ export const options: NextAuthOptions = { // Extending session object session({ session, token }) { - session.accessToken = token.accessToken as string; + session.accessToken = token.accessToken; + session.user = token.user; return session; }, }, }; +export function auth( + ...args: + | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res']] + | [NextApiRequest, NextApiResponse] + | [] +) { + return getServerSession(...args, options); +} + export default NextAuth(options); diff --git a/client/src/pages/data/index.tsx b/client/src/pages/data/index.tsx index c6e69d30c..443c52af7 100644 --- a/client/src/pages/data/index.tsx +++ b/client/src/pages/data/index.tsx @@ -1,6 +1,9 @@ import { useMemo } from 'react'; import Head from 'next/head'; +import { GetServerSideProps } from 'next'; +import { dehydrate } from '@tanstack/react-query'; +import { auth } from '@/pages/api/auth/[...nextauth]'; import { useSourcingLocations } from 'hooks/sourcing-locations'; import { useLasTask } from 'hooks/tasks'; import AdminLayout from 'layouts/admin'; @@ -8,6 +11,7 @@ import AdminDataUploader from 'containers/admin/data-uploader'; import AdminDataTable from 'containers/admin/data-table'; import Loading from 'components/loading'; import Search from 'components/search'; +import getQueryClient from '@/lib/react-query'; const AdminDataPage: React.FC = () => { // Getting sourcing locations to check if there are any data @@ -54,4 +58,17 @@ const AdminDataPage: React.FC = () => { ); }; +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const session = await auth(ctx.req, ctx.res); + const queryClient = getQueryClient(); + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { + props: { + session, + dehydratedState: dehydrate(queryClient), + }, + }; +}; + export default AdminDataPage; diff --git a/client/src/pages/data/scenarios/[scenarioId]/edit.tsx b/client/src/pages/data/scenarios/[scenarioId]/edit.tsx index 0b78a6a3f..515da40f4 100644 --- a/client/src/pages/data/scenarios/[scenarioId]/edit.tsx +++ b/client/src/pages/data/scenarios/[scenarioId]/edit.tsx @@ -4,8 +4,9 @@ import toast from 'react-hot-toast'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { useQueryClient } from '@tanstack/react-query'; +import { dehydrate, useQueryClient } from '@tanstack/react-query'; import { PlusIcon, DotsVerticalIcon } from '@heroicons/react/solid'; +import { GetServerSideProps } from 'next'; import InfoTooltip from 'components/info-tooltip'; import { useScenario, useUpdateScenario } from 'hooks/scenarios'; @@ -26,6 +27,8 @@ import Toggle from 'components/toggle'; import Dropdown from 'components/dropdown'; import Badge from 'components/badge'; import { handleResponseError } from 'services/api'; +import { auth } from '@/pages/api/auth/[...nextauth]'; +import getQueryClient from '@/lib/react-query'; import type { ScenarioFormData } from 'containers/scenarios/types'; @@ -238,4 +241,18 @@ const UpdateScenarioPage: React.FC = () => { ); }; +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const session = await auth(ctx.req, ctx.res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { + props: { + session, + dehydratedState: dehydrate(queryClient), + }, + }; +}; + export default UpdateScenarioPage; diff --git a/client/src/pages/data/scenarios/[scenarioId]/interventions/[interventionId]/edit.tsx b/client/src/pages/data/scenarios/[scenarioId]/interventions/[interventionId]/edit.tsx index 1b9d77a3c..e7b9ef18d 100644 --- a/client/src/pages/data/scenarios/[scenarioId]/interventions/[interventionId]/edit.tsx +++ b/client/src/pages/data/scenarios/[scenarioId]/interventions/[interventionId]/edit.tsx @@ -2,6 +2,8 @@ import { useCallback } from 'react'; import Head from 'next/head'; import { useRouter } from 'next/router'; import toast from 'react-hot-toast'; +import { GetServerSideProps } from 'next'; +import { dehydrate } from '@tanstack/react-query'; import { useIntervention, useUpdateIntervention } from 'hooks/interventions'; import { parseInterventionFormDataToDto } from 'containers/interventions/utils'; @@ -10,6 +12,8 @@ import InterventionForm from 'containers/interventions/form'; import BackLink from 'components/back-link/component'; import Loading from 'components/loading'; import { handleResponseError } from 'services/api'; +import { auth } from '@/pages/api/auth/[...nextauth]'; +import getQueryClient from '@/lib/react-query'; import type { InterventionFormData } from 'containers/interventions/types'; @@ -86,4 +90,17 @@ const EditInterventionPage: React.FC = () => { ); }; +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const session = await auth(ctx.req, ctx.res); + const queryClient = getQueryClient(); + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { + props: { + session, + dehydratedState: dehydrate(queryClient), + }, + }; +}; + export default EditInterventionPage; diff --git a/client/src/pages/data/scenarios/[scenarioId]/interventions/new.tsx b/client/src/pages/data/scenarios/[scenarioId]/interventions/new.tsx index 1251c62b4..8e00bedce 100644 --- a/client/src/pages/data/scenarios/[scenarioId]/interventions/new.tsx +++ b/client/src/pages/data/scenarios/[scenarioId]/interventions/new.tsx @@ -3,6 +3,8 @@ import Head from 'next/head'; import router, { useRouter } from 'next/router'; import toast from 'react-hot-toast'; import omitBy from 'lodash-es/omitBy'; +import { GetServerSideProps } from 'next'; +import { dehydrate } from '@tanstack/react-query'; import { useCreateNewIntervention } from 'hooks/interventions'; import CleanLayout from 'layouts/clean'; @@ -10,6 +12,8 @@ import InterventionForm from 'containers/interventions/form'; import { parseInterventionFormDataToDto } from 'containers/interventions/utils'; import BackLink from 'components/back-link'; import { handleResponseError } from 'services/api'; +import { auth } from '@/pages/api/auth/[...nextauth]'; +import getQueryClient from '@/lib/react-query'; import type { InterventionDto, InterventionFormData } from 'containers/interventions/types'; @@ -60,4 +64,17 @@ const CreateInterventionPage: React.FC = () => { ); }; +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const session = await auth(ctx.req, ctx.res); + const queryClient = getQueryClient(); + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { + props: { + session, + dehydratedState: dehydrate(queryClient), + }, + }; +}; + export default CreateInterventionPage; diff --git a/client/src/pages/data/scenarios/index.tsx b/client/src/pages/data/scenarios/index.tsx index dc01fe0c3..84f432c68 100644 --- a/client/src/pages/data/scenarios/index.tsx +++ b/client/src/pages/data/scenarios/index.tsx @@ -5,6 +5,7 @@ import { PlusIcon, SortDescendingIcon } from '@heroicons/react/solid'; import Lottie from 'lottie-react'; import classNames from 'classnames'; import toast from 'react-hot-toast'; +import { dehydrate } from '@tanstack/react-query'; import { tasksSSR } from 'services/ssr'; import newScenarioAnimation from 'containers/scenarios/animations/new-scenario.json'; @@ -22,6 +23,8 @@ import { usePermissions } from 'hooks/permissions'; import { Permission } from 'hooks/permissions/enums'; import ScenarioTable from 'containers/scenarios/table'; import DeleteDialog from 'components/dialogs/delete/component'; +import { auth } from '@/pages/api/auth/[...nextauth]'; +import getQueryClient from '@/lib/react-query'; import type { Option } from 'components/forms/select'; import type { ScenarioCardProps } from 'containers/scenarios/card/types'; @@ -267,7 +270,12 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query } }, }; } - return { props: { query } }; + const session = await auth(req, res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { props: { query, session, dehydratedState: dehydrate(queryClient) } }; } catch (error) { if (error.response.status === 401) { return { diff --git a/client/src/pages/data/scenarios/new.tsx b/client/src/pages/data/scenarios/new.tsx index a50b641ef..10ebb6552 100644 --- a/client/src/pages/data/scenarios/new.tsx +++ b/client/src/pages/data/scenarios/new.tsx @@ -2,12 +2,16 @@ import { useCallback } from 'react'; import toast from 'react-hot-toast'; import Head from 'next/head'; import router from 'next/router'; +import { GetServerSideProps } from 'next'; +import { dehydrate } from '@tanstack/react-query'; import { useCreateScenario } from 'hooks/scenarios'; import CleanLayout from 'layouts/clean'; import BackLink from 'components/back-link'; import ScenarioForm from 'containers/scenarios/form'; import { handleResponseError } from 'services/api'; +import { auth } from '@/pages/api/auth/[...nextauth]'; +import getQueryClient from '@/lib/react-query'; import type { ScenarioFormData } from 'containers/scenarios/types'; @@ -53,4 +57,17 @@ const CreateScenarioPage: React.FC = () => { ); }; +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const session = await auth(ctx.req, ctx.res); + const queryClient = getQueryClient(); + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { + props: { + session, + dehydratedState: dehydrate(queryClient), + }, + }; +}; + export default CreateScenarioPage; diff --git a/client/src/pages/data/targets.tsx b/client/src/pages/data/targets.tsx index 4c1cea092..8c8f91afd 100644 --- a/client/src/pages/data/targets.tsx +++ b/client/src/pages/data/targets.tsx @@ -2,7 +2,9 @@ import Head from 'next/head'; import { useMemo } from 'react'; import { InformationCircleIcon } from '@heroicons/react/outline'; import { PlusIcon } from '@heroicons/react/solid'; +import { dehydrate } from '@tanstack/react-query'; +import { auth } from '@/pages/api/auth/[...nextauth]'; import { tasksSSR } from 'services/ssr'; import Button from 'components/button'; import Radio from 'components/forms/radio'; @@ -12,6 +14,7 @@ import TargetsList from 'containers/targets/list'; import { useIndicators } from 'hooks/indicators'; import { useTargets } from 'hooks/targets'; import AdminLayout from 'layouts/admin'; +import getQueryClient from '@/lib/react-query'; import type { Target } from 'types'; import type { GetServerSideProps } from 'next'; @@ -106,7 +109,12 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query } }, }; } - return { props: { query } }; + const session = await auth(req, res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { props: { query, session, dehydratedState: dehydrate(queryClient) } }; } catch (error) { if (error.response.status === 401) { return { diff --git a/client/src/pages/eudr/index.tsx b/client/src/pages/eudr/index.tsx index e67c40206..4258e2c61 100644 --- a/client/src/pages/eudr/index.tsx +++ b/client/src/pages/eudr/index.tsx @@ -2,7 +2,9 @@ import { useRef, useState } from 'react'; import { Transition } from '@headlessui/react'; import { MapProvider } from 'react-map-gl/maplibre'; import dynamic from 'next/dynamic'; +import { dehydrate } from '@tanstack/react-query'; +import { auth } from '@/pages/api/auth/[...nextauth]'; import { tasksSSR } from 'services/ssr'; import ApplicationLayout from 'layouts/application'; import CollapseButton from 'containers/collapse-button/component'; @@ -11,6 +13,7 @@ import { useAppSelector } from '@/store/hooks'; import SuppliersStackedBar from '@/containers/analysis-eudr/suppliers-stacked-bar'; import EUDRFilters from '@/containers/analysis-eudr/filters/component'; import SupplierListTable from '@/containers/analysis-eudr/supplier-list-table'; +import getQueryClient from '@/lib/react-query'; import type { NextPageWithLayout } from 'pages/_app'; import type { ReactElement } from 'react'; @@ -86,7 +89,13 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query } }, }; } - return { props: { query } }; + + const session = await auth(req, res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { props: { query, session, dehydratedState: dehydrate(queryClient) } }; } catch (error) { if (error.code === '401' || error.response.status === 401) { return { diff --git a/client/src/pages/eudr/suppliers/[supplierId].tsx b/client/src/pages/eudr/suppliers/[supplierId].tsx index a74697c72..6897930cc 100644 --- a/client/src/pages/eudr/suppliers/[supplierId].tsx +++ b/client/src/pages/eudr/suppliers/[supplierId].tsx @@ -5,6 +5,7 @@ import Link from 'next/link'; import { ArrowLeft } from 'lucide-react'; import { useParams } from 'next/navigation'; import dynamic from 'next/dynamic'; +import { dehydrate } from '@tanstack/react-query'; import { tasksSSR } from 'services/ssr'; import ApplicationLayout from 'layouts/application'; @@ -19,6 +20,8 @@ import { Separator } from '@/components/ui/separator'; import DeforestationAlerts from '@/containers/analysis-eudr-detail/deforestation-alerts'; import { eudrDetail } from '@/store/features/eudr-detail'; import { useAppSelector } from '@/store/hooks'; +import { auth } from '@/pages/api/auth/[...nextauth]'; +import getQueryClient from '@/lib/react-query'; import type { NextPageWithLayout } from 'pages/_app'; import type { ReactElement } from 'react'; @@ -119,7 +122,12 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query } }, }; } - return { props: { query } }; + const session = await auth(req, res); + const queryClient = getQueryClient(); + + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { props: { query, session, dehydratedState: dehydrate(queryClient) } }; } catch (error) { if (error.code === '401' || error.response.status === 401) { return { diff --git a/client/src/pages/profile/index.tsx b/client/src/pages/profile/index.tsx index 4f02c2c6b..81c3aee52 100644 --- a/client/src/pages/profile/index.tsx +++ b/client/src/pages/profile/index.tsx @@ -1,8 +1,12 @@ import Head from 'next/head'; +import { GetServerSideProps } from 'next'; +import { dehydrate } from '@tanstack/react-query'; +import { auth } from '@/pages/api/auth/[...nextauth]'; import ProfileLayout from 'layouts/profile'; import UpdateProfileForm from 'containers/update-profile-form'; import UpdatePasswordForm from 'containers/update-password-form'; +import getQueryClient from '@/lib/react-query'; const UserProfile: React.FC = () => ( @@ -23,4 +27,17 @@ const UserProfile: React.FC = () => ( ); +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const session = await auth(ctx.req, ctx.res); + const queryClient = getQueryClient(); + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { + props: { + session, + dehydratedState: dehydrate(queryClient), + }, + }; +}; + export default UserProfile; diff --git a/client/src/pages/profile/users.tsx b/client/src/pages/profile/users.tsx index 9eaccd594..7cf70cd8c 100644 --- a/client/src/pages/profile/users.tsx +++ b/client/src/pages/profile/users.tsx @@ -2,7 +2,10 @@ import { useMemo, useState } from 'react'; import Head from 'next/head'; import { PlusIcon } from '@heroicons/react/solid'; import { useRouter } from 'next/router'; +import { GetServerSideProps } from 'next'; +import { dehydrate } from '@tanstack/react-query'; +import { auth } from '@/pages/api/auth/[...nextauth]'; import { useUsers } from 'hooks/users'; import ProfileLayout from 'layouts/profile'; import Button from 'components/button'; @@ -17,6 +20,7 @@ import { usePermissions } from 'hooks/permissions'; import { RoleName } from 'hooks/permissions/enums'; import Modal from 'components/modal'; import UserForm from 'containers/edit-user/user-form'; +import getQueryClient from '@/lib/react-query'; import type { User } from 'types'; import type { TableProps } from 'components/table/component'; @@ -177,4 +181,17 @@ const AdminUsersPage: React.FC = () => { ); }; +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const session = await auth(ctx.req, ctx.res); + const queryClient = getQueryClient(); + queryClient.setQueryData(['profile', session.accessToken], session.user); + + return { + props: { + session, + dehydratedState: dehydrate(queryClient), + }, + }; +}; + export default AdminUsersPage; diff --git a/client/src/types.d.ts b/client/src/types.d.ts index eb06176e4..e0a5a31ba 100644 --- a/client/src/types.d.ts +++ b/client/src/types.d.ts @@ -1,7 +1,6 @@ import type { AxiosError } from 'axios'; import type { Permission, RoleName } from 'hooks/permissions/enums'; import type { Scenario } from 'containers/scenarios/types'; -import type { Session as NextAuthSession, User as NextAuthUser } from 'next-auth'; export type RGBColor = [number, number, number]; @@ -392,14 +391,3 @@ export type UserPayload = { totalItems: number; }; }; - -// Extend next-auth Session and User to include accessToken -declare module 'next-auth' { - export interface Session extends NextAuthSession { - accessToken: string; - } - - export interface User extends NextAuthUser { - accessToken: string; - } -} diff --git a/client/types/next-auth.d.ts b/client/types/next-auth.d.ts new file mode 100644 index 000000000..f9adaa9b2 --- /dev/null +++ b/client/types/next-auth.d.ts @@ -0,0 +1,22 @@ +import type { User as APIUser } from 'types'; + +// ? Extend next-auth Session and User to include accessToken +declare module 'next-auth' { + interface Session { + accessToken: string; + user?: APIUser; + } + + interface User extends APIUser { + email: string; + accessToken: string; + } +} + +declare module 'next-auth/jwt' { + // ? Returned by the `jwt` callback and `getToken`, when using JWT sessions + interface JWT { + accessToken?: string; + user?: APIUser; + } +}