diff --git a/src/Providers.tsx b/src/Providers.tsx index 5fb64144e..e2376fa8b 100644 --- a/src/Providers.tsx +++ b/src/Providers.tsx @@ -14,6 +14,7 @@ import { translations } from './i18n/components' import { datesLocale } from './i18n/locales' import { RoutesProvider } from './router/Router' import { RainbowKitTheme, Theme } from './Theme' +import { SaasAccountProvider } from '~components/AccountSaas/SaasAccountContext' const queryClient = new QueryClient() @@ -29,6 +30,17 @@ export const Providers = () => { ) } +const SaasProviders = ({ children }: PropsWithChildren<{}>) => { + if (!import.meta.env.SAAS_URL) { + return children + } + return ( + + {children} + + ) +} + export const AppProviders = () => { const { data } = useWalletClient() const { address } = useAccount() @@ -48,10 +60,10 @@ export const AppProviders = () => { datesLocale={datesLocale(i18n.language)} options={{ faucet_url: import.meta.env.CUSTOM_FAUCET_URL }} > - + - + ) diff --git a/src/components/AccountSaas/AccountTypes.ts b/src/components/AccountSaas/AccountTypes.ts index 187dd85ff..0e22713f2 100644 --- a/src/components/AccountSaas/AccountTypes.ts +++ b/src/components/AccountSaas/AccountTypes.ts @@ -1,20 +1,24 @@ -export interface OrgInterface { +import { AccountData, IAccount } from '@vocdoni/sdk' + +export type SaasOrganizationData = { active: boolean address: string createdAt: string - name: string website: string - description: string size: string type: string country: string timezone: string language: string - logo: string header: string subdomain: string color: string communications: boolean } -export type CreateOrgParams = Partial> +export type OrganizationData = SaasOrganizationData & AccountData + +export type CreateOrgParams = Partial< + Pick & + Omit +> diff --git a/src/components/AccountSaas/Create.tsx b/src/components/AccountSaas/Create.tsx index c0f9dd880..f9de2bf3b 100644 --- a/src/components/AccountSaas/Create.tsx +++ b/src/components/AccountSaas/Create.tsx @@ -65,8 +65,8 @@ export const AccountCreate = ({ children, ...props }: FlexProps) => { .then(() => signer.getAddress()) // Get the address of newly created signer .then(() => createAccount({ - name: values.name, - description: values.description, + name: typeof values.name === 'object' ? values.name.default : values.name, + description: typeof values.description === 'object' ? values.description.default : values.description, }) ) // Create the new account on the vochain .finally(() => setIsPending(false)) diff --git a/src/components/AccountSaas/EditProfile.tsx b/src/components/AccountSaas/EditProfile.tsx index b15d51ea8..678380edd 100644 --- a/src/components/AccountSaas/EditProfile.tsx +++ b/src/components/AccountSaas/EditProfile.tsx @@ -15,40 +15,68 @@ import { import { REGEX_AVATAR } from '~constants' import useDarkMode from '~src/themes/saas/hooks/useDarkMode' import fallback from '/assets/default-avatar.png' -import { useEditSaasOrganization, useSaasOrganization } from '~components/AccountSaas/queries' import FormSubmitMessage from '~components/Layout/FormSubmitMessage' +import { useMutation, UseMutationOptions } from '@tanstack/react-query' +import { useAuth } from '~components/Auth/useAuth' +import { ApiEndpoints } from '~components/Auth/api' +import { useSaasAccount } from '~components/AccountSaas/useSaasAccount' +import { useClient } from '@vocdoni/react-providers' +import { Account } from '@vocdoni/sdk' type FormData = CustomOrgFormData & PrivateOrgFormData & CreateOrgParams +const useEditSaasOrganization = (options?: Omit, 'mutationFn'>) => { + const { bearedFetch, signerAddress } = useAuth() + return useMutation({ + mutationFn: (params: CreateOrgParams) => + bearedFetch(ApiEndpoints.ORGANIZATION.replace('{address}', signerAddress), { + body: params, + method: 'PUT', + }), + ...options, + }) +} + const EditProfile = () => { const { t } = useTranslation() + const { + updateAccount, + loading: { update: isUpdateLoading }, + errors: { update: updateError }, + } = useClient() + const { organization } = useSaasAccount() - const { data } = useSaasOrganization() - const { mutate, isPending, isError, error, isSuccess } = useEditSaasOrganization() + const { + mutateAsync, + isPending: isSaasPending, + isError: isSaasError, + error: saasError, + isSuccess, + } = useEditSaasOrganization() const methods = useForm({ defaultValues: { - name: data?.name || '', - website: data?.website || '', - description: data?.description || '', - sizeSelect: data?.size && { - value: data.size, + name: organization?.account.name.default || '', + website: organization?.website || '', + description: organization?.account.description.default || '', + sizeSelect: organization?.size && { + value: organization.size, }, - typeSelect: data?.type && { - value: data.type, + typeSelect: organization?.type && { + value: organization.type, }, - countrySelect: data?.country && { - value: data.country || '', + countrySelect: organization?.country && { + value: organization.country || '', }, - communications: data?.communications || false, - timeZoneSelect: data?.timezone && { - value: data.timezone, + communications: organization?.communications || false, + timeZoneSelect: organization?.timezone && { + value: organization.timezone, }, - languageSelect: data?.language && { - value: data.language, + languageSelect: organization?.language && { + value: organization.language, }, - logo: data?.logo || '', - header: data?.header || '', + logo: organization?.account.avatar || '', + header: organization?.header || '', }, }) @@ -56,23 +84,26 @@ const EditProfile = () => { const onSubmit: SubmitHandler = async (values: FormData) => { const newInfo: CreateOrgParams = { - name: values.name, website: values.website, - description: values.description, size: values.sizeSelect?.value, type: values.typeSelect?.value, country: values.countrySelect?.value, timezone: values.timeZoneSelect.value, language: values.languageSelect.value, - logo: values.logo, - header: values.header, } - mutate({ - ...data, - ...newInfo, - }) + + await mutateAsync({ ...organization, ...newInfo }) + const newAccount = new Account({ ...organization?.account, ...values }) + // Check if account changed before trying to update + if (JSON.stringify(newAccount.generateMetadata()) !== JSON.stringify(organization?.account.generateMetadata())) { + updateAccount(newAccount) + } } + const isPending = isUpdateLoading || isSaasPending + const isError = isSaasError || !!updateError + const error = saasError || updateError + return ( @@ -106,6 +137,7 @@ const EditProfile = () => { })} | undefined>(undefined) + +export const SaasAccountProvider = ({ children }: { children: ReactNode }) => { + const isSaas = !!import.meta.env.SAAS_URL + const saasAcount = useSaasAccountProvider({ options: { enabled: isSaas } }) + if (!isSaas) { + return children + } + return {children} +} diff --git a/src/components/AccountSaas/queries.ts b/src/components/AccountSaas/queries.ts deleted file mode 100644 index fe0acce26..000000000 --- a/src/components/AccountSaas/queries.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from '@tanstack/react-query' -import { useAuth } from '~components/Auth/useAuth' -import { ApiEndpoints } from '~components/Auth/api' -import { CreateOrgParams, OrgInterface } from '~components/AccountSaas/AccountTypes' - -export const useEditSaasOrganization = ( - options?: Omit, 'mutationFn'> -) => { - const { bearedFetch, signerAddress } = useAuth() - return useMutation({ - mutationFn: (params: CreateOrgParams) => - bearedFetch(ApiEndpoints.ORGANIZATION.replace('{address}', signerAddress), { - body: params, - method: 'PUT', - }), - ...options, - }) -} - -export const useSaasOrganization = ({ - options, -}: { - options?: Omit, 'queryKey' | 'queryFn'> -} = {}) => { - const { bearedFetch, signerAddress } = useAuth() - - return useQuery({ - queryKey: ['organizations', 'info', signerAddress], - queryFn: () => bearedFetch(ApiEndpoints.ORGANIZATION.replace('{address}', signerAddress)), - enabled: !!signerAddress, - ...options, - }) -} diff --git a/src/components/AccountSaas/useSaasAccount.ts b/src/components/AccountSaas/useSaasAccount.ts new file mode 100644 index 000000000..7f5bd2551 --- /dev/null +++ b/src/components/AccountSaas/useSaasAccount.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react' +import { SaasAccountContext } from '~components/AccountSaas/SaasAccountContext' + +export const useSaasAccount = () => { + const context = useContext(SaasAccountContext) + if (!context) { + throw new Error('useSaasAccount must be used within an SaasAccountProvider') + } + return context +} diff --git a/src/components/AccountSaas/useSaasAccountProvider.ts b/src/components/AccountSaas/useSaasAccountProvider.ts new file mode 100644 index 000000000..e44778f12 --- /dev/null +++ b/src/components/AccountSaas/useSaasAccountProvider.ts @@ -0,0 +1,50 @@ +import { useClient } from '@vocdoni/react-providers' +import { useQuery, UseQueryOptions } from '@tanstack/react-query' +import { OrganizationData } from '~components/AccountSaas/AccountTypes' +import { useAuth } from '~components/Auth/useAuth' +import { ApiEndpoints } from '~components/Auth/api' +import { useCallback } from 'react' + +const useSaasOrganization = ({ + options, +}: { + options?: Omit, 'queryKey' | 'queryFn'> +} = {}) => { + const { bearedFetch, signerAddress } = useAuth() + + return useQuery({ + queryKey: ['organizations', 'info', signerAddress], + queryFn: () => bearedFetch(ApiEndpoints.ORGANIZATION.replace('{address}', signerAddress)), + enabled: !!signerAddress, + ...options, + }) +} + +export const useSaasAccountProvider = (options?: Parameters[0]) => { + const { + account: accountSDK, + fetchAccount, + errors: { fetch: sdkAccountError }, + loading: { fetch: sdkAccountLoading }, + } = useClient() + const { + data: saasData, + refetch, + isLoading: isSaasLoading, + isError: isSaasError, + error: saasError, + } = useSaasOrganization(options) + + const refetchAccount = useCallback(() => { + refetch() + fetchAccount() + }, [refetch, fetchAccount]) + + const organization: OrganizationData = { ...accountSDK, ...saasData } + + const isLoading = isSaasLoading || sdkAccountLoading + const isError = isSaasError || !!sdkAccountError + const error = saasError || sdkAccountError + + return { organization, refetchAccount, isLoading, isError, error } +} diff --git a/src/components/Layout/ErrorComponent.tsx b/src/components/Layout/ErrorComponent.tsx index 48bda49bf..c11006df2 100644 --- a/src/components/Layout/ErrorComponent.tsx +++ b/src/components/Layout/ErrorComponent.tsx @@ -2,7 +2,7 @@ import { Flex, FlexProps, Text } from '@chakra-ui/react' import { WarningIcon } from '@chakra-ui/icons' import { useTranslation } from 'react-i18next' -const ErrorComponent = ({ error, ...props }: { error: Error } & FlexProps) => { +const ErrorComponent = ({ error, ...props }: { error: Error | string } & FlexProps) => { const { t } = useTranslation() return ( diff --git a/src/components/Layout/FormSubmitMessage.tsx b/src/components/Layout/FormSubmitMessage.tsx index 015e5f04b..302d968c8 100644 --- a/src/components/Layout/FormSubmitMessage.tsx +++ b/src/components/Layout/FormSubmitMessage.tsx @@ -1,17 +1,22 @@ import { Box, BoxProps, FormControl, FormErrorMessage } from '@chakra-ui/react' const FormSubmitMessage = ({ + isLoading, error, isError, isSuccess, success, ...boxProps }: { + isLoading?: boolean error?: Error | string isError?: boolean isSuccess?: boolean success?: string } & BoxProps) => { + if (isLoading) { + return null + } if (isSuccess) { return ( diff --git a/src/components/Layout/QueryDataLayout.tsx b/src/components/Layout/QueryDataLayout.tsx index da5eab6ed..73116fd5c 100644 --- a/src/components/Layout/QueryDataLayout.tsx +++ b/src/components/Layout/QueryDataLayout.tsx @@ -6,7 +6,7 @@ interface IUserQueryLayout { isLoading: boolean isEmpty?: boolean isError: boolean - error?: Error + error?: Error | string } const QueryDataLayout = ({ isLoading, isEmpty, isError, error, children }: IUserQueryLayout & PropsWithChildren) => { diff --git a/src/elements/OrganizationSaas/Edit.tsx b/src/elements/OrganizationSaas/Edit.tsx index 518ddf271..73c5dac56 100644 --- a/src/elements/OrganizationSaas/Edit.tsx +++ b/src/elements/OrganizationSaas/Edit.tsx @@ -1,9 +1,9 @@ import EditProfile from '~components/AccountSaas/EditProfile' -import { useSaasOrganization } from '~components/AccountSaas/queries' import QueryDataLayout from '~components/Layout/QueryDataLayout' +import { useSaasAccount } from '~components/AccountSaas/useSaasAccount' const OrganizationEdit = () => { - const { isLoading, isError, error } = useSaasOrganization() + const { isLoading, isError, error } = useSaasAccount() return (