Compare commits
57 Commits
v6.0.0-bet
...
v6.3.4
Author | SHA1 | Date | |
---|---|---|---|
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:
|
||||
branches:
|
||||
- main
|
||||
- v6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v6
|
||||
|
||||
jobs:
|
||||
|
||||
@ -47,11 +45,9 @@ jobs:
|
||||
- uses: bahmutov/npm-install@v1
|
||||
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
|
||||
run: |
|
||||
yarn build
|
||||
yarn test
|
||||
- if: steps.step1.outputs.npm_or_yarn == 'npm'
|
||||
run: |
|
||||
npm run build
|
||||
npm test
|
||||
check_if_version_upgraded:
|
||||
name: Check if version upgrade
|
||||
|
17
README.md
17
README.md
@ -2,7 +2,7 @@
|
||||
<img src="https://user-images.githubusercontent.com/6702424/109387840-eba11f80-7903-11eb-9050-db1dad883f78.png">
|
||||
</p>
|
||||
<p align="center">
|
||||
<i>🔏 Create Keycloak themes using React 🔏</i>
|
||||
<i>🔏 Create Keycloak themes using React 🔏</i>
|
||||
<br>
|
||||
<br>
|
||||
<a href="https://github.com/garronej/keycloakify/actions">
|
||||
@ -27,7 +27,14 @@
|
||||
<a href="https://www.keycloakify.dev">Home</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>
|
||||
|
||||
@ -36,6 +43,10 @@
|
||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||
</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
|
||||
|
||||
## 6.0.0
|
||||
@ -45,7 +56,7 @@
|
||||
- Real i18n API.
|
||||
- Actual documentation for build options.
|
||||
|
||||
Checkout the migration guide.
|
||||
Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6)
|
||||
|
||||
## 5.8.0
|
||||
|
||||
|
17
package.json
17
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "6.0.0-beta.7",
|
||||
"version": "6.3.4",
|
||||
"description": "Keycloak theme generator for Reacts app",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -13,7 +13,8 @@
|
||||
"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",
|
||||
"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",
|
||||
"link_in_test_app": "node dist/bin/link_in_test_app.js",
|
||||
"_format": "prettier '**/*.{ts,tsx,json,md}'",
|
||||
@ -60,6 +61,7 @@
|
||||
"devDependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@types/memoizee": "^0.4.7",
|
||||
"@types/minimist": "^1.2.2",
|
||||
"@types/node": "^17.0.25",
|
||||
"@types/react": "18.0.9",
|
||||
"copyfiles": "^2.4.1",
|
||||
@ -75,15 +77,16 @@
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"cli-select": "^1.1.2",
|
||||
"evt": "^2.3.1",
|
||||
"evt": "^2.4.1",
|
||||
"memoizee": "^0.4.15",
|
||||
"minimal-polyfills": "^2.2.1",
|
||||
"minimal-polyfills": "^2.2.2",
|
||||
"minimist": "^1.2.6",
|
||||
"path-browserify": "^1.0.1",
|
||||
"powerhooks": "^0.20.10",
|
||||
"powerhooks": "^0.20.16",
|
||||
"react-markdown": "^5.0.3",
|
||||
"scripting-tools": "^0.19.13",
|
||||
"tsafe": "^0.10.1",
|
||||
"tss-react": "^3.7.1",
|
||||
"tsafe": "^1.0.1",
|
||||
"tss-react": "^4.1.3",
|
||||
"zod": "^3.17.10"
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,15 @@ import { join as pathJoin, basename as pathBasename } from "path";
|
||||
import { transformCodebase } from "./tools/transformCodebase";
|
||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||
import * as fs from "fs";
|
||||
import { getCliOptions } from "./tools/cliOptions";
|
||||
import { getLogger } from "./tools/logger";
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const { isSilent } = getCliOptions(process.argv.slice(2));
|
||||
const logger = getLogger({ isSilent });
|
||||
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);
|
||||
}
|
||||
@ -21,7 +25,8 @@ if (require.main === module) {
|
||||
|
||||
downloadBuiltinKeycloakTheme({
|
||||
keycloakVersion,
|
||||
"destDirPath": builtinKeycloakThemeTmpDirPath
|
||||
"destDirPath": builtinKeycloakThemeTmpDirPath,
|
||||
isSilent
|
||||
});
|
||||
|
||||
transformCodebase({
|
||||
@ -29,7 +34,7 @@ if (require.main === module) {
|
||||
"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 });
|
||||
})();
|
||||
|
@ -4,31 +4,37 @@ import { keycloakThemeBuildingDirPath } from "./keycloakify";
|
||||
import { join as pathJoin } from "path";
|
||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||
import { getCliOptions } from "./tools/cliOptions";
|
||||
import { getLogger } from "./tools/logger";
|
||||
|
||||
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string }) {
|
||||
const { keycloakVersion, destDirPath } = params;
|
||||
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) {
|
||||
const { keycloakVersion, destDirPath, isSilent } = params;
|
||||
|
||||
for (const ext of ["", "-community"]) {
|
||||
downloadAndUnzip({
|
||||
"destDirPath": destDirPath,
|
||||
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
||||
"pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
|
||||
"cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache")
|
||||
"cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache"),
|
||||
isSilent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const { isSilent } = getCliOptions(process.argv.slice(2));
|
||||
const logger = getLogger({ isSilent });
|
||||
const { keycloakVersion } = await promptKeycloakVersion();
|
||||
|
||||
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({
|
||||
keycloakVersion,
|
||||
destDirPath
|
||||
destDirPath,
|
||||
isSilent
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import { crawl } from "./tools/crawl";
|
||||
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
|
||||
import { getProjectRoot } from "./tools/getProjectRoot";
|
||||
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,
|
||||
// update the version array for generating for newer version.
|
||||
@ -12,8 +14,11 @@ import { rm_rf, rm_r } from "./tools/rm";
|
||||
//@ts-ignore
|
||||
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"]) {
|
||||
console.log({ keycloakVersion });
|
||||
logger.log(JSON.stringify({ keycloakVersion }));
|
||||
|
||||
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
||||
|
||||
@ -21,7 +26,8 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
|
||||
|
||||
downloadBuiltinKeycloakTheme({
|
||||
keycloakVersion,
|
||||
"destDirPath": tmpDirPath
|
||||
"destDirPath": tmpDirPath,
|
||||
isSilent
|
||||
});
|
||||
|
||||
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?: {
|
||||
extraPages?: string[];
|
||||
extraThemeProperties?: string[];
|
||||
isAppAndKeycloakServerSharingSameDomain?: boolean;
|
||||
areAppAndKeycloakServerSharingSameDomain?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ const zParsedPackageJson = z.object({
|
||||
.object({
|
||||
"extraPages": z.array(z.string()).optional(),
|
||||
"extraThemeProperties": z.array(z.string()).optional(),
|
||||
"isAppAndKeycloakServerSharingSameDomain": z.boolean().optional()
|
||||
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional()
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
@ -35,6 +35,7 @@ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets
|
||||
|
||||
export namespace BuildOptions {
|
||||
export type Common = {
|
||||
isSilent: boolean;
|
||||
version: string;
|
||||
themeName: string;
|
||||
extraPages?: string[];
|
||||
@ -56,11 +57,11 @@ export namespace BuildOptions {
|
||||
};
|
||||
|
||||
export type SameDomain = CommonExternalAssets & {
|
||||
isAppAndKeycloakServerSharingSameDomain: true;
|
||||
areAppAndKeycloakServerSharingSameDomain: true;
|
||||
};
|
||||
|
||||
export type DifferentDomains = CommonExternalAssets & {
|
||||
isAppAndKeycloakServerSharingSameDomain: false;
|
||||
areAppAndKeycloakServerSharingSameDomain: false;
|
||||
urlOrigin: string;
|
||||
urlPathname: string | undefined;
|
||||
};
|
||||
@ -71,8 +72,9 @@ export function readBuildOptions(params: {
|
||||
packageJson: string;
|
||||
CNAME: string | undefined;
|
||||
isExternalAssetsCliParamProvided: boolean;
|
||||
isSilent: boolean;
|
||||
}): BuildOptions {
|
||||
const { packageJson, CNAME, isExternalAssetsCliParamProvided } = params;
|
||||
const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
|
||||
|
||||
const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson));
|
||||
|
||||
@ -130,7 +132,8 @@ export function readBuildOptions(params: {
|
||||
})(),
|
||||
"version": version,
|
||||
extraPages,
|
||||
extraThemeProperties
|
||||
extraThemeProperties,
|
||||
isSilent
|
||||
};
|
||||
})();
|
||||
|
||||
@ -140,10 +143,10 @@ export function readBuildOptions(params: {
|
||||
"isStandalone": false
|
||||
});
|
||||
|
||||
if (parsedPackageJson.keycloakify?.isAppAndKeycloakServerSharingSameDomain) {
|
||||
if (parsedPackageJson.keycloakify?.areAppAndKeycloakServerSharingSameDomain) {
|
||||
return id<BuildOptions.ExternalAssets.SameDomain>({
|
||||
...commonExternalAssets,
|
||||
"isAppAndKeycloakServerSharingSameDomain": true
|
||||
"areAppAndKeycloakServerSharingSameDomain": true
|
||||
});
|
||||
} else {
|
||||
assert(
|
||||
@ -155,14 +158,14 @@ export function readBuildOptions(params: {
|
||||
"public/CNAME file.",
|
||||
"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",
|
||||
'admin UI, you can set "keycloakify": { "isAppAndKeycloakServerSharingSameDomain": true }',
|
||||
'admin UI, you can set "keycloakify": { "areAppAndKeycloakServerSharingSameDomain": true }',
|
||||
"in your package.json"
|
||||
].join(" ")
|
||||
);
|
||||
|
||||
return id<BuildOptions.ExternalAssets.DifferentDomains>({
|
||||
...commonExternalAssets,
|
||||
"isAppAndKeycloakServerSharingSameDomain": false,
|
||||
"areAppAndKeycloakServerSharingSameDomain": false,
|
||||
"urlOrigin": url.origin,
|
||||
"urlPathname": url.pathname
|
||||
});
|
||||
|
@ -27,7 +27,9 @@ export const pageIds = [
|
||||
"login-idp-link-email.ftl",
|
||||
"login-page-expired.ftl",
|
||||
"login-config-totp.ftl",
|
||||
"logout-confirm.ftl"
|
||||
"logout-confirm.ftl",
|
||||
"update-user-profile.ftl",
|
||||
"idp-review-user-profile.ftl"
|
||||
] as const;
|
||||
|
||||
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
|
||||
@ -46,11 +48,11 @@ export namespace BuildOptionsLike {
|
||||
};
|
||||
|
||||
export type SameDomain = CommonExternalAssets & {
|
||||
isAppAndKeycloakServerSharingSameDomain: true;
|
||||
areAppAndKeycloakServerSharingSameDomain: true;
|
||||
};
|
||||
|
||||
export type DifferentDomains = CommonExternalAssets & {
|
||||
isAppAndKeycloakServerSharingSameDomain: false;
|
||||
areAppAndKeycloakServerSharingSameDomain: false;
|
||||
urlOrigin: string;
|
||||
urlPathname: string | undefined;
|
||||
};
|
||||
@ -76,7 +78,7 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
const $ = cheerio.load(indexHtmlCode);
|
||||
|
||||
fix_imports_statements: {
|
||||
if (!buildOptions.isStandalone && buildOptions.isAppAndKeycloakServerSharingSameDomain) {
|
||||
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
|
||||
break fix_imports_statements;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import { isInside } from "../tools/isInside";
|
||||
import type { BuildOptions } from "./BuildOptions";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { Reflect } from "tsafe/Reflect";
|
||||
import { getLogger } from "../tools/logger";
|
||||
|
||||
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
|
||||
|
||||
@ -19,6 +20,7 @@ export namespace BuildOptionsLike {
|
||||
themeName: string;
|
||||
extraPages?: string[];
|
||||
extraThemeProperties?: string[];
|
||||
isSilent: boolean;
|
||||
};
|
||||
|
||||
export type Standalone = Common & {
|
||||
@ -34,11 +36,11 @@ export namespace BuildOptionsLike {
|
||||
};
|
||||
|
||||
export type SameDomain = CommonExternalAssets & {
|
||||
isAppAndKeycloakServerSharingSameDomain: true;
|
||||
areAppAndKeycloakServerSharingSameDomain: true;
|
||||
};
|
||||
|
||||
export type DifferentDomains = CommonExternalAssets & {
|
||||
isAppAndKeycloakServerSharingSameDomain: false;
|
||||
areAppAndKeycloakServerSharingSameDomain: false;
|
||||
urlOrigin: string;
|
||||
urlPathname: string | undefined;
|
||||
};
|
||||
@ -60,6 +62,7 @@ export function generateKeycloakThemeResources(params: {
|
||||
}): { doBundlesEmailTemplate: boolean } {
|
||||
const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, keycloakThemeEmailDirPath, keycloakVersion, buildOptions } = params;
|
||||
|
||||
const logger = getLogger({ isSilent: buildOptions.isSilent });
|
||||
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, "login");
|
||||
|
||||
let allCssGlobalsToDefine: Record<string, string> = {};
|
||||
@ -97,7 +100,7 @@ export function generateKeycloakThemeResources(params: {
|
||||
}
|
||||
|
||||
if (/\.js?$/i.test(filePath)) {
|
||||
if (!buildOptions.isStandalone && buildOptions.isAppAndKeycloakServerSharingSameDomain) {
|
||||
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -117,7 +120,7 @@ export function generateKeycloakThemeResources(params: {
|
||||
|
||||
email: {
|
||||
if (!fs.existsSync(keycloakThemeEmailDirPath)) {
|
||||
console.log(
|
||||
logger.log(
|
||||
[
|
||||
`Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
|
||||
`To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`
|
||||
@ -154,7 +157,8 @@ export function generateKeycloakThemeResources(params: {
|
||||
|
||||
downloadBuiltinKeycloakTheme({
|
||||
keycloakVersion,
|
||||
"destDirPath": tmpDirPath
|
||||
"destDirPath": tmpDirPath,
|
||||
isSilent: buildOptions.isSilent
|
||||
});
|
||||
|
||||
const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
|
||||
|
@ -5,6 +5,8 @@ import * as child_process from "child_process";
|
||||
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
||||
import * as fs from "fs";
|
||||
import { readBuildOptions } from "./BuildOptions";
|
||||
import { getLogger } from "../tools/logger";
|
||||
import { getCliOptions } from "../tools/cliOptions";
|
||||
|
||||
const reactProjectDirPath = process.cwd();
|
||||
|
||||
@ -12,7 +14,9 @@ export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build
|
||||
export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email");
|
||||
|
||||
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({
|
||||
"packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"),
|
||||
@ -25,7 +29,8 @@ export function main() {
|
||||
|
||||
return fs.readFileSync(cnameFilePath).toString("utf8");
|
||||
})(),
|
||||
"isExternalAssetsCliParamProvided": process.argv[2]?.toLowerCase() === "--external-assets"
|
||||
"isExternalAssetsCliParamProvided": hasExternalAssets,
|
||||
"isSilent": isSilent
|
||||
});
|
||||
|
||||
const { doBundlesEmailTemplate } = generateKeycloakThemeResources({
|
||||
@ -51,7 +56,7 @@ export function main() {
|
||||
});
|
||||
|
||||
//We want, however, to test in a container running the latest Keycloak version
|
||||
const containerKeycloakVersion = "18.0.2";
|
||||
const containerKeycloakVersion = "19.0.1";
|
||||
|
||||
generateStartKeycloakTestingContainer({
|
||||
keycloakThemeBuildingDirPath,
|
||||
@ -59,7 +64,7 @@ export function main() {
|
||||
buildOptions
|
||||
});
|
||||
|
||||
console.log(
|
||||
logger.log(
|
||||
[
|
||||
"",
|
||||
`✅ 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 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}[(function(){
|
||||
var pd= Object.getOwnPropertyDescriptor(${n}, "p");
|
||||
if( pd === undefined || pd.configurable ){
|
||||
${
|
||||
buildOptions.isStandalone
|
||||
? `
|
||||
@ -55,10 +57,11 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; build
|
||||
: `
|
||||
var 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;}
|
||||
});
|
||||
`
|
||||
}
|
||||
}
|
||||
return "${u}";
|
||||
})()] = 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]) =>
|
||||
buildOptions.isStandalone
|
||||
? `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
|
||||
.replace(/".chunk.css",([a-zA-Z])+=([a-zA-Z]+\.[a-zA-Z]+)\+([a-zA-Z]+),/, (...[, group1, group2, group3]) =>
|
||||
buildOptions.isStandalone
|
||||
? `".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 };
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { execSync } from "child_process";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import * as fs from "fs";
|
||||
|
||||
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 ===");
|
||||
|
||||
@ -81,22 +103,24 @@ commonThirdPartyDeps.forEach(commonThirdPartyDep => {
|
||||
);
|
||||
|
||||
execYarnLink({ "cwd": localInstallPath });
|
||||
});
|
||||
|
||||
testAppNames.forEach(testAppName =>
|
||||
commonThirdPartyDeps.forEach(commonThirdPartyDep =>
|
||||
testAppPaths.forEach(testAppPath =>
|
||||
execYarnLink({
|
||||
"cwd": getTestAppPath(testAppName),
|
||||
"cwd": testAppPath,
|
||||
"targetModuleName": commonThirdPartyDep
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
|
||||
console.log("=== Linking in house dependencies ===");
|
||||
|
||||
execYarnLink({ "cwd": pathJoin(keycloakifyDirPath, "dist") });
|
||||
|
||||
testAppNames.forEach(testAppName =>
|
||||
testAppPaths.forEach(testAppPath =>
|
||||
execYarnLink({
|
||||
"cwd": getTestAppPath(testAppName),
|
||||
"cwd": testAppPath,
|
||||
"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";
|
||||
|
||||
/** 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 extractDirPath = pathJoin(
|
||||
@ -54,7 +60,7 @@ export function downloadAndUnzip(params: { url: string; destDirPath: string; pat
|
||||
|
||||
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}/**/*"`}`, {
|
||||
"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);
|
||||
}
|
||||
};
|
||||
};
|
46
src/lib/components/IdpReviewUserProfile.tsx
Normal file
46
src/lib/components/IdpReviewUserProfile.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
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, ...props }: { kcContext: KcContextBase.IdpReviewUserProfile; i18n: I18n } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { url } = kcContext;
|
||||
|
||||
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, i18n, ...props }}
|
||||
doFetchDefaultThemeResources={true}
|
||||
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;
|
@ -20,8 +20,10 @@ const LoginPageExpired = lazy(() => import("./LoginPageExpired"));
|
||||
const LoginIdpLinkEmail = lazy(() => import("./LoginIdpLinkEmail"));
|
||||
const LoginConfigTotp = lazy(() => import("./LoginConfigTotp"));
|
||||
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(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcContext: KcContextBase; i18n?: I18n } & KcProps) => {
|
||||
const i18n = (function useClosure() {
|
||||
const i18n = useI18n({
|
||||
kcContext,
|
||||
@ -36,42 +38,48 @@ const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...props }: { kcContext
|
||||
return null;
|
||||
}
|
||||
|
||||
const props = { i18n, ...kcProps };
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
{(() => {
|
||||
switch (kcContext.pageId) {
|
||||
case "login.ftl":
|
||||
return <Login {...{ kcContext, i18n, ...props }} />;
|
||||
return <Login {...{ kcContext, ...props }} />;
|
||||
case "register.ftl":
|
||||
return <Register {...{ kcContext, i18n, ...props }} />;
|
||||
return <Register {...{ kcContext, ...props }} />;
|
||||
case "register-user-profile.ftl":
|
||||
return <RegisterUserProfile {...{ kcContext, i18n, ...props }} />;
|
||||
return <RegisterUserProfile {...{ kcContext, ...props }} />;
|
||||
case "info.ftl":
|
||||
return <Info {...{ kcContext, i18n, ...props }} />;
|
||||
return <Info {...{ kcContext, ...props }} />;
|
||||
case "error.ftl":
|
||||
return <Error {...{ kcContext, i18n, ...props }} />;
|
||||
return <Error {...{ kcContext, ...props }} />;
|
||||
case "login-reset-password.ftl":
|
||||
return <LoginResetPassword {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginResetPassword {...{ kcContext, ...props }} />;
|
||||
case "login-verify-email.ftl":
|
||||
return <LoginVerifyEmail {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
|
||||
case "terms.ftl":
|
||||
return <Terms {...{ kcContext, i18n, ...props }} />;
|
||||
return <Terms {...{ kcContext, ...props }} />;
|
||||
case "login-otp.ftl":
|
||||
return <LoginOtp {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginOtp {...{ kcContext, ...props }} />;
|
||||
case "login-update-password.ftl":
|
||||
return <LoginUpdatePassword {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
|
||||
case "login-update-profile.ftl":
|
||||
return <LoginUpdateProfile {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-confirm.ftl":
|
||||
return <LoginIdpLinkConfirm {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-email.ftl":
|
||||
return <LoginIdpLinkEmail {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
|
||||
case "login-page-expired.ftl":
|
||||
return <LoginPageExpired {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginPageExpired {...{ kcContext, ...props }} />;
|
||||
case "login-config-totp.ftl":
|
||||
return <LoginConfigTotp {...{ kcContext, i18n, ...props }} />;
|
||||
return <LoginConfigTotp {...{ kcContext, ...props }} />;
|
||||
case "logout-confirm.ftl":
|
||||
return <LogoutConfirm {...{ kcContext, i18n, ...props }} />;
|
||||
return <LogoutConfirm {...{ kcContext, ...props }} />;
|
||||
case "update-user-profile.ftl":
|
||||
return <UpdateUserProfile {...{ kcContext, ...props }} />;
|
||||
case "idp-review-user-profile.ftl":
|
||||
return <IdpReviewUserProfile {...{ kcContext, ...props }} />;
|
||||
}
|
||||
})()}
|
||||
</Suspense>
|
||||
|
@ -2,7 +2,7 @@ import React, { useState, memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import type { FormEventHandler } from "react";
|
||||
import type { I18n } from "../i18n";
|
||||
|
@ -2,7 +2,7 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n } & KcProps) => {
|
||||
|
@ -2,7 +2,7 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const LoginIdpLinkConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n } & KcProps) => {
|
||||
|
@ -4,7 +4,7 @@ import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { headInsert } from "../tools/headInsert";
|
||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const LoginOtp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginOtp; i18n: I18n } & KcProps) => {
|
||||
|
@ -2,7 +2,7 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const LoginResetPassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n } & KcProps) => {
|
||||
|
@ -2,7 +2,7 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n } & KcProps) => {
|
||||
|
@ -2,7 +2,7 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n } & KcProps) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { memo } from "react";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
|
@ -2,7 +2,7 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Register; i18n: I18n } & KcProps) => {
|
||||
|
@ -1,12 +1,10 @@
|
||||
import React, { useMemo, memo, useEffect, useState, Fragment } from "react";
|
||||
import React, { useMemo, memo, useState } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import type { ReactComponent } from "../tools/ReactComponent";
|
||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||
import { useFormValidationSlice } from "../useFormValidationSlice";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||
|
||||
const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps) => {
|
||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||
@ -66,155 +64,4 @@ const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: K
|
||||
);
|
||||
});
|
||||
|
||||
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,
|
||||
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>
|
||||
)}
|
||||
<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>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default RegisterUserProfile;
|
||||
|
@ -7,7 +7,7 @@ import { headInsert } from "../tools/headInsert";
|
||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import type { KcTemplateProps } from "./KcProps";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export type TemplateProps = {
|
||||
|
@ -2,7 +2,7 @@ import React, { useEffect, memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import { Evt } from "evt";
|
||||
import { useRerenderOnStateChange } from "evt/hooks";
|
||||
import { assert } from "tsafe/assert";
|
||||
@ -11,6 +11,7 @@ import type { I18n } from "../i18n";
|
||||
import memoize from "memoizee";
|
||||
import { useConst } from "powerhooks/useConst";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { Markdown } from "../tools/Markdown";
|
||||
|
||||
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
||||
|
||||
@ -74,7 +75,7 @@ const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Te
|
||||
headerNode={msg("termsTitle")}
|
||||
formNode={
|
||||
<>
|
||||
<div id="kc-terms-text">{evtTermMarkdown.state}</div>
|
||||
<div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div>
|
||||
<form className="form-actions" action={url.loginAction} method="POST">
|
||||
<input
|
||||
className={cx(
|
||||
|
71
src/lib/components/UpdateUserProfile.tsx
Normal file
71
src/lib/components/UpdateUserProfile.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
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, ...props }: { kcContext: KcContextBase.UpdateUserProfile; i18n: I18n } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { url, isAppInitiatedAction } = kcContext;
|
||||
|
||||
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, i18n, ...props }}
|
||||
doFetchDefaultThemeResources={true}
|
||||
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.LoginPageExpired
|
||||
| KcContextBase.LoginConfigTotp
|
||||
| KcContextBase.LogoutConfirm;
|
||||
| KcContextBase.LogoutConfirm
|
||||
| KcContextBase.UpdateUserProfile
|
||||
| KcContextBase.IdpReviewUserProfile;
|
||||
|
||||
export declare namespace KcContextBase {
|
||||
export type Common = {
|
||||
@ -270,6 +272,23 @@ export declare namespace KcContextBase {
|
||||
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 = {
|
||||
|
@ -47,8 +47,16 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
||||
"source": partialKcContextCustomMock
|
||||
});
|
||||
|
||||
if (partialKcContextCustomMock.pageId === "register-user-profile.ftl") {
|
||||
assert(kcContextDefaultMock?.pageId === "register-user-profile.ftl");
|
||||
if (
|
||||
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;
|
||||
|
||||
@ -82,14 +90,16 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
||||
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute;
|
||||
});
|
||||
|
||||
partialAttributes.forEach(partialAttribute => {
|
||||
const { name } = partialAttribute;
|
||||
partialAttributes
|
||||
.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.attributesByName[name] = partialAttribute as any;
|
||||
});
|
||||
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes.push(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 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 = {
|
||||
"url": {
|
||||
"loginAction": "#",
|
||||
@ -200,104 +294,8 @@ export const kcContextMocks: KcContextBase[] = [
|
||||
...registerCommon,
|
||||
"profile": {
|
||||
"context": "REGISTRATION_PROFILE" as const,
|
||||
...(() => {
|
||||
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"
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
attributes,
|
||||
"attributesByName": Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any
|
||||
} as any;
|
||||
})()
|
||||
attributes,
|
||||
attributesByName
|
||||
}
|
||||
})
|
||||
];
|
||||
@ -423,5 +421,22 @@ export const kcContextMocks: KcContextBase[] = [
|
||||
"baseUrl": "#"
|
||||
},
|
||||
"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";
|
||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import type baseMessages from "./generated_messages/18.0.1/login/en";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { Markdown } from "../tools/Markdown";
|
||||
|
||||
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 refHasStartedFetching = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (doSkip) {
|
||||
if (doSkip || refHasStartedFetching.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isMounted = true;
|
||||
refHasStartedFetching.current = true;
|
||||
|
||||
(async () => {
|
||||
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));
|
||||
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setI18n({
|
||||
...createI18nTranslationFunctions({
|
||||
"fallbackMessages": {
|
||||
@ -176,10 +174,6 @@ export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params:
|
||||
)
|
||||
});
|
||||
})();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return i18n ?? null;
|
||||
@ -240,9 +234,9 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
})();
|
||||
|
||||
return doRenderMarkdown ? (
|
||||
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
|
||||
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}>
|
||||
{messageWithArgsInjectedIfAny}
|
||||
</ReactMarkdown>
|
||||
</Markdown>
|
||||
) : (
|
||||
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",
|
||||
"rootDir": ".",
|
||||
"module": "ES2020",
|
||||
"target": "ES2020",
|
||||
"target": "ES2017",
|
||||
"lib": ["es2015", "DOM", "ES2019.Object"],
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
|
@ -304,13 +304,17 @@ export function useGetErrors(params: {
|
||||
return { getErrors };
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: The attributesWithPassword returned is actually augmented with
|
||||
* artificial password related attributes only if kcContext.passwordRequired === true
|
||||
*/
|
||||
export function useFormValidationSlice(params: {
|
||||
kcContext: {
|
||||
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
||||
profile: {
|
||||
attributes: Attribute[];
|
||||
};
|
||||
passwordRequired: boolean;
|
||||
passwordRequired?: boolean;
|
||||
realm: { registrationEmailAsUsername: boolean };
|
||||
};
|
||||
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
||||
|
@ -14,6 +14,7 @@ generateKeycloakThemeResources({
|
||||
"extraPages": ["my-custom-page.ftl"],
|
||||
"extraThemeProperties": ["env=test"],
|
||||
"isStandalone": true,
|
||||
"urlPathname": "/keycloakify-demo-app/"
|
||||
"urlPathname": "/keycloakify-demo-app/",
|
||||
"isSilent": false
|
||||
}
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
||||
}[e] + ".chunk.js"
|
||||
}
|
||||
|
||||
n.u=function(e){return"static/js/" + e + "." + {
|
||||
__webpack_require__.u=function(e){return"static/js/" + e + "." + {
|
||||
147: "6c5cee76",
|
||||
787: "8da10fcf",
|
||||
922: "be170a73"
|
||||
@ -54,11 +54,14 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
||||
}[e] + ".chunk.js"
|
||||
}
|
||||
|
||||
n[(function (){
|
||||
Object.defineProperty(n, "p", {
|
||||
get: function() { return window.kcContext.url.resourcesPath; },
|
||||
set: function (){}
|
||||
});
|
||||
__webpack_require__[(function (){
|
||||
var pd= Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
||||
if( pd === undefined || pd.configurable ){
|
||||
Object.defineProperty(__webpack_require__, "p", {
|
||||
get: function() { return window.kcContext.url.resourcesPath; },
|
||||
set: function (){}
|
||||
});
|
||||
}
|
||||
return "u";
|
||||
})()] = function(e) {
|
||||
return "/build/static/js/" + e + "." + {
|
||||
@ -69,10 +72,13 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
||||
}
|
||||
|
||||
t[(function (){
|
||||
Object.defineProperty(t, "p", {
|
||||
get: function() { return window.kcContext.url.resourcesPath; },
|
||||
set: function (){}
|
||||
});
|
||||
var pd= Object.getOwnPropertyDescriptor(t, "p");
|
||||
if( pd === undefined || pd.configurable ){
|
||||
Object.defineProperty(t, "p", {
|
||||
get: function() { return window.kcContext.url.resourcesPath; },
|
||||
set: function (){}
|
||||
});
|
||||
}
|
||||
return "miniCssF";
|
||||
})()] = function(e) {
|
||||
return "/build/static/css/" + e + "." + {
|
||||
@ -97,23 +103,26 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
||||
|
||||
const fixedJsCodeExpected = `
|
||||
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"
|
||||
}[e] + ".chunk.js"
|
||||
}
|
||||
|
||||
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"
|
||||
}[e] + ".chunk.js"
|
||||
}
|
||||
|
||||
n[(function (){
|
||||
var p= "";
|
||||
Object.defineProperty(n, "p", {
|
||||
get: function() { return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + p; },
|
||||
set: function (value){ p = value; }
|
||||
});
|
||||
__webpack_require__[(function (){
|
||||
var pd= Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
||||
if( pd === undefined || pd.configurable ){
|
||||
var p= "";
|
||||
Object.defineProperty(__webpack_require__, "p", {
|
||||
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
|
||||
set: function (value){ p = value; }
|
||||
});
|
||||
}
|
||||
return "u";
|
||||
})()] = function(e) {
|
||||
return "static/js/" + e + "." + {
|
||||
@ -124,11 +133,14 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
||||
}
|
||||
|
||||
t[(function (){
|
||||
var p= "";
|
||||
Object.defineProperty(t, "p", {
|
||||
get: function() { return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + p; },
|
||||
set: function (value){ p = value; }
|
||||
});
|
||||
var pd= Object.getOwnPropertyDescriptor(t, "p");
|
||||
if( pd === undefined || pd.configurable ){
|
||||
var p= "";
|
||||
Object.defineProperty(t, "p", {
|
||||
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
|
||||
set: function (value){ p = value; }
|
||||
});
|
||||
}
|
||||
return "miniCssF";
|
||||
})()] = function(e) {
|
||||
return "static/css/" + e + "." + {
|
||||
|
@ -8,6 +8,7 @@ export function setupSampleReactProject() {
|
||||
downloadAndUnzip({
|
||||
"url": "https://github.com/InseeFrLab/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||
"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"
|
||||
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":
|
||||
version "7.18.6"
|
||||
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"
|
||||
|
||||
"@babel/types@^7.18.6":
|
||||
version "7.18.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f"
|
||||
integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==
|
||||
version "7.18.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
|
||||
integrity sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.18.10"
|
||||
"@babel/helper-validator-identifier" "^7.18.6"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@emotion/babel-plugin@^11.10.0":
|
||||
version "11.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.0.tgz#ae545b8faa6b42d3a50ec86b70b758296f3c4467"
|
||||
integrity sha512-xVnpDAAbtxL1dsuSelU5A7BnY/lftws0wUexNJZTPsvX/1tM4GZJbclgODhvW4E+NH7E5VFcH0bBn30NvniPJA==
|
||||
version "11.10.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz#879db80ba622b3f6076917a1e6f648b1c7d008c7"
|
||||
integrity sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.16.7"
|
||||
"@babel/plugin-syntax-jsx" "^7.17.12"
|
||||
@ -76,9 +82,9 @@
|
||||
stylis "4.0.13"
|
||||
|
||||
"@emotion/cache@*", "@emotion/cache@^11.10.0":
|
||||
version "11.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.0.tgz#9eb9d3245c2c25ae31028d5ff0929987824c77da"
|
||||
integrity sha512-3FoUWnDbHWg/pXGCvL46jvpOSJP0xvRZLY8khUcUHGOBcp0S/MCIk+osp84/dby2Ctahw/Ev4KTHWkY3i0g39g==
|
||||
version "11.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87"
|
||||
integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==
|
||||
dependencies:
|
||||
"@emotion/memoize" "^0.8.0"
|
||||
"@emotion/sheet" "^1.2.0"
|
||||
@ -97,14 +103,15 @@
|
||||
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
|
||||
|
||||
"@emotion/react@^11.4.1":
|
||||
version "11.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.0.tgz#53c577f063f26493f68a05188fb87528d912ff2e"
|
||||
integrity sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ==
|
||||
version "11.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199"
|
||||
integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@emotion/babel-plugin" "^11.10.0"
|
||||
"@emotion/cache" "^11.10.0"
|
||||
"@emotion/serialize" "^1.1.0"
|
||||
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
|
||||
"@emotion/utils" "^1.2.0"
|
||||
"@emotion/weak-memoize" "^0.3.0"
|
||||
hoist-non-react-statics "^3.3.1"
|
||||
@ -130,6 +137,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db"
|
||||
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":
|
||||
version "1.2.0"
|
||||
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"
|
||||
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":
|
||||
version "17.0.45"
|
||||
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"
|
||||
|
||||
entities@^4.2.0, entities@^4.3.0:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.1.tgz#c34062a94c865c322f9d67b4384e4169bcede6a4"
|
||||
integrity sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
|
||||
integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
|
||||
|
||||
error-ex@^1.3.1:
|
||||
version "1.3.2"
|
||||
@ -673,9 +690,9 @@ error-ex@^1.3.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:
|
||||
version "0.10.61"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.61.tgz#311de37949ef86b6b0dcea894d1ffedb909d3269"
|
||||
integrity sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==
|
||||
version "0.10.62"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5"
|
||||
integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==
|
||||
dependencies:
|
||||
es6-iterator "^2.0.3"
|
||||
es6-symbol "^3.1.3"
|
||||
@ -731,14 +748,14 @@ event-emitter@^0.3.5:
|
||||
d "1"
|
||||
es5-ext "~0.10.14"
|
||||
|
||||
evt@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/evt/-/evt-2.3.1.tgz#988fc6fc255db8999240e918afe63ba6c325db99"
|
||||
integrity sha512-+MU1aA0as6hOnGxzQOw9hV/xiKIB1vAY90S+WD6zMzvvhQHlY4aPHk2b8WpWsVs3XErDzlhGzCESVCAuH9kUiA==
|
||||
evt@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.1.tgz#68beca2f7bd7eb755fdda5b263a80b934100e046"
|
||||
integrity sha512-eo7sZcfDbiVWD5Aw6STkIEMmchYLdeGnJB6tVaM9AXZc7pViin3PmQhb6fgFIFHfl0re9zSEHtSjyu70Y7dRJg==
|
||||
dependencies:
|
||||
minimal-polyfills "^2.2.1"
|
||||
minimal-polyfills "^2.2.2"
|
||||
run-exclusive "^2.2.16"
|
||||
tsafe "^0.10.1"
|
||||
tsafe "^1.0.1"
|
||||
|
||||
execa@^5.1.1:
|
||||
version "5.1.1"
|
||||
@ -756,11 +773,11 @@ execa@^5.1.1:
|
||||
strip-final-newline "^2.0.0"
|
||||
|
||||
ext@^1.1.2:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52"
|
||||
integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
|
||||
integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==
|
||||
dependencies:
|
||||
type "^2.5.0"
|
||||
type "^2.7.2"
|
||||
|
||||
extend@^3.0.0:
|
||||
version "3.0.2"
|
||||
@ -945,9 +962,9 @@ is-buffer@^2.0.0:
|
||||
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
|
||||
|
||||
is-core-module@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
|
||||
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
|
||||
integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
|
||||
dependencies:
|
||||
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"
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
|
||||
minimal-polyfills@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.1.tgz#7249d7ece666d3b4e1ec1c1b8f949eb9d44e2308"
|
||||
integrity sha512-WLmHQrsZob4rVYf8yHapZPNJZ3sspGa/sN8abuSD59b0FifDEE7HMfLUi24z7mPZqTpBXy4Svp+iGvAmclCmXg==
|
||||
minimal-polyfills@^2.2.1, minimal-polyfills@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.2.tgz#6b06a004acce420eb91cf94698f5e6e7f2518378"
|
||||
integrity sha512-eEOUq/LH/DbLWihrxUP050Wi7H/N/I2dQT98Ep6SqOpmIbk4sXOI4wqalve66QoZa+6oljbZWU6I6T4dehQGmw==
|
||||
|
||||
minimatch@^3.0.3, minimatch@^3.1.1:
|
||||
version "3.1.2"
|
||||
@ -1176,6 +1193,11 @@ minimatch@^3.0.3, minimatch@^3.1.1:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
@ -1363,15 +1385,15 @@ please-upgrade-node@^3.2.0:
|
||||
dependencies:
|
||||
semver-compare "^1.0.0"
|
||||
|
||||
powerhooks@^0.20.10:
|
||||
version "0.20.10"
|
||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.10.tgz#c30ba493ea324806100d9f5df889dea19f3a73ff"
|
||||
integrity sha512-RiQVBgxLavWFVb3hKmzluykvgeVYgVyGtITlnIjVrtfql3jpCHzuVVTq/49oXp18BNEOKCQXzKfFeb42f1v4Pg==
|
||||
powerhooks@^0.20.16:
|
||||
version "0.20.16"
|
||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.16.tgz#03aadcccb4f70ca1c768369c01b84197cfeb97aa"
|
||||
integrity sha512-pCDaiGe5p3NrTvtelDBjrclJtjmLQlf99EjHPpQCeQ1H1ZmwQDxV8DaSbguQq0jc1PGcJ5D6Ts3gcEr/e680fg==
|
||||
dependencies:
|
||||
evt "^2.3.1"
|
||||
evt "^2.4.1"
|
||||
memoizee "^0.4.15"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
tsafe "^0.10.1"
|
||||
tsafe "^1.0.1"
|
||||
|
||||
prettier@^2.3.0:
|
||||
version "2.7.1"
|
||||
@ -1711,20 +1733,20 @@ trough@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
||||
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
||||
|
||||
tsafe@^0.10.1:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.10.1.tgz#8f100b901e4467c43c0484f56a063f4276683ce0"
|
||||
integrity sha512-S+LrpSjoH5Pah201KS0MxtJn88HVtKf4ZxUoQuW/Hnl4IK6ALu9Qwjed7RbohDeHn+iMuug4c5Mk/z1Cq2G3nw==
|
||||
tsafe@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.0.1.tgz#c8c4eb2d75d1478418a4941307c5dd667fd76d23"
|
||||
integrity sha512-FgJ1a4rE7YbmW5QIzpsfFl4tsAp0x74FH2bVE6qODb2U8jSrwTr5/ckIazeylme5zXndVbtgKm4BZdqmoGhiPw==
|
||||
|
||||
tslib@^2.1.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
||||
tss-react@^3.7.1:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-3.7.1.tgz#119647731490f9e7e62c7f6a38a78df981929a4b"
|
||||
integrity sha512-dfWUoxBlKZfIG9UC1A2h02OmcE/Ni0itCmmZu94E9g+KyBhKMHKcsKvUm0bNlRqTmYjXiCgPJDmj5fyc8CSrLg==
|
||||
tss-react@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-4.1.3.tgz#170a2edd85e32ba3b752416eeb45438c35e90243"
|
||||
integrity sha512-Ul99aVoVngHY2nwFC87jZlGP+K3LfG9ZRakimOzRwgJz77f/zXWuY2FVzwaSc+OW804twNaxZZRtLlpagex8sg==
|
||||
dependencies:
|
||||
"@emotion/cache" "*"
|
||||
"@emotion/serialize" "*"
|
||||
@ -1740,15 +1762,15 @@ type@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
||||
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
|
||||
|
||||
type@^2.5.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/type/-/type-2.6.1.tgz#808f389ec777205cc3cd97c1c88ec1a913105aae"
|
||||
integrity sha512-OvgH5rB0XM+iDZGQ1Eg/o7IZn0XYJFVrN/9FQ4OWIYILyJJgVP2s1hLTOFn6UOZoDUI/HctGa0PFlE2n2HW3NQ==
|
||||
type@^2.7.2:
|
||||
version "2.7.2"
|
||||
resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
|
||||
integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==
|
||||
|
||||
typescript@^4.2.3:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
version "4.8.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
|
||||
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
|
||||
|
||||
unified@^9.0.0:
|
||||
version "9.2.2"
|
||||
@ -1916,6 +1938,6 @@ yocto-queue@^0.1.0:
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zod@^3.17.10:
|
||||
version "3.17.10"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.17.10.tgz#8716a05e6869df6faaa878a44ffe3c79e615defb"
|
||||
integrity sha512-IHXnQYQuOOOL/XgHhgl8YjNxBHi3xX0mVcHmqsvJgcxKkEczPshoWdxqyFwsARpf41E0v9U95WUROqsHHxt0UQ==
|
||||
version "3.18.0"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc"
|
||||
integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==
|
||||
|
Reference in New Issue
Block a user