From 4390816744740824b1a4677931f6d8f28223856d Mon Sep 17 00:00:00 2001 From: Antonin Date: Mon, 23 Sep 2024 18:10:51 +0200 Subject: [PATCH] refactor(profile): information personnel (#864) --- src/components/Cards/AlertCard/AlertCard.tsx | 2 +- src/components/Chip/Chip.tsx | 2 +- .../account/form/AbstractProfilForm.tsx | 73 ++++ .../profil/account/form/AccountForm.tsx | 346 ------------------ .../profil/account/form/ContactForm.tsx | 95 +++++ .../account/form/ForceBirthdateModal.tsx | 68 ++++ .../profil/account/form/InformationForm.tsx | 150 ++++++++ .../profil/account/form/LocationForm.tsx | 151 ++++++++ src/screens/profil/account/form/RSForm.tsx | 60 +++ src/screens/profil/account/form/schema.ts | 58 +++ src/screens/profil/account/page.tsx | 45 ++- .../profil/elu/components/DeclaForm.tsx | 2 +- .../profil/elu/components/IbanForm.tsx | 2 +- src/screens/profil/menu/Menu.tsx | 4 +- src/services/profile/schema.ts | 13 +- 15 files changed, 699 insertions(+), 372 deletions(-) create mode 100644 src/screens/profil/account/form/AbstractProfilForm.tsx delete mode 100644 src/screens/profil/account/form/AccountForm.tsx create mode 100644 src/screens/profil/account/form/ContactForm.tsx create mode 100644 src/screens/profil/account/form/ForceBirthdateModal.tsx create mode 100644 src/screens/profil/account/form/InformationForm.tsx create mode 100644 src/screens/profil/account/form/LocationForm.tsx create mode 100644 src/screens/profil/account/form/RSForm.tsx diff --git a/src/components/Cards/AlertCard/AlertCard.tsx b/src/components/Cards/AlertCard/AlertCard.tsx index 5b9be5efa..612feb585 100644 --- a/src/components/Cards/AlertCard/AlertCard.tsx +++ b/src/components/Cards/AlertCard/AlertCard.tsx @@ -20,7 +20,7 @@ const AlertCard = ({ payload, ...props }: AlertVoxCardProps) => { - {payload.label} + {payload.label} {payload.title} {payload.description} diff --git a/src/components/Chip/Chip.tsx b/src/components/Chip/Chip.tsx index 65c17b25d..fcd67a9f6 100644 --- a/src/components/Chip/Chip.tsx +++ b/src/components/Chip/Chip.tsx @@ -15,7 +15,7 @@ export type ChipProps = { const Chip = ({ children, ...props }: ChipProps) => { return ( - + {children} diff --git a/src/screens/profil/account/form/AbstractProfilForm.tsx b/src/screens/profil/account/form/AbstractProfilForm.tsx new file mode 100644 index 000000000..049bf9f0c --- /dev/null +++ b/src/screens/profil/account/form/AbstractProfilForm.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import { VoxButton } from '@/components/Button' +import VoxCard from '@/components/VoxCard/VoxCard' +import { ProfileFormError } from '@/services/profile/error' +import { useMutationUpdateProfil } from '@/services/profile/hook' +import { ErrorMonitor } from '@/utils/ErrorMonitor' +import { zodResolver } from '@hookform/resolvers/zod' +import { has } from 'lodash' +import { Control, DefaultValues, FieldValues, FormState, Path, useForm } from 'react-hook-form' +import { XStack } from 'tamagui' +import * as z from 'zod' + +const isPathExist = (path: S, obj: DefaultValues): path is Path => { + return has(obj, path) +} + +const AbstractForm = , TF extends FieldValues>(props: { + defaultValues: DefaultValues + uuid: string + validatorSchema: T + onErrors?: (errors: FormState['errors']) => void + children: (props: { control: Control; formState: FormState }) => React.ReactNode +}) => { + const { control, handleSubmit, formState, reset, setError } = useForm({ + resolver: zodResolver(props.validatorSchema), + defaultValues: props.defaultValues, + mode: 'all', + }) + + React.useEffect(() => { + if (props.onErrors) { + props.onErrors(formState.errors) + } + }, [formState.errors]) + + const { mutateAsync, isPending } = useMutationUpdateProfil({ userUuid: props.uuid }) + + const onSubmit = handleSubmit((data) => { + mutateAsync(data) + .then(() => { + reset() + }) + .catch((e) => { + if (e instanceof ProfileFormError) { + e.violations.forEach((violation) => { + if (isPathExist(violation.propertyPath, props.defaultValues)) { + setError(violation.propertyPath, { message: violation.message }) + } else { + ErrorMonitor.log('Unknown property path / profil form', violation) + } + }) + } + }) + }) + + return ( + + + {props.children({ control, formState })} + + reset()}> + Annuler + + + Enregister + + + + + ) +} + +export default AbstractForm diff --git a/src/screens/profil/account/form/AccountForm.tsx b/src/screens/profil/account/form/AccountForm.tsx deleted file mode 100644 index 13bdc34a6..000000000 --- a/src/screens/profil/account/form/AccountForm.tsx +++ /dev/null @@ -1,346 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' -import { TouchableOpacity } from 'react-native' -import { Button } from '@/components' -import AddressAutocomplete from '@/components/AddressAutoComplete/AddressAutocomplete' -import Input from '@/components/base/Input/Input' -import Select from '@/components/base/Select/Select' -import Text from '@/components/base/Text' -import { VoxButton } from '@/components/Button' -import CountrySelect from '@/components/CountrySelect/CountrySelect' -import DatePickerField from '@/components/DatePicker' -import NationalitySelect from '@/components/NationalitySelect/NationalitySelect' -import { ProfileFormError } from '@/services/profile/error' -import { useMutationUpdateProfil } from '@/services/profile/hook' -import { RestDetailedProfileResponse } from '@/services/profile/schema' -import isoToEmoji from '@/utils/isoToEmoji' -import { zodResolver } from '@hookform/resolvers/zod' -import { getCountryCodeForRegionCode, getSupportedRegionCodes } from 'awesome-phonenumber' -import { Controller, useForm } from 'react-hook-form' -import { PortalItem, Spinner, useMedia, View, XStack } from 'tamagui' -import { validateAccountFormSchema } from './schema' - -const phoneCodes = getSupportedRegionCodes().map((code) => { - return { - value: code, - label: `${isoToEmoji(code)} +${getCountryCodeForRegionCode(code)}`, - } -}) - -const socialPlatforms = [ - { id: 'facebook', placeholder: 'facebook.com/nom-utilisateur', label: 'Facebook', starturl: 'https://facebook.com/' }, - { id: 'telegram', placeholder: 't.me/nom-utilisateur', label: 'Telegram', starturl: 'https://t.me/' }, - { id: 'instagram', placeholder: 'instagram.com/nom-utilisateur', label: 'Instagram', starturl: 'https://instagram.com/' }, - { id: 'twitter', placeholder: 'twitter.com/nom-utilisateur', label: 'Twitter', starturl: 'https://twitter.com/' }, - { id: 'linkedin', placeholder: 'linkedin.com/nom-utilisateur', label: 'Linkedin', starturl: 'https://linkedin.com/in/' }, -] as const - -export const AccountForm = ({ profile }: { profile: RestDetailedProfileResponse }) => { - const { control, handleSubmit, formState, reset, setError } = useForm({ - resolver: zodResolver(validateAccountFormSchema), - defaultValues: profile, - }) - - const media = useMedia() - - const $updateProfile = useMutationUpdateProfil({ - userUuid: profile!.uuid, - }) - - const onSubmit = handleSubmit((data) => { - $updateProfile - .mutateAsync(data) - .then(() => { - reset(data) - }) - .catch((e) => { - if (e instanceof ProfileFormError) { - e.violations.forEach((violation) => { - setError(violation.propertyPath, { message: violation.message }) - }) - } - }) - }) - - const [manualAddress, setManualAddress] = useState(false) - - useEffect(() => { - if (formState.errors.post_address) { - setManualAddress(true) - } - }, [formState.errors.post_address]) - - const onManualAddressToggle = useCallback(() => { - setManualAddress((v) => !v) - }, []) - - const ButtonSave = (props: React.ComponentProps) => ( - - Enregistrer - - ) - - return ( - - - Mes informations - - - - Identité{' '} - {profile?.certified && ( - - (Certifié) - - )} - - ( - - )} - /> - - - - ( - - )} - /> - - - - - - ( - - )} - /> - - - - ( - - )} - /> - - - - - Coordonnées - - - ( - - )} - /> - - ( - - - onChange({ number: x, country: value?.country })} - error={error?.message} - /> - - - )} - /> - - {manualAddress ? ( - - ( - - )} - /> - - ( - - )} - /> - - ( - - )} - /> - - ( - - )} - /> - - ) : ( - ( - - onChange({ - address: x.address, - city_name: x.city, - postal_code: x.postalCode, - country: x.country, - }) - } - error={error?.message} - /> - )} - /> - )} - - - {manualAddress ? ( - - Revenir à une saisie simplifiée - - ) : ( - <> - - Un problème ? - - - Cliquez ici pour saisir manuellement votre adresse. - - - )} - - - - Réseaux sociaux - - - {socialPlatforms.map((platform) => ( - ( - - )} - /> - ))} - - {media.gtMd && formState.isDirty && ( - - - - )} - - {[media.md, formState.isDirty].every(Boolean) ? ( - - - Enregistrer - - - ) : null} - - - ) -} diff --git a/src/screens/profil/account/form/ContactForm.tsx b/src/screens/profil/account/form/ContactForm.tsx new file mode 100644 index 000000000..bfc255e7c --- /dev/null +++ b/src/screens/profil/account/form/ContactForm.tsx @@ -0,0 +1,95 @@ +import Input from '@/components/base/Input/Input' +import Select from '@/components/base/Select/Select' +import Text from '@/components/base/Text' +import VoxCard from '@/components/VoxCard/VoxCard' +import { RestDetailedProfileResponse } from '@/services/profile/schema' +import isoToEmoji from '@/utils/isoToEmoji' +import { Info } from '@tamagui/lucide-icons' +import { getCountryCodeForRegionCode, getSupportedRegionCodes } from 'awesome-phonenumber' +import { Controller } from 'react-hook-form' +import { View, XStack } from 'tamagui' +import AbstractProfilForm from './AbstractProfilForm' +import { validateCoordFormSchema } from './schema' + +const phoneCodes = getSupportedRegionCodes().map((code) => { + return { + value: code, + label: `${isoToEmoji(code)} +${getCountryCodeForRegionCode(code)}`, + } +}) + +const ContactForm = ({ profile }: { profile: RestDetailedProfileResponse }) => { + return ( + + {({ control }) => ( + <> + Contact + {profile.change_email_token?.email ? ( + + + + + + + + Confirmez le changement de votre email en cliquant sur le lien que vous venez de recevoir sur « {profile.change_email_token.email} ». Il est + actif pour 24h. + + + + + ) : null} + ( + + )} + /> + + ( + + + onChange({ number: x, country: value?.country })} + error={error?.message} + /> + + + )} + /> + + )} + + ) +} + +export default ContactForm diff --git a/src/screens/profil/account/form/ForceBirthdateModal.tsx b/src/screens/profil/account/form/ForceBirthdateModal.tsx new file mode 100644 index 000000000..55b85ab3d --- /dev/null +++ b/src/screens/profil/account/form/ForceBirthdateModal.tsx @@ -0,0 +1,68 @@ +import { Fragment } from 'react' +import Text from '@/components/base/Text' +import DatePickerField from '@/components/DatePicker' +import ModalOrPageBase from '@/components/ModalOrPageBase/ModalOrPageBase' +import VoxCard from '@/components/VoxCard/VoxCard' +import { RestDetailedProfileResponse } from '@/services/profile/schema' +import { Info } from '@tamagui/lucide-icons' +import { Controller } from 'react-hook-form' +import { View, XStack } from 'tamagui' +import * as z from 'zod' +import AbstractProfilForm from './AbstractProfilForm' +import { validateBirthdateFormSchema } from './schema' + +const ForceBirthdateModal = ({ profile }: { profile: RestDetailedProfileResponse }) => { + return ( + }> + + {({ control }) => ( + + + + + + + + + Pour accéder à votre profil, veuillez renseigner votre date de naissance. + + + + + + + ( + + )} + /> + + + + )} + + + ) +} + +export default ForceBirthdateModal diff --git a/src/screens/profil/account/form/InformationForm.tsx b/src/screens/profil/account/form/InformationForm.tsx new file mode 100644 index 000000000..5a6c18072 --- /dev/null +++ b/src/screens/profil/account/form/InformationForm.tsx @@ -0,0 +1,150 @@ +import { Fragment } from 'react' +import Input from '@/components/base/Input/Input' +import Select from '@/components/base/Select/Select' +import Text from '@/components/base/Text' +import DatePickerField from '@/components/DatePicker' +import NationalitySelect from '@/components/NationalitySelect/NationalitySelect' +import VoxCard from '@/components/VoxCard/VoxCard' +import { RestDetailedProfileResponse } from '@/services/profile/schema' +import { Info } from '@tamagui/lucide-icons' +import { Controller } from 'react-hook-form' +import { View, XStack } from 'tamagui' +import AbstractProfilForm from './AbstractProfilForm' +import { validateInformationsFormSchema } from './schema' + +const InformationsForm = ({ profile }: { profile: RestDetailedProfileResponse }) => { + return ( + + {({ control }) => ( + + {profile.certified && ( + + + + + + + + Votre profil étant certifié, vous ne pouvez pas modifier vos informations d’identité. + + + + + )} + Identité + + ( + + )} + /> + + + + ( + + )} + /> + + + + + ( + + )} + /> + + + + ( + + )} + /> + + + + + )} + + ) +} + +export default InformationsForm diff --git a/src/screens/profil/account/form/LocationForm.tsx b/src/screens/profil/account/form/LocationForm.tsx new file mode 100644 index 000000000..5656ea16d --- /dev/null +++ b/src/screens/profil/account/form/LocationForm.tsx @@ -0,0 +1,151 @@ +import { Fragment, useCallback, useEffect, useState } from 'react' +import AddressAutocomplete from '@/components/AddressAutoComplete/AddressAutocomplete' +import Input from '@/components/base/Input/Input' +import Text from '@/components/base/Text' +import { VoxButton } from '@/components/Button' +import CountrySelect from '@/components/CountrySelect/CountrySelect' +import { RestDetailedProfileResponse } from '@/services/profile/schema' +import { Controller, useForm } from 'react-hook-form' +import { View, YStack } from 'tamagui' +import AbstractProfilForm from './AbstractProfilForm' +import { validateLocationFormSchema } from './schema' + +export const LocationForm = ({ profile }: { profile: RestDetailedProfileResponse }) => { + const [manualAddress, setManualAddress] = useState(false) + + const onManualAddressToggle = useCallback(() => { + setManualAddress((v) => !v) + }, []) + + return ( + { + console.log(errors) + if (errors.post_address) { + setManualAddress(true) + } + }} + > + {({ control }) => ( + + + Localisation + + + + Votre localisation détermine votre Assemblée départementale et votre circonscription legislative. Elle présellectionne également votre comité, mais + vous pouvez en changer dans la limite du périmètre de votre Assemblée départementale. + + + Si vous êtes adhérent, c’est sur cette adresse que vous recevrez votre relevé fiscal. + + + {manualAddress ? ( + + ( + + )} + /> + + ( + + )} + /> + + ( + + )} + /> + + ( + + )} + /> + + ) : ( + ( + + onChange({ + address: x.address, + city_name: x.city, + postal_code: x.postalCode, + country: x.country, + }) + } + error={ + errorObject?.address || errorObject?.city_name || errorObject?.postal_code || errorObject?.country + ? 'Veuillez saisir une adresse valide (passez en manuel si besoin)' + : error?.message + } + /> + )} + /> + )} + + + {manualAddress ? ( + + Revenir à la saisie simplifiée. + + ) : ( + <> + Un problème ? + + Saisir manuellement votre adresse. + + + )} + + + )} + + ) +} + +export default LocationForm diff --git a/src/screens/profil/account/form/RSForm.tsx b/src/screens/profil/account/form/RSForm.tsx new file mode 100644 index 000000000..4fbbb1967 --- /dev/null +++ b/src/screens/profil/account/form/RSForm.tsx @@ -0,0 +1,60 @@ +import { Fragment } from 'react' +import Input from '@/components/base/Input/Input' +import Text from '@/components/base/Text' +import { RestDetailedProfileResponse } from '@/services/profile/schema' +import { Controller, useForm } from 'react-hook-form' +import { View } from 'tamagui' +import AbstractForm from './AbstractProfilForm' +import { validateRSFormSchema } from './schema' + +const socialPlatforms = [ + { id: 'facebook', placeholder: 'facebook.com/nom-utilisateur', label: 'Facebook', starturl: 'https://facebook.com/' }, + { id: 'telegram', placeholder: 't.me/nom-utilisateur', label: 'Telegram', starturl: 'https://t.me/' }, + { id: 'instagram', placeholder: 'instagram.com/nom-utilisateur', label: 'Instagram', starturl: 'https://instagram.com/' }, + { id: 'twitter', placeholder: 'twitter.com/nom-utilisateur', label: 'Twitter', starturl: 'https://twitter.com/' }, + { id: 'linkedin', placeholder: 'linkedin.com/nom-utilisateur', label: 'Linkedin', starturl: 'https://linkedin.com/in/' }, +] as const + +export const RSForm = ({ profile }: { profile: RestDetailedProfileResponse }) => { + return ( + + {({ control }) => ( + + Réseaux sociaux + + {socialPlatforms.map((platform) => ( + ( + + )} + /> + ))} + + )} + + ) +} + +export default RSForm diff --git a/src/screens/profil/account/form/schema.ts b/src/screens/profil/account/form/schema.ts index 620b6ddd0..f882eb813 100644 --- a/src/screens/profil/account/form/schema.ts +++ b/src/screens/profil/account/form/schema.ts @@ -10,6 +10,64 @@ const requiredString = (start: string) => z.string().min(1, `${start} est obliga const buildLinkError = (start: string) => `le lien ${start} n’est pas valide.` +export const validateBirthdateFormSchema = z.date().refine((birthdate) => isBefore(birthdate, subYears(new Date(), 15)), { + message: "L'âge doit être d'au moins 15 ans.", +}) + +export const validateInformationsFormSchema = z.object({ + first_name: requiredString('Le prénom'), + last_name: requiredString('Le nom'), + gender: z.enum(['male', 'female', 'other'], buildReqError('Le genre')), + nationality: z.string().length(2, 'Le code pays doit être de deux lettres').optional(), + birthdate: validateBirthdateFormSchema, +}) + +export const validateCoordFormSchema = z.object({ + phone: z + .object({ + country: z.string(buildReqError('Le code pays')).refine((x) => getCountryCodeForRegionCode(x) !== undefined, { + message: 'Le code pays doit être de deux lettres', + }), + number: requiredString('Le numéro de téléphone'), + }) + .refine( + ({ country, number }) => { + return parsePhoneNumber(number, { regionCode: country }).valid + }, + { + message: 'Le numéro de téléphone n’est pas valide', + }, + ), + email_address: z.string().email('L’adresse email n’est pas valide'), +}) + +export const validateLocationFormSchema = z.object({ + post_address: z + .object({ + address: requiredString('L’adresse'), + postal_code: z.string(), + city_name: requiredString('La ville'), + country: requiredString('Le pays'), + }) + .superRefine(({ postal_code, country }, ctx) => { + if (country === 'FR' && !postal_code) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['postal_code'], + message: 'Le code postal est obligatoire pour la France', + }) + } + }), +}) + +export const validateRSFormSchema = z.object({ + facebook_page_url: z.string().url(buildLinkError('facebook')).optional().nullable(), + twitter_page_url: z.string().url(buildLinkError('twitter')).optional().nullable(), + linkedin_page_url: z.string().url(buildLinkError('linkedin')).optional().nullable(), + instagram_page_url: z.string().url(buildLinkError('instagram')).optional().nullable(), + telegram_page_url: z.string().url(buildLinkError('telegram')).optional().nullable(), +}) + export const validateAccountFormSchema = z.object({ first_name: requiredString('Le prénom'), last_name: requiredString('Le nom'), diff --git a/src/screens/profil/account/page.tsx b/src/screens/profil/account/page.tsx index c50ffe1f2..10b27e1ed 100644 --- a/src/screens/profil/account/page.tsx +++ b/src/screens/profil/account/page.tsx @@ -6,14 +6,17 @@ import PageLayout from '@/components/layouts/PageLayout/PageLayout' import VoxCard from '@/components/VoxCard/VoxCard' import clientEnv from '@/config/clientEnv' import { useSession } from '@/ctx/SessionProvider' -import { AccountForm } from '@/screens/profil/account/form/AccountForm' import { AlertUtils } from '@/screens/shared/AlertUtils' import { useDeleteProfil, useGetDetailProfil } from '@/services/profile/hook' import { useUserStore } from '@/store/user-store' import { nativeBuildVersion } from 'expo-application' import Constants from 'expo-constants' -import * as WebBrowser from 'expo-web-browser' import { isWeb, ScrollView, useMedia, YStack } from 'tamagui' +import ContactForm from './form/ContactForm' +import ForceBirthdateModal from './form/ForceBirthdateModal' +import InformationsForm from './form/InformationForm' +import LocationForm from './form/LocationForm' +import RSForm from './form/RSForm' const EditInformations = () => { const media = useMedia() @@ -52,24 +55,30 @@ const EditInformations = () => { return ( + - - - - - Version: v{Constants.expoConfig?.version ?? '0.0.0'} [{isWeb ? '???' : nativeBuildVersion} - {clientEnv.ENVIRONMENT}] - - - - {credentials?.isAdmin ? 'Quitter l’impersonnification' : 'Me déconnecter'} - - - {isAdherent ? 'Supprimer mon compte' : 'Supprimer mon compte'} - - - - + + + + + + + + + Version: v{Constants.expoConfig?.version ?? '0.0.0'} [{isWeb ? '???' : nativeBuildVersion} - {clientEnv.ENVIRONMENT}] + + + + {credentials?.isAdmin ? 'Quitter l’impersonnification' : 'Me déconnecter'} + + + {isAdherent ? 'Supprimer mon compte' : 'Supprimer mon compte'} + + + + + diff --git a/src/screens/profil/elu/components/DeclaForm.tsx b/src/screens/profil/elu/components/DeclaForm.tsx index 7849e9cb5..3dc62234d 100644 --- a/src/screens/profil/elu/components/DeclaForm.tsx +++ b/src/screens/profil/elu/components/DeclaForm.tsx @@ -28,7 +28,7 @@ export default function (props: Props) { revenue_amount: props.declaration ?? 0, }, resolver: zodResolver(schema), - mode: 'onChange', + mode: 'all', }) const { mutateAsync, isPending } = usePostElectDeclaration() diff --git a/src/screens/profil/elu/components/IbanForm.tsx b/src/screens/profil/elu/components/IbanForm.tsx index 7195afbc6..e8ff2a893 100644 --- a/src/screens/profil/elu/components/IbanForm.tsx +++ b/src/screens/profil/elu/components/IbanForm.tsx @@ -73,7 +73,7 @@ export default function (props: Props) { account_country: 'FR', }, resolver: zodResolver(schema), - mode: 'onChange', + mode: 'all', }) const { mutateAsync, isPending } = usePostElectPayment() diff --git a/src/screens/profil/menu/Menu.tsx b/src/screens/profil/menu/Menu.tsx index fc3b1066f..954bfd996 100644 --- a/src/screens/profil/menu/Menu.tsx +++ b/src/screens/profil/menu/Menu.tsx @@ -1,6 +1,6 @@ import { ComponentProps } from 'react' import Menu from '@/components/menu/Menu' -import { BadgeCheck, CircleUser, HelpingHand, KeyRound, Mail, MessageCircle, PlusCircle, Settings, TreeDeciduous } from '@tamagui/lucide-icons' +import { BadgeCheck, CircleUser, HelpingHand, KeyRound, Mail, MessageCircle, PlusCircle, Settings2, TreeDeciduous } from '@tamagui/lucide-icons' import { Href, Link, usePathname } from 'expo-router' import { isWeb, useMedia } from 'tamagui' @@ -15,7 +15,7 @@ export const menuData: Array & { pathname?: Hre pathname: '/profil/cotisation-et-dons', }, { - icon: Settings, + icon: Settings2, children: 'Information personnelles', pathname: '/profil/informations-personnelles', }, diff --git a/src/services/profile/schema.ts b/src/services/profile/schema.ts index 72c63b351..1209d1a5a 100644 --- a/src/services/profile/schema.ts +++ b/src/services/profile/schema.ts @@ -16,7 +16,7 @@ export const RestProfilResponseSchema = z.object({ postal_code: z.string(), email_address: z.string().email(), cadre_access: z.boolean(), - cadre_auth_path: z.string().nullable(), + cadre_auth_path: z.string().nullish(), certified: z.boolean(), country: z.string(), image_url: z.string().url().nullish(), @@ -49,7 +49,7 @@ export const RestDetailedProfileResponseSchema = z.object({ first_name: z.string(), last_name: z.string(), gender: z.string(), - custom_gender: z.string().nullable(), + custom_gender: z.string().nullish(), nationality: z.string(), birthdate: z.coerce.date().nullable(), last_membership_donation: z.coerce.date().nullable(), @@ -65,6 +65,13 @@ export const RestDetailedProfileResponseSchema = z.object({ }) .nullable() .optional(), + change_email_token: z + .object({ + email: z.string(), + uuid: z.string(), + expired_at: z.coerce.date(), + }) + .nullish(), email_address: z.string().email(), facebook_page_url: z.string().nullable().optional(), twitter_page_url: z.string().nullable().optional(), @@ -165,6 +172,8 @@ export const propertyPathSchema = z.enum([ 'telegram_page_url', ]) +export type ProfileUpdatePropertyPath = z.infer + export const RestUpdateProfileResponseSchema = z.string() export type RestUpdateProfileResponse = z.infer