Skip to content

Commit

Permalink
ensures session on first render
Browse files Browse the repository at this point in the history
  • Loading branch information
andresgnlez committed Apr 29, 2024
1 parent 4915af7 commit 131e5bb
Show file tree
Hide file tree
Showing 20 changed files with 259 additions and 64 deletions.
40 changes: 15 additions & 25 deletions client/src/hooks/profile/index.ts
Original file line number Diff line number Diff line change
@@ -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<User>;
export function useProfile() {
const { data: session } = useSession();

const DEFAULT_QUERY_OPTIONS: UseQueryOptions<User> = {
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>((): ResponseData => fetchProfile, [fetchProfile]);
}

export function useUpdateProfile() {
Expand Down
13 changes: 13 additions & 0 deletions client/src/lib/react-query/index.ts
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 0 additions & 7 deletions client/src/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -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<DocumentInitialProps> {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}

render(): JSX.Element {
return (
<Html className="h-full bg-gray-100">
Expand Down
10 changes: 9 additions & 1 deletion client/src/pages/analysis/chart.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 10 additions & 1 deletion client/src/pages/analysis/map.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 11 additions & 1 deletion client/src/pages/analysis/table.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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 {
Expand Down
34 changes: 22 additions & 12 deletions client/src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 },
Expand All @@ -50,7 +50,6 @@ export const options: NextAuthOptions = {

const { data, status } = signInRequest;

// Parsing session data
if (data && status === 201) {
return {
...data.user,
Expand All @@ -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)) {
Expand All @@ -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);
17 changes: 17 additions & 0 deletions client/src/pages/data/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
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';
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
Expand Down Expand Up @@ -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;
19 changes: 18 additions & 1 deletion client/src/pages/data/scenarios/[scenarioId]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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;
17 changes: 17 additions & 0 deletions client/src/pages/data/scenarios/[scenarioId]/interventions/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ 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';
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';

Expand Down Expand Up @@ -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;
Loading

0 comments on commit 131e5bb

Please sign in to comment.