Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement edit profile logic #779

Merged
merged 12 commits into from
Oct 18, 2024
16 changes: 14 additions & 2 deletions src/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -29,6 +30,17 @@ export const Providers = () => {
)
}

const SaasProviders = ({ children }: PropsWithChildren<{}>) => {
if (!import.meta.env.SAAS_URL) {
return children
}
return (
<AuthProvider>
<SaasAccountProvider>{children}</SaasAccountProvider>
</AuthProvider>
)
}

export const AppProviders = () => {
const { data } = useWalletClient()
const { address } = useAccount()
Expand All @@ -48,10 +60,10 @@ export const AppProviders = () => {
datesLocale={datesLocale(i18n.language)}
options={{ faucet_url: import.meta.env.CUSTOM_FAUCET_URL }}
>
<AuthProvider>
<SaasProviders>
<ColorModeScript />
<RoutesProvider />
</AuthProvider>
</SaasProviders>
</ClientProvider>
</RainbowKitTheme>
)
Expand Down
1 change: 0 additions & 1 deletion src/components/Account/useAccountCreate.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useClient } from '@vocdoni/react-providers'
import { useMutation, UseMutationOptions, useQuery } from '@tanstack/react-query'
import { Account } from '@vocdoni/sdk'
import { useAccountHealthTools } from '~components/Account/use-account-health-tools'

Expand Down
18 changes: 13 additions & 5 deletions src/components/AccountSaas/AccountTypes.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
export interface OrgInterface {
name: string
import { AccountData, IAccount } from '@vocdoni/sdk'

export type SaasOrganizationData = {
active: boolean
address: string
createdAt: 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<OrgInterface>
export type OrganizationData = SaasOrganizationData & AccountData

export type CreateOrgParams = Partial<
Pick<IAccount, 'name' | 'description' | 'logo' | 'header'> &
Omit<SaasOrganizationData, 'active' | 'address' | 'createdAt'>
>
34 changes: 16 additions & 18 deletions src/components/AccountSaas/Create.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Flex, FlexProps, FormControl, FormErrorMessage, Heading, Text } from '@chakra-ui/react'
import { Box, Flex, FlexProps, Heading, Text } from '@chakra-ui/react'
import { Button } from '@vocdoni/chakra-components'
import { FormProvider, useForm } from 'react-hook-form'
import { Trans, useTranslation } from 'react-i18next'
Expand All @@ -7,14 +7,15 @@ import { useMutation, UseMutationOptions } from '@tanstack/react-query'
import { useClient } from '@vocdoni/react-providers'
import { useState } from 'react'
import { useAccountCreate } from '~components/Account/useAccountCreate'
import { CreateOrgParams, OrgInterface } from '~components/AccountSaas/AccountTypes'
import { PrivateOrgFormData, PrivateOrgForm, PublicOrgForm } from '~components/AccountSaas/Layout'
import { CreateOrgParams } from '~components/AccountSaas/AccountTypes'
import { PrivateOrgForm, PrivateOrgFormData, PublicOrgForm } from '~components/AccountSaas/Layout'
import LogoutBtn from '~components/AccountSaas/LogoutBtn'
import { ApiEndpoints } from '~components/Auth/api'
import { useAuth } from '~components/Auth/useAuth'
import FormSubmitMessage from '~components/Layout/FormSubmitMessage'
import useDarkMode from '~src/themes/saas/hooks/useDarkMode'
import LogoutBtn from '~components/AccountSaas/LogoutBtn'

type FormData = PrivateOrgFormData & Pick<OrgInterface, 'name' | 'website' | 'description'>
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.
Expand All @@ -29,7 +30,7 @@ const useSaasAccountCreate = (options?: Omit<UseMutationOptions<void, Error, Cre
const { bearedFetch } = useAuth()
return useMutation<void, Error, CreateOrgParams>({
mutationFn: (params: CreateOrgParams) =>
bearedFetch<void>(ApiEndpoints.ACCOUNT_CREATE, { body: params, method: 'POST' }),
bearedFetch<void>(ApiEndpoints.Organizations, { body: params, method: 'POST' }),
...options,
})
}
Expand All @@ -48,8 +49,6 @@ export const AccountCreate = ({ children, ...props }: FlexProps) => {
const { create: createAccount, error: accountError } = useAccountCreate()
const { mutateAsync: createSaasAccount, isError: isSaasError, error: saasError } = useSaasAccountCreate()

const isError = !!accountError || isSaasError

const error = saasError || accountError

const onSubmit = (values: FormData) => {
Expand All @@ -64,10 +63,17 @@ export const AccountCreate = ({ children, ...props }: FlexProps) => {
type: values.typeSelect?.value,
})
.then(() => signer.getAddress()) // Get the address of newly created signer
.then(() => createAccount({ name: values.name, description: values.description })) // Create the new account on the vochain
.then(() =>
createAccount({
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))
}

const isError = (!!accountError || isSaasError) && error !== IgnoreAccountError

return (
<FormProvider {...methods}>
<Flex
Expand Down Expand Up @@ -95,15 +101,7 @@ export const AccountCreate = ({ children, ...props }: FlexProps) => {
<Button form='process-create-form' type='submit' isLoading={isPending} mx='auto' mt={8} w='80%'>
{t('organization.create_org')}
</Button>
<Box pt={2}>
<FormControl isInvalid={isError}>
{isError && error !== IgnoreAccountError && (
<FormErrorMessage>
{typeof error === 'string' ? error : error?.message || 'Error performing the operation'}
</FormErrorMessage>
)}
</FormControl>
</Box>
<FormSubmitMessage isError={isError} error={error} />
<Text color={textColorSecondary} fontSize='sm' textAlign='center' py={5} mt='auto'>
<Trans i18nKey='create_org.already_profile'>
If your organization already have a profile, ask the admin to invite you to your organization.
Expand Down
125 changes: 84 additions & 41 deletions src/components/AccountSaas/EditProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { AspectRatio, Box, Flex, FormControl, FormLabel, IconButton, Image, Input, Text } from '@chakra-ui/react'
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
import { Button } from '@vocdoni/chakra-components'
import { useClient } from '@vocdoni/react-providers'
import { Account } from '@vocdoni/sdk'
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { BiTrash } from 'react-icons/bi'
import { BsFillTrashFill } from 'react-icons/bs'
import { MdBrowserUpdated } from 'react-icons/md'
import { OrgInterface } from '~components/AccountSaas/AccountTypes'
import { CreateOrgParams } from '~components/AccountSaas/AccountTypes'
import { PrivateOrgForm, PrivateOrgFormData, PublicOrgForm } from '~components/AccountSaas/Layout'
import { useSaasAccount } from '~components/AccountSaas/useSaasAccount'
import { ApiEndpoints } from '~components/Auth/api'
import { useAuth } from '~components/Auth/useAuth'
import FormSubmitMessage from '~components/Layout/FormSubmitMessage'
import {
CustomizationLanguageSelector,
CustomizationTimeZoneSelector,
Expand All @@ -17,47 +23,87 @@ import { REGEX_AVATAR } from '~constants'
import useDarkMode from '~src/themes/saas/hooks/useDarkMode'
import fallback from '/assets/default-avatar.png'

type FormData = CustomOrgFormData &
PrivateOrgFormData &
Pick<OrgInterface, 'name' | 'website' | 'description' | 'logo' | 'header'>
type FormData = CustomOrgFormData & PrivateOrgFormData & CreateOrgParams

const useEditSaasOrganization = (options?: Omit<UseMutationOptions<void, Error, CreateOrgParams>, 'mutationFn'>) => {
const { bearedFetch, signerAddress } = useAuth()
return useMutation<void, Error, CreateOrgParams>({
mutationFn: (params: CreateOrgParams) =>
bearedFetch<void>(ApiEndpoints.Organization.replace('{address}', signerAddress), {
body: params,
method: 'PUT',
}),
...options,
})
}

const EditProfile = () => {
const { account } = useClient()
const { t } = useTranslation()
const {
updateAccount,
loading: { update: isUpdateLoading },
errors: { update: updateError },
} = useClient()
const { organization } = useSaasAccount()

const {
mutateAsync,
isPending: isSaasPending,
isError: isSaasError,
error: saasError,
isSuccess,
} = useEditSaasOrganization()

const methods = useForm<FormData>({
defaultValues: {
name: account?.account.name.default || '',
// website: account?.account. || '',
description: account?.account.description.default || '',
// size: account?.account.name.default || '',
// type: account?.account.name.default || '',
// country: account?.account.name.default || '',
// timezone: account?.account.name.default || '',
// language: account?.account.name.default || '',
logo: account?.account.avatar || '',
header: account?.account.header || '',
name: organization?.account.name.default || '',
website: organization?.website || '',
description: organization?.account.description.default || '',
sizeSelect: organization?.size && {
value: organization.size,
},
typeSelect: organization?.type && {
value: organization.type,
},
countrySelect: organization?.country && {
value: organization.country || '',
},
communications: organization?.communications || false,
timeZoneSelect: organization?.timezone && {
value: organization.timezone,
},
languageSelect: organization?.language && {
value: organization.language,
},
logo: organization?.account.avatar || '',
header: organization?.header || '',
},
})

const { handleSubmit } = methods

const onSubmit: SubmitHandler<FormData> = async (values: FormData) => {
const newInfo = {
name: values.name,
const newInfo: CreateOrgParams = {
website: values.website,
description: values.description,
size: values.sizeSelect?.value,
type: values.typeSelect?.value,
country: values.countrySelect?.value,
timeZone: values.timeZoneSelect.value,
timezone: values.timeZoneSelect.value,
language: values.languageSelect.value,
logo: values.logo,
header: values.header,
}
console.log(values, 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 (
<FormProvider {...methods}>
<Box height='100%' maxH='100%' overflowY='auto'>
Expand All @@ -77,29 +123,26 @@ const EditProfile = () => {
<PublicOrgForm />
<PrivateOrgForm />
<CustomizeOrgForm />
<Flex justifyContent='center'>
<Flex align='center' direction={'column'}>
<Button
type={'submit'}
leftIcon={<MdBrowserUpdated />}
isLoading={isPending}
aria-label=''
w='full'
maxW='400px'
sx={{
span: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: 2,
},
}}
>
<Box>
<MdBrowserUpdated />
</Box>
<Text as='span'>
{t('udpate', {
defaultValue: 'Update',
})}
</Text>
{t('update', {
defaultValue: 'Update',
})}
</Button>
<FormSubmitMessage
isLoading={isPending}
isError={isError}
error={error}
isSuccess={isSuccess}
success={t('edit_saas_profile.edited_successfully', { defaultValue: 'Updated successfully' })}
/>
</Flex>
</Flex>
</Box>
Expand Down Expand Up @@ -133,8 +176,8 @@ const CustomizeOrgForm = () => {
</Text>
</Box>
<Flex flexDirection='column' gap={6} px={{ base: 5, md: 10 }}>
<CustomizationTimeZoneSelector name={'timeZoneSelect'} required />
<CustomizationLanguageSelector name={'languageSelect'} required />
<CustomizationTimeZoneSelector name={'timeZoneSelect'} />
<CustomizationLanguageSelector name={'languageSelect'} />
<FormControl>
<FormLabel display='flex' ms={1} fontSize='sm' fontWeight='500' color={textColor} mb={2}>
{t('logo', {
Expand Down
2 changes: 0 additions & 2 deletions src/components/AccountSaas/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const PublicOrgForm = () => {
defaultValue: 'https://example.com',
})}
type='text'
required
/>

<FormControl>
Expand All @@ -56,7 +55,6 @@ export const PublicOrgForm = () => {
}

export type PrivateOrgFormData = {
communications: boolean
sizeSelect: SelectOptionType
typeSelect: SelectOptionType
countrySelect: SelectOptionType
Expand Down
13 changes: 13 additions & 0 deletions src/components/AccountSaas/SaasAccountContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext, ReactNode } from 'react'
import { useSaasAccountProvider } from './useSaasAccountProvider'

export const SaasAccountContext = createContext<ReturnType<typeof useSaasAccountProvider> | 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 <SaasAccountContext.Provider value={saasAcount}>{children}</SaasAccountContext.Provider>
}
10 changes: 10 additions & 0 deletions src/components/AccountSaas/useSaasAccount.ts
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading