From f4ed6173d2cc8ca123fef44a8e257c3042100b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=92scar=20Casajuana?= Date: Tue, 24 Dec 2024 18:24:55 +0100 Subject: [PATCH] Do not initialize the signer if there's no org created yet - Removed tricks like silent error handling due to having the signer always initialized - Cleared profile queries on account create, to ensure the organization information is ready as soon as we create it --- src/components/Account/useAccountCreate.tsx | 2 +- src/components/Auth/useAuthProvider.ts | 28 +++++++--------- src/components/Organization/Create.tsx | 36 +++++++++++++-------- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/components/Account/useAccountCreate.tsx b/src/components/Account/useAccountCreate.tsx index 76ff7741..d740b10e 100644 --- a/src/components/Account/useAccountCreate.tsx +++ b/src/components/Account/useAccountCreate.tsx @@ -6,7 +6,7 @@ export const useAccountCreate = () => { const { createAccount, updateAccount, - errors: { account: error }, + errors: { create: error }, } = useClient() // we want to know if account exists, not the organization (slight difference) diff --git a/src/components/Auth/useAuthProvider.ts b/src/components/Auth/useAuthProvider.ts index 00799fae..ea449f6d 100644 --- a/src/components/Auth/useAuthProvider.ts +++ b/src/components/Auth/useAuthProvider.ts @@ -1,6 +1,6 @@ import { useMutation } from '@tanstack/react-query' import { useClient } from '@vocdoni/react-providers' -import { NoOrganizationsError, RemoteSigner } from '@vocdoni/sdk' +import { RemoteSigner } from '@vocdoni/sdk' import { useCallback, useEffect, useMemo, useState } from 'react' import { api, ApiEndpoints, ApiParams, UnauthorizedApiError } from '~components/Auth/api' import { LoginResponse, useLogin, useRegister, useVerifyMail } from '~components/Auth/authQueries' @@ -22,19 +22,10 @@ const useSigner = () => { url: import.meta.env.SAAS_URL, token, }) - setSigner(signer) // Once the signer is set, try to get the signer address - // This is an asynchronous call because the address are fetched from the server, - // and we don't know if we need to create an organization until we try to retrieve the address - - try { - return await signer.getAddress() - } catch (e) { - // If is NoOrganizationsError ignore the error - if (!(e instanceof NoOrganizationsError)) { - throw e - } - } + const address = await signer.getAddress() + setSigner(signer) + return address }, []) return useMutation({ mutationFn: updateSigner }) @@ -93,14 +84,18 @@ export const useAuthProvider = () => { clear() }, []) - // If no signer but berarer instantiate the signer - // For example when bearer is on local storage but no login was done to instantiate the signer - useEffect(() => { + const signerRefresh = useCallback(() => { if (bearer && !clientSigner) { updateSigner(bearer) } }, [bearer, clientSigner]) + // If no signer but berarer instantiate the signer + // For example when bearer is on local storage but no login was done to instantiate the signer + useEffect(() => { + signerRefresh() + }, [bearer, clientSigner]) + const isAuthenticated = useMemo(() => !!bearer, [bearer]) const isAuthLoading = useMemo( () => (isAuthenticated && signerIdle) || (isAuthenticated && !signerIdle && signerPending), @@ -116,5 +111,6 @@ export const useAuthProvider = () => { bearedFetch, isAuthLoading, signerAddress, + signerRefresh, } } diff --git a/src/components/Organization/Create.tsx b/src/components/Organization/Create.tsx index 93836530..4c9578fb 100644 --- a/src/components/Organization/Create.tsx +++ b/src/components/Organization/Create.tsx @@ -1,6 +1,7 @@ import { Button, Flex, FlexProps, Stack, Text } from '@chakra-ui/react' -import { useMutation, UseMutationOptions } from '@tanstack/react-query' +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { useClient } from '@vocdoni/react-providers' import { useEffect, useState } from 'react' import { FormProvider, useForm } from 'react-hook-form' import { Trans, useTranslation } from 'react-i18next' @@ -10,25 +11,21 @@ import LogoutBtn from '~components/Account/LogoutBtn' import { useAccountCreate } from '~components/Account/useAccountCreate' import { ApiEndpoints } from '~components/Auth/api' import { useAuth } from '~components/Auth/useAuth' +import { useAuthProvider } from '~components/Auth/useAuthProvider' import FormSubmitMessage from '~components/Layout/FormSubmitMessage' import { Routes } from '~src/router/routes' import { PrivateOrgForm, PrivateOrgFormData, PublicOrgForm } from './Form' type FormData = PrivateOrgFormData & CreateOrgParams -// This specific error message should be ignored and not displayed in the UI. -// Context: After login, a RemoteSigner is created and passed to the SDK via the useClient hook. -// Immediately following this, the provider attempts to fetch the signer's address. However, -// at this point, the signer has not yet been associated with any organization. -// As a result, the backend returns an error, which is stored in the provider's state. -// We rely on this error message for handling because no error code is provided, -// and the error is not thrown as an exception. -const IgnoreAccountError = 'this user has not been assigned to any organization' - const useOrganizationCreate = (options?: Omit, 'mutationFn'>) => { const { bearedFetch } = useAuth() + const client = useQueryClient() return useMutation({ mutationFn: (params: CreateOrgParams) => bearedFetch(ApiEndpoints.Organizations, { body: params, method: 'POST' }), + onSuccess: () => { + client.invalidateQueries({ queryKey: ['profile'] }) + }, ...options, }) } @@ -47,6 +44,9 @@ export const OrganizationCreate = ({ const methods = useForm() const { handleSubmit } = methods const [success, setSuccess] = useState(null) + const { signerRefresh } = useAuthProvider() + const { signer } = useClient() + const [promiseError, setPromiseError] = useState(null) const { create: createAccount, error: accountError } = useAccountCreate() const { mutateAsync: createSaasAccount, isError: isSaasError, error: saasError } = useOrganizationCreate() @@ -64,18 +64,28 @@ export const OrganizationCreate = ({ country: values.countrySelect?.value, type: values.typeSelect?.value, }) + // ensure the signer is properly initialized + .then(() => { + signerRefresh() + }) + // we need to ensure the SDK populated the signer with our account (otherwise "createAccount" would fail) + .then(() => signer.getAddress()) + // then we create the account on the vochain .then(() => - // Create the new account on the vochain createAccount({ name: typeof values.name === 'object' ? values.name.default : values.name, description: typeof values.description === 'object' ? values.description.default : values.description, }) ) + // save on success to redirect (cannot directly redirect due to a re-render during the promise chain) .then(() => setSuccess(onSuccessRoute as unknown as string)) + .catch((e) => { + setPromiseError(e) + }) .finally(() => setIsPending(false)) } - const isError = (!!accountError || isSaasError) && error !== IgnoreAccountError + const isError = !!accountError || isSaasError || !!promiseError // The promise chain breaks the redirection due to some re-render in between, so we need to redirect in an effect useEffect(() => { @@ -112,7 +122,7 @@ export const OrganizationCreate = ({ {t('organization.create_org')} - + If your organization already have a profile, ask the admin to invite you to your organization.