diff --git a/app/(app)/profil/_layout.tsx b/app/(app)/profil/_layout.tsx
index 175f65600..65d73fe27 100644
--- a/app/(app)/profil/_layout.tsx
+++ b/app/(app)/profil/_layout.tsx
@@ -43,25 +43,7 @@ function CustomRouter() {
-
- {[1, 2, 3].map((x) => (
-
- ))}
-
- }
- >
-
-
+
diff --git a/app/(app)/profil/mot-de-passe.tsx b/app/(app)/profil/mot-de-passe.tsx
new file mode 100644
index 000000000..8d5d5a30b
--- /dev/null
+++ b/app/(app)/profil/mot-de-passe.tsx
@@ -0,0 +1,21 @@
+import React from 'react'
+import ProfilLayout from '@/components/layouts/ProfilLayout'
+import * as metatags from '@/config/metatags'
+import ChangePassScreen from '@/screens/profil/password/page'
+import Head from 'expo-router/head'
+
+function ChangePasswordScreen() {
+ return (
+ <>
+
+ {metatags.createTitle('Cotisation et dons')}
+
+
+
+
+
+ >
+ )
+}
+
+export default ChangePasswordScreen
diff --git a/src/components/layouts/ProfilLayout.tsx b/src/components/layouts/ProfilLayout.tsx
index 5945e92ff..f3fb03316 100644
--- a/src/components/layouts/ProfilLayout.tsx
+++ b/src/components/layouts/ProfilLayout.tsx
@@ -50,6 +50,24 @@ export default function ProfilLayout({ children }: { children: React.ReactNode }
) : (
- children
+
+ {[1, 2, 3].map((x) => (
+
+ ))}
+
+ }
+ >
+ {children}
+
)
}
diff --git a/src/screens/profil/menu/Menu.tsx b/src/screens/profil/menu/Menu.tsx
index 0d5377bb6..e5e937490 100644
--- a/src/screens/profil/menu/Menu.tsx
+++ b/src/screens/profil/menu/Menu.tsx
@@ -37,10 +37,11 @@ export const menuData: Array & { pathname?: Hre
children: 'Communications',
pathname: '/profil/communications',
},
- // {
- // icon: KeyRound,
- // children: 'Mot de passe',
- // },
+ {
+ icon: KeyRound,
+ children: 'Mot de passe',
+ pathname: '/profil/mot-de-passe',
+ },
// {
// icon: BadgeCheck,
// children: 'Certification du profil',
diff --git a/src/screens/profil/password/page.tsx b/src/screens/profil/password/page.tsx
new file mode 100644
index 000000000..8f826da12
--- /dev/null
+++ b/src/screens/profil/password/page.tsx
@@ -0,0 +1,164 @@
+import { useMemo } from 'react'
+import { KeyboardAvoidingView, Platform } from 'react-native'
+import Input from '@/components/base/Input/Input'
+import Text from '@/components/base/Text'
+import { VoxButton } from '@/components/Button'
+import PageLayout from '@/components/layouts/PageLayout/PageLayout'
+import VoxCard from '@/components/VoxCard/VoxCard'
+import { ProfilChangePasswordFormError } from '@/services/profile/error'
+import { usetPostChangePassword } from '@/services/profile/hook'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { Controller, useForm } from 'react-hook-form'
+import { isWeb, ScrollView, useMedia, XStack, YStack } from 'tamagui'
+import * as z from 'zod'
+
+const ChangePasswordSchema = z
+ .object({
+ old_password: z.string().min(1, "L'ancien mot de passe est requis"),
+ new_password: z
+ .string()
+ .min(8, 'Le mot de passe doit contenir au moins 8 caractères')
+ .regex(/[A-Z]/, 'Le mot de passe doit contenir au moins une lettre majuscule')
+ .regex(/[a-z]/, 'Le mot de passe doit contenir au moins une lettre minuscule')
+ .regex(/[!@#$%^&*()\-=+{}\|:;\"'<>,.?[\]\/\\]/, 'Le mot de passe doit contenir au moins un charactère spécial'),
+ new_password_confirmation: z.string().min(1, 'La confirmation du mot de passe est requise'),
+ })
+ .superRefine((data, ctx) => {
+ if (data.new_password !== data.new_password_confirmation) {
+ return ctx.addIssue({
+ path: ['new_password_confirmation'],
+ code: z.ZodIssueCode.custom,
+ message: 'Les mots de passe ne correspondent pas',
+ })
+ }
+ })
+
+export default function ChangePasswordScreen() {
+ const media = useMedia()
+ const scrollViewContainerStyle = useMemo(
+ () => ({
+ pt: media.gtSm ? '$8' : undefined,
+ pl: media.gtSm ? '$8' : undefined,
+ pr: media.gtSm ? '$8' : undefined,
+ pb: isWeb ? '$10' : '$12',
+ }),
+ [media],
+ )
+ const { control, formState, handleSubmit, reset, setError } = useForm({
+ resolver: zodResolver(ChangePasswordSchema),
+ mode: 'all',
+ defaultValues: {
+ old_password: '',
+ new_password: '',
+ new_password_confirmation: '',
+ },
+ })
+ const { isDirty, isValid } = formState
+ const { isPending, mutateAsync } = usetPostChangePassword()
+
+ const onSubmit = handleSubmit((data) => {
+ mutateAsync(data)
+ .then(() => {
+ reset()
+ })
+ .catch((e) => {
+ if (e instanceof ProfilChangePasswordFormError) {
+ e.violations.forEach((violation) => {
+ setError(violation.propertyPath, { message: violation.message })
+ })
+ }
+ })
+ })
+
+ return (
+
+
+
+
+
+ Modifier mon mot de passe
+ Vous devez renseigner votre mot de passe actuel pour changer de mot de passe.
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ Afin de garantir un minimum de sécurité sur les accès votre mot de passe doit contenir au moins :
+
+
+ •
+ 8 caractères minimum
+
+
+ •
+ Une lettre majuscule
+
+
+ •
+ Une lettre minuscule
+
+
+ •
+ Un caractère spécial
+
+
+
+
+
+ reset()}>
+ Annuler
+
+
+ Enregister
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/services/profile/api.ts b/src/services/profile/api.ts
index 0c3d9c42f..ba6e03da7 100644
--- a/src/services/profile/api.ts
+++ b/src/services/profile/api.ts
@@ -1,4 +1,9 @@
-import { profileElectDeclarationFormErrorThrower, profileElectPaymentFormErrorThrower, profileFormErrorThrower } from '@/services/profile/error'
+import {
+ profilChangePasswordFormErrorThrower,
+ profileElectDeclarationFormErrorThrower,
+ profileElectPaymentFormErrorThrower,
+ profileFormErrorThrower,
+} from '@/services/profile/error'
import * as schemas from '@/services/profile/schema'
import type * as Types from '@/services/profile/schema'
import { api } from '@/utils/api'
@@ -130,3 +135,11 @@ export const getTaxReceiptFile = (taxReceiptUuid: string) => {
},
})()
}
+export const postChangePassword = api({
+ method: 'POST',
+ path: '/api/v3/profile/me/password-change',
+ requestSchema: schemas.RestChangePasswordRequestSchema,
+ responseSchema: schemas.RestChangePasswordResponseSchema,
+ errorThrowers: [profilChangePasswordFormErrorThrower],
+ type: 'private',
+})
diff --git a/src/services/profile/error.ts b/src/services/profile/error.ts
index 98880352b..cacceecaa 100644
--- a/src/services/profile/error.ts
+++ b/src/services/profile/error.ts
@@ -1,9 +1,8 @@
import { z } from 'zod'
import { createFormErrorResponseSchema, createFormErrorThrower } from '../common/errors/form-errors'
-import { propertyPathDeclarationPaymentSchema, propertyPathElectPaymentSchema, propertyPathSchema } from './schema'
+import { propertyPathChangePasswordSchema, propertyPathDeclarationPaymentSchema, propertyPathElectPaymentSchema, propertyPathSchema } from './schema'
const profileFormErrorSchema = createFormErrorResponseSchema(propertyPathSchema)
-
export class ProfileFormError extends Error {
violations: z.infer['violations']
constructor(public errors: z.infer) {
@@ -11,11 +10,9 @@ export class ProfileFormError extends Error {
this.violations = errors.violations
}
}
-
export const profileFormErrorThrower = createFormErrorThrower(ProfileFormError, profileFormErrorSchema)
const profileElectPaymentFormErrorSchema = createFormErrorResponseSchema(propertyPathElectPaymentSchema)
-
export class profileElectPaymentFormError extends Error {
violations: z.infer['violations']
constructor(public errors: z.infer) {
@@ -23,11 +20,9 @@ export class profileElectPaymentFormError extends Error {
this.violations = errors.violations
}
}
-
export const profileElectPaymentFormErrorThrower = createFormErrorThrower(profileElectPaymentFormError, profileElectPaymentFormErrorSchema)
const profileElectDeclarationFormErrorSchema = createFormErrorResponseSchema(propertyPathDeclarationPaymentSchema)
-
export class profileElectDeclarationFormError extends Error {
violations: z.infer['violations']
constructor(public errors: z.infer) {
@@ -35,5 +30,14 @@ export class profileElectDeclarationFormError extends Error {
this.violations = errors.violations
}
}
-
export const profileElectDeclarationFormErrorThrower = createFormErrorThrower(profileElectDeclarationFormError, profileElectDeclarationFormErrorSchema)
+
+const profilChangePasswordFormErrorSchema = createFormErrorResponseSchema(propertyPathChangePasswordSchema)
+export class ProfilChangePasswordFormError extends Error {
+ violations: z.infer['violations']
+ constructor(public errors: z.infer) {
+ super('FormError')
+ this.violations = errors.violations
+ }
+}
+export const profilChangePasswordFormErrorThrower = createFormErrorThrower(ProfilChangePasswordFormError, profilChangePasswordFormErrorSchema)
diff --git a/src/services/profile/hook.ts b/src/services/profile/hook.ts
index 1dc16ed28..c5017068a 100644
--- a/src/services/profile/hook.ts
+++ b/src/services/profile/hook.ts
@@ -248,3 +248,17 @@ export const useGetTaxReceiptFile = () => {
},
})
}
+
+export const usetPostChangePassword = () => {
+ const toast = useToastController()
+ return useMutation({
+ mutationFn: api.postChangePassword,
+ onSuccess: () => {
+ toast.show('Succès', { message: 'Mot de passe modifié', type: 'success' })
+ },
+ onError: (e) => {
+ toast.show('Erreur', { message: 'Impossible de modifier le mot de passe', type: 'error' })
+ return e
+ },
+ })
+}
diff --git a/src/services/profile/schema.ts b/src/services/profile/schema.ts
index 44e1561e6..f930d9643 100644
--- a/src/services/profile/schema.ts
+++ b/src/services/profile/schema.ts
@@ -297,3 +297,15 @@ export const RestTaxReceiptsResponseSchema = z.array(
export const RestTaxReceiptFileRequestSchema = z.void()
export const RestTaxReceiptFileResponseSchema = z.any()
+
+export const RestChangePasswordRequestSchema = z.object({
+ old_password: z.string(),
+ new_password: z.string(),
+ new_password_confirmation: z.string(),
+})
+
+export type RestChangePasswordRequest = z.infer
+
+export const RestChangePasswordResponseSchema = z.any()
+
+export const propertyPathChangePasswordSchema = z.enum(['old_password', 'new_password', 'new_password_confirmation'])