Compare commits
66 Commits
v6.0.0-bet
...
v6.4.3
Author | SHA1 | Date | |
---|---|---|---|
4d3220820b | |||
a4ac9fb0f3 | |||
1ff79ecf07 | |||
1166b16420 | |||
213224942f | |||
ff16e66275 | |||
3c338e983f | |||
2c11ba6520 | |||
9a21656706 | |||
e96ee5ba53 | |||
b421633a8a | |||
e2e0d62560 | |||
c71fb06940 | |||
e2171af99c | |||
8cebf049d4 | |||
ef139ed1cc | |||
d717de006a | |||
a44f091878 | |||
1b37ba5339 | |||
bbaa90e997 | |||
86e6c4a419 | |||
4159883791 | |||
d8b00da3a1 | |||
a24945bc1b | |||
158759493f | |||
36e32d6ddc | |||
84908e2ec0 | |||
a2dc51d811 | |||
fb3b0e2c29 | |||
1a3e4c68bb | |||
11b2342da0 | |||
80d4a808d3 | |||
da4146eb59 | |||
a0be35db8b | |||
14db9cd523 | |||
0c315385dd | |||
c0a0eb02fb | |||
ee407c32ad | |||
9262d21829 | |||
a13f710325 | |||
eac1a6036f | |||
987f3d7586 | |||
875322669c | |||
33a264b3d0 | |||
c059eff170 | |||
b4a22fc9dd | |||
6d1cbdc463 | |||
2bfbba4daf | |||
21ffe82bde | |||
8e6f597027 | |||
16c5065560 | |||
c4b985f1a4 | |||
042747c7d2 | |||
e4a46f31de | |||
6d9e62d2b4 | |||
9caaa507b1 | |||
5c7d3c5b44 | |||
8bac57d87a | |||
b8d759cd63 | |||
da72e3e5ac | |||
2afd36fee0 | |||
b7e75d8828 | |||
30e20f4e7d | |||
ce0ab8dccf | |||
5b20ab2f7c | |||
daaaed43df |
4
.github/FUNDING.yaml
vendored
Normal file
4
.github/FUNDING.yaml
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [garronej]
|
||||||
|
custom: ['https://www.ringerhq.com/experts/garronej']
|
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@ -3,11 +3,9 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- v6
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- v6
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@ -47,11 +45,9 @@ jobs:
|
|||||||
- uses: bahmutov/npm-install@v1
|
- uses: bahmutov/npm-install@v1
|
||||||
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
|
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
|
||||||
run: |
|
run: |
|
||||||
yarn build
|
|
||||||
yarn test
|
yarn test
|
||||||
- if: steps.step1.outputs.npm_or_yarn == 'npm'
|
- if: steps.step1.outputs.npm_or_yarn == 'npm'
|
||||||
run: |
|
run: |
|
||||||
npm run build
|
|
||||||
npm test
|
npm test
|
||||||
check_if_version_upgraded:
|
check_if_version_upgraded:
|
||||||
name: Check if version upgrade
|
name: Check if version upgrade
|
||||||
|
23
README.md
23
README.md
@ -2,7 +2,7 @@
|
|||||||
<img src="https://user-images.githubusercontent.com/6702424/109387840-eba11f80-7903-11eb-9050-db1dad883f78.png">
|
<img src="https://user-images.githubusercontent.com/6702424/109387840-eba11f80-7903-11eb-9050-db1dad883f78.png">
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<i>🔏 Create Keycloak themes using React 🔏</i>
|
<i>🔏 Create Keycloak themes using React 🔏</i>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/garronej/keycloakify/actions">
|
<a href="https://github.com/garronej/keycloakify/actions">
|
||||||
@ -27,7 +27,14 @@
|
|||||||
<a href="https://www.keycloakify.dev">Home</a>
|
<a href="https://www.keycloakify.dev">Home</a>
|
||||||
-
|
-
|
||||||
<a href="https://docs.keycloakify.dev">Documentation</a>
|
<a href="https://docs.keycloakify.dev">Documentation</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p align="center"> ---- Project starter / Demo setup ---- </p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/garronej/keycloakify-starter">CSS Level customization</a>
|
||||||
|
-
|
||||||
|
<a href="https://github.com/garronej/keycloakify-advanced-starter">Component Level customization</a>
|
||||||
|
</p>
|
||||||
|
<p align="center"> ---- </p>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -36,8 +43,18 @@
|
|||||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
> 🗣 V6 have been released 🎉
|
||||||
|
> [It features major improvements](https://github.com/InseeFrLab/keycloakify#600).
|
||||||
|
> Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6).
|
||||||
|
|
||||||
# Changelog highlights
|
# Changelog highlights
|
||||||
|
|
||||||
|
## 6.4.0
|
||||||
|
|
||||||
|
- You can now optionally pass a `doFetchDefaultThemeResources: boolean` prop to every page component and the default `<KcApp />`
|
||||||
|
This enables you to prevent the default CSS and JS that comes with the builtin Keycloak theme to be downloaded.
|
||||||
|
You'll get [a black slate](https://user-images.githubusercontent.com/6702424/192619083-4baa5df4-4a21-4ec7-8e28-d200d1208299.png).
|
||||||
|
|
||||||
## 6.0.0
|
## 6.0.0
|
||||||
|
|
||||||
- Bundle size drastically reduced, locals and component dynamically loaded.
|
- Bundle size drastically reduced, locals and component dynamically loaded.
|
||||||
@ -45,7 +62,7 @@
|
|||||||
- Real i18n API.
|
- Real i18n API.
|
||||||
- Actual documentation for build options.
|
- Actual documentation for build options.
|
||||||
|
|
||||||
Checkout the migration guide.
|
Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6)
|
||||||
|
|
||||||
## 5.8.0
|
## 5.8.0
|
||||||
|
|
||||||
|
17
package.json
17
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "6.0.0-beta.7",
|
"version": "6.4.3",
|
||||||
"description": "Keycloak theme generator for Reacts app",
|
"description": "Keycloak theme generator for Reacts app",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -13,7 +13,8 @@
|
|||||||
"build:test": "rimraf dist_test/ && tsc -p src/test && yarn copy-files dist_test/",
|
"build:test": "rimraf dist_test/ && tsc -p src/test && yarn copy-files dist_test/",
|
||||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||||
"copy-files": "copyfiles -u 1 src/**/*.ftl",
|
"copy-files": "copyfiles -u 1 src/**/*.ftl",
|
||||||
"test": "yarn build:test && node dist_test/test/bin && node dist_test/test/lib",
|
"pretest": "yarn build:test",
|
||||||
|
"test": "node dist_test/test/bin && node dist_test/test/lib",
|
||||||
"generate-messages": "node dist/bin/generate-i18n-messages.js",
|
"generate-messages": "node dist/bin/generate-i18n-messages.js",
|
||||||
"link_in_test_app": "node dist/bin/link_in_test_app.js",
|
"link_in_test_app": "node dist/bin/link_in_test_app.js",
|
||||||
"_format": "prettier '**/*.{ts,tsx,json,md}'",
|
"_format": "prettier '**/*.{ts,tsx,json,md}'",
|
||||||
@ -60,6 +61,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@types/memoizee": "^0.4.7",
|
"@types/memoizee": "^0.4.7",
|
||||||
|
"@types/minimist": "^1.2.2",
|
||||||
"@types/node": "^17.0.25",
|
"@types/node": "^17.0.25",
|
||||||
"@types/react": "18.0.9",
|
"@types/react": "18.0.9",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
@ -75,15 +77,16 @@
|
|||||||
"@octokit/rest": "^18.12.0",
|
"@octokit/rest": "^18.12.0",
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.5",
|
||||||
"cli-select": "^1.1.2",
|
"cli-select": "^1.1.2",
|
||||||
"evt": "^2.3.1",
|
"evt": "^2.4.4",
|
||||||
"memoizee": "^0.4.15",
|
"memoizee": "^0.4.15",
|
||||||
"minimal-polyfills": "^2.2.1",
|
"minimal-polyfills": "^2.2.2",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"powerhooks": "^0.20.10",
|
"powerhooks": "^0.20.20",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"scripting-tools": "^0.19.13",
|
"scripting-tools": "^0.19.13",
|
||||||
"tsafe": "^0.10.1",
|
"tsafe": "^1.1.1",
|
||||||
"tss-react": "^3.7.1",
|
"tss-react": "^4.3.3",
|
||||||
"zod": "^3.17.10"
|
"zod": "^3.17.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,15 @@ import { join as pathJoin, basename as pathBasename } from "path";
|
|||||||
import { transformCodebase } from "./tools/transformCodebase";
|
import { transformCodebase } from "./tools/transformCodebase";
|
||||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { getCliOptions } from "./tools/cliOptions";
|
||||||
|
import { getLogger } from "./tools/logger";
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
const { isSilent } = getCliOptions(process.argv.slice(2));
|
||||||
|
const logger = getLogger({ isSilent });
|
||||||
if (fs.existsSync(keycloakThemeEmailDirPath)) {
|
if (fs.existsSync(keycloakThemeEmailDirPath)) {
|
||||||
console.log(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`);
|
logger.warn(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`);
|
||||||
|
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
@ -21,7 +25,8 @@ if (require.main === module) {
|
|||||||
|
|
||||||
downloadBuiltinKeycloakTheme({
|
downloadBuiltinKeycloakTheme({
|
||||||
keycloakVersion,
|
keycloakVersion,
|
||||||
"destDirPath": builtinKeycloakThemeTmpDirPath
|
"destDirPath": builtinKeycloakThemeTmpDirPath,
|
||||||
|
isSilent
|
||||||
});
|
});
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
@ -29,7 +34,7 @@ if (require.main === module) {
|
|||||||
"destDirPath": keycloakThemeEmailDirPath
|
"destDirPath": keycloakThemeEmailDirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`);
|
logger.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`);
|
||||||
|
|
||||||
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
|
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
|
||||||
})();
|
})();
|
||||||
|
@ -4,31 +4,37 @@ import { keycloakThemeBuildingDirPath } from "./keycloakify";
|
|||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||||
|
import { getCliOptions } from "./tools/cliOptions";
|
||||||
|
import { getLogger } from "./tools/logger";
|
||||||
|
|
||||||
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string }) {
|
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) {
|
||||||
const { keycloakVersion, destDirPath } = params;
|
const { keycloakVersion, destDirPath, isSilent } = params;
|
||||||
|
|
||||||
for (const ext of ["", "-community"]) {
|
for (const ext of ["", "-community"]) {
|
||||||
downloadAndUnzip({
|
downloadAndUnzip({
|
||||||
"destDirPath": destDirPath,
|
"destDirPath": destDirPath,
|
||||||
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
||||||
"pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
|
"pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
|
||||||
"cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache")
|
"cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache"),
|
||||||
|
isSilent
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
const { isSilent } = getCliOptions(process.argv.slice(2));
|
||||||
|
const logger = getLogger({ isSilent });
|
||||||
const { keycloakVersion } = await promptKeycloakVersion();
|
const { keycloakVersion } = await promptKeycloakVersion();
|
||||||
|
|
||||||
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
|
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
|
||||||
|
|
||||||
console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
|
logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
|
||||||
|
|
||||||
downloadBuiltinKeycloakTheme({
|
downloadBuiltinKeycloakTheme({
|
||||||
keycloakVersion,
|
keycloakVersion,
|
||||||
destDirPath
|
destDirPath,
|
||||||
|
isSilent
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import { crawl } from "./tools/crawl";
|
|||||||
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
|
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
|
||||||
import { getProjectRoot } from "./tools/getProjectRoot";
|
import { getProjectRoot } from "./tools/getProjectRoot";
|
||||||
import { rm_rf, rm_r } from "./tools/rm";
|
import { rm_rf, rm_r } from "./tools/rm";
|
||||||
|
import { getCliOptions } from "./tools/cliOptions";
|
||||||
|
import { getLogger } from "./tools/logger";
|
||||||
|
|
||||||
//NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
|
//NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
|
||||||
// update the version array for generating for newer version.
|
// update the version array for generating for newer version.
|
||||||
@ -12,8 +14,11 @@ import { rm_rf, rm_r } from "./tools/rm";
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const propertiesParser = require("properties-parser");
|
const propertiesParser = require("properties-parser");
|
||||||
|
|
||||||
|
const { isSilent } = getCliOptions(process.argv.slice(2));
|
||||||
|
const logger = getLogger({ isSilent });
|
||||||
|
|
||||||
for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
|
for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
|
||||||
console.log({ keycloakVersion });
|
logger.log(JSON.stringify({ keycloakVersion }));
|
||||||
|
|
||||||
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
||||||
|
|
||||||
@ -21,7 +26,8 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
|
|||||||
|
|
||||||
downloadBuiltinKeycloakTheme({
|
downloadBuiltinKeycloakTheme({
|
||||||
keycloakVersion,
|
keycloakVersion,
|
||||||
"destDirPath": tmpDirPath
|
"destDirPath": tmpDirPath,
|
||||||
|
isSilent
|
||||||
});
|
});
|
||||||
|
|
||||||
type Dictionary = { [idiomId: string]: string };
|
type Dictionary = { [idiomId: string]: string };
|
||||||
@ -75,7 +81,7 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`${filePath} wrote`);
|
logger.log(`${filePath} wrote`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ type ParsedPackageJson = {
|
|||||||
keycloakify?: {
|
keycloakify?: {
|
||||||
extraPages?: string[];
|
extraPages?: string[];
|
||||||
extraThemeProperties?: string[];
|
extraThemeProperties?: string[];
|
||||||
isAppAndKeycloakServerSharingSameDomain?: boolean;
|
areAppAndKeycloakServerSharingSameDomain?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ const zParsedPackageJson = z.object({
|
|||||||
.object({
|
.object({
|
||||||
"extraPages": z.array(z.string()).optional(),
|
"extraPages": z.array(z.string()).optional(),
|
||||||
"extraThemeProperties": z.array(z.string()).optional(),
|
"extraThemeProperties": z.array(z.string()).optional(),
|
||||||
"isAppAndKeycloakServerSharingSameDomain": z.boolean().optional()
|
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
});
|
});
|
||||||
@ -35,6 +35,7 @@ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets
|
|||||||
|
|
||||||
export namespace BuildOptions {
|
export namespace BuildOptions {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
|
isSilent: boolean;
|
||||||
version: string;
|
version: string;
|
||||||
themeName: string;
|
themeName: string;
|
||||||
extraPages?: string[];
|
extraPages?: string[];
|
||||||
@ -56,11 +57,11 @@ export namespace BuildOptions {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SameDomain = CommonExternalAssets & {
|
export type SameDomain = CommonExternalAssets & {
|
||||||
isAppAndKeycloakServerSharingSameDomain: true;
|
areAppAndKeycloakServerSharingSameDomain: true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DifferentDomains = CommonExternalAssets & {
|
export type DifferentDomains = CommonExternalAssets & {
|
||||||
isAppAndKeycloakServerSharingSameDomain: false;
|
areAppAndKeycloakServerSharingSameDomain: false;
|
||||||
urlOrigin: string;
|
urlOrigin: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
};
|
};
|
||||||
@ -71,8 +72,9 @@ export function readBuildOptions(params: {
|
|||||||
packageJson: string;
|
packageJson: string;
|
||||||
CNAME: string | undefined;
|
CNAME: string | undefined;
|
||||||
isExternalAssetsCliParamProvided: boolean;
|
isExternalAssetsCliParamProvided: boolean;
|
||||||
|
isSilent: boolean;
|
||||||
}): BuildOptions {
|
}): BuildOptions {
|
||||||
const { packageJson, CNAME, isExternalAssetsCliParamProvided } = params;
|
const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
|
||||||
|
|
||||||
const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson));
|
const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson));
|
||||||
|
|
||||||
@ -130,7 +132,8 @@ export function readBuildOptions(params: {
|
|||||||
})(),
|
})(),
|
||||||
"version": version,
|
"version": version,
|
||||||
extraPages,
|
extraPages,
|
||||||
extraThemeProperties
|
extraThemeProperties,
|
||||||
|
isSilent
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -140,10 +143,10 @@ export function readBuildOptions(params: {
|
|||||||
"isStandalone": false
|
"isStandalone": false
|
||||||
});
|
});
|
||||||
|
|
||||||
if (parsedPackageJson.keycloakify?.isAppAndKeycloakServerSharingSameDomain) {
|
if (parsedPackageJson.keycloakify?.areAppAndKeycloakServerSharingSameDomain) {
|
||||||
return id<BuildOptions.ExternalAssets.SameDomain>({
|
return id<BuildOptions.ExternalAssets.SameDomain>({
|
||||||
...commonExternalAssets,
|
...commonExternalAssets,
|
||||||
"isAppAndKeycloakServerSharingSameDomain": true
|
"areAppAndKeycloakServerSharingSameDomain": true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
assert(
|
assert(
|
||||||
@ -155,14 +158,14 @@ export function readBuildOptions(params: {
|
|||||||
"public/CNAME file.",
|
"public/CNAME file.",
|
||||||
"Alternatively, if your app and the Keycloak server are on the same domain, ",
|
"Alternatively, if your app and the Keycloak server are on the same domain, ",
|
||||||
"eg https://example.com is your app and https://example.com/auth is the keycloak",
|
"eg https://example.com is your app and https://example.com/auth is the keycloak",
|
||||||
'admin UI, you can set "keycloakify": { "isAppAndKeycloakServerSharingSameDomain": true }',
|
'admin UI, you can set "keycloakify": { "areAppAndKeycloakServerSharingSameDomain": true }',
|
||||||
"in your package.json"
|
"in your package.json"
|
||||||
].join(" ")
|
].join(" ")
|
||||||
);
|
);
|
||||||
|
|
||||||
return id<BuildOptions.ExternalAssets.DifferentDomains>({
|
return id<BuildOptions.ExternalAssets.DifferentDomains>({
|
||||||
...commonExternalAssets,
|
...commonExternalAssets,
|
||||||
"isAppAndKeycloakServerSharingSameDomain": false,
|
"areAppAndKeycloakServerSharingSameDomain": false,
|
||||||
"urlOrigin": url.origin,
|
"urlOrigin": url.origin,
|
||||||
"urlPathname": url.pathname
|
"urlPathname": url.pathname
|
||||||
});
|
});
|
||||||
|
@ -272,6 +272,11 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
|||||||
|
|
||||||
<#list object as array_item>
|
<#list object as array_item>
|
||||||
|
|
||||||
|
<#if !array_item??>
|
||||||
|
<#local out_seq += ["null,"]>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
|
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
|
||||||
|
|
||||||
<#local i = i + 1>
|
<#local i = i + 1>
|
||||||
|
@ -27,7 +27,9 @@ export const pageIds = [
|
|||||||
"login-idp-link-email.ftl",
|
"login-idp-link-email.ftl",
|
||||||
"login-page-expired.ftl",
|
"login-page-expired.ftl",
|
||||||
"login-config-totp.ftl",
|
"login-config-totp.ftl",
|
||||||
"logout-confirm.ftl"
|
"logout-confirm.ftl",
|
||||||
|
"update-user-profile.ftl",
|
||||||
|
"idp-review-user-profile.ftl"
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
|
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
|
||||||
@ -46,11 +48,11 @@ export namespace BuildOptionsLike {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SameDomain = CommonExternalAssets & {
|
export type SameDomain = CommonExternalAssets & {
|
||||||
isAppAndKeycloakServerSharingSameDomain: true;
|
areAppAndKeycloakServerSharingSameDomain: true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DifferentDomains = CommonExternalAssets & {
|
export type DifferentDomains = CommonExternalAssets & {
|
||||||
isAppAndKeycloakServerSharingSameDomain: false;
|
areAppAndKeycloakServerSharingSameDomain: false;
|
||||||
urlOrigin: string;
|
urlOrigin: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
};
|
};
|
||||||
@ -76,7 +78,7 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
const $ = cheerio.load(indexHtmlCode);
|
const $ = cheerio.load(indexHtmlCode);
|
||||||
|
|
||||||
fix_imports_statements: {
|
fix_imports_statements: {
|
||||||
if (!buildOptions.isStandalone && buildOptions.isAppAndKeycloakServerSharingSameDomain) {
|
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
|
||||||
break fix_imports_statements;
|
break fix_imports_statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import { isInside } from "../tools/isInside";
|
|||||||
import type { BuildOptions } from "./BuildOptions";
|
import type { BuildOptions } from "./BuildOptions";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { Reflect } from "tsafe/Reflect";
|
import { Reflect } from "tsafe/Reflect";
|
||||||
|
import { getLogger } from "../tools/logger";
|
||||||
|
|
||||||
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
|
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ export namespace BuildOptionsLike {
|
|||||||
themeName: string;
|
themeName: string;
|
||||||
extraPages?: string[];
|
extraPages?: string[];
|
||||||
extraThemeProperties?: string[];
|
extraThemeProperties?: string[];
|
||||||
|
isSilent: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Standalone = Common & {
|
export type Standalone = Common & {
|
||||||
@ -34,11 +36,11 @@ export namespace BuildOptionsLike {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SameDomain = CommonExternalAssets & {
|
export type SameDomain = CommonExternalAssets & {
|
||||||
isAppAndKeycloakServerSharingSameDomain: true;
|
areAppAndKeycloakServerSharingSameDomain: true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DifferentDomains = CommonExternalAssets & {
|
export type DifferentDomains = CommonExternalAssets & {
|
||||||
isAppAndKeycloakServerSharingSameDomain: false;
|
areAppAndKeycloakServerSharingSameDomain: false;
|
||||||
urlOrigin: string;
|
urlOrigin: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
};
|
};
|
||||||
@ -60,6 +62,7 @@ export function generateKeycloakThemeResources(params: {
|
|||||||
}): { doBundlesEmailTemplate: boolean } {
|
}): { doBundlesEmailTemplate: boolean } {
|
||||||
const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, keycloakThemeEmailDirPath, keycloakVersion, buildOptions } = params;
|
const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, keycloakThemeEmailDirPath, keycloakVersion, buildOptions } = params;
|
||||||
|
|
||||||
|
const logger = getLogger({ isSilent: buildOptions.isSilent });
|
||||||
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, "login");
|
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, "login");
|
||||||
|
|
||||||
let allCssGlobalsToDefine: Record<string, string> = {};
|
let allCssGlobalsToDefine: Record<string, string> = {};
|
||||||
@ -97,7 +100,7 @@ export function generateKeycloakThemeResources(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (/\.js?$/i.test(filePath)) {
|
if (/\.js?$/i.test(filePath)) {
|
||||||
if (!buildOptions.isStandalone && buildOptions.isAppAndKeycloakServerSharingSameDomain) {
|
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +120,7 @@ export function generateKeycloakThemeResources(params: {
|
|||||||
|
|
||||||
email: {
|
email: {
|
||||||
if (!fs.existsSync(keycloakThemeEmailDirPath)) {
|
if (!fs.existsSync(keycloakThemeEmailDirPath)) {
|
||||||
console.log(
|
logger.log(
|
||||||
[
|
[
|
||||||
`Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
|
`Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
|
||||||
`To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`
|
`To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`
|
||||||
@ -154,7 +157,8 @@ export function generateKeycloakThemeResources(params: {
|
|||||||
|
|
||||||
downloadBuiltinKeycloakTheme({
|
downloadBuiltinKeycloakTheme({
|
||||||
keycloakVersion,
|
keycloakVersion,
|
||||||
"destDirPath": tmpDirPath
|
"destDirPath": tmpDirPath,
|
||||||
|
isSilent: buildOptions.isSilent
|
||||||
});
|
});
|
||||||
|
|
||||||
const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
|
const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
|
||||||
|
@ -5,6 +5,8 @@ import * as child_process from "child_process";
|
|||||||
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { readBuildOptions } from "./BuildOptions";
|
import { readBuildOptions } from "./BuildOptions";
|
||||||
|
import { getLogger } from "../tools/logger";
|
||||||
|
import { getCliOptions } from "../tools/cliOptions";
|
||||||
|
|
||||||
const reactProjectDirPath = process.cwd();
|
const reactProjectDirPath = process.cwd();
|
||||||
|
|
||||||
@ -12,7 +14,9 @@ export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build
|
|||||||
export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email");
|
export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email");
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
console.log("🔏 Building the keycloak theme...⌚");
|
const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2));
|
||||||
|
const logger = getLogger({ isSilent });
|
||||||
|
logger.log("🔏 Building the keycloak theme...⌚");
|
||||||
|
|
||||||
const buildOptions = readBuildOptions({
|
const buildOptions = readBuildOptions({
|
||||||
"packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"),
|
"packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"),
|
||||||
@ -25,7 +29,8 @@ export function main() {
|
|||||||
|
|
||||||
return fs.readFileSync(cnameFilePath).toString("utf8");
|
return fs.readFileSync(cnameFilePath).toString("utf8");
|
||||||
})(),
|
})(),
|
||||||
"isExternalAssetsCliParamProvided": process.argv[2]?.toLowerCase() === "--external-assets"
|
"isExternalAssetsCliParamProvided": hasExternalAssets,
|
||||||
|
"isSilent": isSilent
|
||||||
});
|
});
|
||||||
|
|
||||||
const { doBundlesEmailTemplate } = generateKeycloakThemeResources({
|
const { doBundlesEmailTemplate } = generateKeycloakThemeResources({
|
||||||
@ -51,7 +56,7 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//We want, however, to test in a container running the latest Keycloak version
|
//We want, however, to test in a container running the latest Keycloak version
|
||||||
const containerKeycloakVersion = "18.0.2";
|
const containerKeycloakVersion = "19.0.1";
|
||||||
|
|
||||||
generateStartKeycloakTestingContainer({
|
generateStartKeycloakTestingContainer({
|
||||||
keycloakThemeBuildingDirPath,
|
keycloakThemeBuildingDirPath,
|
||||||
@ -59,7 +64,7 @@ export function main() {
|
|||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
|
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
|
||||||
|
@ -41,9 +41,11 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; build
|
|||||||
const { jsCode, buildOptions } = params;
|
const { jsCode, buildOptions } = params;
|
||||||
|
|
||||||
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
|
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
|
||||||
new RegExp(`([a-zA-Z]+)\\.([a-zA-Z]+)=function\\(([a-zA-Z]+)\\){return"static\\/${language}\\/"`, "g"),
|
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=function\\(([a-zA-Z]+)\\){return"static\\/${language}\\/"`, "g"),
|
||||||
(...[, n, u, e]) => `
|
(...[, n, u, e]) => `
|
||||||
${n}[(function(){
|
${n}[(function(){
|
||||||
|
var pd= Object.getOwnPropertyDescriptor(${n}, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
${
|
${
|
||||||
buildOptions.isStandalone
|
buildOptions.isStandalone
|
||||||
? `
|
? `
|
||||||
@ -55,10 +57,11 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; build
|
|||||||
: `
|
: `
|
||||||
var p= "";
|
var p= "";
|
||||||
Object.defineProperty(${n}, "p", {
|
Object.defineProperty(${n}, "p", {
|
||||||
get: function() { return ("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}" : "") + p; },
|
get: function() { return "${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : p; },
|
||||||
set: function (value){ p = value;}
|
set: function (value){ p = value;}
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "${u}";
|
return "${u}";
|
||||||
})()] = function(${e}) { return "${buildOptions.isStandalone ? "/build/" : ""}static/${language}/"`
|
})()] = function(${e}) { return "${buildOptions.isStandalone ? "/build/" : ""}static/${language}/"`
|
||||||
@ -70,13 +73,13 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; build
|
|||||||
.replace(/([a-zA-Z]+\.[a-zA-Z]+)\+"static\//g, (...[, group]) =>
|
.replace(/([a-zA-Z]+\.[a-zA-Z]+)\+"static\//g, (...[, group]) =>
|
||||||
buildOptions.isStandalone
|
buildOptions.isStandalone
|
||||||
? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
|
? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
|
||||||
: `("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}" : "") + ${group} + "static/`
|
: `("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : ${group}) + "static/`
|
||||||
)
|
)
|
||||||
//TODO: Write a test case for this
|
//TODO: Write a test case for this
|
||||||
.replace(/".chunk.css",([a-zA-Z])+=([a-zA-Z]+\.[a-zA-Z]+)\+([a-zA-Z]+),/, (...[, group1, group2, group3]) =>
|
.replace(/".chunk.css",([a-zA-Z])+=([a-zA-Z]+\.[a-zA-Z]+)\+([a-zA-Z]+),/, (...[, group1, group2, group3]) =>
|
||||||
buildOptions.isStandalone
|
buildOptions.isStandalone
|
||||||
? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},`
|
? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},`
|
||||||
: `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}" : "") + ${group2} + ${group3},`
|
: `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : ${group2}) + ${group3},`
|
||||||
);
|
);
|
||||||
|
|
||||||
return { fixedJsCode };
|
return { fixedJsCode };
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
|
import { exclude } from "tsafe/exclude";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
|
||||||
const keycloakifyDirPath = pathJoin(__dirname, "..", "..");
|
const keycloakifyDirPath = pathJoin(__dirname, "..", "..");
|
||||||
@ -60,11 +61,32 @@ const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const testAppNames = [process.argv[2] ?? "keycloakify-demo-app"] as const;
|
const testAppPaths = (() => {
|
||||||
|
const arg = process.argv[2];
|
||||||
|
|
||||||
const getTestAppPath = (testAppName: typeof testAppNames[number]) => pathJoin(keycloakifyDirPath, "..", testAppName);
|
const testAppNames = arg !== undefined ? [arg] : ["keycloakify-starter", "keycloakify-advanced-starter"];
|
||||||
|
|
||||||
testAppNames.forEach(testAppName => execSync("yarn install", { "cwd": getTestAppPath(testAppName) }));
|
return testAppNames
|
||||||
|
.map(testAppName => {
|
||||||
|
const testAppPath = pathJoin(keycloakifyDirPath, "..", testAppName);
|
||||||
|
|
||||||
|
if (fs.existsSync(testAppPath)) {
|
||||||
|
return testAppPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(`Skipping ${testAppName} since it cant be found here: ${testAppPath}`);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.filter(exclude(undefined));
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (testAppPaths.length === 0) {
|
||||||
|
console.error("No test app to link into!");
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
testAppPaths.forEach(testAppPath => execSync("yarn install", { "cwd": testAppPath }));
|
||||||
|
|
||||||
console.log("=== Linking common dependencies ===");
|
console.log("=== Linking common dependencies ===");
|
||||||
|
|
||||||
@ -81,22 +103,24 @@ commonThirdPartyDeps.forEach(commonThirdPartyDep => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
execYarnLink({ "cwd": localInstallPath });
|
execYarnLink({ "cwd": localInstallPath });
|
||||||
|
});
|
||||||
|
|
||||||
testAppNames.forEach(testAppName =>
|
commonThirdPartyDeps.forEach(commonThirdPartyDep =>
|
||||||
|
testAppPaths.forEach(testAppPath =>
|
||||||
execYarnLink({
|
execYarnLink({
|
||||||
"cwd": getTestAppPath(testAppName),
|
"cwd": testAppPath,
|
||||||
"targetModuleName": commonThirdPartyDep
|
"targetModuleName": commonThirdPartyDep
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
});
|
);
|
||||||
|
|
||||||
console.log("=== Linking in house dependencies ===");
|
console.log("=== Linking in house dependencies ===");
|
||||||
|
|
||||||
execYarnLink({ "cwd": pathJoin(keycloakifyDirPath, "dist") });
|
execYarnLink({ "cwd": pathJoin(keycloakifyDirPath, "dist") });
|
||||||
|
|
||||||
testAppNames.forEach(testAppName =>
|
testAppPaths.forEach(testAppPath =>
|
||||||
execYarnLink({
|
execYarnLink({
|
||||||
"cwd": getTestAppPath(testAppName),
|
"cwd": testAppPath,
|
||||||
"targetModuleName": "keycloakify"
|
"targetModuleName": "keycloakify"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
15
src/bin/tools/cliOptions.ts
Normal file
15
src/bin/tools/cliOptions.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import parseArgv from "minimist";
|
||||||
|
|
||||||
|
export type CliOptions = {
|
||||||
|
isSilent: boolean;
|
||||||
|
hasExternalAssets: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCliOptions = (processArgv: string[]): CliOptions => {
|
||||||
|
const argv = parseArgv(processArgv);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSilent: typeof argv["silent"] === "boolean" ? argv["silent"] : false,
|
||||||
|
hasExternalAssets: typeof argv["external-assets"] === "boolean" ? argv["external-assets"] : false
|
||||||
|
};
|
||||||
|
};
|
@ -6,7 +6,13 @@ import { rm, rm_rf } from "./rm";
|
|||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
/** assert url ends with .zip */
|
/** assert url ends with .zip */
|
||||||
export function downloadAndUnzip(params: { url: string; destDirPath: string; pathOfDirToExtractInArchive?: string; cacheDirPath: string }) {
|
export function downloadAndUnzip(params: {
|
||||||
|
isSilent: boolean;
|
||||||
|
url: string;
|
||||||
|
destDirPath: string;
|
||||||
|
pathOfDirToExtractInArchive?: string;
|
||||||
|
cacheDirPath: string;
|
||||||
|
}) {
|
||||||
const { url, destDirPath, pathOfDirToExtractInArchive, cacheDirPath } = params;
|
const { url, destDirPath, pathOfDirToExtractInArchive, cacheDirPath } = params;
|
||||||
|
|
||||||
const extractDirPath = pathJoin(
|
const extractDirPath = pathJoin(
|
||||||
@ -54,7 +60,7 @@ export function downloadAndUnzip(params: { url: string; destDirPath: string; pat
|
|||||||
|
|
||||||
const zipFileBasename = pathBasename(url);
|
const zipFileBasename = pathBasename(url);
|
||||||
|
|
||||||
execSync(`curl -L ${url} -o ${zipFileBasename}`, { "cwd": extractDirPath });
|
execSync(`curl -L ${url} -o ${zipFileBasename} ${params.isSilent ? "-s" : ""}`, { "cwd": extractDirPath });
|
||||||
|
|
||||||
execSync(`unzip -o ${zipFileBasename}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/**/*"`}`, {
|
execSync(`unzip -o ${zipFileBasename}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/**/*"`}`, {
|
||||||
"cwd": extractDirPath
|
"cwd": extractDirPath
|
||||||
|
27
src/bin/tools/logger.ts
Normal file
27
src/bin/tools/logger.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
type LoggerOpts = {
|
||||||
|
force?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Logger = {
|
||||||
|
log: (message: string, opts?: LoggerOpts) => void;
|
||||||
|
warn: (message: string) => void;
|
||||||
|
error: (message: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLogger = ({ isSilent }: { isSilent?: boolean } = {}): Logger => {
|
||||||
|
return {
|
||||||
|
log: (message, { force } = {}) => {
|
||||||
|
if (isSilent && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(message);
|
||||||
|
},
|
||||||
|
warn: message => {
|
||||||
|
console.warn(message);
|
||||||
|
},
|
||||||
|
error: message => {
|
||||||
|
console.error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@ -4,31 +4,37 @@ import type { KcProps } from "./KcProps";
|
|||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const Error = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Error; i18n: I18n } & KcProps) => {
|
const Error = memo(
|
||||||
const { message, client } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.Error; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { message, client } = kcContext;
|
||||||
|
|
||||||
const { msg } = i18n;
|
const { msg } = i18n;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
displayMessage={false}
|
||||||
displayMessage={false}
|
headerNode={msg("errorTitle")}
|
||||||
headerNode={msg("errorTitle")}
|
formNode={
|
||||||
formNode={
|
<div id="kc-error-message">
|
||||||
<div id="kc-error-message">
|
<p className="instruction">{message.summary}</p>
|
||||||
<p className="instruction">{message.summary}</p>
|
{client !== undefined && client.baseUrl !== undefined && (
|
||||||
{client !== undefined && client.baseUrl !== undefined && (
|
<p>
|
||||||
<p>
|
<a id="backToApplication" href={client.baseUrl}>
|
||||||
<a id="backToApplication" href={client.baseUrl}>
|
{msg("backToApplication")}
|
||||||
{msg("backToApplication")}
|
</a>
|
||||||
</a>
|
</p>
|
||||||
</p>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default Error;
|
export default Error;
|
||||||
|
57
src/lib/components/IdpReviewUserProfile.tsx
Normal file
57
src/lib/components/IdpReviewUserProfile.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React, { useState, memo } from "react";
|
||||||
|
import Template from "./Template";
|
||||||
|
import type { KcProps } from "./KcProps";
|
||||||
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
|
import type { I18n } from "../i18n";
|
||||||
|
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||||
|
|
||||||
|
const IdpReviewUserProfile = memo(
|
||||||
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.IdpReviewUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
|
const { url } = kcContext;
|
||||||
|
|
||||||
|
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
|
headerNode={msg("loginIdpReviewProfileTitle")}
|
||||||
|
formNode={
|
||||||
|
<form id="kc-idp-review-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
|
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
|
<div className={cx(props.kcFormOptionsWrapperClass)} />
|
||||||
|
</div>
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
|
<input
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doSubmit")}
|
||||||
|
disabled={!isFomSubmittable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default IdpReviewUserProfile;
|
@ -5,47 +5,53 @@ import { assert } from "../tools/assert";
|
|||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const Info = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Info; i18n: I18n } & KcProps) => {
|
const Info = memo(
|
||||||
const { msgStr, msg } = i18n;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.Info; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { msgStr, msg } = i18n;
|
||||||
|
|
||||||
assert(kcContext.message !== undefined);
|
assert(kcContext.message !== undefined);
|
||||||
|
|
||||||
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
|
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
displayMessage={false}
|
||||||
displayMessage={false}
|
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
|
||||||
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
|
formNode={
|
||||||
formNode={
|
<div id="kc-info-message">
|
||||||
<div id="kc-info-message">
|
<p className="instruction">
|
||||||
<p className="instruction">
|
{message.summary}
|
||||||
{message.summary}
|
|
||||||
|
|
||||||
{requiredActions !== undefined && (
|
{requiredActions !== undefined && (
|
||||||
<b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
|
<b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
|
||||||
)}
|
)}
|
||||||
</p>
|
|
||||||
{!skipLink && pageRedirectUri !== undefined ? (
|
|
||||||
<p>
|
|
||||||
<a href={pageRedirectUri}>{msg("backToApplication")}</a>
|
|
||||||
</p>
|
</p>
|
||||||
) : actionUri !== undefined ? (
|
{!skipLink && pageRedirectUri !== undefined ? (
|
||||||
<p>
|
|
||||||
<a href={actionUri}>{msg("proceedWithAction")}</a>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
client.baseUrl !== undefined && (
|
|
||||||
<p>
|
<p>
|
||||||
<a href={client.baseUrl}>{msg("backToApplication")}</a>
|
<a href={pageRedirectUri}>{msg("backToApplication")}</a>
|
||||||
</p>
|
</p>
|
||||||
)
|
) : actionUri !== undefined ? (
|
||||||
)}
|
<p>
|
||||||
</div>
|
<a href={actionUri}>{msg("proceedWithAction")}</a>
|
||||||
}
|
</p>
|
||||||
/>
|
) : (
|
||||||
);
|
client.baseUrl !== undefined && (
|
||||||
});
|
<p>
|
||||||
|
<a href={client.baseUrl}>{msg("backToApplication")}</a>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default Info;
|
export default Info;
|
||||||
|
@ -20,62 +20,76 @@ const LoginPageExpired = lazy(() => import("./LoginPageExpired"));
|
|||||||
const LoginIdpLinkEmail = lazy(() => import("./LoginIdpLinkEmail"));
|
const LoginIdpLinkEmail = lazy(() => import("./LoginIdpLinkEmail"));
|
||||||
const LoginConfigTotp = lazy(() => import("./LoginConfigTotp"));
|
const LoginConfigTotp = lazy(() => import("./LoginConfigTotp"));
|
||||||
const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
|
const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
|
||||||
|
const UpdateUserProfile = lazy(() => import("./UpdateUserProfile"));
|
||||||
|
const IdpReviewUserProfile = lazy(() => import("./IdpReviewUserProfile"));
|
||||||
|
|
||||||
const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...props }: { kcContext: KcContextBase; i18n?: I18n } & KcProps) => {
|
const KcApp = memo(
|
||||||
const i18n = (function useClosure() {
|
({
|
||||||
const i18n = useI18n({
|
kcContext,
|
||||||
kcContext,
|
i18n: userProvidedI18n,
|
||||||
"extraMessages": {},
|
...kcProps
|
||||||
"doSkip": userProvidedI18n !== undefined
|
}: { kcContext: KcContextBase; i18n?: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
});
|
const i18n = (function useClosure() {
|
||||||
|
const i18n = useI18n({
|
||||||
|
kcContext,
|
||||||
|
"extraMessages": {},
|
||||||
|
"doSkip": userProvidedI18n !== undefined
|
||||||
|
});
|
||||||
|
|
||||||
return userProvidedI18n ?? i18n;
|
return userProvidedI18n ?? i18n;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (i18n === null) {
|
if (i18n === null) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = { i18n, ...kcProps };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
{(() => {
|
||||||
|
switch (kcContext.pageId) {
|
||||||
|
case "login.ftl":
|
||||||
|
return <Login {...{ kcContext, ...props }} />;
|
||||||
|
case "register.ftl":
|
||||||
|
return <Register {...{ kcContext, ...props }} />;
|
||||||
|
case "register-user-profile.ftl":
|
||||||
|
return <RegisterUserProfile {...{ kcContext, ...props }} />;
|
||||||
|
case "info.ftl":
|
||||||
|
return <Info {...{ kcContext, ...props }} />;
|
||||||
|
case "error.ftl":
|
||||||
|
return <Error {...{ kcContext, ...props }} />;
|
||||||
|
case "login-reset-password.ftl":
|
||||||
|
return <LoginResetPassword {...{ kcContext, ...props }} />;
|
||||||
|
case "login-verify-email.ftl":
|
||||||
|
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
|
||||||
|
case "terms.ftl":
|
||||||
|
return <Terms {...{ kcContext, ...props }} />;
|
||||||
|
case "login-otp.ftl":
|
||||||
|
return <LoginOtp {...{ kcContext, ...props }} />;
|
||||||
|
case "login-update-password.ftl":
|
||||||
|
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
|
||||||
|
case "login-update-profile.ftl":
|
||||||
|
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
|
||||||
|
case "login-idp-link-confirm.ftl":
|
||||||
|
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
|
||||||
|
case "login-idp-link-email.ftl":
|
||||||
|
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
|
||||||
|
case "login-page-expired.ftl":
|
||||||
|
return <LoginPageExpired {...{ kcContext, ...props }} />;
|
||||||
|
case "login-config-totp.ftl":
|
||||||
|
return <LoginConfigTotp {...{ kcContext, ...props }} />;
|
||||||
|
case "logout-confirm.ftl":
|
||||||
|
return <LogoutConfirm {...{ kcContext, ...props }} />;
|
||||||
|
case "update-user-profile.ftl":
|
||||||
|
return <UpdateUserProfile {...{ kcContext, ...props }} />;
|
||||||
|
case "idp-review-user-profile.ftl":
|
||||||
|
return <IdpReviewUserProfile {...{ kcContext, ...props }} />;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return (
|
|
||||||
<Suspense>
|
|
||||||
{(() => {
|
|
||||||
switch (kcContext.pageId) {
|
|
||||||
case "login.ftl":
|
|
||||||
return <Login {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "register.ftl":
|
|
||||||
return <Register {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "register-user-profile.ftl":
|
|
||||||
return <RegisterUserProfile {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "info.ftl":
|
|
||||||
return <Info {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "error.ftl":
|
|
||||||
return <Error {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-reset-password.ftl":
|
|
||||||
return <LoginResetPassword {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-verify-email.ftl":
|
|
||||||
return <LoginVerifyEmail {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "terms.ftl":
|
|
||||||
return <Terms {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-otp.ftl":
|
|
||||||
return <LoginOtp {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-update-password.ftl":
|
|
||||||
return <LoginUpdatePassword {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-update-profile.ftl":
|
|
||||||
return <LoginUpdateProfile {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-idp-link-confirm.ftl":
|
|
||||||
return <LoginIdpLinkConfirm {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-idp-link-email.ftl":
|
|
||||||
return <LoginIdpLinkEmail {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-page-expired.ftl":
|
|
||||||
return <LoginPageExpired {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "login-config-totp.ftl":
|
|
||||||
return <LoginConfigTotp {...{ kcContext, i18n, ...props }} />;
|
|
||||||
case "logout-confirm.ftl":
|
|
||||||
return <LogoutConfirm {...{ kcContext, i18n, ...props }} />;
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default KcApp;
|
export default KcApp;
|
||||||
|
@ -2,194 +2,202 @@ import React, { useState, memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
import type { FormEventHandler } from "react";
|
import type { FormEventHandler } from "react";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Login; i18n: I18n } & KcProps) => {
|
const Login = memo(
|
||||||
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.Login; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
||||||
|
|
||||||
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
|
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
setIsLoginButtonDisabled(true);
|
setIsLoginButtonDisabled(true);
|
||||||
|
|
||||||
const formElement = e.target as HTMLFormElement;
|
const formElement = e.target as HTMLFormElement;
|
||||||
|
|
||||||
//NOTE: Even if we login with email Keycloak expect username and password in
|
//NOTE: Even if we login with email Keycloak expect username and password in
|
||||||
//the POST request.
|
//the POST request.
|
||||||
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
|
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
|
||||||
|
|
||||||
formElement.submit();
|
formElement.submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
displayInfo={social.displayInfo}
|
||||||
displayInfo={social.displayInfo}
|
displayWide={realm.password && social.providers !== undefined}
|
||||||
displayWide={realm.password && social.providers !== undefined}
|
headerNode={msg("doLogIn")}
|
||||||
headerNode={msg("doLogIn")}
|
formNode={
|
||||||
formNode={
|
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
|
||||||
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
|
<div
|
||||||
<div
|
id="kc-form-wrapper"
|
||||||
id="kc-form-wrapper"
|
className={cx(
|
||||||
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}
|
realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass]
|
||||||
>
|
)}
|
||||||
{realm.password && (
|
>
|
||||||
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
|
{realm.password && (
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
|
||||||
{(() => {
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
const label = !realm.loginWithEmailAllowed
|
{(() => {
|
||||||
? "username"
|
const label = !realm.loginWithEmailAllowed
|
||||||
: realm.registrationEmailAsUsername
|
? "username"
|
||||||
? "email"
|
: realm.registrationEmailAsUsername
|
||||||
: "usernameOrEmail";
|
? "email"
|
||||||
|
: "usernameOrEmail";
|
||||||
|
|
||||||
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
|
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}>
|
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}>
|
||||||
{msg(label)}
|
{msg(label)}
|
||||||
</label>
|
</label>
|
||||||
<input
|
|
||||||
tabIndex={1}
|
|
||||||
id={autoCompleteHelper}
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
//NOTE: This is used by Google Chrome auto fill so we use it to tell
|
|
||||||
//the browser how to pre fill the form but before submit we put it back
|
|
||||||
//to username because it is what keycloak expects.
|
|
||||||
name={autoCompleteHelper}
|
|
||||||
defaultValue={login.username ?? ""}
|
|
||||||
type="text"
|
|
||||||
{...(usernameEditDisabled
|
|
||||||
? { "disabled": true }
|
|
||||||
: {
|
|
||||||
"autoFocus": true,
|
|
||||||
"autoComplete": "off"
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
|
||||||
<label htmlFor="password" className={cx(props.kcLabelClass)}>
|
|
||||||
{msg("password")}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
tabIndex={2}
|
|
||||||
id="password"
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
|
|
||||||
<div id="kc-form-options">
|
|
||||||
{realm.rememberMe && !usernameEditDisabled && (
|
|
||||||
<div className="checkbox">
|
|
||||||
<label>
|
|
||||||
<input
|
<input
|
||||||
tabIndex={3}
|
tabIndex={1}
|
||||||
id="rememberMe"
|
id={autoCompleteHelper}
|
||||||
name="rememberMe"
|
className={cx(props.kcInputClass)}
|
||||||
type="checkbox"
|
//NOTE: This is used by Google Chrome auto fill so we use it to tell
|
||||||
{...(login.rememberMe
|
//the browser how to pre fill the form but before submit we put it back
|
||||||
? {
|
//to username because it is what keycloak expects.
|
||||||
"checked": true
|
name={autoCompleteHelper}
|
||||||
}
|
defaultValue={login.username ?? ""}
|
||||||
: {})}
|
type="text"
|
||||||
|
{...(usernameEditDisabled
|
||||||
|
? { "disabled": true }
|
||||||
|
: {
|
||||||
|
"autoFocus": true,
|
||||||
|
"autoComplete": "off"
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
{msg("rememberMe")}
|
</>
|
||||||
</label>
|
);
|
||||||
</div>
|
})()}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
{realm.resetPasswordAllowed && (
|
<label htmlFor="password" className={cx(props.kcLabelClass)}>
|
||||||
<span>
|
{msg("password")}
|
||||||
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
|
</label>
|
||||||
{msg("doForgotPassword")}
|
<input
|
||||||
</a>
|
tabIndex={2}
|
||||||
</span>
|
id="password"
|
||||||
)}
|
className={cx(props.kcInputClass)}
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
|
<div id="kc-form-options">
|
||||||
<input
|
{realm.rememberMe && !usernameEditDisabled && (
|
||||||
type="hidden"
|
<div className="checkbox">
|
||||||
id="id-hidden-input"
|
<label>
|
||||||
name="credentialId"
|
<input
|
||||||
{...(auth?.selectedCredential !== undefined
|
tabIndex={3}
|
||||||
? {
|
id="rememberMe"
|
||||||
"value": auth.selectedCredential
|
name="rememberMe"
|
||||||
}
|
type="checkbox"
|
||||||
: {})}
|
{...(login.rememberMe
|
||||||
/>
|
? {
|
||||||
<input
|
"checked": true
|
||||||
tabIndex={4}
|
}
|
||||||
className={cx(
|
: {})}
|
||||||
props.kcButtonClass,
|
/>
|
||||||
props.kcButtonPrimaryClass,
|
{msg("rememberMe")}
|
||||||
props.kcButtonBlockClass,
|
</label>
|
||||||
props.kcButtonLargeClass
|
</div>
|
||||||
)}
|
)}
|
||||||
name="login"
|
</div>
|
||||||
id="kc-login"
|
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||||
type="submit"
|
{realm.resetPasswordAllowed && (
|
||||||
value={msgStr("doLogIn")}
|
<span>
|
||||||
disabled={isLoginButtonDisabled}
|
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
|
||||||
/>
|
{msg("doForgotPassword")}
|
||||||
</div>
|
</a>
|
||||||
</form>
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
id="id-hidden-input"
|
||||||
|
name="credentialId"
|
||||||
|
{...(auth?.selectedCredential !== undefined
|
||||||
|
? {
|
||||||
|
"value": auth.selectedCredential
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
tabIndex={4}
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
name="login"
|
||||||
|
id="kc-login"
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doLogIn")}
|
||||||
|
disabled={isLoginButtonDisabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{realm.password && social.providers !== undefined && (
|
||||||
|
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
|
||||||
|
<ul
|
||||||
|
className={cx(
|
||||||
|
props.kcFormSocialAccountListClass,
|
||||||
|
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{social.providers.map(p => (
|
||||||
|
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
|
||||||
|
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
|
||||||
|
<span>{p.displayName}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{realm.password && social.providers !== undefined && (
|
}
|
||||||
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
|
infoNode={
|
||||||
<ul
|
realm.password &&
|
||||||
className={cx(
|
realm.registrationAllowed &&
|
||||||
props.kcFormSocialAccountListClass,
|
!registrationDisabled && (
|
||||||
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass
|
<div id="kc-registration">
|
||||||
)}
|
<span>
|
||||||
>
|
{msg("noAccount")}
|
||||||
{social.providers.map(p => (
|
<a tabIndex={6} href={url.registrationUrl}>
|
||||||
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
|
{msg("doRegister")}
|
||||||
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
|
</a>
|
||||||
<span>{p.displayName}</span>
|
</span>
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)
|
||||||
</div>
|
}
|
||||||
}
|
/>
|
||||||
infoNode={
|
);
|
||||||
realm.password &&
|
}
|
||||||
realm.registrationAllowed &&
|
);
|
||||||
!registrationDisabled && (
|
|
||||||
<div id="kc-registration">
|
|
||||||
<span>
|
|
||||||
{msg("noAccount")}
|
|
||||||
<a tabIndex={6} href={url.registrationUrl}>
|
|
||||||
{msg("doRegister")}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Login;
|
export default Login;
|
||||||
|
@ -2,185 +2,191 @@ import React, { memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n } & KcProps) => {
|
const LoginConfigTotp = memo(
|
||||||
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
|
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
|
||||||
HmacSHA1: "SHA1",
|
HmacSHA1: "SHA1",
|
||||||
HmacSHA256: "SHA256",
|
HmacSHA256: "SHA256",
|
||||||
HmacSHA512: "SHA512"
|
HmacSHA512: "SHA512"
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
headerNode={msg("loginTotpTitle")}
|
||||||
headerNode={msg("loginTotpTitle")}
|
formNode={
|
||||||
formNode={
|
<>
|
||||||
<>
|
<ol id="kc-totp-settings">
|
||||||
<ol id="kc-totp-settings">
|
<li>
|
||||||
<li>
|
<p>{msg("loginTotpStep1")}</p>
|
||||||
<p>{msg("loginTotpStep1")}</p>
|
|
||||||
|
|
||||||
<ul id="kc-totp-supported-apps">
|
<ul id="kc-totp-supported-apps">
|
||||||
{totp.policy.supportedApplications.map(app => (
|
{totp.policy.supportedApplications.map(app => (
|
||||||
<li>{app}</li>
|
<li>{app}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{mode && mode == "manual" ? (
|
{mode && mode == "manual" ? (
|
||||||
<>
|
<>
|
||||||
|
<li>
|
||||||
|
<p>{msg("loginTotpManualStep2")}</p>
|
||||||
|
<p>
|
||||||
|
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href={totp.qrUrl} id="mode-barcode">
|
||||||
|
{msg("loginTotpScanBarcode")}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>{msg("loginTotpManualStep3")}</p>
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
<li id="kc-totp-type">
|
||||||
|
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
|
||||||
|
</li>
|
||||||
|
<li id="kc-totp-algorithm">
|
||||||
|
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
|
||||||
|
</li>
|
||||||
|
<li id="kc-totp-digits">
|
||||||
|
{msg("loginTotpDigits")}: {totp.policy.digits}
|
||||||
|
</li>
|
||||||
|
{totp.policy.type === "totp" ? (
|
||||||
|
<li id="kc-totp-period">
|
||||||
|
{msg("loginTotpInterval")}: {totp.policy.period}
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li id="kc-totp-counter">
|
||||||
|
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<li>
|
<li>
|
||||||
<p>{msg("loginTotpManualStep2")}</p>
|
<p>{msg("loginTotpStep2")}</p>
|
||||||
|
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" />
|
||||||
|
<br />
|
||||||
<p>
|
<p>
|
||||||
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
|
<a href={totp.manualUrl} id="mode-manual">
|
||||||
</p>
|
{msg("loginTotpUnableToScan")}
|
||||||
<p>
|
|
||||||
<a href={totp.qrUrl} id="mode-barcode">
|
|
||||||
{msg("loginTotpScanBarcode")}
|
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
)}
|
||||||
<p>{msg("loginTotpManualStep3")}</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li id="kc-totp-type">
|
|
||||||
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
|
|
||||||
</li>
|
|
||||||
<li id="kc-totp-algorithm">
|
|
||||||
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
|
|
||||||
</li>
|
|
||||||
<li id="kc-totp-digits">
|
|
||||||
{msg("loginTotpDigits")}: {totp.policy.digits}
|
|
||||||
</li>
|
|
||||||
{totp.policy.type === "totp" ? (
|
|
||||||
<li id="kc-totp-period">
|
|
||||||
{msg("loginTotpInterval")}: {totp.policy.period}
|
|
||||||
</li>
|
|
||||||
) : (
|
|
||||||
<li id="kc-totp-counter">
|
|
||||||
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<li>
|
<li>
|
||||||
<p>{msg("loginTotpStep2")}</p>
|
<p>{msg("loginTotpStep3")}</p>
|
||||||
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" />
|
<p>{msg("loginTotpStep3DeviceName")}</p>
|
||||||
<br />
|
|
||||||
<p>
|
|
||||||
<a href={totp.manualUrl} id="mode-manual">
|
|
||||||
{msg("loginTotpUnableToScan")}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</li>
|
</li>
|
||||||
)}
|
</ol>
|
||||||
<li>
|
|
||||||
<p>{msg("loginTotpStep3")}</p>
|
|
||||||
<p>{msg("loginTotpStep3DeviceName")}</p>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post">
|
<form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post">
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<label htmlFor="totp" className={cx(props.kcLabelClass)}>
|
<label htmlFor="totp" className={cx(props.kcLabelClass)}>
|
||||||
{msg("authenticatorCode")}
|
{msg("authenticatorCode")}
|
||||||
</label>{" "}
|
</label>{" "}
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="totp"
|
id="totp"
|
||||||
name="totp"
|
name="totp"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className={cx(props.kcInputClass)}
|
className={cx(props.kcInputClass)}
|
||||||
aria-invalid={messagesPerField.existsError("totp")}
|
aria-invalid={messagesPerField.existsError("totp")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{messagesPerField.existsError("totp") && (
|
{messagesPerField.existsError("totp") && (
|
||||||
<span id="input-error-otp-code" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
|
<span id="input-error-otp-code" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
|
||||||
{messagesPerField.get("totp")}
|
{messagesPerField.get("totp")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
|
||||||
|
{mode && <input type="hidden" id="mode" value={mode} />}
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
|
|
||||||
{mode && <input type="hidden" id="mode" value={mode} />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<label htmlFor="userLabel" className={cx(props.kcLabelClass)}>
|
<label htmlFor="userLabel" className={cx(props.kcLabelClass)}>
|
||||||
{msg("loginTotpDeviceName")}
|
{msg("loginTotpDeviceName")}
|
||||||
</label>{" "}
|
</label>{" "}
|
||||||
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
|
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="userLabel"
|
||||||
|
name="userLabel"
|
||||||
|
autoComplete="off"
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
aria-invalid={messagesPerField.existsError("userLabel")}
|
||||||
|
/>
|
||||||
|
{messagesPerField.existsError("userLabel") && (
|
||||||
|
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
|
||||||
|
{messagesPerField.get("userLabel")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="userLabel"
|
|
||||||
name="userLabel"
|
|
||||||
autoComplete="off"
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
aria-invalid={messagesPerField.existsError("userLabel")}
|
|
||||||
/>
|
|
||||||
{messagesPerField.existsError("userLabel") && (
|
|
||||||
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
|
|
||||||
{messagesPerField.get("userLabel")}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isAppInitiatedAction ? (
|
{isAppInitiatedAction ? (
|
||||||
<>
|
<>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
||||||
|
id="saveTOTPBtn"
|
||||||
|
value={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonDefaultClass,
|
||||||
|
props.kcButtonLargeClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
id="cancelTOTPBtn"
|
||||||
|
name="cancel-aia"
|
||||||
|
value="true"
|
||||||
|
>
|
||||||
|
${msg("doCancel")}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
||||||
id="saveTOTPBtn"
|
id="saveTOTPBtn"
|
||||||
value={msgStr("doSubmit")}
|
value={msgStr("doSubmit")}
|
||||||
/>
|
/>
|
||||||
<button
|
)}
|
||||||
type="submit"
|
</form>
|
||||||
className={cx(
|
</>
|
||||||
props.kcButtonClass,
|
}
|
||||||
props.kcButtonDefaultClass,
|
/>
|
||||||
props.kcButtonLargeClass,
|
);
|
||||||
props.kcButtonLargeClass
|
}
|
||||||
)}
|
);
|
||||||
id="cancelTOTPBtn"
|
|
||||||
name="cancel-aia"
|
|
||||||
value="true"
|
|
||||||
>
|
|
||||||
${msg("doCancel")}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
|
||||||
id="saveTOTPBtn"
|
|
||||||
value={msgStr("doSubmit")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default LoginConfigTotp;
|
export default LoginConfigTotp;
|
||||||
|
@ -2,47 +2,53 @@ import React, { memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginIdpLinkConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n } & KcProps) => {
|
const LoginIdpLinkConfirm = memo(
|
||||||
const { url, idpAlias } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url, idpAlias } = kcContext;
|
||||||
|
|
||||||
const { msg } = i18n;
|
const { msg } = i18n;
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
headerNode={msg("confirmLinkIdpTitle")}
|
||||||
headerNode={msg("confirmLinkIdpTitle")}
|
formNode={
|
||||||
formNode={
|
<form id="kc-register-form" action={url.loginAction} method="post">
|
||||||
<form id="kc-register-form" action={url.loginAction} method="post">
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
||||||
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
name="submitAction"
|
||||||
name="submitAction"
|
id="updateProfile"
|
||||||
id="updateProfile"
|
value="updateProfile"
|
||||||
value="updateProfile"
|
>
|
||||||
>
|
{msg("confirmLinkIdpReviewProfile")}
|
||||||
{msg("confirmLinkIdpReviewProfile")}
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
||||||
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
name="submitAction"
|
||||||
name="submitAction"
|
id="linkAccount"
|
||||||
id="linkAccount"
|
value="linkAccount"
|
||||||
value="linkAccount"
|
>
|
||||||
>
|
{msg("confirmLinkIdpContinue", idpAlias)}
|
||||||
{msg("confirmLinkIdpContinue", idpAlias)}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LoginIdpLinkConfirm;
|
export default LoginIdpLinkConfirm;
|
||||||
|
@ -4,31 +4,37 @@ import type { KcProps } from "./KcProps";
|
|||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginIdpLinkEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail; i18n: I18n } & KcProps) => {
|
const LoginIdpLinkEmail = memo(
|
||||||
const { url, realm, brokerContext, idpAlias } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginIdpLinkEmail; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url, realm, brokerContext, idpAlias } = kcContext;
|
||||||
|
|
||||||
const { msg } = i18n;
|
const { msg } = i18n;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
headerNode={msg("emailLinkIdpTitle", idpAlias)}
|
||||||
headerNode={msg("emailLinkIdpTitle", idpAlias)}
|
formNode={
|
||||||
formNode={
|
<>
|
||||||
<>
|
<p id="instruction1" className="instruction">
|
||||||
<p id="instruction1" className="instruction">
|
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
|
||||||
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
|
</p>
|
||||||
</p>
|
<p id="instruction2" className="instruction">
|
||||||
<p id="instruction2" className="instruction">
|
{msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")}
|
||||||
{msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")}
|
</p>
|
||||||
</p>
|
<p id="instruction3" className="instruction">
|
||||||
<p id="instruction3" className="instruction">
|
{msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")}
|
||||||
{msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")}
|
</p>
|
||||||
</p>
|
</>
|
||||||
</>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LoginIdpLinkEmail;
|
export default LoginIdpLinkEmail;
|
||||||
|
@ -4,87 +4,98 @@ import type { KcProps } from "./KcProps";
|
|||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { headInsert } from "../tools/headInsert";
|
import { headInsert } from "../tools/headInsert";
|
||||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginOtp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginOtp; i18n: I18n } & KcProps) => {
|
const LoginOtp = memo(
|
||||||
const { otpLogin, url } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginOtp; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { otpLogin, url } = kcContext;
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isCleanedUp = false;
|
let isCleanedUp = false;
|
||||||
|
|
||||||
headInsert({
|
headInsert({
|
||||||
"type": "javascript",
|
"type": "javascript",
|
||||||
"src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js")
|
"src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js")
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (isCleanedUp) return;
|
if (isCleanedUp) return;
|
||||||
|
|
||||||
evaluateInlineScript();
|
evaluateInlineScript();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isCleanedUp = true;
|
isCleanedUp = true;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
headerNode={msg("doLogIn")}
|
||||||
headerNode={msg("doLogIn")}
|
formNode={
|
||||||
formNode={
|
<form id="kc-otp-login-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
<form id="kc-otp-login-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
{otpLogin.userOtpCredentials.length > 1 && (
|
||||||
{otpLogin.userOtpCredentials.length > 1 && (
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
{otpLogin.userOtpCredentials.map(otpCredential => (
|
||||||
{otpLogin.userOtpCredentials.map(otpCredential => (
|
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}>
|
||||||
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}>
|
<input type="hidden" value="${otpCredential.id}" />
|
||||||
<input type="hidden" value="${otpCredential.id}" />
|
<div className={cx(props.kcSelectOTPListItemClass)}>
|
||||||
<div className={cx(props.kcSelectOTPListItemClass)}>
|
<span className={cx(props.kcAuthenticatorOtpCircleClass)} />
|
||||||
<span className={cx(props.kcAuthenticatorOtpCircleClass)} />
|
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
|
||||||
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor="otp" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("loginOtpOneTime")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
|
||||||
<label htmlFor="otp" className={cx(props.kcLabelClass)}>
|
|
||||||
{msg("loginOtpOneTime")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus />
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
</div>
|
<div className={cx(props.kcFormOptionsWrapperClass)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
<input
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)} />
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
name="login"
|
||||||
|
id="kc-login"
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doLogIn")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
}
|
||||||
<input
|
/>
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
);
|
||||||
name="login"
|
}
|
||||||
id="kc-login"
|
);
|
||||||
type="submit"
|
|
||||||
value={msgStr("doLogIn")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
declare const $: any;
|
declare const $: any;
|
||||||
|
|
||||||
|
@ -4,35 +4,41 @@ import type { KcProps } from "./KcProps";
|
|||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginPageExpired = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginPageExpired; i18n: I18n } & KcProps) => {
|
const LoginPageExpired = memo(
|
||||||
const { url } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginPageExpired; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url } = kcContext;
|
||||||
|
|
||||||
const { msg } = i18n;
|
const { msg } = i18n;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
displayMessage={false}
|
||||||
displayMessage={false}
|
headerNode={msg("pageExpiredTitle")}
|
||||||
headerNode={msg("pageExpiredTitle")}
|
formNode={
|
||||||
formNode={
|
<>
|
||||||
<>
|
<p id="instruction1" className="instruction">
|
||||||
<p id="instruction1" className="instruction">
|
{msg("pageExpiredMsg1")}
|
||||||
{msg("pageExpiredMsg1")}
|
<a id="loginRestartLink" href={url.loginRestartFlowUrl}>
|
||||||
<a id="loginRestartLink" href={url.loginRestartFlowUrl}>
|
{msg("doClickHere")}
|
||||||
{msg("doClickHere")}
|
</a>{" "}
|
||||||
</a>{" "}
|
.<br />
|
||||||
.<br />
|
{msg("pageExpiredMsg2")}{" "}
|
||||||
{msg("pageExpiredMsg2")}{" "}
|
<a id="loginContinueLink" href={url.loginAction}>
|
||||||
<a id="loginContinueLink" href={url.loginAction}>
|
{msg("doClickHere")}
|
||||||
{msg("doClickHere")}
|
</a>{" "}
|
||||||
</a>{" "}
|
.
|
||||||
.
|
</p>
|
||||||
</p>
|
</>
|
||||||
</>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LoginPageExpired;
|
export default LoginPageExpired;
|
||||||
|
@ -2,67 +2,78 @@ import React, { memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginResetPassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n } & KcProps) => {
|
const LoginResetPassword = memo(
|
||||||
const { url, realm, auth } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url, realm, auth } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
displayMessage={false}
|
||||||
displayMessage={false}
|
headerNode={msg("emailForgotTitle")}
|
||||||
headerNode={msg("emailForgotTitle")}
|
formNode={
|
||||||
formNode={
|
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
||||||
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
{!realm.loginWithEmailAllowed
|
||||||
{!realm.loginWithEmailAllowed
|
? msg("username")
|
||||||
? msg("username")
|
: !realm.registrationEmailAsUsername
|
||||||
: !realm.registrationEmailAsUsername
|
? msg("usernameOrEmail")
|
||||||
? msg("usernameOrEmail")
|
: msg("email")}
|
||||||
: msg("email")}
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
id="username"
|
||||||
id="username"
|
name="username"
|
||||||
name="username"
|
className={cx(props.kcInputClass)}
|
||||||
className={cx(props.kcInputClass)}
|
autoFocus
|
||||||
autoFocus
|
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
|
||||||
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
|
|
||||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
|
||||||
<span>
|
|
||||||
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
|
||||||
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
|
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||||
|
<span>
|
||||||
|
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
<input
|
<input
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
className={cx(
|
||||||
type="submit"
|
props.kcButtonClass,
|
||||||
value={msgStr("doSubmit")}
|
props.kcButtonPrimaryClass,
|
||||||
/>
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
}
|
||||||
}
|
infoNode={msg("emailInstruction")}
|
||||||
infoNode={msg("emailInstruction")}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LoginResetPassword;
|
export default LoginResetPassword;
|
||||||
|
@ -2,118 +2,124 @@ import React, { memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n } & KcProps) => {
|
const LoginUpdatePassword = memo(
|
||||||
const { cx } = useCssAndCx();
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
|
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
headerNode={msg("updatePasswordTitle")}
|
||||||
headerNode={msg("updatePasswordTitle")}
|
formNode={
|
||||||
formNode={
|
<form id="kc-passwd-update-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
<form id="kc-passwd-update-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
id="username"
|
||||||
id="username"
|
name="username"
|
||||||
name="username"
|
value={username}
|
||||||
value={username}
|
readOnly={true}
|
||||||
readOnly={true}
|
autoComplete="username"
|
||||||
autoComplete="username"
|
style={{ display: "none" }}
|
||||||
style={{ display: "none" }}
|
/>
|
||||||
/>
|
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
|
||||||
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<label htmlFor="password-new" className={cx(props.kcLabelClass)}>
|
<label htmlFor="password-new" className={cx(props.kcLabelClass)}>
|
||||||
{msg("passwordNew")}
|
{msg("passwordNew")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="password-new"
|
id="password-new"
|
||||||
name="password-new"
|
name="password-new"
|
||||||
autoFocus
|
autoFocus
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
className={cx(props.kcInputClass)}
|
className={cx(props.kcInputClass)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
|
||||||
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
|
|
||||||
{msg("passwordConfirm")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="password-confirm"
|
|
||||||
name="password-confirm"
|
|
||||||
autoComplete="new-password"
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
|
||||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
|
||||||
{isAppInitiatedAction && (
|
|
||||||
<div className="checkbox">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
|
|
||||||
{msgStr("logoutOtherSessions")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
|
||||||
{isAppInitiatedAction ? (
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<>
|
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("passwordConfirm")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password-confirm"
|
||||||
|
name="password-confirm"
|
||||||
|
autoComplete="new-password"
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
|
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||||
|
{isAppInitiatedAction && (
|
||||||
|
<div className="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
|
||||||
|
{msgStr("logoutOtherSessions")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
|
{isAppInitiatedAction ? (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
defaultValue={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
name="cancel-aia"
|
||||||
|
value="true"
|
||||||
|
>
|
||||||
|
{msg("doCancel")}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<input
|
<input
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
type="submit"
|
type="submit"
|
||||||
defaultValue={msgStr("doSubmit")}
|
defaultValue={msgStr("doSubmit")}
|
||||||
/>
|
/>
|
||||||
<button
|
)}
|
||||||
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
</div>
|
||||||
type="submit"
|
|
||||||
name="cancel-aia"
|
|
||||||
value="true"
|
|
||||||
>
|
|
||||||
{msg("doCancel")}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
className={cx(
|
|
||||||
props.kcButtonClass,
|
|
||||||
props.kcButtonPrimaryClass,
|
|
||||||
props.kcButtonBlockClass,
|
|
||||||
props.kcButtonLargeClass
|
|
||||||
)}
|
|
||||||
type="submit"
|
|
||||||
defaultValue={msgStr("doSubmit")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LoginUpdatePassword;
|
export default LoginUpdatePassword;
|
||||||
|
@ -2,121 +2,133 @@ import React, { memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n } & KcProps) => {
|
const LoginUpdateProfile = memo(
|
||||||
const { cx } = useCssAndCx();
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
|
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
headerNode={msg("loginProfileTitle")}
|
||||||
headerNode={msg("loginProfileTitle")}
|
formNode={
|
||||||
formNode={
|
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
{user.editUsernameAllowed && (
|
||||||
{user.editUsernameAllowed && (
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("username")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
defaultValue={user.username ?? ""}
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
<label htmlFor="email" className={cx(props.kcLabelClass)}>
|
||||||
{msg("username")}
|
{msg("email")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(props.kcInputClass)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("firstName")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="username"
|
id="firstName"
|
||||||
name="username"
|
name="firstName"
|
||||||
defaultValue={user.username ?? ""}
|
defaultValue={user.firstName ?? ""}
|
||||||
className={cx(props.kcInputClass)}
|
className={cx(props.kcInputClass)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<label htmlFor="email" className={cx(props.kcLabelClass)}>
|
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
|
||||||
{msg("email")}
|
{msg("lastName")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(props.kcInputClass)} />
|
<input
|
||||||
</div>
|
type="text"
|
||||||
</div>
|
id="lastName"
|
||||||
|
name="lastName"
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
|
defaultValue={user.lastName ?? ""}
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
className={cx(props.kcInputClass)}
|
||||||
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
|
/>
|
||||||
{msg("firstName")}
|
</div>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="firstName"
|
|
||||||
name="firstName"
|
|
||||||
defaultValue={user.firstName ?? ""}
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
|
||||||
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
|
|
||||||
{msg("lastName")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<input type="text" id="lastName" name="lastName" defaultValue={user.lastName ?? ""} className={cx(props.kcInputClass)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
|
||||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
{isAppInitiatedAction ? (
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
<>
|
<div className={cx(props.kcFormOptionsWrapperClass)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
|
{isAppInitiatedAction ? (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
defaultValue={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
name="cancel-aia"
|
||||||
|
value="true"
|
||||||
|
>
|
||||||
|
{msg("doCancel")}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<input
|
<input
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
type="submit"
|
type="submit"
|
||||||
defaultValue={msgStr("doSubmit")}
|
defaultValue={msgStr("doSubmit")}
|
||||||
/>
|
/>
|
||||||
<button
|
)}
|
||||||
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
</div>
|
||||||
type="submit"
|
|
||||||
name="cancel-aia"
|
|
||||||
value="true"
|
|
||||||
>
|
|
||||||
{msg("doCancel")}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
className={cx(
|
|
||||||
props.kcButtonClass,
|
|
||||||
props.kcButtonPrimaryClass,
|
|
||||||
props.kcButtonBlockClass,
|
|
||||||
props.kcButtonLargeClass
|
|
||||||
)}
|
|
||||||
type="submit"
|
|
||||||
defaultValue={msgStr("doSubmit")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LoginUpdateProfile;
|
export default LoginUpdateProfile;
|
||||||
|
@ -4,31 +4,37 @@ import type { KcProps } from "./KcProps";
|
|||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LoginVerifyEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginVerifyEmail; i18n: I18n } & KcProps) => {
|
const LoginVerifyEmail = memo(
|
||||||
const { msg } = i18n;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LoginVerifyEmail; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { msg } = i18n;
|
||||||
|
|
||||||
const { url, user } = kcContext;
|
const { url, user } = kcContext;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
displayMessage={false}
|
||||||
displayMessage={false}
|
headerNode={msg("emailVerifyTitle")}
|
||||||
headerNode={msg("emailVerifyTitle")}
|
formNode={
|
||||||
formNode={
|
<>
|
||||||
<>
|
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
|
||||||
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
|
<p className="instruction">
|
||||||
<p className="instruction">
|
{msg("emailVerifyInstruction2")}
|
||||||
{msg("emailVerifyInstruction2")}
|
<br />
|
||||||
<br />
|
<a href={url.loginAction}>{msg("doClickHere")}</a>
|
||||||
<a href={url.loginAction}>{msg("doClickHere")}</a>
|
|
||||||
|
{msg("emailVerifyInstruction3")}
|
||||||
{msg("emailVerifyInstruction3")}
|
</p>
|
||||||
</p>
|
</>
|
||||||
</>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LoginVerifyEmail;
|
export default LoginVerifyEmail;
|
||||||
|
@ -1,62 +1,68 @@
|
|||||||
import React, { memo } from "react";
|
import React, { memo } from "react";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const LogoutConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LogoutConfirm; i18n: I18n } & KcProps) => {
|
const LogoutConfirm = memo(
|
||||||
const { url, client, logoutConfirm } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.LogoutConfirm; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url, client, logoutConfirm } = kcContext;
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
displayMessage={false}
|
||||||
displayMessage={false}
|
headerNode={msg("logoutConfirmTitle")}
|
||||||
headerNode={msg("logoutConfirmTitle")}
|
formNode={
|
||||||
formNode={
|
<>
|
||||||
<>
|
<div id="kc-logout-confirm" className="content-area">
|
||||||
<div id="kc-logout-confirm" className="content-area">
|
<p className="instruction">{msg("logoutConfirmHeader")}</p>
|
||||||
<p className="instruction">{msg("logoutConfirmHeader")}</p>
|
<form className="form-actions" action={url.logoutConfirmAction} method="POST">
|
||||||
<form className="form-actions" action={url.logoutConfirmAction} method="POST">
|
<input type="hidden" name="session_code" value={logoutConfirm.code} />
|
||||||
<input type="hidden" name="session_code" value={logoutConfirm.code} />
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
<div id="kc-form-options">
|
||||||
<div id="kc-form-options">
|
<div className={cx(props.kcFormOptionsWrapperClass)}></div>
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)}></div>
|
</div>
|
||||||
</div>
|
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
|
<input
|
||||||
<input
|
tabIndex={4}
|
||||||
tabIndex={4}
|
className={cx(
|
||||||
className={cx(
|
props.kcButtonClass,
|
||||||
props.kcButtonClass,
|
props.kcButtonPrimaryClass,
|
||||||
props.kcButtonPrimaryClass,
|
props.kcButtonBlockClass,
|
||||||
props.kcButtonBlockClass,
|
props.kcButtonLargeClass
|
||||||
props.kcButtonLargeClass
|
)}
|
||||||
)}
|
name="confirmLogout"
|
||||||
name="confirmLogout"
|
id="kc-logout"
|
||||||
id="kc-logout"
|
type="submit"
|
||||||
type="submit"
|
value={msgStr("doLogout")}
|
||||||
value={msgStr("doLogout")}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="kc-info-message">
|
||||||
|
{!logoutConfirm.skipLink && client.baseUrl && (
|
||||||
|
<p>
|
||||||
|
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
<div id="kc-info-message">
|
|
||||||
{!logoutConfirm.skipLink && client.baseUrl && (
|
|
||||||
<p>
|
|
||||||
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default LogoutConfirm;
|
export default LogoutConfirm;
|
||||||
|
@ -2,157 +2,168 @@ import React, { memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Register; i18n: I18n } & KcProps) => {
|
const Register = memo(
|
||||||
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.Register; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, ...props }}
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
doFetchDefaultThemeResources={true}
|
headerNode={msg("registerTitle")}
|
||||||
headerNode={msg("registerTitle")}
|
formNode={
|
||||||
formNode={
|
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
||||||
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
|
||||||
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
|
|
||||||
{msg("firstName")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="firstName"
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
name="firstName"
|
|
||||||
defaultValue={register.formData.firstName ?? ""}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
|
||||||
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
|
|
||||||
{msg("lastName")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="lastName"
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
name="lastName"
|
|
||||||
defaultValue={register.formData.lastName ?? ""}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
|
||||||
<label htmlFor="email" className={cx(props.kcLabelClass)}>
|
|
||||||
{msg("email")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="email"
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
name="email"
|
|
||||||
defaultValue={register.formData.email ?? ""}
|
|
||||||
autoComplete="email"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!realm.registrationEmailAsUsername && (
|
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
|
||||||
{msg("username")}
|
{msg("firstName")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="username"
|
id="firstName"
|
||||||
className={cx(props.kcInputClass)}
|
className={cx(props.kcInputClass)}
|
||||||
name="username"
|
name="firstName"
|
||||||
defaultValue={register.formData.username ?? ""}
|
defaultValue={register.formData.firstName ?? ""}
|
||||||
autoComplete="username"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{passwordRequired && (
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
|
||||||
<>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
|
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("lastName")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastName"
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
name="lastName"
|
||||||
|
defaultValue={register.formData.lastName ?? ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor="email" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("email")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="email"
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
name="email"
|
||||||
|
defaultValue={register.formData.email ?? ""}
|
||||||
|
autoComplete="email"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!realm.registrationEmailAsUsername && (
|
||||||
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
<label htmlFor="password" className={cx(props.kcLabelClass)}>
|
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
||||||
{msg("password")}
|
{msg("username")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="text"
|
||||||
id="password"
|
id="username"
|
||||||
className={cx(props.kcInputClass)}
|
className={cx(props.kcInputClass)}
|
||||||
name="password"
|
name="username"
|
||||||
autoComplete="new-password"
|
defaultValue={register.formData.username ?? ""}
|
||||||
|
autoComplete="username"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div
|
{passwordRequired && (
|
||||||
className={cx(
|
<>
|
||||||
props.kcFormGroupClass,
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
|
||||||
messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass)
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
)}
|
<label htmlFor="password" className={cx(props.kcLabelClass)}>
|
||||||
>
|
{msg("password")}
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
</label>
|
||||||
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
|
</div>
|
||||||
{msg("passwordConfirm")}
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
</label>
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
name="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
props.kcFormGroupClass,
|
||||||
|
messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("passwordConfirm")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{recaptchaRequired && (
|
||||||
|
<div className="form-group">
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" />
|
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
)}
|
||||||
)}
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
{recaptchaRequired && (
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
<div className="form-group">
|
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
<span>
|
||||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
|
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
|
||||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
|
||||||
<span>
|
|
||||||
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
<input
|
<input
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
className={cx(
|
||||||
type="submit"
|
props.kcButtonClass,
|
||||||
value={msgStr("doRegister")}
|
props.kcButtonPrimaryClass,
|
||||||
/>
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doRegister")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default Register;
|
export default Register;
|
||||||
|
@ -1,220 +1,78 @@
|
|||||||
import React, { useMemo, memo, useEffect, useState, Fragment } from "react";
|
import React, { useMemo, memo, useState } from "react";
|
||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { ReactComponent } from "../tools/ReactComponent";
|
|
||||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
|
||||||
import { useFormValidationSlice } from "../useFormValidationSlice";
|
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||||
|
|
||||||
const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps) => {
|
const RegisterUserProfile = memo(
|
||||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
({
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
|
||||||
|
|
||||||
const { cx, css } = useCssAndCx();
|
|
||||||
|
|
||||||
const props = useMemo(
|
|
||||||
() => ({
|
|
||||||
...props_,
|
|
||||||
"kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 }))
|
|
||||||
}),
|
|
||||||
[cx, css]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Template
|
|
||||||
{...{ kcContext, i18n, ...props }}
|
|
||||||
displayMessage={messagesPerField.exists("global")}
|
|
||||||
displayRequiredFields={true}
|
|
||||||
doFetchDefaultThemeResources={true}
|
|
||||||
headerNode={msg("registerTitle")}
|
|
||||||
formNode={
|
|
||||||
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
|
||||||
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
|
|
||||||
{recaptchaRequired && (
|
|
||||||
<div className="form-group">
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={cx(props.kcFormGroupClass)}>
|
|
||||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
|
||||||
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
|
||||||
<span>
|
|
||||||
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
|
||||||
<input
|
|
||||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
|
||||||
type="submit"
|
|
||||||
value={msgStr("doRegister")}
|
|
||||||
disabled={!isFomSubmittable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps &
|
|
||||||
Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
|
|
||||||
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, i18n, ...props }: UserProfileFormFieldsProps) => {
|
|
||||||
const { cx, css } = useCssAndCx();
|
|
||||||
|
|
||||||
const { advancedMsg } = i18n;
|
|
||||||
|
|
||||||
const {
|
|
||||||
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
|
||||||
formValidationReducer,
|
|
||||||
attributesWithPassword
|
|
||||||
} = useFormValidationSlice({
|
|
||||||
kcContext,
|
kcContext,
|
||||||
i18n
|
i18n,
|
||||||
});
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props_
|
||||||
|
}: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||||
|
|
||||||
useEffect(() => {
|
const { msg, msgStr } = i18n;
|
||||||
onIsFormSubmittableValueChange(isFormSubmittable);
|
|
||||||
}, [isFormSubmittable]);
|
|
||||||
|
|
||||||
const onChangeFactory = useCallbackFactory(
|
const { cx, css } = useCssAndCx();
|
||||||
(
|
|
||||||
[name]: [string],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
target: { value }
|
|
||||||
}
|
|
||||||
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>]
|
|
||||||
) =>
|
|
||||||
formValidationReducer({
|
|
||||||
"action": "update value",
|
|
||||||
name,
|
|
||||||
"newValue": value
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const onBlurFactory = useCallbackFactory(([name]: [string]) =>
|
const props = useMemo(
|
||||||
formValidationReducer({
|
() => ({
|
||||||
"action": "focus lost",
|
...props_,
|
||||||
name
|
"kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 }))
|
||||||
})
|
}),
|
||||||
);
|
[cx, css]
|
||||||
|
);
|
||||||
|
|
||||||
let currentGroup = "";
|
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Template
|
||||||
{attributesWithPassword.map((attribute, i) => {
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
|
displayMessage={messagesPerField.exists("global")}
|
||||||
|
displayRequiredFields={true}
|
||||||
const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
|
headerNode={msg("registerTitle")}
|
||||||
|
formNode={
|
||||||
const formGroupClassName = cx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
|
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
||||||
|
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
|
||||||
return (
|
{recaptchaRequired && (
|
||||||
<Fragment key={i}>
|
<div className="form-group">
|
||||||
{group !== currentGroup && (currentGroup = group) !== "" && (
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
<div className={formGroupClassName}>
|
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
|
||||||
<div className={cx(props.kcContentWrapperClass)}>
|
|
||||||
<label id={`header-${group}`} className={cx(props.kcFormGroupHeader)}>
|
|
||||||
{advancedMsg(groupDisplayHeader) || currentGroup}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{groupDisplayDescription !== "" && (
|
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
|
||||||
<label id={`description-${group}`} className={`${cx(props.kcLabelClass)}`}>
|
|
||||||
{advancedMsg(groupDisplayDescription)}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={formGroupClassName}>
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
<div className={cx(props.kcLabelWrapperClass)}>
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
<label htmlFor={attribute.name} className={cx(props.kcLabelClass)}>
|
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||||
{advancedMsg(attribute.displayName ?? "")}
|
<span>
|
||||||
</label>
|
<a href={url.loginUrl}>{msg("backToLogin")}</a>
|
||||||
{attribute.required && <>*</>}
|
|
||||||
</div>
|
|
||||||
<div className={cx(props.kcInputWrapperClass)}>
|
|
||||||
{(() => {
|
|
||||||
const { options } = attribute.validators;
|
|
||||||
|
|
||||||
if (options !== undefined) {
|
|
||||||
return (
|
|
||||||
<select
|
|
||||||
id={attribute.name}
|
|
||||||
name={attribute.name}
|
|
||||||
onChange={onChangeFactory(attribute.name)}
|
|
||||||
onBlur={onBlurFactory(attribute.name)}
|
|
||||||
value={value}
|
|
||||||
>
|
|
||||||
{options.options.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type={(() => {
|
|
||||||
switch (attribute.name) {
|
|
||||||
case "password-confirm":
|
|
||||||
case "password":
|
|
||||||
return "password";
|
|
||||||
default:
|
|
||||||
return "text";
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
id={attribute.name}
|
|
||||||
name={attribute.name}
|
|
||||||
value={value}
|
|
||||||
onChange={onChangeFactory(attribute.name)}
|
|
||||||
className={cx(props.kcInputClass)}
|
|
||||||
aria-invalid={displayableErrors.length !== 0}
|
|
||||||
disabled={attribute.readOnly}
|
|
||||||
autoComplete={attribute.autocomplete}
|
|
||||||
onBlur={onBlurFactory(attribute.name)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
{displayableErrors.length !== 0 && (
|
|
||||||
<span
|
|
||||||
id={`input-error-${attribute.name}`}
|
|
||||||
className={cx(
|
|
||||||
props.kcInputErrorMessageClass,
|
|
||||||
css({
|
|
||||||
"position": displayableErrors.length === 1 ? "absolute" : undefined,
|
|
||||||
"& > span": { "display": "block" }
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
{displayableErrors.map(({ errorMessage }) => errorMessage)}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
|
<input
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doRegister")}
|
||||||
|
disabled={!isFomSubmittable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</form>
|
||||||
);
|
}
|
||||||
})}
|
/>
|
||||||
</>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export default RegisterUserProfile;
|
export default RegisterUserProfile;
|
||||||
|
@ -7,7 +7,7 @@ import { headInsert } from "../tools/headInsert";
|
|||||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
import type { KcTemplateProps } from "./KcProps";
|
import type { KcTemplateProps } from "./KcProps";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
export type TemplateProps = {
|
export type TemplateProps = {
|
||||||
|
@ -2,7 +2,7 @@ import React, { useEffect, memo } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import type { KcProps } from "./KcProps";
|
import type { KcProps } from "./KcProps";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
import { Evt } from "evt";
|
import { Evt } from "evt";
|
||||||
import { useRerenderOnStateChange } from "evt/hooks";
|
import { useRerenderOnStateChange } from "evt/hooks";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
@ -11,6 +11,7 @@ import type { I18n } from "../i18n";
|
|||||||
import memoize from "memoizee";
|
import memoize from "memoizee";
|
||||||
import { useConst } from "powerhooks/useConst";
|
import { useConst } from "powerhooks/useConst";
|
||||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
|
import { Markdown } from "../tools/Markdown";
|
||||||
|
|
||||||
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
||||||
|
|
||||||
@ -53,55 +54,61 @@ export function useDownloadTerms(params: {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Terms; i18n: I18n } & KcProps) => {
|
const Terms = memo(
|
||||||
const { msg, msgStr } = i18n;
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.Terms; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
useRerenderOnStateChange(evtTermMarkdown);
|
useRerenderOnStateChange(evtTermMarkdown);
|
||||||
|
|
||||||
const { cx } = useCssAndCx();
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
const { url } = kcContext;
|
const { url } = kcContext;
|
||||||
|
|
||||||
if (evtTermMarkdown.state === undefined) {
|
if (evtTermMarkdown.state === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
|
displayMessage={false}
|
||||||
|
headerNode={msg("termsTitle")}
|
||||||
|
formNode={
|
||||||
|
<>
|
||||||
|
<div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div>
|
||||||
|
<form className="form-actions" action={url.loginAction} method="POST">
|
||||||
|
<input
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
name="accept"
|
||||||
|
id="kc-accept"
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doAccept")}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
||||||
|
name="cancel"
|
||||||
|
id="kc-decline"
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doDecline")}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<div className="clearfix" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return (
|
|
||||||
<Template
|
|
||||||
{...{ kcContext, i18n, ...props }}
|
|
||||||
doFetchDefaultThemeResources={true}
|
|
||||||
displayMessage={false}
|
|
||||||
headerNode={msg("termsTitle")}
|
|
||||||
formNode={
|
|
||||||
<>
|
|
||||||
<div id="kc-terms-text">{evtTermMarkdown.state}</div>
|
|
||||||
<form className="form-actions" action={url.loginAction} method="POST">
|
|
||||||
<input
|
|
||||||
className={cx(
|
|
||||||
props.kcButtonClass,
|
|
||||||
props.kcButtonClass,
|
|
||||||
props.kcButtonClass,
|
|
||||||
props.kcButtonPrimaryClass,
|
|
||||||
props.kcButtonLargeClass
|
|
||||||
)}
|
|
||||||
name="accept"
|
|
||||||
id="kc-accept"
|
|
||||||
type="submit"
|
|
||||||
value={msgStr("doAccept")}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
|
||||||
name="cancel"
|
|
||||||
id="kc-decline"
|
|
||||||
type="submit"
|
|
||||||
value={msgStr("doDecline")}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
<div className="clearfix" />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Terms;
|
export default Terms;
|
||||||
|
77
src/lib/components/UpdateUserProfile.tsx
Normal file
77
src/lib/components/UpdateUserProfile.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import React, { useState, memo } from "react";
|
||||||
|
import Template from "./Template";
|
||||||
|
import type { KcProps } from "./KcProps";
|
||||||
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
|
import type { I18n } from "../i18n";
|
||||||
|
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||||
|
|
||||||
|
const UpdateUserProfile = memo(
|
||||||
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.UpdateUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
|
const { url, isAppInitiatedAction } = kcContext;
|
||||||
|
|
||||||
|
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
|
headerNode={msg("loginProfileTitle")}
|
||||||
|
formNode={
|
||||||
|
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
|
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
|
<div className={cx(props.kcFormOptionsWrapperClass)}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
|
{isAppInitiatedAction ? (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
value={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
name="cancel-aia"
|
||||||
|
value="true"
|
||||||
|
formNoValidate
|
||||||
|
>
|
||||||
|
{msg("doCancel")}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
defaultValue={msgStr("doSubmit")}
|
||||||
|
disabled={!isFomSubmittable}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default UpdateUserProfile;
|
170
src/lib/components/shared/UserProfileCommons.tsx
Normal file
170
src/lib/components/shared/UserProfileCommons.tsx
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import React, { memo, useEffect, Fragment } from "react";
|
||||||
|
import type { KcProps } from "../KcProps";
|
||||||
|
import type { Attribute } from "../../getKcContext/KcContextBase";
|
||||||
|
import { useCssAndCx } from "../../tools/useCssAndCx";
|
||||||
|
import type { ReactComponent } from "../../tools/ReactComponent";
|
||||||
|
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||||
|
import { useFormValidationSlice } from "../../useFormValidationSlice";
|
||||||
|
import type { I18n } from "../../i18n";
|
||||||
|
import type { Param0 } from "tsafe/Param0";
|
||||||
|
|
||||||
|
export type UserProfileFormFieldsProps = {
|
||||||
|
kcContext: Param0<typeof useFormValidationSlice>["kcContext"];
|
||||||
|
i18n: I18n;
|
||||||
|
} & KcProps &
|
||||||
|
Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
|
||||||
|
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserProfileFormFields = memo(
|
||||||
|
({ kcContext, onIsFormSubmittableValueChange, i18n, BeforeField, AfterField, ...props }: UserProfileFormFieldsProps) => {
|
||||||
|
const { cx, css } = useCssAndCx();
|
||||||
|
|
||||||
|
const { advancedMsg } = i18n;
|
||||||
|
|
||||||
|
const {
|
||||||
|
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
||||||
|
formValidationReducer,
|
||||||
|
attributesWithPassword
|
||||||
|
} = useFormValidationSlice({
|
||||||
|
kcContext,
|
||||||
|
i18n
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onIsFormSubmittableValueChange(isFormSubmittable);
|
||||||
|
}, [isFormSubmittable]);
|
||||||
|
|
||||||
|
const onChangeFactory = useCallbackFactory(
|
||||||
|
(
|
||||||
|
[name]: [string],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
target: { value }
|
||||||
|
}
|
||||||
|
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>]
|
||||||
|
) =>
|
||||||
|
formValidationReducer({
|
||||||
|
"action": "update value",
|
||||||
|
name,
|
||||||
|
"newValue": value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const onBlurFactory = useCallbackFactory(([name]: [string]) =>
|
||||||
|
formValidationReducer({
|
||||||
|
"action": "focus lost",
|
||||||
|
name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let currentGroup = "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{attributesWithPassword.map((attribute, i) => {
|
||||||
|
const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
|
||||||
|
|
||||||
|
const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
|
||||||
|
|
||||||
|
const formGroupClassName = cx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment key={i}>
|
||||||
|
{group !== currentGroup && (currentGroup = group) !== "" && (
|
||||||
|
<div className={formGroupClassName}>
|
||||||
|
<div className={cx(props.kcContentWrapperClass)}>
|
||||||
|
<label id={`header-${group}`} className={cx(props.kcFormGroupHeader)}>
|
||||||
|
{advancedMsg(groupDisplayHeader) || currentGroup}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{groupDisplayDescription !== "" && (
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label id={`description-${group}`} className={`${cx(props.kcLabelClass)}`}>
|
||||||
|
{advancedMsg(groupDisplayDescription)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{BeforeField && <BeforeField attribute={attribute} />}
|
||||||
|
|
||||||
|
<div className={formGroupClassName}>
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor={attribute.name} className={cx(props.kcLabelClass)}>
|
||||||
|
{advancedMsg(attribute.displayName ?? "")}
|
||||||
|
</label>
|
||||||
|
{attribute.required && <>*</>}
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
{(() => {
|
||||||
|
const { options } = attribute.validators;
|
||||||
|
|
||||||
|
if (options !== undefined) {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
id={attribute.name}
|
||||||
|
name={attribute.name}
|
||||||
|
onChange={onChangeFactory(attribute.name)}
|
||||||
|
onBlur={onBlurFactory(attribute.name)}
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
{options.options.map(option => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={(() => {
|
||||||
|
switch (attribute.name) {
|
||||||
|
case "password-confirm":
|
||||||
|
case "password":
|
||||||
|
return "password";
|
||||||
|
default:
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
id={attribute.name}
|
||||||
|
name={attribute.name}
|
||||||
|
value={value}
|
||||||
|
onChange={onChangeFactory(attribute.name)}
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
aria-invalid={displayableErrors.length !== 0}
|
||||||
|
disabled={attribute.readOnly}
|
||||||
|
autoComplete={attribute.autocomplete}
|
||||||
|
onBlur={onBlurFactory(attribute.name)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
{displayableErrors.length !== 0 && (
|
||||||
|
<span
|
||||||
|
id={`input-error-${attribute.name}`}
|
||||||
|
className={cx(
|
||||||
|
props.kcInputErrorMessageClass,
|
||||||
|
css({
|
||||||
|
"position": displayableErrors.length === 1 ? "absolute" : undefined,
|
||||||
|
"& > span": { "display": "block" }
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
{displayableErrors.map(({ errorMessage }) => errorMessage)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{AfterField && <AfterField attribute={attribute} />}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
@ -25,7 +25,9 @@ export type KcContextBase =
|
|||||||
| KcContextBase.LoginIdpLinkEmail
|
| KcContextBase.LoginIdpLinkEmail
|
||||||
| KcContextBase.LoginPageExpired
|
| KcContextBase.LoginPageExpired
|
||||||
| KcContextBase.LoginConfigTotp
|
| KcContextBase.LoginConfigTotp
|
||||||
| KcContextBase.LogoutConfirm;
|
| KcContextBase.LogoutConfirm
|
||||||
|
| KcContextBase.UpdateUserProfile
|
||||||
|
| KcContextBase.IdpReviewUserProfile;
|
||||||
|
|
||||||
export declare namespace KcContextBase {
|
export declare namespace KcContextBase {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
@ -270,6 +272,23 @@ export declare namespace KcContextBase {
|
|||||||
skipLink?: boolean;
|
skipLink?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UpdateUserProfile = Common & {
|
||||||
|
pageId: "update-user-profile.ftl";
|
||||||
|
profile: {
|
||||||
|
attributes: Attribute[];
|
||||||
|
attributesByName: Record<string, Attribute>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IdpReviewUserProfile = Common & {
|
||||||
|
pageId: "idp-review-user-profile.ftl";
|
||||||
|
profile: {
|
||||||
|
context: "IDP_REVIEW";
|
||||||
|
attributes: Attribute[];
|
||||||
|
attributesByName: Record<string, Attribute>;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Attribute = {
|
export type Attribute = {
|
||||||
|
@ -47,8 +47,16 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
|||||||
"source": partialKcContextCustomMock
|
"source": partialKcContextCustomMock
|
||||||
});
|
});
|
||||||
|
|
||||||
if (partialKcContextCustomMock.pageId === "register-user-profile.ftl") {
|
if (
|
||||||
assert(kcContextDefaultMock?.pageId === "register-user-profile.ftl");
|
partialKcContextCustomMock.pageId === "register-user-profile.ftl" ||
|
||||||
|
partialKcContextCustomMock.pageId === "update-user-profile.ftl" ||
|
||||||
|
partialKcContextCustomMock.pageId === "idp-review-user-profile.ftl"
|
||||||
|
) {
|
||||||
|
assert(
|
||||||
|
kcContextDefaultMock?.pageId === "register-user-profile.ftl" ||
|
||||||
|
kcContextDefaultMock?.pageId === "update-user-profile.ftl" ||
|
||||||
|
kcContextDefaultMock?.pageId === "idp-review-user-profile.ftl"
|
||||||
|
);
|
||||||
|
|
||||||
const { attributes } = kcContextDefaultMock.profile;
|
const { attributes } = kcContextDefaultMock.profile;
|
||||||
|
|
||||||
@ -82,14 +90,16 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
|||||||
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute;
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute;
|
||||||
});
|
});
|
||||||
|
|
||||||
partialAttributes.forEach(partialAttribute => {
|
partialAttributes
|
||||||
const { name } = partialAttribute;
|
.map(partialAttribute => ({ "validators": {}, ...partialAttribute }))
|
||||||
|
.forEach(partialAttribute => {
|
||||||
|
const { name } = partialAttribute;
|
||||||
|
|
||||||
assert(name !== undefined, "If you define a mock attribute it must have at least a name");
|
assert(name !== undefined, "If you define a mock attribute it must have at least a name");
|
||||||
|
|
||||||
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes.push(partialAttribute as any);
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes.push(partialAttribute as any);
|
||||||
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[name] = partialAttribute as any;
|
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[name] = partialAttribute as any;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,100 @@ import { pathJoin } from "../../../bin/tools/pathJoin";
|
|||||||
|
|
||||||
const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
|
const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
|
||||||
|
|
||||||
|
const attributes: Attribute[] = [
|
||||||
|
{
|
||||||
|
"validators": {
|
||||||
|
"username-prohibited-characters": {
|
||||||
|
"ignore.empty.value": true
|
||||||
|
},
|
||||||
|
"up-username-has-value": {},
|
||||||
|
"length": {
|
||||||
|
"ignore.empty.value": true,
|
||||||
|
"min": "3",
|
||||||
|
"max": "255"
|
||||||
|
},
|
||||||
|
"up-duplicate-username": {},
|
||||||
|
"up-username-mutation": {}
|
||||||
|
},
|
||||||
|
"displayName": "${username}",
|
||||||
|
"annotations": {},
|
||||||
|
"required": true,
|
||||||
|
"groupAnnotations": {},
|
||||||
|
"autocomplete": "username",
|
||||||
|
"readOnly": false,
|
||||||
|
"name": "username",
|
||||||
|
"value": "xxxx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"validators": {
|
||||||
|
"up-email-exists-as-username": {},
|
||||||
|
"length": {
|
||||||
|
"max": "255",
|
||||||
|
"ignore.empty.value": true
|
||||||
|
},
|
||||||
|
"up-blank-attribute-value": {
|
||||||
|
"error-message": "missingEmailMessage",
|
||||||
|
"fail-on-null": false
|
||||||
|
},
|
||||||
|
"up-duplicate-email": {},
|
||||||
|
"email": {
|
||||||
|
"ignore.empty.value": true
|
||||||
|
},
|
||||||
|
"pattern": {
|
||||||
|
"ignore.empty.value": true,
|
||||||
|
"pattern": "gmail\\.com$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"displayName": "${email}",
|
||||||
|
"annotations": {},
|
||||||
|
"required": true,
|
||||||
|
"groupAnnotations": {},
|
||||||
|
"autocomplete": "email",
|
||||||
|
"readOnly": false,
|
||||||
|
"name": "email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"validators": {
|
||||||
|
"length": {
|
||||||
|
"max": "255",
|
||||||
|
"ignore.empty.value": true
|
||||||
|
},
|
||||||
|
"person-name-prohibited-characters": {
|
||||||
|
"ignore.empty.value": true
|
||||||
|
},
|
||||||
|
"up-immutable-attribute": {},
|
||||||
|
"up-attribute-required-by-metadata-value": {}
|
||||||
|
},
|
||||||
|
"displayName": "${firstName}",
|
||||||
|
"annotations": {},
|
||||||
|
"required": true,
|
||||||
|
"groupAnnotations": {},
|
||||||
|
"readOnly": false,
|
||||||
|
"name": "firstName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"validators": {
|
||||||
|
"length": {
|
||||||
|
"max": "255",
|
||||||
|
"ignore.empty.value": true
|
||||||
|
},
|
||||||
|
"person-name-prohibited-characters": {
|
||||||
|
"ignore.empty.value": true
|
||||||
|
},
|
||||||
|
"up-immutable-attribute": {},
|
||||||
|
"up-attribute-required-by-metadata-value": {}
|
||||||
|
},
|
||||||
|
"displayName": "${lastName}",
|
||||||
|
"annotations": {},
|
||||||
|
"required": true,
|
||||||
|
"groupAnnotations": {},
|
||||||
|
"readOnly": false,
|
||||||
|
"name": "lastName"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const attributesByName = Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any;
|
||||||
|
|
||||||
export const kcContextCommonMock: KcContextBase.Common = {
|
export const kcContextCommonMock: KcContextBase.Common = {
|
||||||
"url": {
|
"url": {
|
||||||
"loginAction": "#",
|
"loginAction": "#",
|
||||||
@ -200,104 +294,8 @@ export const kcContextMocks: KcContextBase[] = [
|
|||||||
...registerCommon,
|
...registerCommon,
|
||||||
"profile": {
|
"profile": {
|
||||||
"context": "REGISTRATION_PROFILE" as const,
|
"context": "REGISTRATION_PROFILE" as const,
|
||||||
...(() => {
|
attributes,
|
||||||
const attributes: Attribute[] = [
|
attributesByName
|
||||||
{
|
|
||||||
"validators": {
|
|
||||||
"username-prohibited-characters": {
|
|
||||||
"ignore.empty.value": true
|
|
||||||
},
|
|
||||||
"up-username-has-value": {},
|
|
||||||
"length": {
|
|
||||||
"ignore.empty.value": true,
|
|
||||||
"min": "3",
|
|
||||||
"max": "255"
|
|
||||||
},
|
|
||||||
"up-duplicate-username": {},
|
|
||||||
"up-username-mutation": {}
|
|
||||||
},
|
|
||||||
"displayName": "${username}",
|
|
||||||
"annotations": {},
|
|
||||||
"required": true,
|
|
||||||
"groupAnnotations": {},
|
|
||||||
"autocomplete": "username",
|
|
||||||
"readOnly": false,
|
|
||||||
"name": "username",
|
|
||||||
"value": "xxxx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"validators": {
|
|
||||||
"up-email-exists-as-username": {},
|
|
||||||
"length": {
|
|
||||||
"max": "255",
|
|
||||||
"ignore.empty.value": true
|
|
||||||
},
|
|
||||||
"up-blank-attribute-value": {
|
|
||||||
"error-message": "missingEmailMessage",
|
|
||||||
"fail-on-null": false
|
|
||||||
},
|
|
||||||
"up-duplicate-email": {},
|
|
||||||
"email": {
|
|
||||||
"ignore.empty.value": true
|
|
||||||
},
|
|
||||||
"pattern": {
|
|
||||||
"ignore.empty.value": true,
|
|
||||||
"pattern": "gmail\\.com$"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"displayName": "${email}",
|
|
||||||
"annotations": {},
|
|
||||||
"required": true,
|
|
||||||
"groupAnnotations": {},
|
|
||||||
"autocomplete": "email",
|
|
||||||
"readOnly": false,
|
|
||||||
"name": "email"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"validators": {
|
|
||||||
"length": {
|
|
||||||
"max": "255",
|
|
||||||
"ignore.empty.value": true
|
|
||||||
},
|
|
||||||
"person-name-prohibited-characters": {
|
|
||||||
"ignore.empty.value": true
|
|
||||||
},
|
|
||||||
"up-immutable-attribute": {},
|
|
||||||
"up-attribute-required-by-metadata-value": {}
|
|
||||||
},
|
|
||||||
"displayName": "${firstName}",
|
|
||||||
"annotations": {},
|
|
||||||
"required": true,
|
|
||||||
"groupAnnotations": {},
|
|
||||||
"readOnly": false,
|
|
||||||
"name": "firstName"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"validators": {
|
|
||||||
"length": {
|
|
||||||
"max": "255",
|
|
||||||
"ignore.empty.value": true
|
|
||||||
},
|
|
||||||
"person-name-prohibited-characters": {
|
|
||||||
"ignore.empty.value": true
|
|
||||||
},
|
|
||||||
"up-immutable-attribute": {},
|
|
||||||
"up-attribute-required-by-metadata-value": {}
|
|
||||||
},
|
|
||||||
"displayName": "${lastName}",
|
|
||||||
"annotations": {},
|
|
||||||
"required": true,
|
|
||||||
"groupAnnotations": {},
|
|
||||||
"readOnly": false,
|
|
||||||
"name": "lastName"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
attributes,
|
|
||||||
"attributesByName": Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any
|
|
||||||
} as any;
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@ -423,5 +421,22 @@ export const kcContextMocks: KcContextBase[] = [
|
|||||||
"baseUrl": "#"
|
"baseUrl": "#"
|
||||||
},
|
},
|
||||||
"logoutConfirm": { "code": "123", skipLink: false }
|
"logoutConfirm": { "code": "123", skipLink: false }
|
||||||
|
}),
|
||||||
|
id<KcContextBase.UpdateUserProfile>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "update-user-profile.ftl",
|
||||||
|
"profile": {
|
||||||
|
attributes,
|
||||||
|
attributesByName
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
id<KcContextBase.IdpReviewUserProfile>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "idp-review-user-profile.ftl",
|
||||||
|
"profile": {
|
||||||
|
context: "IDP_REVIEW",
|
||||||
|
attributes,
|
||||||
|
attributesByName
|
||||||
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import "minimal-polyfills/Object.fromEntries";
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
|
||||||
import type baseMessages from "./generated_messages/18.0.1/login/en";
|
import type baseMessages from "./generated_messages/18.0.1/login/en";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
|
import { Markdown } from "../tools/Markdown";
|
||||||
|
|
||||||
export const fallbackLanguageTag = "en";
|
export const fallbackLanguageTag = "en";
|
||||||
|
|
||||||
@ -76,12 +76,14 @@ export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params:
|
|||||||
|
|
||||||
const [i18n, setI18n] = useState<I18n<ExtraMessageKey | MessageKeyBase> | undefined>(undefined);
|
const [i18n, setI18n] = useState<I18n<ExtraMessageKey | MessageKeyBase> | undefined>(undefined);
|
||||||
|
|
||||||
|
const refHasStartedFetching = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (doSkip) {
|
if (doSkip || refHasStartedFetching.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isMounted = true;
|
refHasStartedFetching.current = true;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const { currentLanguageTag = fallbackLanguageTag } = kcContext.locale ?? {};
|
const { currentLanguageTag = fallbackLanguageTag } = kcContext.locale ?? {};
|
||||||
@ -140,10 +142,6 @@ export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params:
|
|||||||
})()
|
})()
|
||||||
]).then(modules => modules.map(module => module.default));
|
]).then(modules => modules.map(module => module.default));
|
||||||
|
|
||||||
if (!isMounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setI18n({
|
setI18n({
|
||||||
...createI18nTranslationFunctions({
|
...createI18nTranslationFunctions({
|
||||||
"fallbackMessages": {
|
"fallbackMessages": {
|
||||||
@ -176,10 +174,6 @@ export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params:
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return () => {
|
|
||||||
isMounted = false;
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return i18n ?? null;
|
return i18n ?? null;
|
||||||
@ -240,9 +234,9 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
return doRenderMarkdown ? (
|
return doRenderMarkdown ? (
|
||||||
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
|
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}>
|
||||||
{messageWithArgsInjectedIfAny}
|
{messageWithArgsInjectedIfAny}
|
||||||
</ReactMarkdown>
|
</Markdown>
|
||||||
) : (
|
) : (
|
||||||
messageWithArgsInjectedIfAny
|
messageWithArgsInjectedIfAny
|
||||||
);
|
);
|
||||||
|
3
src/lib/tools/Markdown.ts
Normal file
3
src/lib/tools/Markdown.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Markdown from "react-markdown";
|
||||||
|
|
||||||
|
export { Markdown };
|
11
src/lib/tools/useCssAndCx.ts
Normal file
11
src/lib/tools/useCssAndCx.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { createMakeStyles } from "tss-react";
|
||||||
|
|
||||||
|
const { useStyles } = createMakeStyles({
|
||||||
|
"useTheme": () => ({})
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useCssAndCx() {
|
||||||
|
const { css, cx } = useStyles();
|
||||||
|
|
||||||
|
return { css, cx };
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
"outDir": "../../dist/lib",
|
"outDir": "../../dist/lib",
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"module": "ES2020",
|
"module": "ES2020",
|
||||||
"target": "ES2020",
|
"target": "ES2017",
|
||||||
"lib": ["es2015", "DOM", "ES2019.Object"],
|
"lib": ["es2015", "DOM", "ES2019.Object"],
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
|
@ -304,13 +304,17 @@ export function useGetErrors(params: {
|
|||||||
return { getErrors };
|
return { getErrors };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: The attributesWithPassword returned is actually augmented with
|
||||||
|
* artificial password related attributes only if kcContext.passwordRequired === true
|
||||||
|
*/
|
||||||
export function useFormValidationSlice(params: {
|
export function useFormValidationSlice(params: {
|
||||||
kcContext: {
|
kcContext: {
|
||||||
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
||||||
profile: {
|
profile: {
|
||||||
attributes: Attribute[];
|
attributes: Attribute[];
|
||||||
};
|
};
|
||||||
passwordRequired: boolean;
|
passwordRequired?: boolean;
|
||||||
realm: { registrationEmailAsUsername: boolean };
|
realm: { registrationEmailAsUsername: boolean };
|
||||||
};
|
};
|
||||||
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
||||||
|
@ -14,6 +14,7 @@ generateKeycloakThemeResources({
|
|||||||
"extraPages": ["my-custom-page.ftl"],
|
"extraPages": ["my-custom-page.ftl"],
|
||||||
"extraThemeProperties": ["env=test"],
|
"extraThemeProperties": ["env=test"],
|
||||||
"isStandalone": true,
|
"isStandalone": true,
|
||||||
"urlPathname": "/keycloakify-demo-app/"
|
"urlPathname": "/keycloakify-demo-app/",
|
||||||
|
"isSilent": false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}[e] + ".chunk.js"
|
}[e] + ".chunk.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
n.u=function(e){return"static/js/" + e + "." + {
|
__webpack_require__.u=function(e){return"static/js/" + e + "." + {
|
||||||
147: "6c5cee76",
|
147: "6c5cee76",
|
||||||
787: "8da10fcf",
|
787: "8da10fcf",
|
||||||
922: "be170a73"
|
922: "be170a73"
|
||||||
@ -54,11 +54,14 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}[e] + ".chunk.js"
|
}[e] + ".chunk.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
n[(function (){
|
__webpack_require__[(function (){
|
||||||
Object.defineProperty(n, "p", {
|
var pd= Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
if( pd === undefined || pd.configurable ){
|
||||||
set: function (){}
|
Object.defineProperty(__webpack_require__, "p", {
|
||||||
});
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function (){}
|
||||||
|
});
|
||||||
|
}
|
||||||
return "u";
|
return "u";
|
||||||
})()] = function(e) {
|
})()] = function(e) {
|
||||||
return "/build/static/js/" + e + "." + {
|
return "/build/static/js/" + e + "." + {
|
||||||
@ -69,10 +72,13 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
|
|
||||||
t[(function (){
|
t[(function (){
|
||||||
Object.defineProperty(t, "p", {
|
var pd= Object.getOwnPropertyDescriptor(t, "p");
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
if( pd === undefined || pd.configurable ){
|
||||||
set: function (){}
|
Object.defineProperty(t, "p", {
|
||||||
});
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function (){}
|
||||||
|
});
|
||||||
|
}
|
||||||
return "miniCssF";
|
return "miniCssF";
|
||||||
})()] = function(e) {
|
})()] = function(e) {
|
||||||
return "/build/static/css/" + e + "." + {
|
return "/build/static/css/" + e + "." + {
|
||||||
@ -97,23 +103,26 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
|
|
||||||
const fixedJsCodeExpected = `
|
const fixedJsCodeExpected = `
|
||||||
function f() {
|
function f() {
|
||||||
return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + a.p + "static/js/" + ({}[e] || e) + "." + {
|
return ("kcContext" in window ? "https://demo-app.keycloakify.dev/" : a.p) + "static/js/" + ({}[e] || e) + "." + {
|
||||||
3: "0664cdc0"
|
3: "0664cdc0"
|
||||||
}[e] + ".chunk.js"
|
}[e] + ".chunk.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameAsF() {
|
function sameAsF() {
|
||||||
return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + a.p + "static/js/" + ({}[e] || e) + "." + {
|
return ("kcContext" in window ? "https://demo-app.keycloakify.dev/" : a.p) + "static/js/" + ({}[e] || e) + "." + {
|
||||||
3: "0664cdc0"
|
3: "0664cdc0"
|
||||||
}[e] + ".chunk.js"
|
}[e] + ".chunk.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
n[(function (){
|
__webpack_require__[(function (){
|
||||||
var p= "";
|
var pd= Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
||||||
Object.defineProperty(n, "p", {
|
if( pd === undefined || pd.configurable ){
|
||||||
get: function() { return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + p; },
|
var p= "";
|
||||||
set: function (value){ p = value; }
|
Object.defineProperty(__webpack_require__, "p", {
|
||||||
});
|
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
|
||||||
|
set: function (value){ p = value; }
|
||||||
|
});
|
||||||
|
}
|
||||||
return "u";
|
return "u";
|
||||||
})()] = function(e) {
|
})()] = function(e) {
|
||||||
return "static/js/" + e + "." + {
|
return "static/js/" + e + "." + {
|
||||||
@ -124,11 +133,14 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
|
|
||||||
t[(function (){
|
t[(function (){
|
||||||
var p= "";
|
var pd= Object.getOwnPropertyDescriptor(t, "p");
|
||||||
Object.defineProperty(t, "p", {
|
if( pd === undefined || pd.configurable ){
|
||||||
get: function() { return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + p; },
|
var p= "";
|
||||||
set: function (value){ p = value; }
|
Object.defineProperty(t, "p", {
|
||||||
});
|
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
|
||||||
|
set: function (value){ p = value; }
|
||||||
|
});
|
||||||
|
}
|
||||||
return "miniCssF";
|
return "miniCssF";
|
||||||
})()] = function(e) {
|
})()] = function(e) {
|
||||||
return "static/css/" + e + "." + {
|
return "static/css/" + e + "." + {
|
||||||
|
@ -8,6 +8,7 @@ export function setupSampleReactProject() {
|
|||||||
downloadAndUnzip({
|
downloadAndUnzip({
|
||||||
"url": "https://github.com/InseeFrLab/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
"url": "https://github.com/InseeFrLab/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||||
"destDirPath": sampleReactProjectDirPath,
|
"destDirPath": sampleReactProjectDirPath,
|
||||||
"cacheDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak", ".cache")
|
"cacheDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak", ".cache"),
|
||||||
|
"isSilent": false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
140
yarn.lock
140
yarn.lock
@ -21,6 +21,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f"
|
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f"
|
||||||
integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==
|
integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==
|
||||||
|
|
||||||
|
"@babel/helper-string-parser@^7.18.10":
|
||||||
|
version "7.18.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
|
||||||
|
integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
|
||||||
|
|
||||||
"@babel/helper-validator-identifier@^7.18.6":
|
"@babel/helper-validator-identifier@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
|
||||||
@ -50,17 +55,18 @@
|
|||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/types@^7.18.6":
|
"@babel/types@^7.18.6":
|
||||||
version "7.18.9"
|
version "7.18.13"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
|
||||||
integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==
|
integrity sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@babel/helper-string-parser" "^7.18.10"
|
||||||
"@babel/helper-validator-identifier" "^7.18.6"
|
"@babel/helper-validator-identifier" "^7.18.6"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@emotion/babel-plugin@^11.10.0":
|
"@emotion/babel-plugin@^11.10.0":
|
||||||
version "11.10.0"
|
version "11.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.0.tgz#ae545b8faa6b42d3a50ec86b70b758296f3c4467"
|
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz#879db80ba622b3f6076917a1e6f648b1c7d008c7"
|
||||||
integrity sha512-xVnpDAAbtxL1dsuSelU5A7BnY/lftws0wUexNJZTPsvX/1tM4GZJbclgODhvW4E+NH7E5VFcH0bBn30NvniPJA==
|
integrity sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-module-imports" "^7.16.7"
|
"@babel/helper-module-imports" "^7.16.7"
|
||||||
"@babel/plugin-syntax-jsx" "^7.17.12"
|
"@babel/plugin-syntax-jsx" "^7.17.12"
|
||||||
@ -76,9 +82,9 @@
|
|||||||
stylis "4.0.13"
|
stylis "4.0.13"
|
||||||
|
|
||||||
"@emotion/cache@*", "@emotion/cache@^11.10.0":
|
"@emotion/cache@*", "@emotion/cache@^11.10.0":
|
||||||
version "11.10.0"
|
version "11.10.3"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.0.tgz#9eb9d3245c2c25ae31028d5ff0929987824c77da"
|
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87"
|
||||||
integrity sha512-3FoUWnDbHWg/pXGCvL46jvpOSJP0xvRZLY8khUcUHGOBcp0S/MCIk+osp84/dby2Ctahw/Ev4KTHWkY3i0g39g==
|
integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@emotion/memoize" "^0.8.0"
|
"@emotion/memoize" "^0.8.0"
|
||||||
"@emotion/sheet" "^1.2.0"
|
"@emotion/sheet" "^1.2.0"
|
||||||
@ -97,14 +103,15 @@
|
|||||||
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
|
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
|
||||||
|
|
||||||
"@emotion/react@^11.4.1":
|
"@emotion/react@^11.4.1":
|
||||||
version "11.10.0"
|
version "11.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.0.tgz#53c577f063f26493f68a05188fb87528d912ff2e"
|
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199"
|
||||||
integrity sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ==
|
integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.18.3"
|
"@babel/runtime" "^7.18.3"
|
||||||
"@emotion/babel-plugin" "^11.10.0"
|
"@emotion/babel-plugin" "^11.10.0"
|
||||||
"@emotion/cache" "^11.10.0"
|
"@emotion/cache" "^11.10.0"
|
||||||
"@emotion/serialize" "^1.1.0"
|
"@emotion/serialize" "^1.1.0"
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
|
||||||
"@emotion/utils" "^1.2.0"
|
"@emotion/utils" "^1.2.0"
|
||||||
"@emotion/weak-memoize" "^0.3.0"
|
"@emotion/weak-memoize" "^0.3.0"
|
||||||
hoist-non-react-statics "^3.3.1"
|
hoist-non-react-statics "^3.3.1"
|
||||||
@ -130,6 +137,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db"
|
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db"
|
||||||
integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==
|
integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==
|
||||||
|
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df"
|
||||||
|
integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==
|
||||||
|
|
||||||
"@emotion/utils@*", "@emotion/utils@^1.2.0":
|
"@emotion/utils@*", "@emotion/utils@^1.2.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561"
|
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561"
|
||||||
@ -253,6 +265,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.8.tgz#04adc0c266a0f5d72db0556fdda2ba17dad9b519"
|
resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.8.tgz#04adc0c266a0f5d72db0556fdda2ba17dad9b519"
|
||||||
integrity sha512-qDpXKGgwKywnQt/64fH1O0LiPA++QGIYeykEUiZ51HymKVRLnUSGcRuF60IfpPeeXiuRwiR/W4y7S5VzbrgLCA==
|
integrity sha512-qDpXKGgwKywnQt/64fH1O0LiPA++QGIYeykEUiZ51HymKVRLnUSGcRuF60IfpPeeXiuRwiR/W4y7S5VzbrgLCA==
|
||||||
|
|
||||||
|
"@types/minimist@^1.2.2":
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
|
||||||
|
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
|
||||||
|
|
||||||
"@types/node@^17.0.25":
|
"@types/node@^17.0.25":
|
||||||
version "17.0.45"
|
version "17.0.45"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
|
||||||
@ -661,9 +678,9 @@ enquirer@^2.3.6:
|
|||||||
ansi-colors "^4.1.1"
|
ansi-colors "^4.1.1"
|
||||||
|
|
||||||
entities@^4.2.0, entities@^4.3.0:
|
entities@^4.2.0, entities@^4.3.0:
|
||||||
version "4.3.1"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.1.tgz#c34062a94c865c322f9d67b4384e4169bcede6a4"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
|
||||||
integrity sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==
|
integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
|
||||||
|
|
||||||
error-ex@^1.3.1:
|
error-ex@^1.3.1:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
@ -673,9 +690,9 @@ error-ex@^1.3.1:
|
|||||||
is-arrayish "^0.2.1"
|
is-arrayish "^0.2.1"
|
||||||
|
|
||||||
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
|
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
|
||||||
version "0.10.61"
|
version "0.10.62"
|
||||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.61.tgz#311de37949ef86b6b0dcea894d1ffedb909d3269"
|
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5"
|
||||||
integrity sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==
|
integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-iterator "^2.0.3"
|
es6-iterator "^2.0.3"
|
||||||
es6-symbol "^3.1.3"
|
es6-symbol "^3.1.3"
|
||||||
@ -731,14 +748,14 @@ event-emitter@^0.3.5:
|
|||||||
d "1"
|
d "1"
|
||||||
es5-ext "~0.10.14"
|
es5-ext "~0.10.14"
|
||||||
|
|
||||||
evt@^2.3.1:
|
evt@^2.4.4:
|
||||||
version "2.3.1"
|
version "2.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/evt/-/evt-2.3.1.tgz#988fc6fc255db8999240e918afe63ba6c325db99"
|
resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.4.tgz#37d6e28ccb5b1bc91162fc3d5bcfbeb1ef3191cf"
|
||||||
integrity sha512-+MU1aA0as6hOnGxzQOw9hV/xiKIB1vAY90S+WD6zMzvvhQHlY4aPHk2b8WpWsVs3XErDzlhGzCESVCAuH9kUiA==
|
integrity sha512-w/ZYdPCRdSfslOhcQHq7DuYoaU04YZKkFPyBwF8pYmOkRizivpbI0jZ8ffY/jITzbLo7RZ0wxN2dqyi62kyGwg==
|
||||||
dependencies:
|
dependencies:
|
||||||
minimal-polyfills "^2.2.1"
|
minimal-polyfills "^2.2.2"
|
||||||
run-exclusive "^2.2.16"
|
run-exclusive "^2.2.16"
|
||||||
tsafe "^0.10.1"
|
tsafe "^1.1.1"
|
||||||
|
|
||||||
execa@^5.1.1:
|
execa@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
@ -756,11 +773,11 @@ execa@^5.1.1:
|
|||||||
strip-final-newline "^2.0.0"
|
strip-final-newline "^2.0.0"
|
||||||
|
|
||||||
ext@^1.1.2:
|
ext@^1.1.2:
|
||||||
version "1.6.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52"
|
resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
|
||||||
integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==
|
integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==
|
||||||
dependencies:
|
dependencies:
|
||||||
type "^2.5.0"
|
type "^2.7.2"
|
||||||
|
|
||||||
extend@^3.0.0:
|
extend@^3.0.0:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
@ -945,9 +962,9 @@ is-buffer@^2.0.0:
|
|||||||
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
|
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
|
||||||
|
|
||||||
is-core-module@^2.9.0:
|
is-core-module@^2.9.0:
|
||||||
version "2.9.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
|
||||||
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
|
integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
|
||||||
dependencies:
|
dependencies:
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
|
|
||||||
@ -1164,10 +1181,10 @@ mimic-fn@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||||
|
|
||||||
minimal-polyfills@^2.2.1:
|
minimal-polyfills@^2.2.1, minimal-polyfills@^2.2.2:
|
||||||
version "2.2.1"
|
version "2.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.1.tgz#7249d7ece666d3b4e1ec1c1b8f949eb9d44e2308"
|
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.2.tgz#6b06a004acce420eb91cf94698f5e6e7f2518378"
|
||||||
integrity sha512-WLmHQrsZob4rVYf8yHapZPNJZ3sspGa/sN8abuSD59b0FifDEE7HMfLUi24z7mPZqTpBXy4Svp+iGvAmclCmXg==
|
integrity sha512-eEOUq/LH/DbLWihrxUP050Wi7H/N/I2dQT98Ep6SqOpmIbk4sXOI4wqalve66QoZa+6oljbZWU6I6T4dehQGmw==
|
||||||
|
|
||||||
minimatch@^3.0.3, minimatch@^3.1.1:
|
minimatch@^3.0.3, minimatch@^3.1.1:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
@ -1176,6 +1193,11 @@ minimatch@^3.0.3, minimatch@^3.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
|
minimist@^1.2.6:
|
||||||
|
version "1.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
|
||||||
mkdirp@^1.0.4:
|
mkdirp@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
@ -1363,15 +1385,15 @@ please-upgrade-node@^3.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver-compare "^1.0.0"
|
semver-compare "^1.0.0"
|
||||||
|
|
||||||
powerhooks@^0.20.10:
|
powerhooks@^0.20.20:
|
||||||
version "0.20.10"
|
version "0.20.20"
|
||||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.10.tgz#c30ba493ea324806100d9f5df889dea19f3a73ff"
|
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.20.tgz#f9b2549710f5166f63d80e07c46c16d6da4c6f78"
|
||||||
integrity sha512-RiQVBgxLavWFVb3hKmzluykvgeVYgVyGtITlnIjVrtfql3jpCHzuVVTq/49oXp18BNEOKCQXzKfFeb42f1v4Pg==
|
integrity sha512-98Ymz0bjo5Ds9u273wYz1tdJ51sB1jcyjqGa08mRY5dKumewydA/+71zrFelfgkOLRRhVZ+mWynG6DZ7zOVjrQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
evt "^2.3.1"
|
evt "^2.4.4"
|
||||||
memoizee "^0.4.15"
|
memoizee "^0.4.15"
|
||||||
resize-observer-polyfill "^1.5.1"
|
resize-observer-polyfill "^1.5.1"
|
||||||
tsafe "^0.10.1"
|
tsafe "^1.1.1"
|
||||||
|
|
||||||
prettier@^2.3.0:
|
prettier@^2.3.0:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
@ -1711,20 +1733,20 @@ trough@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
||||||
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
||||||
|
|
||||||
tsafe@^0.10.1:
|
tsafe@^1.1.1:
|
||||||
version "0.10.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.10.1.tgz#8f100b901e4467c43c0484f56a063f4276683ce0"
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.1.1.tgz#8d6998c726f8c63c518e1d1e283bbcd282a2b9a9"
|
||||||
integrity sha512-S+LrpSjoH5Pah201KS0MxtJn88HVtKf4ZxUoQuW/Hnl4IK6ALu9Qwjed7RbohDeHn+iMuug4c5Mk/z1Cq2G3nw==
|
integrity sha512-Ogblm3uh0dVupcCcC4IT641rnSQ7CW9IO0q8yIncG8OBe4DDXEqGtUE8LWf7+0MK1qZGeWPWEqSxlLzY2xzREA==
|
||||||
|
|
||||||
tslib@^2.1.0:
|
tslib@^2.1.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||||
|
|
||||||
tss-react@^3.7.1:
|
tss-react@^4.3.3:
|
||||||
version "3.7.1"
|
version "4.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-3.7.1.tgz#119647731490f9e7e62c7f6a38a78df981929a4b"
|
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-4.3.3.tgz#fca93e91b93a2e52c40dc904c7067a2b1905a160"
|
||||||
integrity sha512-dfWUoxBlKZfIG9UC1A2h02OmcE/Ni0itCmmZu94E9g+KyBhKMHKcsKvUm0bNlRqTmYjXiCgPJDmj5fyc8CSrLg==
|
integrity sha512-gXCocAaCDkLpMy0yYdr6AB9/FAetkiRsFnkoDYhjngMBni6ELyZStUwOmZ/LFdBGzKRFlrtext63J+UaaavgZg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@emotion/cache" "*"
|
"@emotion/cache" "*"
|
||||||
"@emotion/serialize" "*"
|
"@emotion/serialize" "*"
|
||||||
@ -1740,15 +1762,15 @@ type@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
||||||
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
|
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
|
||||||
|
|
||||||
type@^2.5.0:
|
type@^2.7.2:
|
||||||
version "2.6.1"
|
version "2.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/type/-/type-2.6.1.tgz#808f389ec777205cc3cd97c1c88ec1a913105aae"
|
resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
|
||||||
integrity sha512-OvgH5rB0XM+iDZGQ1Eg/o7IZn0XYJFVrN/9FQ4OWIYILyJJgVP2s1hLTOFn6UOZoDUI/HctGa0PFlE2n2HW3NQ==
|
integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==
|
||||||
|
|
||||||
typescript@^4.2.3:
|
typescript@^4.2.3:
|
||||||
version "4.7.4"
|
version "4.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
|
||||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
|
||||||
|
|
||||||
unified@^9.0.0:
|
unified@^9.0.0:
|
||||||
version "9.2.2"
|
version "9.2.2"
|
||||||
@ -1916,6 +1938,6 @@ yocto-queue@^0.1.0:
|
|||||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||||
|
|
||||||
zod@^3.17.10:
|
zod@^3.17.10:
|
||||||
version "3.17.10"
|
version "3.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.17.10.tgz#8716a05e6869df6faaa878a44ffe3c79e615defb"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc"
|
||||||
integrity sha512-IHXnQYQuOOOL/XgHhgl8YjNxBHi3xX0mVcHmqsvJgcxKkEczPshoWdxqyFwsARpf41E0v9U95WUROqsHHxt0UQ==
|
integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==
|
||||||
|
Reference in New Issue
Block a user