keycloakify 6.12.6 → 6.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/pages/shared/UserProfileCommons.d.ts +46 -4
- package/lib/pages/shared/UserProfileCommons.js +336 -15
- package/lib/pages/shared/UserProfileCommons.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/useFormValidationSlice.d.ts +30 -28
- package/lib/useFormValidationSlice.js +104 -102
- package/lib/useFormValidationSlice.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/pages/shared/UserProfileCommons.tsx +510 -36
- package/src/lib/useFormValidationSlice.tsx +174 -172
@@ -6,6 +6,180 @@ import { useConstCallback } from "./tools/useConstCallback";
|
|
6
6
|
import { id } from "tsafe/id";
|
7
7
|
import { emailRegexp } from "./tools/emailRegExp";
|
8
8
|
|
9
|
+
/** @deprecated: Will be removed in the next major. Use this instead:
|
10
|
+
* import { useFormValidation } from "keycloakify/lib/pages/shares/UserProfileCommons";
|
11
|
+
*
|
12
|
+
* The API is the same only the returned value formValidationReducer have been renamed formValidationDispatch
|
13
|
+
* (a it should have been named from the beginning 😬)
|
14
|
+
*/
|
15
|
+
export function useFormValidationSlice(params: {
|
16
|
+
kcContext: {
|
17
|
+
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
18
|
+
profile: {
|
19
|
+
attributes: Attribute[];
|
20
|
+
};
|
21
|
+
passwordRequired?: boolean;
|
22
|
+
realm: { registrationEmailAsUsername: boolean };
|
23
|
+
};
|
24
|
+
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
25
|
+
passwordValidators?: Validators;
|
26
|
+
i18n: I18nBase;
|
27
|
+
}) {
|
28
|
+
const {
|
29
|
+
kcContext,
|
30
|
+
passwordValidators = {
|
31
|
+
"length": {
|
32
|
+
"ignore.empty.value": true,
|
33
|
+
"min": "4"
|
34
|
+
}
|
35
|
+
},
|
36
|
+
i18n
|
37
|
+
} = params;
|
38
|
+
|
39
|
+
const attributesWithPassword = useMemo(
|
40
|
+
() =>
|
41
|
+
!kcContext.passwordRequired
|
42
|
+
? kcContext.profile.attributes
|
43
|
+
: (() => {
|
44
|
+
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
|
45
|
+
|
46
|
+
return kcContext.profile.attributes.reduce<Attribute[]>(
|
47
|
+
(prev, curr) => [
|
48
|
+
...prev,
|
49
|
+
...(curr.name !== name
|
50
|
+
? [curr]
|
51
|
+
: [
|
52
|
+
curr,
|
53
|
+
id<Attribute>({
|
54
|
+
"name": "password",
|
55
|
+
"displayName": id<`\${${MessageKeyBase}}`>("${password}"),
|
56
|
+
"required": true,
|
57
|
+
"readOnly": false,
|
58
|
+
"validators": passwordValidators,
|
59
|
+
"annotations": {},
|
60
|
+
"groupAnnotations": {},
|
61
|
+
"autocomplete": "new-password"
|
62
|
+
}),
|
63
|
+
id<Attribute>({
|
64
|
+
"name": "password-confirm",
|
65
|
+
"displayName": id<`\${${MessageKeyBase}}`>("${passwordConfirm}"),
|
66
|
+
"required": true,
|
67
|
+
"readOnly": false,
|
68
|
+
"validators": {
|
69
|
+
"_compareToOther": {
|
70
|
+
"name": "password",
|
71
|
+
"ignore.empty.value": true,
|
72
|
+
"shouldBe": "equal",
|
73
|
+
"error-message": id<`\${${MessageKeyBase}}`>("${invalidPasswordConfirmMessage}")
|
74
|
+
}
|
75
|
+
},
|
76
|
+
"annotations": {},
|
77
|
+
"groupAnnotations": {},
|
78
|
+
"autocomplete": "new-password"
|
79
|
+
})
|
80
|
+
])
|
81
|
+
],
|
82
|
+
[]
|
83
|
+
);
|
84
|
+
})(),
|
85
|
+
[kcContext, passwordValidators]
|
86
|
+
);
|
87
|
+
|
88
|
+
const { getErrors } = useGetErrors({
|
89
|
+
"kcContext": {
|
90
|
+
"messagesPerField": kcContext.messagesPerField,
|
91
|
+
"profile": {
|
92
|
+
"attributes": attributesWithPassword
|
93
|
+
}
|
94
|
+
},
|
95
|
+
i18n
|
96
|
+
});
|
97
|
+
|
98
|
+
const initialInternalState = useMemo(
|
99
|
+
() =>
|
100
|
+
Object.fromEntries(
|
101
|
+
attributesWithPassword
|
102
|
+
.map(attribute => ({
|
103
|
+
attribute,
|
104
|
+
"errors": getErrors({
|
105
|
+
"name": attribute.name,
|
106
|
+
"fieldValueByAttributeName": Object.fromEntries(
|
107
|
+
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }])
|
108
|
+
)
|
109
|
+
})
|
110
|
+
}))
|
111
|
+
.map(({ attribute, errors }) => [
|
112
|
+
attribute.name,
|
113
|
+
{
|
114
|
+
"value": attribute.value ?? "",
|
115
|
+
errors,
|
116
|
+
"doDisplayPotentialErrorMessages": errors.length !== 0
|
117
|
+
}
|
118
|
+
])
|
119
|
+
),
|
120
|
+
[attributesWithPassword]
|
121
|
+
);
|
122
|
+
|
123
|
+
type InternalState = typeof initialInternalState;
|
124
|
+
|
125
|
+
const [formValidationInternalState, formValidationReducer] = useReducer(
|
126
|
+
(
|
127
|
+
state: InternalState,
|
128
|
+
params:
|
129
|
+
| {
|
130
|
+
action: "update value";
|
131
|
+
name: string;
|
132
|
+
newValue: string;
|
133
|
+
}
|
134
|
+
| {
|
135
|
+
action: "focus lost";
|
136
|
+
name: string;
|
137
|
+
}
|
138
|
+
): InternalState => ({
|
139
|
+
...state,
|
140
|
+
[params.name]: {
|
141
|
+
...state[params.name],
|
142
|
+
...(() => {
|
143
|
+
switch (params.action) {
|
144
|
+
case "focus lost":
|
145
|
+
return { "doDisplayPotentialErrorMessages": true };
|
146
|
+
case "update value":
|
147
|
+
return {
|
148
|
+
"value": params.newValue,
|
149
|
+
"errors": getErrors({
|
150
|
+
"name": params.name,
|
151
|
+
"fieldValueByAttributeName": {
|
152
|
+
...state,
|
153
|
+
[params.name]: { "value": params.newValue }
|
154
|
+
}
|
155
|
+
})
|
156
|
+
};
|
157
|
+
}
|
158
|
+
})()
|
159
|
+
}
|
160
|
+
}),
|
161
|
+
initialInternalState
|
162
|
+
);
|
163
|
+
|
164
|
+
const formValidationState = useMemo(
|
165
|
+
() => ({
|
166
|
+
"fieldStateByAttributeName": Object.fromEntries(
|
167
|
+
Object.entries(formValidationInternalState).map(([name, { value, errors, doDisplayPotentialErrorMessages }]) => [
|
168
|
+
name,
|
169
|
+
{ value, "displayableErrors": doDisplayPotentialErrorMessages ? errors : [] }
|
170
|
+
])
|
171
|
+
),
|
172
|
+
"isFormSubmittable": Object.entries(formValidationInternalState).every(
|
173
|
+
([name, { value, errors }]) =>
|
174
|
+
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required)
|
175
|
+
)
|
176
|
+
}),
|
177
|
+
[formValidationInternalState, attributesWithPassword]
|
178
|
+
);
|
179
|
+
|
180
|
+
return { formValidationState, formValidationReducer, attributesWithPassword };
|
181
|
+
}
|
182
|
+
|
9
183
|
/** Expect to be used in a component wrapped within a <I18nProvider> */
|
10
184
|
export function useGetErrors(params: {
|
11
185
|
kcContext: {
|
@@ -303,175 +477,3 @@ export function useGetErrors(params: {
|
|
303
477
|
|
304
478
|
return { getErrors };
|
305
479
|
}
|
306
|
-
|
307
|
-
/**
|
308
|
-
* NOTE: The attributesWithPassword returned is actually augmented with
|
309
|
-
* artificial password related attributes only if kcContext.passwordRequired === true
|
310
|
-
*/
|
311
|
-
export function useFormValidationSlice(params: {
|
312
|
-
kcContext: {
|
313
|
-
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
314
|
-
profile: {
|
315
|
-
attributes: Attribute[];
|
316
|
-
};
|
317
|
-
passwordRequired?: boolean;
|
318
|
-
realm: { registrationEmailAsUsername: boolean };
|
319
|
-
};
|
320
|
-
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
321
|
-
passwordValidators?: Validators;
|
322
|
-
i18n: I18nBase;
|
323
|
-
}) {
|
324
|
-
const {
|
325
|
-
kcContext,
|
326
|
-
passwordValidators = {
|
327
|
-
"length": {
|
328
|
-
"ignore.empty.value": true,
|
329
|
-
"min": "4"
|
330
|
-
}
|
331
|
-
},
|
332
|
-
i18n
|
333
|
-
} = params;
|
334
|
-
|
335
|
-
const attributesWithPassword = useMemo(
|
336
|
-
() =>
|
337
|
-
!kcContext.passwordRequired
|
338
|
-
? kcContext.profile.attributes
|
339
|
-
: (() => {
|
340
|
-
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
|
341
|
-
|
342
|
-
return kcContext.profile.attributes.reduce<Attribute[]>(
|
343
|
-
(prev, curr) => [
|
344
|
-
...prev,
|
345
|
-
...(curr.name !== name
|
346
|
-
? [curr]
|
347
|
-
: [
|
348
|
-
curr,
|
349
|
-
id<Attribute>({
|
350
|
-
"name": "password",
|
351
|
-
"displayName": id<`\${${MessageKeyBase}}`>("${password}"),
|
352
|
-
"required": true,
|
353
|
-
"readOnly": false,
|
354
|
-
"validators": passwordValidators,
|
355
|
-
"annotations": {},
|
356
|
-
"groupAnnotations": {},
|
357
|
-
"autocomplete": "new-password"
|
358
|
-
}),
|
359
|
-
id<Attribute>({
|
360
|
-
"name": "password-confirm",
|
361
|
-
"displayName": id<`\${${MessageKeyBase}}`>("${passwordConfirm}"),
|
362
|
-
"required": true,
|
363
|
-
"readOnly": false,
|
364
|
-
"validators": {
|
365
|
-
"_compareToOther": {
|
366
|
-
"name": "password",
|
367
|
-
"ignore.empty.value": true,
|
368
|
-
"shouldBe": "equal",
|
369
|
-
"error-message": id<`\${${MessageKeyBase}}`>("${invalidPasswordConfirmMessage}")
|
370
|
-
}
|
371
|
-
},
|
372
|
-
"annotations": {},
|
373
|
-
"groupAnnotations": {},
|
374
|
-
"autocomplete": "new-password"
|
375
|
-
})
|
376
|
-
])
|
377
|
-
],
|
378
|
-
[]
|
379
|
-
);
|
380
|
-
})(),
|
381
|
-
[kcContext, passwordValidators]
|
382
|
-
);
|
383
|
-
|
384
|
-
const { getErrors } = useGetErrors({
|
385
|
-
"kcContext": {
|
386
|
-
"messagesPerField": kcContext.messagesPerField,
|
387
|
-
"profile": {
|
388
|
-
"attributes": attributesWithPassword
|
389
|
-
}
|
390
|
-
},
|
391
|
-
i18n
|
392
|
-
});
|
393
|
-
|
394
|
-
const initialInternalState = useMemo(
|
395
|
-
() =>
|
396
|
-
Object.fromEntries(
|
397
|
-
attributesWithPassword
|
398
|
-
.map(attribute => ({
|
399
|
-
attribute,
|
400
|
-
"errors": getErrors({
|
401
|
-
"name": attribute.name,
|
402
|
-
"fieldValueByAttributeName": Object.fromEntries(
|
403
|
-
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }])
|
404
|
-
)
|
405
|
-
})
|
406
|
-
}))
|
407
|
-
.map(({ attribute, errors }) => [
|
408
|
-
attribute.name,
|
409
|
-
{
|
410
|
-
"value": attribute.value ?? "",
|
411
|
-
errors,
|
412
|
-
"doDisplayPotentialErrorMessages": errors.length !== 0
|
413
|
-
}
|
414
|
-
])
|
415
|
-
),
|
416
|
-
[attributesWithPassword]
|
417
|
-
);
|
418
|
-
|
419
|
-
type InternalState = typeof initialInternalState;
|
420
|
-
|
421
|
-
const [formValidationInternalState, formValidationReducer] = useReducer(
|
422
|
-
(
|
423
|
-
state: InternalState,
|
424
|
-
params:
|
425
|
-
| {
|
426
|
-
action: "update value";
|
427
|
-
name: string;
|
428
|
-
newValue: string;
|
429
|
-
}
|
430
|
-
| {
|
431
|
-
action: "focus lost";
|
432
|
-
name: string;
|
433
|
-
}
|
434
|
-
): InternalState => ({
|
435
|
-
...state,
|
436
|
-
[params.name]: {
|
437
|
-
...state[params.name],
|
438
|
-
...(() => {
|
439
|
-
switch (params.action) {
|
440
|
-
case "focus lost":
|
441
|
-
return { "doDisplayPotentialErrorMessages": true };
|
442
|
-
case "update value":
|
443
|
-
return {
|
444
|
-
"value": params.newValue,
|
445
|
-
"errors": getErrors({
|
446
|
-
"name": params.name,
|
447
|
-
"fieldValueByAttributeName": {
|
448
|
-
...state,
|
449
|
-
[params.name]: { "value": params.newValue }
|
450
|
-
}
|
451
|
-
})
|
452
|
-
};
|
453
|
-
}
|
454
|
-
})()
|
455
|
-
}
|
456
|
-
}),
|
457
|
-
initialInternalState
|
458
|
-
);
|
459
|
-
|
460
|
-
const formValidationState = useMemo(
|
461
|
-
() => ({
|
462
|
-
"fieldStateByAttributeName": Object.fromEntries(
|
463
|
-
Object.entries(formValidationInternalState).map(([name, { value, errors, doDisplayPotentialErrorMessages }]) => [
|
464
|
-
name,
|
465
|
-
{ value, "displayableErrors": doDisplayPotentialErrorMessages ? errors : [] }
|
466
|
-
])
|
467
|
-
),
|
468
|
-
"isFormSubmittable": Object.entries(formValidationInternalState).every(
|
469
|
-
([name, { value, errors }]) =>
|
470
|
-
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required)
|
471
|
-
)
|
472
|
-
}),
|
473
|
-
[formValidationInternalState, attributesWithPassword]
|
474
|
-
);
|
475
|
-
|
476
|
-
return { formValidationState, formValidationReducer, attributesWithPassword };
|
477
|
-
}
|