diff --git a/src/hooks/api/authenticated-query-v2.ts b/src/hooks/api/authenticated-query-v2.ts index 2c2a136e7..b039427cf 100644 --- a/src/hooks/api/authenticated-query-v2.ts +++ b/src/hooks/api/authenticated-query-v2.ts @@ -1,5 +1,5 @@ -import { NextApiRequest } from 'next'; import { useRouter } from 'next/router'; +import { GetServerSidePropsContext } from 'next/types'; import { Cookies } from 'react-cookie'; import { FetchQueryOptions, @@ -14,7 +14,6 @@ import { FetchError } from '@/utils/fetchFromApi'; import { isTokenValid } from '@/utils/isTokenValid'; import { useCookiesWithOptions } from '../useCookiesWithOptions'; -import { Headers } from './types/Headers'; import { createHeaders, useHeaders } from './useHeaders'; type QueryArguments = Record; @@ -33,7 +32,7 @@ type AuthenticatedQueryFunctionContext = }; type ServerSideOptions = { - req: NextApiRequest; + req: GetServerSidePropsContext['req']; queryClient: QueryClient; }; @@ -72,7 +71,7 @@ const generateQueryKey = ({ type GetPreparedOptionsArguments = { options: UseAuthenticatedQueryOptions; isTokenPresent: boolean; - headers: Headers; + headers: HeadersInit; }; const getPreparedOptions = ({ @@ -116,14 +115,7 @@ const prefetchAuthenticatedQuery = async ({ headers, }); - try { - await queryClient.prefetchQuery( - queryKey, - queryFn, - ); - } catch {} - - return await queryClient.getQueryData(queryKey); + return queryClient.fetchQuery(queryKey, queryFn); }; const useAuthenticatedQuery = ( diff --git a/src/hooks/api/authenticated-query.ts b/src/hooks/api/authenticated-query.ts index 66aeb6791..f13851465 100644 --- a/src/hooks/api/authenticated-query.ts +++ b/src/hooks/api/authenticated-query.ts @@ -1,16 +1,18 @@ import { isEqual } from 'lodash'; import flatten from 'lodash/flatten'; -import type { NextApiRequest } from 'next'; import { useRouter } from 'next/router'; +import { GetServerSidePropsContext } from 'next/types'; import { useCallback } from 'react'; import { Cookies } from 'react-cookie'; import { MutationFunction, QueryClient, + useMutation, + useQueries, + useQuery, useQueryClient, UseQueryResult, } from 'react-query'; -import { useMutation, useQueries, useQuery } from 'react-query'; import { useCookiesWithOptions } from '@/hooks/useCookiesWithOptions'; import type { CalendarSummaryFormat } from '@/utils/createEmbededCalendarSummaries'; @@ -21,7 +23,7 @@ import { isTokenValid } from '@/utils/isTokenValid'; import { createHeaders, useHeaders } from './useHeaders'; type ServerSideQueryOptions = { - req?: NextApiRequest; + req?: GetServerSidePropsContext['req']; queryClient?: QueryClient; }; diff --git a/src/hooks/api/useHeaders.js b/src/hooks/api/useHeaders.ts similarity index 83% rename from src/hooks/api/useHeaders.js rename to src/hooks/api/useHeaders.ts index f5a18ab0c..a268b5926 100644 --- a/src/hooks/api/useHeaders.js +++ b/src/hooks/api/useHeaders.ts @@ -2,7 +2,10 @@ import getConfig from 'next/config'; import { useCookiesWithOptions } from '../useCookiesWithOptions'; -const createHeaders = (token, extraHeaders) => { +const createHeaders = ( + token: string, + extraHeaders: HeadersInit = {}, +): HeadersInit => { const { publicRuntimeConfig } = getConfig(); return { diff --git a/src/hooks/api/user.ts b/src/hooks/api/user.ts index f94a109b3..f8b7f968c 100644 --- a/src/hooks/api/user.ts +++ b/src/hooks/api/user.ts @@ -1,13 +1,13 @@ import jwt_decode from 'jwt-decode'; -import getConfig from 'next/config'; import { FetchError, fetchFromApi, isErrorObject } from '@/utils/fetchFromApi'; import { Cookies, useCookiesWithOptions } from '../useCookiesWithOptions'; +import { ServerSideQueryOptions } from './authenticated-query'; import { - ServerSideQueryOptions, - useAuthenticatedQuery, -} from './authenticated-query'; + prefetchAuthenticatedQuery, + useAuthenticatedQuery as useAuthenticatedQueryV2, +} from './authenticated-query-v2'; type User = { sub: string; @@ -45,7 +45,7 @@ const getUser = async (cookies: Cookies) => { throw new FetchError(401, 'Unauthorized'); } - const userInfo = jwt_decode(cookies.idToken) as User; + const userInfo = jwt_decode(cookies.idToken); const decodedAccessToken = jwt_decode(cookies.token) as decodedAccessToken; if (Date.now() >= decodedAccessToken.exp * 1000) { @@ -58,7 +58,7 @@ const getUser = async (cookies: Cookies) => { const useGetUserQuery = () => { const { cookies } = useCookiesWithOptions(['idToken']); - return useAuthenticatedQuery({ + return useAuthenticatedQueryV2({ queryKey: ['user'], queryFn: () => getUser(cookies), }); @@ -70,7 +70,7 @@ const useGetUserQueryServerSide = ( ) => { const cookies = req.cookies; - return useAuthenticatedQuery({ + return prefetchAuthenticatedQuery({ req, queryClient, queryKey: ['user'], @@ -84,16 +84,18 @@ const getPermissions = async ({ headers }) => { path: '/user/permissions/', options: { headers }, }); + if (isErrorObject(res)) { // eslint-disable-next-line no-console console.error(res); return; } + return (await res.json()) as string[]; }; const useGetPermissionsQuery = (configuration = {}) => - useAuthenticatedQuery({ + useAuthenticatedQueryV2({ queryKey: ['user', 'permissions'], queryFn: getPermissions, ...configuration, @@ -117,7 +119,7 @@ const getRoles = async ({ headers }) => { }; const useGetRolesQuery = (configuration = {}) => - useAuthenticatedQuery({ + useAuthenticatedQueryV2({ queryKey: ['user', 'roles'], queryFn: getRoles, ...configuration, diff --git a/src/hooks/useFeatureFlag.ts b/src/hooks/useFeatureFlag.ts index e6721346e..55a1f0e5a 100644 --- a/src/hooks/useFeatureFlag.ts +++ b/src/hooks/useFeatureFlag.ts @@ -80,3 +80,5 @@ export { isFeatureFlagEnabledInCookies, useFeatureFlag, }; + +export type { FeatureFlagName }; diff --git a/src/layouts/Sidebar.tsx b/src/layouts/Sidebar.tsx index be9861aaf..02aeef95e 100644 --- a/src/layouts/Sidebar.tsx +++ b/src/layouts/Sidebar.tsx @@ -187,8 +187,7 @@ const ProfileMenu = ({ defaultProfileImageUrl }: ProfileMenuProps) => { const queryClient = useQueryClient(); const getUserQuery = useGetUserQuery(); - // @ts-expect-error - const user = getUserQuery.data as User; + const user = getUserQuery.data; const loginMenu = [ { @@ -407,19 +406,16 @@ const Sidebar = () => { }, [rawAnnouncements]); useEffect(() => { - // @ts-expect-error if (!getRolesQuery.data) { return; } - // @ts-expect-error const validationQuery = getRolesQuery.data .map((role) => (role.constraints?.v3 ? role.constraints.v3 : null)) .filter((constraint) => constraint !== null) .join(' OR '); setSearchQuery(validationQuery); - // @ts-expect-error }, [getRolesQuery.data]); const announcements = useMemo( @@ -465,7 +461,6 @@ const Sidebar = () => { ]; const filteredManageMenu = useMemo(() => { - // @ts-expect-error if (!getPermissionsQuery.data) { return []; } @@ -520,125 +515,125 @@ const Sidebar = () => { ]; return manageMenu.filter((menuItem) => { - // @ts-expect-error return getPermissionsQuery.data.includes(menuItem.permission); }); - // @ts-expect-error }, [countEventsToModerate, getPermissionsQuery.data, i18n.language, t]); - return [ - { - if (!sidebarComponent?.current) return; - if (document.activeElement?.tagName?.toLowerCase() !== 'iframe') { - return; - } - // @ts-expect-error - document.activeElement.blur(); - }} - > - - - + return ( + <> :not(:first-child) { - border-top: 1px solid ${getValueForMenu('borderColor')}; - } + overflow: hidden; `} + width={{ default: '230px', s: '65px' }} + backgroundColor={getValueForSidebar('backgroundColor')} + color={getValueForSidebar('color')} + zIndex={getValueForSidebar('zIndex')} + padding={{ default: 2, s: 1 }} + spacing={3} + ref={sidebarComponent} + onMouseOver={() => { + if (!sidebarComponent?.current) return; + if (document.activeElement?.tagName?.toLowerCase() !== 'iframe') { + return; + } + // @ts-expect-error + document.activeElement.blur(); + }} > - + + + 0 ? 'space-between' : 'flex-end' - } + paddingTop={4} + spacing={4} flex={1} + css={` + > :not(:first-child) { + border-top: 1px solid ${getValueForMenu('borderColor')}; + } + `} > - {filteredManageMenu.length > 0 && ( - - )} - - + + 0 ? 'space-between' : 'flex-end' + } + flex={1} + > + {filteredManageMenu.length > 0 && ( + + )} + - - { - setIsNewCreateEnabled((prev) => !prev); - }} - /> - - {!isSmallView && ( - - - - )} - - - + + { + setIsNewCreateEnabled((prev) => !prev); + }} + /> + + {!isSmallView && ( + + + + )} + + + + - , - setIsJobLoggerVisible((oldState) => !oldState)} - onStatusChange={setJobLoggerState} - />, - , - ]; + setIsJobLoggerVisible((oldState) => !oldState)} + onStatusChange={setJobLoggerState} + /> + + + ); }; export { Sidebar }; diff --git a/src/layouts/index.js b/src/layouts/index.tsx similarity index 96% rename from src/layouts/index.js rename to src/layouts/index.tsx index 785b7fb50..18a08336b 100644 --- a/src/layouts/index.js +++ b/src/layouts/index.tsx @@ -33,10 +33,10 @@ const useHandleAuthentication = () => { const getUserQuery = useGetUserQuery(); useEffect(() => { - if (!getUserQuery.data) return; - Sentry.setUser({ id: getUserQuery.data.id }); + if (!getUserQuery?.data) return; + Sentry.setUser({ id: getUserQuery.data.sub }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getUserQuery.data]); + }, [getUserQuery?.data]); // redirect when there is no token or user cookie // manipulation from outside the application diff --git a/src/pages/dashboard/index.page.tsx b/src/pages/dashboard/index.page.tsx index bd20003df..0897e3d96 100644 --- a/src/pages/dashboard/index.page.tsx +++ b/src/pages/dashboard/index.page.tsx @@ -22,11 +22,7 @@ import { useDeletePlaceByIdMutation, useGetPlacesByCreatorQuery, } from '@/hooks/api/places'; -import { - useGetUserQuery, - useGetUserQueryServerSide, - User, -} from '@/hooks/api/user'; +import { useGetUserQuery, useGetUserQueryServerSide } from '@/hooks/api/user'; import { FeatureFlags, useFeatureFlag } from '@/hooks/useFeatureFlag'; import { Footer } from '@/pages/Footer'; import type { Event } from '@/types/Event'; @@ -241,9 +237,7 @@ const OfferRow = ({ item: offer, onDelete, ...props }: OfferRowProps) => { const { t, i18n } = useTranslation(); const getUserQuery = useGetUserQuery(); - // @ts-expect-error const userId = getUserQuery.data?.sub; - // @ts-expect-error const userIdv1 = getUserQuery.data?.['https://publiq.be/uitidv1id']; const isExternalCreator = ![userId, userIdv1].includes(offer.creator); @@ -361,9 +355,7 @@ const OrganizerRow = ({ const { t, i18n } = useTranslation(); const getUserQuery = useGetUserQuery(); - // @ts-expect-error const userId = getUserQuery.data?.sub; - // @ts-expect-error const userIdv1 = getUserQuery.data?.['https://publiq.be/uitidv1id']; const isExternalCreator = ![userId, userIdv1].includes(organizer.creator); @@ -550,8 +542,7 @@ const Dashboard = (): any => { ); const getUserQuery = useGetUserQuery(); - // @ts-expect-error - const user = getUserQuery.data as User; + const user = getUserQuery.data; const handleSelectSorting = (event) => { const sortValue = event.target.value; @@ -730,18 +721,21 @@ const Dashboard = (): any => { const getServerSideProps = getApplicationServerSideProps( async ({ req, query, cookies: rawCookies, queryClient }) => { - const user = (await useGetUserQueryServerSide({ + const user = await useGetUserQueryServerSide({ req, queryClient, - })) as User; + }); await Promise.all( Object.entries(UseGetItemsByCreatorMap).map(([key, hook]) => { const page = - query.tab === key ? (query.page ? parseInt(query.page) : 1) : 1; + query.tab === key && query.page + ? parseInt((query.page as string) ?? '1') + : 1; - const sortingField = query?.sort?.split('_')[0] ?? SortingField.CREATED; - const sortingOrder = query?.sort?.split('_')[1] ?? SortingOrder.DESC; + const sort = query?.sort as string | undefined; + const sortingField = sort?.split('_')[0] ?? SortingField.CREATED; + const sortingOrder = sort?.split('_')[1] ?? SortingOrder.DESC; return hook({ req, diff --git a/src/pages/events/[eventId]/duplicate/index.page.tsx b/src/pages/events/[eventId]/duplicate/index.page.tsx index 4b57e5d2e..27924e3e1 100644 --- a/src/pages/events/[eventId]/duplicate/index.page.tsx +++ b/src/pages/events/[eventId]/duplicate/index.page.tsx @@ -10,7 +10,7 @@ export const getServerSideProps = getApplicationServerSideProps( const { eventId } = query; await useGetEventByIdQuery({ - id: eventId, + id: eventId as string, req, queryClient, }); diff --git a/src/pages/events/[eventId]/edit/index.page.tsx b/src/pages/events/[eventId]/edit/index.page.tsx index fbd82928f..34df25bec 100644 --- a/src/pages/events/[eventId]/edit/index.page.tsx +++ b/src/pages/events/[eventId]/edit/index.page.tsx @@ -10,11 +10,11 @@ export const getServerSideProps = getApplicationServerSideProps( async ({ req, query, queryClient, cookies }) => { const { eventId } = query; - const event = (await useGetEventByIdQuery({ - id: eventId, + await useGetEventByIdQuery({ + id: eventId as string, req, queryClient, - })) as Event; + }); return { props: { diff --git a/src/pages/manage/movies/[eventId]/edit.page.tsx b/src/pages/manage/movies/[eventId]/edit.page.tsx index fe1276cba..e6a8054d7 100644 --- a/src/pages/manage/movies/[eventId]/edit.page.tsx +++ b/src/pages/manage/movies/[eventId]/edit.page.tsx @@ -12,7 +12,7 @@ export const getServerSideProps = getApplicationServerSideProps( await useGetEventByIdQuery({ req, queryClient, - id: eventId, + id: eventId as string, }); return { diff --git a/src/pages/steps/AdditionalInformationStep/OrganizerPicker.tsx b/src/pages/steps/AdditionalInformationStep/OrganizerPicker.tsx index 1baed1b33..04ed50993 100644 --- a/src/pages/steps/AdditionalInformationStep/OrganizerPicker.tsx +++ b/src/pages/steps/AdditionalInformationStep/OrganizerPicker.tsx @@ -136,7 +136,6 @@ const OrganizerPicker = ({ const queryClient = useQueryClient(); const getUserQuery = useGetUserQuery(); - // @ts-expect-error const user = getUserQuery.data; const [addButtonHasBeenPressed, setAddButtonHasBeenPressed] = useState(false); diff --git a/src/pages/steps/LocationStep.tsx b/src/pages/steps/LocationStep.tsx index 541897a91..3680945fb 100644 --- a/src/pages/steps/LocationStep.tsx +++ b/src/pages/steps/LocationStep.tsx @@ -524,7 +524,6 @@ const RecentLocations = ({ onFieldChange, ...props }) => { const getOffersQuery = useGetOffersByCreatorQuery( { advancedQuery: '_exists_:location.id AND NOT (audienceType:"education")', - // @ts-expect-error creator: getUserQuery?.data, sortOptions: { field: 'modified', diff --git a/src/redirects.tsx b/src/redirects.tsx index e6afc9d01..b96a12a5a 100644 --- a/src/redirects.tsx +++ b/src/redirects.tsx @@ -1,4 +1,4 @@ -import { FeatureFlags } from './hooks/useFeatureFlag'; +import { FeatureFlagName, FeatureFlags } from './hooks/useFeatureFlag'; import type { SupportedLanguages } from './i18n'; import type { Values } from './types/Values'; @@ -42,7 +42,12 @@ const createDashboardRedirects = (environment: Environment) => { const getRedirects = ( environment: Environment, language: Values = 'nl', -) => [ +): { + featureFlag?: FeatureFlagName; + permanent: boolean; + destination: string; + source: string; +}[] => [ // Only make the permanent redirects really permanent in environments other // than development, so we don't get permanent redirects on localhost which // may conflict with other projects. diff --git a/src/utils/getApplicationServerSideProps.js b/src/utils/getApplicationServerSideProps.ts similarity index 75% rename from src/utils/getApplicationServerSideProps.js rename to src/utils/getApplicationServerSideProps.ts index 26e1be804..3de3b2cea 100644 --- a/src/utils/getApplicationServerSideProps.js +++ b/src/utils/getApplicationServerSideProps.ts @@ -1,17 +1,16 @@ import * as Sentry from '@sentry/nextjs'; import getConfig from 'next/config'; +import { GetServerSidePropsContext } from 'next/types'; import absoluteUrl from 'next-absolute-url'; import { QueryClient } from 'react-query'; import { generatePath, matchPath } from 'react-router'; import UniversalCookies from 'universal-cookie'; -import { useGetUserQuery, useGetUserQueryServerSide } from '@/hooks/api/user'; -import { defaultCookieOptions } from '@/hooks/useCookiesWithOptions'; +import { useGetUserQueryServerSide } from '@/hooks/api/user'; import { isFeatureFlagEnabledInCookies } from '@/hooks/useFeatureFlag'; import { getRedirects } from '../redirects'; import { FetchError } from './fetchFromApi'; -import { isTokenValid } from './isTokenValid'; class Cookies extends UniversalCookies { toString() { @@ -19,7 +18,7 @@ class Cookies extends UniversalCookies { return cookieEntries.reduce((previous, [key, value], currentIndex) => { const end = currentIndex !== cookieEntries.length - 1 ? '; ' : ''; - const pair = `${key}=${encodeURIComponent(value)}${end}`; + const pair = `${key}=${encodeURIComponent(value as string)}${end}`; return `${previous}${pair}`; }, ''); } @@ -61,7 +60,13 @@ const getRedirect = (originalPath, environment, cookies) => { .find((match) => !!match); }; -const redirectToLogin = (cookies, req, resolvedUrl) => { +const redirectToLogin = ({ + cookies, + req, + resolvedUrl, +}: Pick & { + cookies: Cookies; +}) => { Sentry.setUser(null); cookies.remove('token'); @@ -76,22 +81,31 @@ const redirectToLogin = (cookies, req, resolvedUrl) => { }; }; +type ExtendedGetServerSidePropsContext = GetServerSidePropsContext & { + cookies: string; + queryClient: QueryClient; +}; + const getApplicationServerSideProps = - (callbackFn) => - async ({ req, query, resolvedUrl }) => { + (callbackFn?: (context: ExtendedGetServerSidePropsContext) => any) => + async ({ + req, + query, + resolvedUrl, + ...context + }: GetServerSidePropsContext) => { const { publicRuntimeConfig } = getConfig(); if (publicRuntimeConfig.environment === 'development') { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } - const rawCookies = req?.headers?.cookie ?? ''; - - const cookies = new Cookies(rawCookies, defaultCookieOptions); + const cookies = new Cookies(req.cookies); req.headers.cookie = cookies.toString(); - const isDynamicUrl = !!query.params; - const path = isDynamicUrl ? `/${query.params.join('/')}` : resolvedUrl; + const params = (query.params as string[]) ?? []; + const isDynamicUrl = Object.keys(params).length > 0; + const path = isDynamicUrl ? `/${params.join('/')}` : resolvedUrl; const redirect = getRedirect( path, @@ -102,7 +116,9 @@ const getApplicationServerSideProps = if (redirect) { // Don't include the `params` in the redirect URL's query. delete query.params; - const queryParameters = new URLSearchParams(query); + const queryParameters = new URLSearchParams( + query as Record, + ); // Return the redirect as-is if there are no additional query parameters // to append. @@ -124,7 +140,11 @@ const getApplicationServerSideProps = await useGetUserQueryServerSide({ req, queryClient }); } catch (error) { if (error instanceof FetchError) { - return redirectToLogin(cookies, req, resolvedUrl); + return redirectToLogin({ + cookies, + req, + resolvedUrl, + }); } } @@ -133,6 +153,8 @@ const getApplicationServerSideProps = if (!callbackFn) return { props: { cookies: cookies.toString() } }; return await callbackFn({ + ...context, + resolvedUrl, req, query, queryClient,