keycloakify 6.0.2 → 6.2.0

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.
Files changed (68) hide show
  1. package/README.md +8 -1
  2. package/bin/create-keycloak-email-directory.js +9 -4
  3. package/bin/create-keycloak-email-directory.js.map +1 -1
  4. package/bin/download-builtin-keycloak-theme.d.ts +1 -0
  5. package/bin/download-builtin-keycloak-theme.js +13 -6
  6. package/bin/download-builtin-keycloak-theme.js.map +1 -1
  7. package/bin/generate-i18n-messages.js +8 -3
  8. package/bin/generate-i18n-messages.js.map +1 -1
  9. package/bin/keycloakify/BuildOptions.d.ts +2 -0
  10. package/bin/keycloakify/BuildOptions.js +3 -2
  11. package/bin/keycloakify/BuildOptions.js.map +1 -1
  12. package/bin/keycloakify/generateFtl/generateFtl.d.ts +1 -1
  13. package/bin/keycloakify/generateFtl/generateFtl.js +2 -1
  14. package/bin/keycloakify/generateFtl/generateFtl.js.map +1 -1
  15. package/bin/keycloakify/generateKeycloakThemeResources.d.ts +1 -0
  16. package/bin/keycloakify/generateKeycloakThemeResources.js +5 -2
  17. package/bin/keycloakify/generateKeycloakThemeResources.js.map +1 -1
  18. package/bin/keycloakify/keycloakify.js +8 -4
  19. package/bin/keycloakify/keycloakify.js.map +1 -1
  20. package/bin/tools/cliOptions.d.ts +5 -0
  21. package/bin/tools/cliOptions.js +16 -0
  22. package/bin/tools/cliOptions.js.map +1 -0
  23. package/bin/tools/downloadAndUnzip.d.ts +1 -0
  24. package/bin/tools/downloadAndUnzip.js +1 -1
  25. package/bin/tools/downloadAndUnzip.js.map +1 -1
  26. package/bin/tools/logger.d.ts +12 -0
  27. package/bin/tools/logger.js +23 -0
  28. package/bin/tools/logger.js.map +1 -0
  29. package/bin/tsconfig.tsbuildinfo +1 -1
  30. package/lib/components/KcApp.js +3 -0
  31. package/lib/components/KcApp.js.map +1 -1
  32. package/lib/components/RegisterUserProfile.js +3 -62
  33. package/lib/components/RegisterUserProfile.js.map +1 -1
  34. package/lib/components/UpdateUserProfile.d.ts +9 -0
  35. package/lib/components/UpdateUserProfile.js +32 -0
  36. package/lib/components/UpdateUserProfile.js.map +1 -0
  37. package/lib/components/shared/UserProfileCommons.d.ts +17 -0
  38. package/lib/components/shared/UserProfileCommons.js +76 -0
  39. package/lib/components/shared/UserProfileCommons.js.map +1 -0
  40. package/lib/getKcContext/KcContextBase.d.ts +9 -1
  41. package/lib/getKcContext/KcContextBase.js.map +1 -1
  42. package/lib/getKcContext/kcContextMocks/kcContextMocks.js +103 -98
  43. package/lib/getKcContext/kcContextMocks/kcContextMocks.js.map +1 -1
  44. package/lib/i18n/index.js +0 -7
  45. package/lib/i18n/index.js.map +1 -1
  46. package/lib/tsconfig.tsbuildinfo +1 -1
  47. package/lib/useFormValidationSlice.d.ts +1 -1
  48. package/package.json +20 -2
  49. package/src/bin/create-keycloak-email-directory.ts +8 -3
  50. package/src/bin/download-builtin-keycloak-theme.ts +11 -5
  51. package/src/bin/generate-i18n-messages.ts +9 -3
  52. package/src/bin/keycloakify/BuildOptions.ts +5 -2
  53. package/src/bin/keycloakify/generateFtl/generateFtl.ts +2 -1
  54. package/src/bin/keycloakify/generateKeycloakThemeResources.ts +6 -2
  55. package/src/bin/keycloakify/keycloakify.ts +8 -3
  56. package/src/bin/tools/cliOptions.ts +15 -0
  57. package/src/bin/tools/downloadAndUnzip.ts +8 -2
  58. package/src/bin/tools/logger.ts +27 -0
  59. package/src/lib/components/KcApp.tsx +3 -0
  60. package/src/lib/components/RegisterUserProfile.tsx +10 -157
  61. package/src/lib/components/UpdateUserProfile.tsx +77 -0
  62. package/src/lib/components/shared/UserProfileCommons.tsx +172 -0
  63. package/src/lib/getKcContext/KcContextBase.ts +11 -1
  64. package/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts +105 -98
  65. package/src/lib/i18n/index.tsx +0 -10
  66. package/src/lib/useFormValidationSlice.tsx +1 -1
  67. package/src/test/bin/generateKeycloakThemeResources.ts +2 -1
  68. package/src/test/bin/setupSampleReactProject.ts +2 -1
@@ -33,7 +33,7 @@ export declare function useFormValidationSlice(params: {
33
33
  profile: {
34
34
  attributes: Attribute[];
35
35
  };
36
- passwordRequired: boolean;
36
+ passwordRequired?: boolean;
37
37
  realm: {
38
38
  registrationEmailAsUsername: boolean;
39
39
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keycloakify",
3
- "version": "6.0.2",
3
+ "version": "6.2.0",
4
4
  "description": "Keycloak theme generator for Reacts app",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,11 +46,13 @@
46
46
  "src/bin/mockTestingResourcesPath.ts",
47
47
  "src/bin/promptKeycloakVersion.ts",
48
48
  "src/bin/tools/NpmModuleVersion.ts",
49
+ "src/bin/tools/cliOptions.ts",
49
50
  "src/bin/tools/crawl.ts",
50
51
  "src/bin/tools/downloadAndUnzip.ts",
51
52
  "src/bin/tools/getProjectRoot.ts",
52
53
  "src/bin/tools/grant-exec-perms.ts",
53
54
  "src/bin/tools/isInside.ts",
55
+ "src/bin/tools/logger.ts",
54
56
  "src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts",
55
57
  "src/bin/tools/octokit-addons/listTags.ts",
56
58
  "src/bin/tools/pathJoin.ts",
@@ -76,6 +78,8 @@
76
78
  "src/lib/components/RegisterUserProfile.tsx",
77
79
  "src/lib/components/Template.tsx",
78
80
  "src/lib/components/Terms.tsx",
81
+ "src/lib/components/UpdateUserProfile.tsx",
82
+ "src/lib/components/shared/UserProfileCommons.tsx",
79
83
  "src/lib/getKcContext/KcContextBase.ts",
80
84
  "src/lib/getKcContext/getKcContext.ts",
81
85
  "src/lib/getKcContext/getKcContextFromWindow.ts",
@@ -391,6 +395,9 @@
391
395
  "bin/tools/NpmModuleVersion.d.ts",
392
396
  "bin/tools/NpmModuleVersion.js",
393
397
  "bin/tools/NpmModuleVersion.js.map",
398
+ "bin/tools/cliOptions.d.ts",
399
+ "bin/tools/cliOptions.js",
400
+ "bin/tools/cliOptions.js.map",
394
401
  "bin/tools/crawl.d.ts",
395
402
  "bin/tools/crawl.js",
396
403
  "bin/tools/crawl.js.map",
@@ -406,6 +413,9 @@
406
413
  "bin/tools/isInside.d.ts",
407
414
  "bin/tools/isInside.js",
408
415
  "bin/tools/isInside.js.map",
416
+ "bin/tools/logger.d.ts",
417
+ "bin/tools/logger.js",
418
+ "bin/tools/logger.js.map",
409
419
  "bin/tools/octokit-addons/getLatestsSemVersionedTag.d.ts",
410
420
  "bin/tools/octokit-addons/getLatestsSemVersionedTag.js",
411
421
  "bin/tools/octokit-addons/getLatestsSemVersionedTag.js.map",
@@ -479,6 +489,12 @@
479
489
  "lib/components/Terms.d.ts",
480
490
  "lib/components/Terms.js",
481
491
  "lib/components/Terms.js.map",
492
+ "lib/components/UpdateUserProfile.d.ts",
493
+ "lib/components/UpdateUserProfile.js",
494
+ "lib/components/UpdateUserProfile.js.map",
495
+ "lib/components/shared/UserProfileCommons.d.ts",
496
+ "lib/components/shared/UserProfileCommons.js",
497
+ "lib/components/shared/UserProfileCommons.js.map",
482
498
  "lib/getKcContext/KcContextBase.d.ts",
483
499
  "lib/getKcContext/KcContextBase.js",
484
500
  "lib/getKcContext/KcContextBase.js.map",
@@ -1237,6 +1253,7 @@
1237
1253
  "devDependencies": {
1238
1254
  "@emotion/react": "^11.4.1",
1239
1255
  "@types/memoizee": "^0.4.7",
1256
+ "@types/minimist": "^1.2.2",
1240
1257
  "@types/node": "^17.0.25",
1241
1258
  "@types/react": "18.0.9",
1242
1259
  "copyfiles": "^2.4.1",
@@ -1255,12 +1272,13 @@
1255
1272
  "evt": "^2.4.0",
1256
1273
  "memoizee": "^0.4.15",
1257
1274
  "minimal-polyfills": "^2.2.2",
1275
+ "minimist": "^1.2.6",
1258
1276
  "path-browserify": "^1.0.1",
1259
1277
  "powerhooks": "^0.20.15",
1260
1278
  "react-markdown": "^5.0.3",
1261
1279
  "scripting-tools": "^0.19.13",
1262
1280
  "tsafe": "^1.0.1",
1263
- "tss-react": "^4.1.1",
1281
+ "tss-react": "^4.1.3",
1264
1282
  "zod": "^3.17.10"
1265
1283
  }
1266
1284
  }
@@ -6,11 +6,15 @@ import { join as pathJoin, basename as pathBasename } from "path";
6
6
  import { transformCodebase } from "./tools/transformCodebase";
7
7
  import { promptKeycloakVersion } from "./promptKeycloakVersion";
8
8
  import * as fs from "fs";
9
+ import { getCliOptions } from "./tools/cliOptions";
10
+ import { getLogger } from "./tools/logger";
9
11
 
10
12
  if (require.main === module) {
11
13
  (async () => {
14
+ const { isSilent } = getCliOptions(process.argv.slice(2));
15
+ const logger = getLogger({ isSilent });
12
16
  if (fs.existsSync(keycloakThemeEmailDirPath)) {
13
- console.log(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`);
17
+ logger.warn(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`);
14
18
 
15
19
  process.exit(-1);
16
20
  }
@@ -21,7 +25,8 @@ if (require.main === module) {
21
25
 
22
26
  downloadBuiltinKeycloakTheme({
23
27
  keycloakVersion,
24
- "destDirPath": builtinKeycloakThemeTmpDirPath
28
+ "destDirPath": builtinKeycloakThemeTmpDirPath,
29
+ isSilent
25
30
  });
26
31
 
27
32
  transformCodebase({
@@ -29,7 +34,7 @@ if (require.main === module) {
29
34
  "destDirPath": keycloakThemeEmailDirPath
30
35
  });
31
36
 
32
- console.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`);
37
+ logger.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`);
33
38
 
34
39
  fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
35
40
  })();
@@ -4,31 +4,37 @@ import { keycloakThemeBuildingDirPath } from "./keycloakify";
4
4
  import { join as pathJoin } from "path";
5
5
  import { downloadAndUnzip } from "./tools/downloadAndUnzip";
6
6
  import { promptKeycloakVersion } from "./promptKeycloakVersion";
7
+ import { getCliOptions } from "./tools/cliOptions";
8
+ import { getLogger } from "./tools/logger";
7
9
 
8
- export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string }) {
9
- const { keycloakVersion, destDirPath } = params;
10
+ export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) {
11
+ const { keycloakVersion, destDirPath, isSilent } = params;
10
12
 
11
13
  for (const ext of ["", "-community"]) {
12
14
  downloadAndUnzip({
13
15
  "destDirPath": destDirPath,
14
16
  "url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
15
17
  "pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
16
- "cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache")
18
+ "cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache"),
19
+ isSilent
17
20
  });
18
21
  }
19
22
  }
20
23
 
21
24
  if (require.main === module) {
22
25
  (async () => {
26
+ const { isSilent } = getCliOptions(process.argv.slice(2));
27
+ const logger = getLogger({ isSilent });
23
28
  const { keycloakVersion } = await promptKeycloakVersion();
24
29
 
25
30
  const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
26
31
 
27
- console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
32
+ logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
28
33
 
29
34
  downloadBuiltinKeycloakTheme({
30
35
  keycloakVersion,
31
- destDirPath
36
+ destDirPath,
37
+ isSilent
32
38
  });
33
39
  })();
34
40
  }
@@ -5,6 +5,8 @@ import { crawl } from "./tools/crawl";
5
5
  import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
6
6
  import { getProjectRoot } from "./tools/getProjectRoot";
7
7
  import { rm_rf, rm_r } from "./tools/rm";
8
+ import { getCliOptions } from "./tools/cliOptions";
9
+ import { getLogger } from "./tools/logger";
8
10
 
9
11
  //NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
10
12
  // update the version array for generating for newer version.
@@ -12,8 +14,11 @@ import { rm_rf, rm_r } from "./tools/rm";
12
14
  //@ts-ignore
13
15
  const propertiesParser = require("properties-parser");
14
16
 
17
+ const { isSilent } = getCliOptions(process.argv.slice(2));
18
+ const logger = getLogger({ isSilent });
19
+
15
20
  for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
16
- console.log({ keycloakVersion });
21
+ logger.log(JSON.stringify({ keycloakVersion }));
17
22
 
18
23
  const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
19
24
 
@@ -21,7 +26,8 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
21
26
 
22
27
  downloadBuiltinKeycloakTheme({
23
28
  keycloakVersion,
24
- "destDirPath": tmpDirPath
29
+ "destDirPath": tmpDirPath,
30
+ isSilent
25
31
  });
26
32
 
27
33
  type Dictionary = { [idiomId: string]: string };
@@ -75,7 +81,7 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
75
81
  )
76
82
  );
77
83
 
78
- console.log(`${filePath} wrote`);
84
+ logger.log(`${filePath} wrote`);
79
85
  });
80
86
  });
81
87
  }
@@ -35,6 +35,7 @@ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets
35
35
 
36
36
  export namespace BuildOptions {
37
37
  export type Common = {
38
+ isSilent: boolean;
38
39
  version: string;
39
40
  themeName: string;
40
41
  extraPages?: string[];
@@ -71,8 +72,9 @@ export function readBuildOptions(params: {
71
72
  packageJson: string;
72
73
  CNAME: string | undefined;
73
74
  isExternalAssetsCliParamProvided: boolean;
75
+ isSilent: boolean;
74
76
  }): BuildOptions {
75
- const { packageJson, CNAME, isExternalAssetsCliParamProvided } = params;
77
+ const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
76
78
 
77
79
  const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson));
78
80
 
@@ -130,7 +132,8 @@ export function readBuildOptions(params: {
130
132
  })(),
131
133
  "version": version,
132
134
  extraPages,
133
- extraThemeProperties
135
+ extraThemeProperties,
136
+ isSilent
134
137
  };
135
138
  })();
136
139
 
@@ -27,7 +27,8 @@ export const pageIds = [
27
27
  "login-idp-link-email.ftl",
28
28
  "login-page-expired.ftl",
29
29
  "login-config-totp.ftl",
30
- "logout-confirm.ftl"
30
+ "logout-confirm.ftl",
31
+ "update-user-profile.ftl"
31
32
  ] as const;
32
33
 
33
34
  export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
@@ -11,6 +11,7 @@ import { isInside } from "../tools/isInside";
11
11
  import type { BuildOptions } from "./BuildOptions";
12
12
  import { assert } from "tsafe/assert";
13
13
  import { Reflect } from "tsafe/Reflect";
14
+ import { getLogger } from "../tools/logger";
14
15
 
15
16
  export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
16
17
 
@@ -19,6 +20,7 @@ export namespace BuildOptionsLike {
19
20
  themeName: string;
20
21
  extraPages?: string[];
21
22
  extraThemeProperties?: string[];
23
+ isSilent: boolean;
22
24
  };
23
25
 
24
26
  export type Standalone = Common & {
@@ -60,6 +62,7 @@ export function generateKeycloakThemeResources(params: {
60
62
  }): { doBundlesEmailTemplate: boolean } {
61
63
  const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, keycloakThemeEmailDirPath, keycloakVersion, buildOptions } = params;
62
64
 
65
+ const logger = getLogger({ isSilent: buildOptions.isSilent });
63
66
  const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, "login");
64
67
 
65
68
  let allCssGlobalsToDefine: Record<string, string> = {};
@@ -117,7 +120,7 @@ export function generateKeycloakThemeResources(params: {
117
120
 
118
121
  email: {
119
122
  if (!fs.existsSync(keycloakThemeEmailDirPath)) {
120
- console.log(
123
+ logger.log(
121
124
  [
122
125
  `Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
123
126
  `To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`
@@ -154,7 +157,8 @@ export function generateKeycloakThemeResources(params: {
154
157
 
155
158
  downloadBuiltinKeycloakTheme({
156
159
  keycloakVersion,
157
- "destDirPath": tmpDirPath
160
+ "destDirPath": tmpDirPath,
161
+ isSilent: buildOptions.isSilent
158
162
  });
159
163
 
160
164
  const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
@@ -5,6 +5,8 @@ import * as child_process from "child_process";
5
5
  import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
6
6
  import * as fs from "fs";
7
7
  import { readBuildOptions } from "./BuildOptions";
8
+ import { getLogger } from "../tools/logger";
9
+ import { getCliOptions } from "../tools/cliOptions";
8
10
 
9
11
  const reactProjectDirPath = process.cwd();
10
12
 
@@ -12,7 +14,9 @@ export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build
12
14
  export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email");
13
15
 
14
16
  export function main() {
15
- console.log("🔏 Building the keycloak theme...⌚");
17
+ const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2));
18
+ const logger = getLogger({ isSilent });
19
+ logger.log("🔏 Building the keycloak theme...⌚");
16
20
 
17
21
  const buildOptions = readBuildOptions({
18
22
  "packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"),
@@ -25,7 +29,8 @@ export function main() {
25
29
 
26
30
  return fs.readFileSync(cnameFilePath).toString("utf8");
27
31
  })(),
28
- "isExternalAssetsCliParamProvided": process.argv[2]?.toLowerCase() === "--external-assets"
32
+ "isExternalAssetsCliParamProvided": hasExternalAssets,
33
+ "isSilent": isSilent
29
34
  });
30
35
 
31
36
  const { doBundlesEmailTemplate } = generateKeycloakThemeResources({
@@ -59,7 +64,7 @@ export function main() {
59
64
  buildOptions
60
65
  });
61
66
 
62
- console.log(
67
+ logger.log(
63
68
  [
64
69
  "",
65
70
  `✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
@@ -0,0 +1,15 @@
1
+ import parseArgv from "minimist";
2
+
3
+ export type CliOptions = {
4
+ isSilent: boolean;
5
+ hasExternalAssets: boolean;
6
+ };
7
+
8
+ export const getCliOptions = (processArgv: string[]): CliOptions => {
9
+ const argv = parseArgv(processArgv);
10
+
11
+ return {
12
+ isSilent: typeof argv["silent"] === "boolean" ? argv["silent"] : false,
13
+ hasExternalAssets: typeof argv["external-assets"] === "boolean" ? argv["external-assets"] : false
14
+ };
15
+ };
@@ -6,7 +6,13 @@ import { rm, rm_rf } from "./rm";
6
6
  import * as crypto from "crypto";
7
7
 
8
8
  /** assert url ends with .zip */
9
- export function downloadAndUnzip(params: { url: string; destDirPath: string; pathOfDirToExtractInArchive?: string; cacheDirPath: string }) {
9
+ export function downloadAndUnzip(params: {
10
+ isSilent: boolean;
11
+ url: string;
12
+ destDirPath: string;
13
+ pathOfDirToExtractInArchive?: string;
14
+ cacheDirPath: string;
15
+ }) {
10
16
  const { url, destDirPath, pathOfDirToExtractInArchive, cacheDirPath } = params;
11
17
 
12
18
  const extractDirPath = pathJoin(
@@ -54,7 +60,7 @@ export function downloadAndUnzip(params: { url: string; destDirPath: string; pat
54
60
 
55
61
  const zipFileBasename = pathBasename(url);
56
62
 
57
- execSync(`curl -L ${url} -o ${zipFileBasename}`, { "cwd": extractDirPath });
63
+ execSync(`curl -L ${url} -o ${zipFileBasename} ${params.isSilent ? "-s" : ""}`, { "cwd": extractDirPath });
58
64
 
59
65
  execSync(`unzip -o ${zipFileBasename}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/**/*"`}`, {
60
66
  "cwd": extractDirPath
@@ -0,0 +1,27 @@
1
+ type LoggerOpts = {
2
+ force?: boolean;
3
+ };
4
+
5
+ type Logger = {
6
+ log: (message: string, opts?: LoggerOpts) => void;
7
+ warn: (message: string) => void;
8
+ error: (message: string) => void;
9
+ };
10
+
11
+ export const getLogger = ({ isSilent }: { isSilent?: boolean } = {}): Logger => {
12
+ return {
13
+ log: (message, { force } = {}) => {
14
+ if (isSilent && !force) {
15
+ return;
16
+ }
17
+
18
+ console.log(message);
19
+ },
20
+ warn: message => {
21
+ console.warn(message);
22
+ },
23
+ error: message => {
24
+ console.error(message);
25
+ }
26
+ };
27
+ };
@@ -20,6 +20,7 @@ const LoginPageExpired = lazy(() => import("./LoginPageExpired"));
20
20
  const LoginIdpLinkEmail = lazy(() => import("./LoginIdpLinkEmail"));
21
21
  const LoginConfigTotp = lazy(() => import("./LoginConfigTotp"));
22
22
  const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
23
+ const UpdateUserProfile = lazy(() => import("./UpdateUserProfile"));
23
24
 
24
25
  const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcContext: KcContextBase; i18n?: I18n } & KcProps) => {
25
26
  const i18n = (function useClosure() {
@@ -74,6 +75,8 @@ const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcConte
74
75
  return <LoginConfigTotp {...{ kcContext, ...props }} />;
75
76
  case "logout-confirm.ftl":
76
77
  return <LogoutConfirm {...{ kcContext, ...props }} />;
78
+ case "update-user-profile.ftl":
79
+ return <UpdateUserProfile {...{ kcContext, ...props }} />;
77
80
  }
78
81
  })()}
79
82
  </Suspense>
@@ -1,12 +1,10 @@
1
- import React, { useMemo, memo, useEffect, useState, Fragment } from "react";
1
+ import React, { useMemo, memo, useState } from "react";
2
2
  import Template from "./Template";
3
3
  import type { KcProps } from "./KcProps";
4
- import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
4
+ import type { KcContextBase } from "../getKcContext/KcContextBase";
5
5
  import { useCssAndCx } from "../tools/useCssAndCx";
6
- import type { ReactComponent } from "../tools/ReactComponent";
7
- import { useCallbackFactory } from "powerhooks/useCallbackFactory";
8
- import { useFormValidationSlice } from "../useFormValidationSlice";
9
6
  import type { I18n } from "../i18n";
7
+ import { UserProfileFormFields } from "./shared/UserProfileCommons";
10
8
 
11
9
  const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps) => {
12
10
  const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
@@ -34,7 +32,13 @@ const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: K
34
32
  headerNode={msg("registerTitle")}
35
33
  formNode={
36
34
  <form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
37
- <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
35
+ <UserProfileFormFields
36
+ kcContext={kcContext}
37
+ doInsertPasswordFields={true}
38
+ onIsFormSubmittableValueChange={setIsFomSubmittable}
39
+ i18n={i18n}
40
+ {...props}
41
+ />
38
42
  {recaptchaRequired && (
39
43
  <div className="form-group">
40
44
  <div className={cx(props.kcInputWrapperClass)}>
@@ -66,155 +70,4 @@ const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: K
66
70
  );
67
71
  });
68
72
 
69
- type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps &
70
- Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
71
- onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
72
- };
73
-
74
- const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, i18n, ...props }: UserProfileFormFieldsProps) => {
75
- const { cx, css } = useCssAndCx();
76
-
77
- const { advancedMsg } = i18n;
78
-
79
- const {
80
- formValidationState: { fieldStateByAttributeName, isFormSubmittable },
81
- formValidationReducer,
82
- attributesWithPassword
83
- } = useFormValidationSlice({
84
- kcContext,
85
- i18n
86
- });
87
-
88
- useEffect(() => {
89
- onIsFormSubmittableValueChange(isFormSubmittable);
90
- }, [isFormSubmittable]);
91
-
92
- const onChangeFactory = useCallbackFactory(
93
- (
94
- [name]: [string],
95
- [
96
- {
97
- target: { value }
98
- }
99
- ]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>]
100
- ) =>
101
- formValidationReducer({
102
- "action": "update value",
103
- name,
104
- "newValue": value
105
- })
106
- );
107
-
108
- const onBlurFactory = useCallbackFactory(([name]: [string]) =>
109
- formValidationReducer({
110
- "action": "focus lost",
111
- name
112
- })
113
- );
114
-
115
- let currentGroup = "";
116
-
117
- return (
118
- <>
119
- {attributesWithPassword.map((attribute, i) => {
120
- const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
121
-
122
- const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
123
-
124
- const formGroupClassName = cx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
125
-
126
- return (
127
- <Fragment key={i}>
128
- {group !== currentGroup && (currentGroup = group) !== "" && (
129
- <div className={formGroupClassName}>
130
- <div className={cx(props.kcContentWrapperClass)}>
131
- <label id={`header-${group}`} className={cx(props.kcFormGroupHeader)}>
132
- {advancedMsg(groupDisplayHeader) || currentGroup}
133
- </label>
134
- </div>
135
- {groupDisplayDescription !== "" && (
136
- <div className={cx(props.kcLabelWrapperClass)}>
137
- <label id={`description-${group}`} className={`${cx(props.kcLabelClass)}`}>
138
- {advancedMsg(groupDisplayDescription)}
139
- </label>
140
- </div>
141
- )}
142
- </div>
143
- )}
144
- <div className={formGroupClassName}>
145
- <div className={cx(props.kcLabelWrapperClass)}>
146
- <label htmlFor={attribute.name} className={cx(props.kcLabelClass)}>
147
- {advancedMsg(attribute.displayName ?? "")}
148
- </label>
149
- {attribute.required && <>*</>}
150
- </div>
151
- <div className={cx(props.kcInputWrapperClass)}>
152
- {(() => {
153
- const { options } = attribute.validators;
154
-
155
- if (options !== undefined) {
156
- return (
157
- <select
158
- id={attribute.name}
159
- name={attribute.name}
160
- onChange={onChangeFactory(attribute.name)}
161
- onBlur={onBlurFactory(attribute.name)}
162
- value={value}
163
- >
164
- {options.options.map(option => (
165
- <option key={option} value={option}>
166
- {option}
167
- </option>
168
- ))}
169
- </select>
170
- );
171
- }
172
-
173
- return (
174
- <input
175
- type={(() => {
176
- switch (attribute.name) {
177
- case "password-confirm":
178
- case "password":
179
- return "password";
180
- default:
181
- return "text";
182
- }
183
- })()}
184
- id={attribute.name}
185
- name={attribute.name}
186
- value={value}
187
- onChange={onChangeFactory(attribute.name)}
188
- className={cx(props.kcInputClass)}
189
- aria-invalid={displayableErrors.length !== 0}
190
- disabled={attribute.readOnly}
191
- autoComplete={attribute.autocomplete}
192
- onBlur={onBlurFactory(attribute.name)}
193
- />
194
- );
195
- })()}
196
- {displayableErrors.length !== 0 && (
197
- <span
198
- id={`input-error-${attribute.name}`}
199
- className={cx(
200
- props.kcInputErrorMessageClass,
201
- css({
202
- "position": displayableErrors.length === 1 ? "absolute" : undefined,
203
- "& > span": { "display": "block" }
204
- })
205
- )}
206
- aria-live="polite"
207
- >
208
- {displayableErrors.map(({ errorMessage }) => errorMessage)}
209
- </span>
210
- )}
211
- </div>
212
- </div>
213
- </Fragment>
214
- );
215
- })}
216
- </>
217
- );
218
- });
219
-
220
73
  export default RegisterUserProfile;