From b10aca6abe0461723a7dc9e448a0fccf1ade53d4 Mon Sep 17 00:00:00 2001 From: angarita-dev <44899916+angarita-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:48:14 -0500 Subject: [PATCH 01/11] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20Updated=20resp?= =?UTF-8?q?onsibilities=20of=20AuthProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAuthProviders.ts | 68 -------- src/providers/AuthProvider.tsx | 275 +++++++++++++++++++----------- src/providers/DynamicProvider.tsx | 73 +++++--- 3 files changed, 225 insertions(+), 191 deletions(-) delete mode 100644 src/hooks/useAuthProviders.ts diff --git a/src/hooks/useAuthProviders.ts b/src/hooks/useAuthProviders.ts deleted file mode 100644 index a23c240e..00000000 --- a/src/hooks/useAuthProviders.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useDynamicContext } from '@dynamic-labs/sdk-react-core'; - -import { useLoginWithDynamicMutation } from '@/generated/graphqlClient'; -import { useCookies } from '@/providers/CookiesProvider'; -import { secrets } from '@/secrets'; - -export type AuthProviders = 'dynamic'; - -export type AuthWith = { - handleLogin: () => void; - handleLogout: () => void; - requestAccessToken: (projectId?: string) => Promise; - token: string | undefined; -}; - -export const useAuthProviders = (): Record => { - const dynamic = useAuthWithDynamic(); - - return { - dynamic, - ...(secrets.TEST_MODE ? { mocked: getMockedProvider() } : {}), - }; -}; - -const useAuthWithDynamic = (): AuthWith => { - const dynamic = useDynamicContext(); - - const [, loginWithDynamic] = useLoginWithDynamicMutation(); - - const handleLogin = () => dynamic.setShowAuthFlow(true); - - const handleLogout = () => dynamic.handleLogOut(); - - const requestAccessToken = async (projectId?: string): Promise => { - if (!dynamic.authToken) { - return ''; - } - - const { data, error } = await loginWithDynamic({ - data: { authToken: dynamic.authToken, projectId }, - }); - - if (data && data.loginWithDynamic) { - return data.loginWithDynamic; - } - - throw error; - }; - - return { - handleLogin, - handleLogout, - requestAccessToken, - token: dynamic.authToken, - }; -}; - -const getMockedProvider: () => AuthWith = () => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const cookies = useCookies(); - - return { - handleLogin: () => {}, - handleLogout: () => {}, - requestAccessToken: async () => 'mocked-token', - token: cookies.values.authProviderToken, - }; -}; diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index fde2568a..8d57a4e5 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -1,28 +1,36 @@ +import { useDynamicContext } from '@dynamic-labs/sdk-react-core'; import { routes } from '@fleek-platform/utils-routes'; -import { useCallback, useEffect, useState } from 'react'; +import { decodeJwt } from 'jose'; +import React, { useCallback, useMemo, useState } from 'react'; import { constants } from '@/constants'; -import { useAuthCookie } from '@/hooks/useAuthCookie'; import { - AuthProviders, - AuthWith, - useAuthProviders, -} from '@/hooks/useAuthProviders'; + LoginWithDynamicDocument, + LoginWithDynamicMutation, + LoginWithDynamicMutationVariables, +} from '@/generated/graphqlClient'; +import { useAuthCookie } from '@/hooks/useAuthCookie'; import { usePostHog } from '@/hooks/usePostHog'; import { useRouter } from '@/hooks/useRouter'; import { createContext } from '@/utils/createContext'; import { useCookies } from './CookiesProvider'; +import { DynamicProvider } from './DynamicProvider'; +import { ChildrenProps } from '@/types/Props'; +import { Log } from '@/utils/log'; +import { GraphqlApiClient } from '@/integrations/graphql/GraphqlApi'; +//import gql from 'graphql-tag'; export type AuthContext = { - loading: boolean; + isLoading: boolean; error?: unknown; token?: string; + tokenProjectId?: string; redirectUrl: string | null; - login: (provider: AuthProviders, redirectUrl?: string) => void; + login: (redirectUrl?: string) => void; logout: () => void; - switchProjectAuth: (projectId: string) => Promise; + switchProjectAuth: (projectId: string, silent?: boolean) => Promise; setRedirectUrl: React.Dispatch>; }; @@ -32,145 +40,205 @@ const [Provider, useContext] = createContext({ providerName: 'AuthProvider', }); -export const AuthProvider: React.FC> = ({ - children, -}) => { - const [accessToken, setAccessToken, clearAccessToken] = useAuthCookie(); +export const AuthProvider: React.FC = ({ children }) => { + const [accessToken, setAccessToken, removeAccessToken] = useAuthCookie(); const posthog = usePostHog(); - const [redirectUrl, setRedirectUrl] = useState(null); const cookies = useCookies(); - - const [loading, setLoading] = useState(false); - const [error, setError] = useState(); - - const providers = useAuthProviders(); - const providersValues = Object.values(providers); const router = useRouter(); - const login = useCallback( - (providerName: AuthProviders, redirectUrl?: string) => { - if (redirectUrl) { - setRedirectUrl(redirectUrl); - } - - const provider = providers[providerName]; - provider.handleLogin(); - }, - [providers], - ); + const [error, setError] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [redirectUrl, setRedirectUrl] = useState(null); - const logout = useCallback(async () => { + const handleLogout = useCallback(async () => { + posthog.reset(); cookies.remove('authProviderToken'); + const invitationHash = router.query.invitation; + removeAccessToken(); + if (!constants.PUBLIC_ROUTES.includes(router.pathname.toLowerCase())) { await router.replace({ pathname: routes.home(), - query: invitationHash ? `invitation=${invitationHash}` : undefined, + query: invitationHash ? { invitation: invitationHash } : undefined, }); } + }, [cookies, posthog, removeAccessToken, router]); + + const requestAccessToken = async ( + authProviderToken: string, + projectId?: string, + silent?: boolean, + ): Promise => { + if (isLoading || !authProviderToken) { + return ''; + } - providersValues.forEach((provider) => { - if (provider.token) { - provider.handleLogout(); + try { + setIsLoading(true); + setError(undefined); + const result = await loginWithDynamic({ + data: { authToken: authProviderToken, projectId }, + }); + + if (projectId) { + cookies.set('lastProjectId', projectId); + setAccessToken(result.loginWithDynamic); } - }); - cookies.remove('projectId'); - clearAccessToken(); - posthog.reset(); - }, [cookies, clearAccessToken, router, providersValues]); + return result.loginWithDynamic; + } catch (error) { + const parsedError = error || new Error('Failed to login with dynamic'); - const requestAccessToken = useCallback( - async (provider: AuthWith, projectId?: string) => { - if (loading) { - return; + if (silent) { + Log.error('Failed request for accessToken'); + } else { + setError(parsedError); } + } finally { + setIsLoading(false); + } + }; - try { - setLoading(true); - setError(undefined); - - const token = await provider.requestAccessToken(projectId); - setAccessToken(token); - } catch (requestError) { - logout(); - setError(requestError); - } finally { - setLoading(false); + const handleAuthSuccess = useCallback( + async (authProviderToken: string, cbRedirectUrl?: string) => { + setIsLoading(true); + cookies.set('authProviderToken', authProviderToken); + const result = await requestAccessToken(authProviderToken); + + if (!result) { + return; } - }, - [setAccessToken, loading, logout], - ); - const switchProjectAuth = useCallback( - async (projectId: string) => { - const provider = providersValues.find((provider) => provider.token); + setAccessToken(result); - if (provider) { - // if in site page, redirect to sites list first - if (router.query.siteId) { - await router.replace(routes.project.site.list({ projectId })); - delete router.query.siteId; - } + console.info('auth success redirect url ->', cbRedirectUrl, redirectUrl); - return requestAccessToken(provider, projectId); + if (cbRedirectUrl || redirectUrl) { + console.log( + 'redirecting from authProvider', + cbRedirectUrl, + redirectUrl, + ); + await router.replace(cbRedirectUrl ?? redirectUrl!); } + + setIsLoading(false); }, + [cookies, redirectUrl, requestAccessToken, router, setAccessToken], + ); + + const loginWithDynamic = async (data: LoginWithDynamicMutationVariables) => { + const graphqlApi = new GraphqlApiClient({ accessToken }); - // eslint-disable-next-line react-hooks/exhaustive-deps - [providersValues, requestAccessToken], + return graphqlApi.fetch< + LoginWithDynamicMutation, + LoginWithDynamicMutationVariables + >({ + document: LoginWithDynamicDocument.loc.source.body, + variables: data, + }); + }; + + return ( + + + {children} + + ); +}; - useEffect(() => { - const provider = providersValues.find((provider) => provider.token); +type InnerProviderProps = ChildrenProps<{ + isLoading: boolean; + error?: unknown; + setRedirectUrl: React.Dispatch>; + redirectUrl: string | null; + requestAccessToken: ( + authProviderToken: string, + projectId?: string, + silent?: boolean, + ) => void; + handleAuthSuccess: (authProviderToken: string, redirectUrl?: string) => void; +}>; + +const InnerProvider: React.FC = ({ + children, + isLoading, + error, + requestAccessToken, + redirectUrl, + setRedirectUrl, + handleAuthSuccess, +}) => { + const [accessToken] = useAuthCookie(); + const dynamic = useDynamicContext(); + const router = useRouter(); + const cookies = useCookies(); - if (provider?.token) { - // if has a provider token, it means that auth provider is authenticated - cookies.set('authProviderToken', provider.token); + const tokenProjectId = useMemo(() => { + return accessToken + ? (decodeJwt(accessToken)?.projectId as string) + : undefined; + }, [accessToken]); - const projectId = - cookies.values.projectId || constants.DEFAULT_PROJECT_ID; + const login = (redirectUrl?: string) => { + if (dynamic.authToken || cookies.values.authProviderToken) { + handleAuthSuccess( + dynamic.authToken ?? cookies.values.authProviderToken!, + redirectUrl, + ); - // redirect if is in home page - if (router.pathname === routes.home()) { - // keep query on redirect - router.push({ - pathname: routes.project.home({ projectId }), - query: router.query, - }); - } + return; + } + + if (redirectUrl) { + setRedirectUrl(redirectUrl); + } - // redirect if has a redirect url pending - if (redirectUrl) { - router.push(redirectUrl.replace('[projectid]', projectId)); + dynamic.setShowAuthFlow(true); + }; - setRedirectUrl(null); + const logout = dynamic.handleLogOut; + + const switchProjectAuth = async (projectId: string, silent?: boolean) => { + if (dynamic.authToken) { + // if in site page, redirect to sites list first + if (router.query.siteId) { + await router.replace(routes.project.site.list({ projectId })); + delete router.query.siteId; } - // uses the auth provider token to request the access token from graphql - if (!accessToken) { - requestAccessToken(provider); + if (projectId !== tokenProjectId) { + requestAccessToken(dynamic.authToken, projectId, silent); } } else { - logout(); + dynamic.handleLogOut(); } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - cookies.values.authProviderToken, - ...providersValues.map((provider) => provider.token), - ]); + }; return ( > = ({ ); }; - export const useAuthContext = useContext; diff --git a/src/providers/DynamicProvider.tsx b/src/providers/DynamicProvider.tsx index 44be5750..1f0d9143 100644 --- a/src/providers/DynamicProvider.tsx +++ b/src/providers/DynamicProvider.tsx @@ -4,20 +4,49 @@ import { WalletConnector, } from '@dynamic-labs/sdk-react-core'; import { DynamicWagmiConnector } from '@dynamic-labs/wagmi-connector'; +import { useCallback } from 'react'; -import { useMeQuery, useUpdateUserMutation } from '@/generated/graphqlClient'; +import { + MeDocument, + MeQuery, + MeQueryVariables, +} from '@/generated/graphqlClient'; +import { useAuthCookie } from '@/hooks/useAuthCookie'; +import { useUpdateUserMutation } from '@/hooks/useUpdateUserMutation'; +import { GraphqlApiClient } from '@/integrations/graphql/GraphqlApi'; import { secrets } from '@/secrets'; +import { ChildrenProps } from '@/types/Props'; +import { Log } from '@/utils/log'; -import { useCookies } from './CookiesProvider'; - -export type DynamicProviderProps = React.PropsWithChildren<{}>; +type DynamicProviderProps = ChildrenProps<{ + handleAuthSuccess: (authProviderToken: string) => void; + handleLogout: () => void; +}>; export const DynamicProvider: React.FC = ({ + handleLogout, + handleAuthSuccess, children, }) => { - const [, updateUser] = useUpdateUserMutation(); - const [meQuery] = useMeQuery(); - const cookies = useCookies(); + const [accessToken] = useAuthCookie(); + const { mutate: updateUser } = useUpdateUserMutation(); + + const getUserQuery = useCallback(() => { + try { + if (!accessToken) { + return undefined; + } + + const graphqlApi = new GraphqlApiClient({ accessToken }); + + return graphqlApi.fetch({ + document: MeDocument.loc.source.body, + variables: {}, + }); + } catch (error) { + Log.error('Failed to get user on auth provider callbacks', error); + } + }, [accessToken]); return ( = ({ environmentId: secrets.NEXT_PUBLIC_UI__DYNAMIC_ENVIRONMENT_ID, walletConnectors: [EthereumWalletConnectors], eventsCallbacks: { - onLogout: () => { - cookies.remove('authProviderToken'); - cookies.remove('accessToken'); + onAuthSuccess: (args) => { + handleAuthSuccess(args.authToken); }, + onLogout: handleLogout, onLinkSuccess: async (args) => { + const data = await getUserQuery(); + + if (!data?.user) { + return; + } + // for now we want to save the first wallet linked - if ( - !meQuery.fetching && - !meQuery.data?.user.walletAddress && - args.walletConnector - ) { + if (!data.user.walletAddress && args.walletConnector) { const wallet = await ( args.walletConnector as WalletConnector ).fetchPublicAddress(); - updateUser({ data: { walletAddress: wallet } }); } }, onUnlinkSuccess: async (wallet) => { + const data = await getUserQuery(); + + if (!data?.user) { + return; + } + // remove wallet if is the one we have stored if ( - !meQuery.fetching && - meQuery.data?.user.walletAddress && - meQuery.data?.user.walletAddress === wallet.address + data.user.walletAddress && + data.user.walletAddress === wallet.address ) { updateUser({ data: { walletAddress: null, walletChain: null } }); } From c0d6291168ade95a5da670356c2d15ad6a676070 Mon Sep 17 00:00:00 2001 From: angarita-dev <44899916+angarita-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:49:08 -0500 Subject: [PATCH 02/11] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Added=20GraphqlAp?= =?UTF-8?q?i=20for=20calls=20outside=20urql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useUpdateUserMutation.ts | 22 +++++++ src/integrations/graphql/GraphqlApi.ts | 83 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/hooks/useUpdateUserMutation.ts create mode 100644 src/integrations/graphql/GraphqlApi.ts diff --git a/src/hooks/useUpdateUserMutation.ts b/src/hooks/useUpdateUserMutation.ts new file mode 100644 index 00000000..30dd24ee --- /dev/null +++ b/src/hooks/useUpdateUserMutation.ts @@ -0,0 +1,22 @@ +import { useMutation } from '@tanstack/react-query'; + +import { + UpdateUserDocument, + UpdateUserMutation, + UpdateUserMutationVariables, +} from '@/generated/graphqlClient'; +import { GraphqlApiClient } from '@/integrations/graphql/GraphqlApi'; + +import { useAuthCookie } from './useAuthCookie'; + +export const useUpdateUserMutation = () => { + const [accessToken] = useAuthCookie(); + const graphqlApi = new GraphqlApiClient({ accessToken }); + + return useMutation(async (data: UpdateUserMutationVariables) => { + return graphqlApi.fetch({ + document: UpdateUserDocument.loc.source.body, + variables: data, + }); + }); +}; diff --git a/src/integrations/graphql/GraphqlApi.ts b/src/integrations/graphql/GraphqlApi.ts new file mode 100644 index 00000000..b508addd --- /dev/null +++ b/src/integrations/graphql/GraphqlApi.ts @@ -0,0 +1,83 @@ +import { constants } from '@fleek-platform/utils-permissions'; + +import { secrets } from '@/secrets'; + +export class GraphqlApiClient { + private baseURL: string | undefined = + secrets.NEXT_PUBLIC_SDK__AUTHENTICATION_URL; + private headers: HeadersInit; + + constructor(args: GraphqlApiClient.Arguments) { + this.headers = { + 'Content-Type': 'application/json', + [constants.AUTHORIZATION_HEADER_NAME]: `Bearer ${args.accessToken}`, + [constants.CUSTOM_HEADERS.clientType]: constants.UI_CLIENT_TYPE_NAME, + }; + } + + async fetch( + args: GraphqlApiClient.FetchProps, + ): Promise { + const { document, variables, extraHeaders } = args; + + const response = await fetch(this.baseURL!, { + method: 'POST', + headers: { + ...this.headers, + ...extraHeaders, + }, + body: JSON.stringify({ query: document, variables }), + }); + + const json = await response.json(); + + // Handle HTTP errors + if (!response.ok) { + throw new GraphqlApiClient.NetworkError( + response.status, + json.message || response.statusText, + ); + } + + // Handle GraphQL errors + if (json.errors) { + throw new GraphqlApiClient.GraphqlError(json.errors); + } + + return json.data; + } +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace GraphqlApiClient { + export type Arguments = { accessToken?: string }; + + export type FetchProps = { + document: unknown | string; + variables?: TVariables; + extraHeaders?: HeadersInit; + }; + + export class NetworkError extends Error { + constructor( + public status: number, + public message: string, + ) { + super(message); + this.name = 'NetworkError'; + } + } + + export class GraphqlError extends Error { + constructor( + public errors: Array<{ + message: string; + locations?: unknown; + path?: unknown; + }>, + ) { + super(errors.map((e) => e.message).join(', ')); + this.name = 'GraphqlError'; + } + } +} From 5b800c486e3d23ffddc5a2e9ae39ff0b5d269786 Mon Sep 17 00:00:00 2001 From: angarita-dev <44899916+angarita-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:52:34 -0500 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20Updated=20prov?= =?UTF-8?q?iders=20hierarchy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/providers/CookiesProvider.tsx | 2 +- src/providers/LaunchDarklyProvider.tsx | 4 +- src/providers/LogRocketProvider.tsx | 4 +- src/providers/PHProvider.tsx | 11 +++-- src/providers/ProjectProvider.tsx | 12 +++--- src/providers/Providers.tsx | 45 ++++++++++---------- src/providers/SessionProvider.tsx | 59 +++++++++++++++++++------- src/providers/UrqlProvider.tsx | 12 +++--- 8 files changed, 88 insertions(+), 61 deletions(-) diff --git a/src/providers/CookiesProvider.tsx b/src/providers/CookiesProvider.tsx index 8a5a0873..087f7117 100644 --- a/src/providers/CookiesProvider.tsx +++ b/src/providers/CookiesProvider.tsx @@ -12,7 +12,7 @@ class CookiesError extends Error { } } -export type AppCookies = 'authProviderToken' | 'accessToken' | 'projectId'; +export type AppCookies = 'authProviderToken' | 'accessToken' | 'lastProjectId'; export type CookiesContext = { values: { [key in AppCookies]?: string }; diff --git a/src/providers/LaunchDarklyProvider.tsx b/src/providers/LaunchDarklyProvider.tsx index 94cbb5e3..c33e20c6 100644 --- a/src/providers/LaunchDarklyProvider.tsx +++ b/src/providers/LaunchDarklyProvider.tsx @@ -3,6 +3,7 @@ import { useEffect } from 'react'; import { useMeQuery } from '@/generated/graphqlClient'; import { secrets } from '@/secrets'; +import { useAuthContext } from './AuthProvider'; type LaunchDarklyProviderProps = React.PropsWithChildren<{}>; @@ -18,7 +19,8 @@ export const LaunchDarklyProvider: React.FC = ({ }; const Identifier: React.FC = () => { - const [meQuery] = useMeQuery(); + const auth = useAuthContext(); + const [meQuery] = useMeQuery({ pause: !auth.token }); const ldClient = useLDClient(); useEffect(() => { diff --git a/src/providers/LogRocketProvider.tsx b/src/providers/LogRocketProvider.tsx index 250a8777..ad3a2447 100644 --- a/src/providers/LogRocketProvider.tsx +++ b/src/providers/LogRocketProvider.tsx @@ -3,9 +3,11 @@ import { useEffect } from 'react'; import { useMeQuery } from '@/generated/graphqlClient'; import { ChildrenProps } from '@/types/Props'; +import { useAuthContext } from './AuthProvider'; export const LogRocketProvider: React.FC = ({ children }) => { - const [meQuery] = useMeQuery(); + const auth = useAuthContext(); + const [meQuery] = useMeQuery({ pause: !auth.token }); useEffect(() => { if (meQuery.data) { diff --git a/src/providers/PHProvider.tsx b/src/providers/PHProvider.tsx index 07dbd38d..ea64ceaf 100644 --- a/src/providers/PHProvider.tsx +++ b/src/providers/PHProvider.tsx @@ -9,6 +9,7 @@ import { isServerSide } from '@/utils/isServerSide'; import { Log } from '@/utils/log'; import { CustomPostHogProvider } from './CustomPostHogProvider'; +import { useSessionContext } from './SessionProvider'; if (!isServerSide()) { posthogJs.init(secrets.NEXT_PUBLIC_UI__POSTHOG_KEY!, { @@ -27,16 +28,14 @@ if (!isServerSide()) { export const PHProvider: React.FC = ({ children }) => { return ( - - - {children} - + {children} ); }; -const Identifier: React.FC = () => { - const [meQuery] = useMeQuery(); +export const PHIdentifier: React.FC = () => { + const session = useSessionContext(); const posthog = usePostHog(); + const [meQuery] = useMeQuery({ pause: !session.auth.token }); // If user is logged in, identify them useEffect(() => { diff --git a/src/providers/ProjectProvider.tsx b/src/providers/ProjectProvider.tsx index 1c8a4129..54d6eb1c 100644 --- a/src/providers/ProjectProvider.tsx +++ b/src/providers/ProjectProvider.tsx @@ -113,19 +113,19 @@ export const ProjectProvider: React.FC> = ({ try { await auth.switchProjectAuth(newProjectId); await redirect(); - cookies.set('projectId', newProjectId); + cookies.set('lastProjectId', newProjectId); } catch (error) { Log.error('Failed to switch project', error); } }; - changeProject(cookies.values.projectId ?? projects[0].id); + changeProject(cookies.values.lastProjectId ?? projects[0].id); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cookies.values.projectId, projectsQuery]); + }, [cookies.values.lastProjectId, projectsQuery]); useEffect(() => { if (router.query.projectId) { - cookies.set('projectId', router.query.projectId); + cookies.set('lastProjectId', router.query.projectId); } // Update cookie on first run if it is present in the url // eslint-disable-next-line react-hooks/exhaustive-deps @@ -152,10 +152,10 @@ export const ProjectProvider: React.FC> = ({ const projects = data.projects.data; return ( - projects.find((project) => project.id === cookies.values.projectId) || + projects.find((project) => project.id === cookies.values.lastProjectId) || defaultProject ); - }, [cookies.values.projectId, projectsQuery, router]); + }, [cookies.values.lastProjectId, projectsQuery, router]); const isLoading = useMemo(() => { if (!cookies.values.authProviderToken) { diff --git a/src/providers/Providers.tsx b/src/providers/Providers.tsx index fae68677..383124e0 100644 --- a/src/providers/Providers.tsx +++ b/src/providers/Providers.tsx @@ -13,6 +13,7 @@ import { ToastProvider } from './ToastProvider'; import { UploadProvider } from './UploadProvider'; import { UrqlProvider } from './UrqlProvider'; import { WagmiProvider } from './WagmiProvider'; +import { AuthProvider } from './AuthProvider'; type ProvidersProps = ChildrenProps<{ requestCookies?: CookiesContext['values']; @@ -26,33 +27,31 @@ export const Providers: React.FC = ({ }) => { return ( - - - - - - - - - + + + + + + + + + - - - {children} - - + + {children} + - - - - - - - - - + + + + + + + + + ); }; diff --git a/src/providers/SessionProvider.tsx b/src/providers/SessionProvider.tsx index ce55b8b8..49aaff28 100644 --- a/src/providers/SessionProvider.tsx +++ b/src/providers/SessionProvider.tsx @@ -1,3 +1,4 @@ +import { routes } from '@fleek-platform/utils-routes'; import { useEffect, useMemo } from 'react'; import { useToast } from '@/hooks/useToast'; @@ -5,7 +6,7 @@ import { createContext } from '@/utils/createContext'; import { AuthContext, AuthProvider, useAuthContext } from './AuthProvider'; import { BillingProvider, useBillingContext } from './BillingProvider'; -import { useCookies } from './CookiesProvider'; +import { PHIdentifier } from './PHProvider'; import { PermissionsContext, PermissionsProvider, @@ -16,6 +17,7 @@ import { ProjectProvider, useProjectContext, } from './ProjectProvider'; +import { useRouter } from '@/hooks/useRouter'; type SessionContext = { loading: boolean; @@ -37,16 +39,19 @@ const [Provider, useContext] = createContext({ const InnerProvider: React.FC> = ({ children }) => { const auth = useAuthContext(); const toast = useToast(); + const router = useRouter(); const permissions = usePermissionsContext(); const billing = useBillingContext(); const project = useProjectContext(); - const cookies = useCookies(); const loading = useMemo( () => - auth.loading || project.loading || permissions.loading || billing.loading, - [auth.loading, billing.loading, permissions.loading, project.loading], + auth.isLoading || + project.loading || + permissions.loading || + billing.loading, + [auth.isLoading, billing.loading, permissions.loading, project.loading], ); const error = useMemo( @@ -62,8 +67,29 @@ const InnerProvider: React.FC> = ({ children }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [error]); - const setProject = (newProjectId: string) => { - cookies.set('projectId', newProjectId); + const handleProjectChange = async (newProjectId: string) => { + if (auth.token) { + auth.switchProjectAuth(newProjectId); + const { _, ...parsedProjectQueryRoute } = router.query; + router.push({ + query: { ...parsedProjectQueryRoute, projectId: newProjectId }, + }); + + return; + } + // TODO: Determine isProjectRequired with Provider & HOC + /* + if (!isProjectRequired) { + } + + if (router.query.siteId) { + await router.push(routes.project.site.list({ projectId: newProjectId })); + delete router.query.siteId; + } else { + const { _, ...parsedProjectQueryRoute } = router.query; + router.push({ query: { ...parsedProjectQueryRoute, projectId: newProjectId } }); + } + */ }; return ( @@ -74,7 +100,7 @@ const InnerProvider: React.FC> = ({ children }) => { auth, project: project.project, permissions: permissions.permissions, - setProject, + setProject: handleProjectChange, }} > {children} @@ -86,15 +112,16 @@ export const SessionProvider: React.FC> = ({ children, }) => { return ( - - - - - {children} - - - - + + + + + + {children} + + + + ); }; diff --git a/src/providers/UrqlProvider.tsx b/src/providers/UrqlProvider.tsx index 1c5e120b..7723171c 100644 --- a/src/providers/UrqlProvider.tsx +++ b/src/providers/UrqlProvider.tsx @@ -2,24 +2,22 @@ import { useMemo } from 'react'; import { createUrqlClient, UrqlProviderComponent } from '@/integrations'; -import { useCookies } from './CookiesProvider'; +import { useAuthContext } from './AuthProvider'; export const UrqlProvider: React.FC> = ({ children, }) => { - const cookies = useCookies(); + const auth = useAuthContext(); const urqlClient = useMemo( () => createUrqlClient({ - token: cookies.values.accessToken, - logout: () => { - cookies.remove('authProviderToken'); - }, + token: auth.token, + logout: auth.logout, }), // Shouldn't update the urql client on cookie change, project is stored in cookies as well // eslint-disable-next-line react-hooks/exhaustive-deps - [cookies.values.accessToken], + [auth.token], ); return ( From 4f841f25f6ca1679cf95d002c739b2487fcfb483 Mon Sep 17 00:00:00 2001 From: angarita-dev <44899916+angarita-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:54:15 -0500 Subject: [PATCH 04/11] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20Updated=20logi?= =?UTF-8?q?n=20&=20switchProject=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CreateProject/CreateProject.tsx | 6 +++--- src/fragments/App/Navbar/Navbar.tsx | 10 +++++++++- .../IpfsPropagation/Sections/CreateGatewayButton.tsx | 1 - src/fragments/Migration/Layout.tsx | 2 +- .../Profile/Settings/Sections/ManageProjects.tsx | 10 +++------- .../Settings/Sections/DeleteProject/DeleteProject.tsx | 5 ++--- src/fragments/Template/List/Hero/Hero.tsx | 2 +- src/pages/index.tsx | 4 +++- src/pages/login/[verificationSessionId].tsx | 6 +++++- src/pages/templates/[templateId]/index.tsx | 1 - 10 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index 9127a85a..778aabc0 100644 --- a/src/components/CreateProject/CreateProject.tsx +++ b/src/components/CreateProject/CreateProject.tsx @@ -7,7 +7,6 @@ import { useCreateProjectMutation, } from '@/generated/graphqlClient'; import { useToast } from '@/hooks/useToast'; -import { useCookies } from '@/providers/CookiesProvider'; import { useProjectContext } from '@/providers/ProjectProvider'; import { Button, Dialog, Text } from '@/ui'; @@ -15,13 +14,14 @@ import { Form } from '../Form/Form'; import { LearnMoreMessage } from '../LearnMoreMessage/LearnMoreMessage'; import { Modal } from '../Modal/Modal'; import { ProjectField } from '../ProjectField/ProjectField'; +import { useSessionContext } from '@/providers/SessionProvider'; export const CreateProject: React.FC = () => { + const session = useSessionContext(); const { isCreateProjectModalOpen: isModalOpen, setIsCreateProjectModalOpen } = useProjectContext(); const toast = useToast(); - const cookies = useCookies(); const client = useClient(); const [, createProject] = useCreateProjectMutation(); @@ -63,7 +63,7 @@ export const CreateProject: React.FC = () => { }); } - cookies.set('projectId', data.createProject.id); + session.setProject(data.createProject.id); handleModalChange(false); } catch (error) { toast.error({ diff --git a/src/fragments/App/Navbar/Navbar.tsx b/src/fragments/App/Navbar/Navbar.tsx index 46917927..ad5da704 100644 --- a/src/fragments/App/Navbar/Navbar.tsx +++ b/src/fragments/App/Navbar/Navbar.tsx @@ -8,6 +8,7 @@ import { Button, ButtonProps, Input } from '@/ui'; import { NavbarStyles as S } from './Navbar.styles'; import { Navigation } from './Navigation'; +import { constants } from '@/constants'; const Search: React.FC = () => { const handleSearch = (/* e: React.ChangeEvent */) => { @@ -68,7 +69,14 @@ const LoginButton: React.FC = ({ title, intent }) => { const session = useSessionContext(); return ( - ); diff --git a/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx b/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx index 0c3a2218..445ebaeb 100644 --- a/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx +++ b/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx @@ -12,7 +12,6 @@ export const CreateGatewayButton: React.FC = () => { const handleClick = () => { if (!hasToken) { session.auth.login( - 'dynamic', routes.project.settings.privateGateways({ projectId: 'project' }), ); } diff --git a/src/fragments/Migration/Layout.tsx b/src/fragments/Migration/Layout.tsx index ab23e93a..892e15ed 100644 --- a/src/fragments/Migration/Layout.tsx +++ b/src/fragments/Migration/Layout.tsx @@ -17,7 +17,7 @@ export const Layout: React.FC = ({ children }) => { const handleLogIn = () => { if (!session.error && !session.loading && !session.auth.token) { - session.auth.login('dynamic', routes.migration()); + session.auth.login(routes.migration()); } }; diff --git a/src/fragments/Profile/Settings/Sections/ManageProjects.tsx b/src/fragments/Profile/Settings/Sections/ManageProjects.tsx index 4709f013..f5a31cad 100644 --- a/src/fragments/Profile/Settings/Sections/ManageProjects.tsx +++ b/src/fragments/Profile/Settings/Sections/ManageProjects.tsx @@ -1,4 +1,3 @@ -import { routes } from '@fleek-platform/utils-routes'; import { useMemo, useState } from 'react'; import { BadgeText, SettingsBox, SettingsListItem } from '@/components'; @@ -9,15 +8,13 @@ import { useMeQuery, useProjectsQuery, } from '@/generated/graphqlClient'; -import { useRouter } from '@/hooks/useRouter'; import { useToast } from '@/hooks/useToast'; -import { useCookies } from '@/providers/CookiesProvider'; import { Icon } from '@/ui'; import { firstLetterUpperCase } from '@/utils/stringFormat'; +import { useSessionContext } from '@/providers/SessionProvider'; export const ManageProjects: React.FC = () => { - const router = useRouter(); - const cookies = useCookies(); + const session = useSessionContext(); const toast = useToast(); const [meQuery] = useMeQuery(); const [projectsQuery] = useProjectsQuery(); @@ -46,8 +43,7 @@ export const ManageProjects: React.FC = () => { } const handleViewProject = ({ projectId }: HandleViewProjectProps) => { - cookies.set('projectId', projectId); - router.push(routes.project.home({ projectId })); + session.setProject(projectId); }; const handleLeaveProject = async ({ projectId }: HandleLeaveProjectProps) => { diff --git a/src/fragments/Projects/Settings/Sections/DeleteProject/DeleteProject.tsx b/src/fragments/Projects/Settings/Sections/DeleteProject/DeleteProject.tsx index 4c1af7f5..0ae17ab0 100644 --- a/src/fragments/Projects/Settings/Sections/DeleteProject/DeleteProject.tsx +++ b/src/fragments/Projects/Settings/Sections/DeleteProject/DeleteProject.tsx @@ -29,7 +29,6 @@ import { usePermissions } from '@/hooks/usePermissions'; import { useRouter } from '@/hooks/useRouter'; import { useToast } from '@/hooks/useToast'; import { useBillingContext } from '@/providers/BillingProvider'; -import { useCookies } from '@/providers/CookiesProvider'; import { useSessionContext } from '@/providers/SessionProvider'; import { ChildrenProps, LoadingProps } from '@/types/Props'; import { Button, Dialog, Text } from '@/ui'; @@ -53,7 +52,7 @@ export const DeleteProject: React.FC = ({ const client = useClient(); const router = useRouter(); const toast = useToast(); - const cookies = useCookies(); + const session = useSessionContext(); const [, createProject] = useCreateProjectMutation(); @@ -127,7 +126,7 @@ export const DeleteProject: React.FC = ({ } if (redirectProjectId) { - cookies.set('projectId', redirectProjectId); + session.setProject(redirectProjectId); } toast.success({ diff --git a/src/fragments/Template/List/Hero/Hero.tsx b/src/fragments/Template/List/Hero/Hero.tsx index d5977643..9d35f4f0 100644 --- a/src/fragments/Template/List/Hero/Hero.tsx +++ b/src/fragments/Template/List/Hero/Hero.tsx @@ -41,7 +41,7 @@ const SubmitTemplateButton: React.FC = () => { const handleSubmitTemplate = () => { if (!session.auth.token) { - return session.auth.login('dynamic', routes.profile.settings.templates()); + return session.auth.login(routes.profile.settings.templates()); } return router.push(routes.profile.settings.templates()); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 262bf578..b7b4b263 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,13 +3,15 @@ import { useEffect } from 'react'; import { Home } from '@/fragments/Home/Home'; import { useSessionContext } from '@/providers/SessionProvider'; import { Page } from '@/types/App'; +import { routes } from '@fleek-platform/utils-routes'; +import { constants } from '@/constants'; const HomePage: Page = () => { const session = useSessionContext(); const handleLogIn = () => { if (!session.error && !session.loading && !session.auth.token) { - session.auth.login('dynamic'); + session.auth.login(routes.project.home({ projectId: '[projectId]' })); } }; diff --git a/src/pages/login/[verificationSessionId].tsx b/src/pages/login/[verificationSessionId].tsx index 0564b4ad..90a8c004 100644 --- a/src/pages/login/[verificationSessionId].tsx +++ b/src/pages/login/[verificationSessionId].tsx @@ -106,7 +106,11 @@ const LoginWithSessionPage: Page = () => { withExternalLink >