Skip to content

Commit

Permalink
Implement edit profile screen (#777)
Browse files Browse the repository at this point in the history
A new view for profile editing.

It also does a refactor of the dashboard layout to allow scrolling while keeping the background always visible.

@selankon editions:

- Moved the `src/components/OrganizationSaas/Dashboard/EditProfile.tsx` into `src/components/AccountSaas/EditProfile.tsx`
- Created a `src/components/AccountSaas/Layout.tsx` to share inputs between forms 
- Same by shared types, created `src/components/AccountSaas/AccountTypes.ts`
- It implements the EditProfile page on `elements` folder
  • Loading branch information
gerouvi authored Oct 3, 2024
1 parent fba2e3e commit d90b806
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 165 deletions.
3 changes: 1 addition & 2 deletions src/components/Account/EditProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ import { SubmitHandler, useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { BiTrash } from 'react-icons/bi'
import fallback from '/assets/default-avatar.png'
import { REGEX_AVATAR } from '~constants'

interface EditFormFields {
name: string
description: string
avatar: string
}

const REGEX_AVATAR = /^(https?:\/\/|ipfs:\/\/)/i

export const EditProfile = () => {
const { account, fetchAccount } = useClient()
const { update } = useOrganization()
Expand Down
16 changes: 16 additions & 0 deletions src/components/AccountSaas/AccountTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface OrgInterface {
name: string
website: string
description: string
size: string
type: string
country: string
timezone: string
language: string
logo: string
header: string
subdomain: string
color: string
}

export type CreateOrgParams = Partial<OrgInterface>
114 changes: 8 additions & 106 deletions src/components/AccountSaas/Create.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
import {
Box,
Flex,
FlexProps,
FormControl,
FormErrorMessage,
FormLabel,
Heading,
Text,
Textarea,
} from '@chakra-ui/react'
import { Box, Flex, FlexProps, FormControl, FormErrorMessage, 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 @@ -17,35 +7,14 @@ 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 { ApiEndpoints } from '~components/Auth/api'
import { useAuth } from '~components/Auth/useAuth'
import CheckboxCustom from '~components/Layout/CheckboxCustom'
import InputCustom from '~components/Layout/InputCustom'
import {
CountriesTypesSelector,
MembershipSizeTypesSelector,
OrganzationTypesSelector,
SelectOptionType,
} from '~components/Layout/SaasSelector'
import useDarkMode from '~src/themes/saas/hooks/useDarkMode'
import LogoutBtn from '~components/AccountSaas/LogoutBtn'

interface OrgInterface {
name: string
website: string
description: string
size: string
type: string
country: string
timezone: string
language: string
logo: string
header: string
subdomain: string
color: string
}

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

// 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 @@ -65,25 +34,14 @@ const useSaasAccountCreate = (options?: Omit<UseMutationOptions<void, Error, Cre
})
}

type FormData = {
communications: boolean
sizeSelect: SelectOptionType
typeSelect: SelectOptionType
countrySelect: SelectOptionType
} & Pick<OrgInterface, 'name' | 'website' | 'description'>

export const AccountCreate = ({ children, ...props }: FlexProps) => {
const { t } = useTranslation()

const [isPending, setIsPending] = useState(false)
const { textColor, textColorBrand, textColorSecondary } = useDarkMode()
const { textColor, textColorSecondary } = useDarkMode()

const methods = useForm<FormData>()
const {
register,
handleSubmit,
formState: { errors },
} = methods
const { handleSubmit } = methods

const { signer } = useClient()

Expand All @@ -94,11 +52,6 @@ export const AccountCreate = ({ children, ...props }: FlexProps) => {

const error = saasError || accountError

const required = {
value: true,
message: t('form.error.field_is_required'),
}

const onSubmit = (values: FormData) => {
setIsPending(true)
// Create account on the saas to generate new priv keys
Expand Down Expand Up @@ -136,60 +89,9 @@ export const AccountCreate = ({ children, ...props }: FlexProps) => {
<Heading color={textColor} fontSize='36px' mb={4}>
<Trans i18nKey='create_org.title'>Create Your Organization</Trans>
</Heading>
<Text color={textColor} fontWeight='bold'>
<Trans i18nKey='create_org.public_info'>Public Organization Information</Trans>
</Text>
</Box>

<Flex flexDirection='column' gap={6} px={{ base: 5, md: 10 }}>
<InputCustom
formValue='name'
label={t('name', { defaultValue: 'Name' })}
placeholder={t('form.account_create.title_placeholder', {
defaultValue: "Enter your organization's emial",
})}
type='text'
required
/>
<InputCustom
formValue='website'
label={t('website', { defaultValue: 'Website' })}
placeholder={t('form.account_create.website_placeholder', {
defaultValue: 'https://example.com',
})}
type='text'
required
/>

<FormControl>
<FormLabel ms='4px' fontSize='sm' fontWeight='500' color={textColor}>
<Trans i18nKey='description'>Description</Trans>
</FormLabel>
<Textarea {...register('description')} placeholder={t('form.account_create.description_placeholder')} />
</FormControl>
</Flex>
<Box>
<Text color={textColor} fontWeight='bold' mb={4}>
<Trans i18nKey='create_org.private_org'>Private Organization Details</Trans>
</Text>
<Text color={textColorSecondary} fontSize='sm'>
<Trans i18nKey='create_org.private_org_description'>
Help us tailor your experience with information about your org. We won't share this info
</Trans>
</Text>
</Box>
<Flex px={{ base: 5, md: 10 }} direction={'column'} gap={6}>
<MembershipSizeTypesSelector name={'sizeSelect'} required />
<OrganzationTypesSelector name={'typeSelect'} required />
<CountriesTypesSelector name={'countrySelect'} required />
</Flex>

<CheckboxCustom
formValue='communications'
label={t('create_org.communication', {
defaultValue: ' I want to receive communications and be contacted to tailor my governance experience.',
})}
/>
<PublicOrgForm />
<PrivateOrgForm />
<Button form='process-create-form' type='submit' isLoading={isPending} mx='auto' mt={8} w='80%'>
{t('organization.create_org')}
</Button>
Expand Down
198 changes: 198 additions & 0 deletions src/components/AccountSaas/EditProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { AspectRatio, Box, Flex, FormControl, FormLabel, IconButton, Image, Input, Text } from '@chakra-ui/react'
import { Button } from '@vocdoni/chakra-components'
import { useClient } from '@vocdoni/react-providers'
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 { PrivateOrgForm, PrivateOrgFormData, PublicOrgForm } from '~components/AccountSaas/Layout'
import {
CustomizationLanguageSelector,
CustomizationTimeZoneSelector,
SelectOptionType,
} from '~components/Layout/SaasSelector'
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'>

const EditProfile = () => {
const { account } = useClient()
const { t } = useTranslation()

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 || '',
},
})

const { handleSubmit } = methods

const onSubmit: SubmitHandler<FormData> = async (values: FormData) => {
const newInfo = {
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,
}
console.log(values, newInfo)
}

return (
<FormProvider {...methods}>
<Box height='100%' maxH='100%' overflowY='auto'>
<Flex
as='form'
id='process-create-form'
direction='column'
gap={6}
maxW='600px'
mx='auto'
onSubmit={(e) => {
e.stopPropagation()
e.preventDefault()
handleSubmit(onSubmit)(e)
}}
>
<PublicOrgForm />
<PrivateOrgForm />
<CustomizeOrgForm />
<Flex justifyContent='center'>
<Button
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>
</Button>
</Flex>
</Flex>
</Box>
</FormProvider>
)
}

export type CustomOrgFormData = {
timeZoneSelect: SelectOptionType
languageSelect: SelectOptionType
}

const CustomizeOrgForm = () => {
const { t } = useTranslation()
const { textColor, textColorSecondary } = useDarkMode()
const { watch, setValue } = useForm<FormData>()

const avatar = watch('logo')
const correctAvatarFormat = (val: string) => REGEX_AVATAR.test(val)

return (
<>
<Box>
<Text color={textColor} fontWeight='bold'>
{t('customization', { defaultValue: 'Customization' })}
</Text>
<Text color={textColorSecondary} fontSize='sm'>
{t('edit_saas_profile.customization_details', {
defaultValue: 'Define the params that will enhance the user experience and customize the voting page',
})}
</Text>
</Box>
<Flex flexDirection='column' gap={6} px={{ base: 5, md: 10 }}>
<CustomizationTimeZoneSelector name={'timeZoneSelect'} required />
<CustomizationLanguageSelector name={'languageSelect'} required />
<FormControl>
<FormLabel display='flex' ms={1} fontSize='sm' fontWeight='500' color={textColor} mb={2}>
{t('logo', {
defaultValue: 'Logo',
})}
</FormLabel>
<Flex gap={2} alignItems='center'>
<Input
placeholder={t('edit_saas_profile.upload_file', {
defaultValue: 'Upload a file',
})}
/>
<Button bgColor='gray' minH={12} borderRadius='xl'>
{t('upload', { defaultValue: 'Upload' })}
</Button>
</Flex>
</FormControl>
<Box position='relative' outline='none' border='none'>
<Text fontSize='sm' fontWeight='500' color={textColor} mb={2}>
{t('edit_saas_profile.header_image', { defaultValue: 'Header Image' })}
</Text>
<Flex gap={2} flexDirection={{ base: 'column', md: 'row' }} alignItems='center'>
<AspectRatio flexShrink={0} flexGrow={1} ratio={5 / 1} borderRadius='xl' overflow='hidden'>
<Image src={avatar} fallbackSrc={fallback} />
</AspectRatio>
<Button
minH={12}
borderRadius='xl'
sx={{
span: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: 2,
},
}}
>
<BsFillTrashFill />
<Text as='span'>{t('remove', { defaultValue: 'Remove' })}</Text>
</Button>
</Flex>
{correctAvatarFormat(avatar) && (
<IconButton
aria-label={t('form.account_create.delete_image')}
icon={<BiTrash />}
onClick={() => setValue('logo', '')}
position='absolute'
top={2}
right={2}
cursor='pointer'
size='xs'
fontSize='md'
/>
)}
</Box>
</Flex>
</>
)
}

export default EditProfile
Loading

2 comments on commit d90b806

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.