1- import React , { useState } from 'react' ;
1+ import React , { useMemo , useState } from 'react' ;
22
33import { isAllUndefined } from '@douglasneuroinformatics/libjs' ;
4+ import { estimatePasswordStrength } from '@douglasneuroinformatics/libpasswd' ;
45import { Button , Dialog , Form } from '@douglasneuroinformatics/libui/components' ;
56import { useTranslation } from '@douglasneuroinformatics/libui/hooks' ;
67import type { FormTypes } from '@opendatacapture/runtime-core' ;
@@ -9,34 +10,12 @@ import type { UserPermission } from '@opendatacapture/schemas/user';
910import type { Promisable } from 'type-fest' ;
1011import { z } from 'zod' ;
1112
12- const $UpdateUserFormData = z
13- . object ( {
14- additionalPermissions : z . array ( $UserPermission . partial ( ) ) . optional ( ) ,
15- groupIds : z . set ( z . string ( ) )
16- } )
17- . transform ( ( arg ) => {
18- const firstPermission = arg . additionalPermissions ?. [ 0 ] ;
19- if ( firstPermission && isAllUndefined ( firstPermission ) ) {
20- arg . additionalPermissions ?. pop ( ) ;
21- }
22- return arg ;
23- } )
24- . superRefine ( ( arg , ctx ) => {
25- arg . additionalPermissions ?. forEach ( ( permission , i ) => {
26- Object . entries ( permission ) . forEach ( ( [ key , val ] ) => {
27- if ( ( val satisfies string ) === undefined ) {
28- ctx . addIssue ( {
29- code : z . ZodIssueCode . invalid_type ,
30- expected : 'string' ,
31- path : [ 'additionalPermissions' , i , key ] ,
32- received : 'undefined'
33- } ) ;
34- }
35- } ) ;
36- } ) ;
37- } ) ;
38-
39- type UpdateUserFormData = z . infer < typeof $UpdateUserFormData > ;
13+ type UpdateUserFormData = {
14+ additionalPermissions ?: Partial < UserPermission > [ ] ;
15+ confirmPassword ?: string | undefined ;
16+ groupIds : Set < string > ;
17+ password ?: string | undefined ;
18+ } ;
4019
4120export type UpdateUserFormInputData = {
4221 disableDelete : boolean ;
@@ -52,9 +31,48 @@ export const UpdateUserForm: React.FC<{
5231 onSubmit : ( data : UpdateUserFormData & { additionalPermissions ?: UserPermission [ ] } ) => Promisable < void > ;
5332} > = ( { data, onDelete, onSubmit } ) => {
5433 const { disableDelete, groupOptions, initialValues } = data ;
55- const { t } = useTranslation ( ) ;
34+ const { resolvedLanguage , t } = useTranslation ( ) ;
5635 const [ isConfirmDeleteOpen , setIsConfirmDeleteOpen ] = useState ( false ) ;
5736
37+ const $UpdateUserFormData = useMemo ( ( ) => {
38+ return z
39+ . object ( {
40+ additionalPermissions : z . array ( $UserPermission . partial ( ) ) . optional ( ) ,
41+ groupIds : z . set ( z . string ( ) ) ,
42+ password : z . string ( ) . min ( 1 ) . optional ( )
43+ } )
44+ . transform ( ( arg ) => {
45+ const firstPermission = arg . additionalPermissions ?. [ 0 ] ;
46+ if ( firstPermission && isAllUndefined ( firstPermission ) ) {
47+ arg . additionalPermissions ?. pop ( ) ;
48+ }
49+ return arg ;
50+ } )
51+ . superRefine ( ( arg , ctx ) => {
52+ if ( arg . password && ! estimatePasswordStrength ( arg . password ) . success ) {
53+ ctx . addIssue ( {
54+ code : z . ZodIssueCode . custom ,
55+ fatal : true ,
56+ message : t ( 'common.insufficientPasswordStrength' ) ,
57+ path : [ 'password' ]
58+ } ) ;
59+ return z . NEVER ;
60+ }
61+ arg . additionalPermissions ?. forEach ( ( permission , i ) => {
62+ Object . entries ( permission ) . forEach ( ( [ key , val ] ) => {
63+ if ( ( val satisfies string ) === undefined ) {
64+ ctx . addIssue ( {
65+ code : z . ZodIssueCode . invalid_type ,
66+ expected : 'string' ,
67+ path : [ 'additionalPermissions' , i , key ] ,
68+ received : 'undefined'
69+ } ) ;
70+ }
71+ } ) ;
72+ } ) ;
73+ } ) satisfies z . ZodType < UpdateUserFormData > ;
74+ } , [ resolvedLanguage ] ) ;
75+
5876 return (
5977 < Dialog open = { isConfirmDeleteOpen } onOpenChange = { setIsConfirmDeleteOpen } >
6078 < Form
@@ -68,6 +86,22 @@ export const UpdateUserForm: React.FC<{
6886 )
6987 } }
7088 content = { [
89+ {
90+ fields : {
91+ password : {
92+ calculateStrength : ( password ) => {
93+ return estimatePasswordStrength ( password ) . score ;
94+ } ,
95+ kind : 'string' ,
96+ label : t ( 'common.password' ) ,
97+ variant : 'password'
98+ }
99+ } ,
100+ title : t ( {
101+ en : 'Login Credentials' ,
102+ fr : 'Identifiants de connexion'
103+ } )
104+ } ,
71105 {
72106 description : t ( {
73107 en : 'IMPORTANT: These permissions are not specific to any group. To manage granular permissions, please use the API.' ,
0 commit comments