Merge branch 'main' into patch-1

This commit is contained in:
Waldemar Reusch
2023-01-08 12:22:14 +01:00
committed by GitHub
54 changed files with 2244 additions and 798 deletions

4
.github/FUNDING.yaml vendored Normal file
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
github: [garronej]
custom: ['https://www.ringerhq.com/experts/garronej']

View File

@ -3,11 +3,9 @@ on:
push: push:
branches: branches:
- main - main
- v6
pull_request: pull_request:
branches: branches:
- main - main
- v6
jobs: jobs:
@ -15,10 +13,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ !github.event.created && github.repository != 'garronej/ts-ci' }} if: ${{ !github.event.created && github.repository != 'garronej/ts-ci' }}
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v3
- uses: actions/setup-node@v2.1.3 - uses: actions/setup-node@v3
- uses: bahmutov/npm-install@v1 - uses: bahmutov/npm-install@v1
- name: If this step fails run 'npm run lint' and 'npm run format' then commit again. - name: If this step fails run 'yarn format' then commit again.
run: | run: |
PACKAGE_MANAGER=npm PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then if [ -f "./yarn.lock" ]; then
@ -26,22 +24,21 @@ jobs:
fi fi
$PACKAGE_MANAGER run format:check $PACKAGE_MANAGER run format:check
test: test:
runs-on: macos-10.15 runs-on: ${{ matrix.os }}
needs: test_lint needs: test_lint
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy: strategy:
matrix: matrix:
node: [ '15', '14' ] node: [ '14', '15' ,'16', '17' ]
name: Test with Node v${{ matrix.node }} os: [ windows-latest, ubuntu-latest ]
name: Test with Node v${{ matrix.node }} on ${{ matrix.os }}
steps: steps:
- name: Tell if project is using npm or yarn - name: Tell if project is using npm or yarn
id: step1 id: step1
uses: garronej/ts-ci@v1.1.7 uses: garronej/ts-ci@v2.0.2
with: with:
action_name: tell_if_project_uses_npm_or_yarn action_name: tell_if_project_uses_npm_or_yarn
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v3
- uses: actions/setup-node@v2.1.3 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
- uses: bahmutov/npm-install@v1 - uses: bahmutov/npm-install@v1
@ -67,9 +64,9 @@ jobs:
from_version: ${{ steps.step1.outputs.from_version }} from_version: ${{ steps.step1.outputs.from_version }}
to_version: ${{ steps.step1.outputs.to_version }} to_version: ${{ steps.step1.outputs.to_version }}
is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }} is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }}
is_release_beta: ${{steps.step1.outputs.is_release_beta }} is_pre_release: ${{steps.step1.outputs.is_pre_release }}
steps: steps:
- uses: garronej/ts-ci@v1.1.7 - uses: garronej/ts-ci@v2.0.2
id: step1 id: step1
with: with:
action_name: is_package_json_version_upgraded action_name: is_package_json_version_upgraded
@ -77,13 +74,13 @@ jobs:
create_github_release: create_github_release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# We create a release only if the version have been upgraded and we are on a default branch # We create a release only if the version have been upgraded and we are on the main branch
# PR on the default branch can release beta but not real release # or if we are on a branch of the repo that has an PR open on main.
if: | if: |
needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' && needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' &&
( (
github.event_name == 'push' || github.event_name == 'push' ||
needs.check_if_version_upgraded.outputs.is_release_beta == 'true' needs.check_if_version_upgraded.outputs.is_pre_release == 'true'
) )
needs: needs:
- check_if_version_upgraded - check_if_version_upgraded
@ -95,7 +92,7 @@ jobs:
target_commitish: ${{ github.head_ref || github.ref }} target_commitish: ${{ github.head_ref || github.ref }}
generate_release_notes: true generate_release_notes: true
draft: false draft: false
prerelease: ${{ needs.check_if_version_upgraded.outputs.is_release_beta == 'true' }} prerelease: ${{ needs.check_if_version_upgraded.outputs.is_pre_release == 'true' }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -105,12 +102,11 @@ jobs:
- create_github_release - create_github_release
- check_if_version_upgraded - check_if_version_upgraded
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v3
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- uses: actions/setup-node@v2.1.3 - uses: actions/setup-node@v3
with: with:
node-version: '15'
registry-url: https://registry.npmjs.org/ registry-url: https://registry.npmjs.org/
- uses: bahmutov/npm-install@v1 - uses: bahmutov/npm-install@v1
- run: | - run: |
@ -119,7 +115,7 @@ jobs:
PACKAGE_MANAGER=yarn PACKAGE_MANAGER=yarn
fi fi
$PACKAGE_MANAGER run build $PACKAGE_MANAGER run build
- run: npx -y -p denoify@1.0.2 enable_short_npm_import_path - run: npx -y -p denoify@1.2.2 enable_short_npm_import_path
env: env:
DRY_RUN: "0" DRY_RUN: "0"
- name: Publishing on NPM - name: Publishing on NPM
@ -133,11 +129,11 @@ jobs:
false false
fi fi
EXTRA_ARGS="" EXTRA_ARGS=""
if [ "$IS_BETA" = "true" ]; then if [ "$IS_PRE_RELEASE" = "true" ]; then
EXTRA_ARGS="--tag beta" EXTRA_ARGS="--tag next"
fi fi
npm publish $EXTRA_ARGS npm publish $EXTRA_ARGS
env: env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }} VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
IS_BETA: ${{ needs.check_if_version_upgraded.outputs.is_release_beta }} IS_PRE_RELEASE: ${{ needs.check_if_version_upgraded.outputs.is_pre_release }}

View File

@ -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,40 @@
<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.8.4
- `@emotion/react` is no longer a peer dependency of Keycloakify.
## 6.8.0
- It is now possible to pass a custom `<Template />` component as a prop to `<KcApp />` and every
individual page (`<Login />`, `<RegisterUserProfile />`, ...) it enables to customize only the header and footer for
example without having to switch to a full-component level customization. [See issue](https://github.com/InseeFrLab/keycloakify/issues/191).
## 6.7.0
- Add support for `webauthn-authenticate.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/185).
## 6.6.0
- Add support for `login-password.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/184).
## 6.5.0
- Add support for `login-username.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/183).
## 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 +84,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

View File

@ -1,6 +1,6 @@
{ {
"name": "keycloakify", "name": "keycloakify",
"version": "6.0.0-beta.14", "version": "6.8.10",
"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}'",
@ -54,12 +55,12 @@
], ],
"homepage": "https://github.com/garronej/keycloakify", "homepage": "https://github.com/garronej/keycloakify",
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.4.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}, },
"devDependencies": { "devDependencies": {
"@emotion/react": "^11.4.1", "@babel/core": "^7.0.0",
"@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",
@ -69,21 +70,24 @@
"properties-parser": "^0.3.1", "properties-parser": "^0.3.1",
"react": "18.1.0", "react": "18.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"@emotion/react": "^11.10.4",
"typescript": "^4.2.3" "typescript": "^4.2.3"
}, },
"dependencies": { "dependencies": {
"@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.4.0", "evt": "^2.4.13",
"memoizee": "^0.4.15", "memoizee": "^0.4.15",
"minimal-polyfills": "^2.2.2", "minimal-polyfills": "^2.2.2",
"minimist": "^1.2.6",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"powerhooks": "^0.20.15", "powerhooks": "^0.20.32",
"react-markdown": "^5.0.3", "react-markdown": "^5.0.3",
"rfc4648": "^1.5.2",
"scripting-tools": "^0.19.13", "scripting-tools": "^0.19.13",
"tsafe": "^1.0.1", "tsafe": "^1.4.1",
"tss-react": "^4.1.1", "tss-react": "4.4.1-rc.0",
"zod": "^3.17.10" "zod": "^3.17.10"
} }
} }

View File

@ -13,11 +13,11 @@
"packageRules": [ "packageRules": [
{ {
"packagePatterns": ["*"], "packagePatterns": ["*"],
"excludePackagePatterns": ["tss-react", "powerhooks", "tsafe", "evt"], "excludePackagePatterns": ["powerhooks", "tsafe", "evt"],
"enabled": false "enabled": false
}, },
{ {
"packagePatterns": ["tss-react", "powerhooks", "tsafe", "evt"], "packagePatterns": ["powerhooks", "tsafe", "evt"],
"matchUpdateTypes": ["minor", "patch"], "matchUpdateTypes": ["minor", "patch"],
"automerge": true, "automerge": true,
"automergeType": "branch", "automergeType": "branch",

View File

@ -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 });
})(); })();

View File

@ -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
}); });
})(); })();
} }

View File

@ -4,7 +4,8 @@ import { join as pathJoin, relative as pathRelative, dirname as pathDirname } fr
import { crawl } from "./tools/crawl"; 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 { rmSync } from "fs"; 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,16 +13,20 @@ import { rmSync } from "fs";
//@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");
rmSync(tmpDirPath, {recursive: true, force: true}); fs.rmSync(tmpDirPath, {recursive: true, force: true});
downloadBuiltinKeycloakTheme({ downloadBuiltinKeycloakTheme({
keycloakVersion, keycloakVersion,
"destDirPath": tmpDirPath "destDirPath": tmpDirPath,
isSilent
}); });
type Dictionary = { [idiomId: string]: string }; type Dictionary = { [idiomId: string]: string };
@ -48,7 +53,7 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
}); });
} }
rmSync(tmpDirPath, {recursive: true, force: true}); fs.rmSync(tmpDirPath, {recursive: true, force: true});
Object.keys(record).forEach(pageType => { Object.keys(record).forEach(pageType => {
const recordForPageType = record[pageType]; const recordForPageType = record[pageType];
@ -75,7 +80,7 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
) )
); );
console.log(`${filePath} wrote`); logger.log(`${filePath} wrote`);
}); });
}); });
} }

View File

@ -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
}); });

View File

@ -32,60 +32,82 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
"printIfExists": function (fieldName, x) { "printIfExists": function (fieldName, x) {
<#if !messagesPerField?? > <#if !messagesPerField?? >
return undefined; return undefined;
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
return <#if messagesPerField.existsError('username', 'password')>x<#else>undefined</#if>;
<#else>
return <#if messagesPerField.existsError('${fieldName}')>x<#else>undefined</#if>;
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if> </#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
return "${messagesPerField.printIfExists(fieldName,'1')}" ? x : undefined;
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
}, },
"existsError": function (fieldName) { "existsError": function (fieldName) {
<#if !messagesPerField?? > <#if !messagesPerField?? >
return false; return false;
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
return <#if messagesPerField.existsError('username', 'password')>true<#else>false</#if>;
<#else>
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if> </#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
}, },
"get": function (fieldName) { "get": function (fieldName) {
<#if !messagesPerField?? > <#if !messagesPerField?? >
return ''; return '';
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
<#if messagesPerField.existsError('username', 'password')>
return 'Invalid username or password.';
</#if>
<#else>
<#if messagesPerField.existsError('${fieldName}')>
return "${messagesPerField.get('${fieldName}')?no_esc}";
</#if>
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if> </#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if messagesPerField.existsError('${fieldName}')>
return "${messagesPerField.get('${fieldName}')?no_esc}";
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
}, },
"exists": function (fieldName) { "exists": function (fieldName) {
<#if !messagesPerField?? > <#if !messagesPerField?? >
return false; return false;
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
return <#if messagesPerField.exists('username', 'password')>true<#else>false</#if>
<#else>
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if> </#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
} }
}; };
@ -272,6 +294,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>

View File

@ -13,6 +13,9 @@ import { Reflect } from "tsafe/Reflect";
// https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java // https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java
export const pageIds = [ export const pageIds = [
"login.ftl", "login.ftl",
"login-username.ftl",
"login-password.ftl",
"webauthn-authenticate.ftl",
"register.ftl", "register.ftl",
"register-user-profile.ftl", "register-user-profile.ftl",
"info.ftl", "info.ftl",
@ -27,7 +30,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 +51,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 +81,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;
} }

View File

@ -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");

View File

@ -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 = "20.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)} 🚀`,

View File

@ -57,7 +57,7 @@ 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;}
}); });
` `
@ -73,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 };

View 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
};
};

View File

@ -5,7 +5,13 @@ import { transformCodebase } from "./transformCodebase";
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(
@ -53,7 +59,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
View 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);
}
};
};

View File

@ -1,18 +1,27 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 Error = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Error; i18n: I18n } & KcProps) => { export type ErrorProps = KcProps & {
kcContext: KcContextBase.Error;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const Error = memo((props: ErrorProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { message, client } = kcContext; const { message, client } = kcContext;
const { msg } = i18n; const { msg } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
doFetchDefaultThemeResources={true}
displayMessage={false} displayMessage={false}
headerNode={msg("errorTitle")} headerNode={msg("errorTitle")}
formNode={ formNode={

View File

@ -0,0 +1,58 @@
import React, { useState, memo } from "react";
import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons";
export type IdpReviewUserProfileProps = KcProps & {
kcContext: KcContextBase.IdpReviewUserProfile;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const IdpReviewUserProfile = memo((props: IdpReviewUserProfileProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n;
const { url } = kcContext;
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("loginIdpReviewProfileTitle")}
formNode={
<form id="kc-idp-review-profile-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
value={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
</div>
</div>
</form>
}
/>
);
});
export default IdpReviewUserProfile;

View File

@ -1,11 +1,21 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert"; 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) => { export type InfoProps = KcProps & {
kcContext: KcContextBase.Info;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const Info = memo((props: InfoProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msgStr, msg } = i18n; const { msgStr, msg } = i18n;
assert(kcContext.message !== undefined); assert(kcContext.message !== undefined);
@ -14,8 +24,7 @@ const Info = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Inf
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
doFetchDefaultThemeResources={true}
displayMessage={false} displayMessage={false}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>} headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
formNode={ formNode={

View File

@ -3,6 +3,8 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { __unsafe_useI18n as useI18n } from "../i18n"; import { __unsafe_useI18n as useI18n } from "../i18n";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
const Login = lazy(() => import("./Login")); const Login = lazy(() => import("./Login"));
const Register = lazy(() => import("./Register")); const Register = lazy(() => import("./Register"));
@ -13,6 +15,9 @@ const LoginResetPassword = lazy(() => import("./LoginResetPassword"));
const LoginVerifyEmail = lazy(() => import("./LoginVerifyEmail")); const LoginVerifyEmail = lazy(() => import("./LoginVerifyEmail"));
const Terms = lazy(() => import("./Terms")); const Terms = lazy(() => import("./Terms"));
const LoginOtp = lazy(() => import("./LoginOtp")); const LoginOtp = lazy(() => import("./LoginOtp"));
const LoginPassword = lazy(() => import("./LoginPassword"));
const LoginUsername = lazy(() => import("./LoginUsername"));
const WebauthnAuthenticate = lazy(() => import("./WebauthnAuthenticate"));
const LoginUpdatePassword = lazy(() => import("./LoginUpdatePassword")); const LoginUpdatePassword = lazy(() => import("./LoginUpdatePassword"));
const LoginUpdateProfile = lazy(() => import("./LoginUpdateProfile")); const LoginUpdateProfile = lazy(() => import("./LoginUpdateProfile"));
const LoginIdpLinkConfirm = lazy(() => import("./LoginIdpLinkConfirm")); const LoginIdpLinkConfirm = lazy(() => import("./LoginIdpLinkConfirm"));
@ -20,8 +25,19 @@ 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"));
export type KcAppProps = KcProps & {
kcContext: KcContextBase;
i18n?: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const KcApp = memo((props_: KcAppProps) => {
const { kcContext, i18n: userProvidedI18n, Template = DefaultTemplate, ...kcProps } = props_;
const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcContext: KcContextBase; i18n?: I18n } & KcProps) => {
const i18n = (function useClosure() { const i18n = (function useClosure() {
const i18n = useI18n({ const i18n = useI18n({
kcContext, kcContext,
@ -36,44 +52,54 @@ const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcConte
return null; return null;
} }
const props = { i18n, ...kcProps }; const commonProps = { i18n, Template, ...kcProps };
return ( return (
<Suspense> <Suspense>
{(() => { {(() => {
switch (kcContext.pageId) { switch (kcContext.pageId) {
case "login.ftl": case "login.ftl":
return <Login {...{ kcContext, ...props }} />; return <Login {...{ kcContext, ...commonProps }} />;
case "register.ftl": case "register.ftl":
return <Register {...{ kcContext, ...props }} />; return <Register {...{ kcContext, ...commonProps }} />;
case "register-user-profile.ftl": case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, ...props }} />; return <RegisterUserProfile {...{ kcContext, ...commonProps }} />;
case "info.ftl": case "info.ftl":
return <Info {...{ kcContext, ...props }} />; return <Info {...{ kcContext, ...commonProps }} />;
case "error.ftl": case "error.ftl":
return <Error {...{ kcContext, ...props }} />; return <Error {...{ kcContext, ...commonProps }} />;
case "login-reset-password.ftl": case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...props }} />; return <LoginResetPassword {...{ kcContext, ...commonProps }} />;
case "login-verify-email.ftl": case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...props }} />; return <LoginVerifyEmail {...{ kcContext, ...commonProps }} />;
case "terms.ftl": case "terms.ftl":
return <Terms {...{ kcContext, ...props }} />; return <Terms {...{ kcContext, ...commonProps }} />;
case "login-otp.ftl": case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...props }} />; return <LoginOtp {...{ kcContext, ...commonProps }} />;
case "login-username.ftl":
return <LoginUsername {...{ kcContext, ...commonProps }} />;
case "login-password.ftl":
return <LoginPassword {...{ kcContext, ...commonProps }} />;
case "webauthn-authenticate.ftl":
return <WebauthnAuthenticate {...{ kcContext, ...commonProps }} />;
case "login-update-password.ftl": case "login-update-password.ftl":
return <LoginUpdatePassword {...{ kcContext, ...props }} />; return <LoginUpdatePassword {...{ kcContext, ...commonProps }} />;
case "login-update-profile.ftl": case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...props }} />; return <LoginUpdateProfile {...{ kcContext, ...commonProps }} />;
case "login-idp-link-confirm.ftl": case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />; return <LoginIdpLinkConfirm {...{ kcContext, ...commonProps }} />;
case "login-idp-link-email.ftl": case "login-idp-link-email.ftl":
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />; return <LoginIdpLinkEmail {...{ kcContext, ...commonProps }} />;
case "login-page-expired.ftl": case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, ...props }} />; return <LoginPageExpired {...{ kcContext, ...commonProps }} />;
case "login-config-totp.ftl": case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, ...props }} />; return <LoginConfigTotp {...{ kcContext, ...commonProps }} />;
case "logout-confirm.ftl": case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, ...props }} />; return <LogoutConfirm {...{ kcContext, ...commonProps }} />;
case "update-user-profile.ftl":
return <UpdateUserProfile {...{ kcContext, ...commonProps }} />;
case "idp-review-user-profile.ftl":
return <IdpReviewUserProfile {...{ kcContext, ...commonProps }} />;
} }
})()} })()}
</Suspense> </Suspense>

View File

@ -84,6 +84,7 @@ export type KcProps = KcPropsGeneric<
| "kcFormSocialAccountDoubleListClass" | "kcFormSocialAccountDoubleListClass"
| "kcFormSocialAccountListLinkClass" | "kcFormSocialAccountListLinkClass"
| "kcWebAuthnKeyIcon" | "kcWebAuthnKeyIcon"
| "kcWebAuthnDefaultIcon"
| "kcFormClass" | "kcFormClass"
| "kcFormGroupErrorClass" | "kcFormGroupErrorClass"
| "kcLabelClass" | "kcLabelClass"
@ -105,12 +106,16 @@ export type KcProps = KcPropsGeneric<
| "kcSrOnlyClass" | "kcSrOnlyClass"
| "kcSelectAuthListClass" | "kcSelectAuthListClass"
| "kcSelectAuthListItemClass" | "kcSelectAuthListItemClass"
| "kcSelectAuthListItemFillClass"
| "kcSelectAuthListItemInfoClass" | "kcSelectAuthListItemInfoClass"
| "kcSelectAuthListItemLeftClass" | "kcSelectAuthListItemLeftClass"
| "kcSelectAuthListItemBodyClass" | "kcSelectAuthListItemBodyClass"
| "kcSelectAuthListItemDescriptionClass" | "kcSelectAuthListItemDescriptionClass"
| "kcSelectAuthListItemHeadingClass" | "kcSelectAuthListItemHeadingClass"
| "kcSelectAuthListItemHelpTextClass" | "kcSelectAuthListItemHelpTextClass"
| "kcSelectAuthListItemIconPropertyClass"
| "kcSelectAuthListItemIconClass"
| "kcSelectAuthListItemTitle"
| "kcAuthenticatorDefaultClass" | "kcAuthenticatorDefaultClass"
| "kcAuthenticatorPasswordClass" | "kcAuthenticatorPasswordClass"
| "kcAuthenticatorOTPClass" | "kcAuthenticatorOTPClass"
@ -138,6 +143,7 @@ export const defaultKcProps = {
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"], "kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"], "kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"], "kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
"kcWebAuthnDefaultIcon": ["pficon", "pficon-key"],
"kcFormClass": ["form-horizontal"], "kcFormClass": ["form-horizontal"],
"kcFormGroupErrorClass": ["has-error"], "kcFormGroupErrorClass": ["has-error"],
@ -173,6 +179,10 @@ export const defaultKcProps = {
// css classes for select-authenticator form // css classes for select-authenticator form
"kcSelectAuthListClass": ["list-group", "list-view-pf"], "kcSelectAuthListClass": ["list-group", "list-view-pf"],
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"], "kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
"kcSelectAuthListItemFillClass": ["pf-l-split__item", "pf-m-fill"],
"kcSelectAuthListItemIconPropertyClass": ["fa-2x", "select-auth-box-icon-properties"],
"kcSelectAuthListItemIconClass": ["pf-l-split__item", "select-auth-box-icon"],
"kcSelectAuthListItemTitle": ["select-auth-box-paragraph"],
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"], "kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"], "kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"], "kcSelectAuthListItemBodyClass": ["list-view-pf-body"],

View File

@ -1,19 +1,27 @@
import React, { useState, memo } from "react"; import React, { useState, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
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) => { export type LoginProps = KcProps & {
kcContext: KcContextBase.Login;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const Login = memo((props: LoginProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext; const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
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 => {
@ -32,20 +40,21 @@ const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Lo
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
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={clsx(realm.password && social.providers !== undefined && kcProps.kcContentWrapperClass)}>
<div <div
id="kc-form-wrapper" id="kc-form-wrapper"
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} className={clsx(
realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
)}
> >
{realm.password && ( {realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post"> <form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
{(() => { {(() => {
const label = !realm.loginWithEmailAllowed const label = !realm.loginWithEmailAllowed
? "username" ? "username"
@ -57,13 +66,13 @@ const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Lo
return ( return (
<> <>
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}> <label htmlFor={autoCompleteHelper} className={clsx(kcProps.kcLabelClass)}>
{msg(label)} {msg(label)}
</label> </label>
<input <input
tabIndex={1} tabIndex={1}
id={autoCompleteHelper} id={autoCompleteHelper}
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
//NOTE: This is used by Google Chrome auto fill so we use it to tell //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 //the browser how to pre fill the form but before submit we put it back
//to username because it is what keycloak expects. //to username because it is what keycloak expects.
@ -81,20 +90,20 @@ const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Lo
); );
})()} })()}
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}> <label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
{msg("password")} {msg("password")}
</label> </label>
<input <input
tabIndex={2} tabIndex={2}
id="password" id="password"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
name="password" name="password"
type="password" type="password"
autoComplete="off" autoComplete="off"
/> />
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}> <div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options"> <div id="kc-form-options">
{realm.rememberMe && !usernameEditDisabled && ( {realm.rememberMe && !usernameEditDisabled && (
<div className="checkbox"> <div className="checkbox">
@ -115,7 +124,7 @@ const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Lo
</div> </div>
)} )}
</div> </div>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
{realm.resetPasswordAllowed && ( {realm.resetPasswordAllowed && (
<span> <span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}> <a tabIndex={5} href={url.loginResetCredentialsUrl}>
@ -125,7 +134,7 @@ const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Lo
)} )}
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<input <input
type="hidden" type="hidden"
id="id-hidden-input" id="id-hidden-input"
@ -138,11 +147,11 @@ const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Lo
/> />
<input <input
tabIndex={4} tabIndex={4}
className={cx( className={clsx(
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonPrimaryClass, kcProps.kcButtonPrimaryClass,
props.kcButtonBlockClass, kcProps.kcButtonBlockClass,
props.kcButtonLargeClass kcProps.kcButtonLargeClass
)} )}
name="login" name="login"
id="kc-login" id="kc-login"
@ -155,16 +164,16 @@ const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Lo
)} )}
</div> </div>
{realm.password && social.providers !== undefined && ( {realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}> <div id="kc-social-providers" className={clsx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
<ul <ul
className={cx( className={clsx(
props.kcFormSocialAccountListClass, kcProps.kcFormSocialAccountListClass,
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
)} )}
> >
{social.providers.map(p => ( {social.providers.map(p => (
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}> <li key={p.providerId} className={clsx(kcProps.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}> <a href={p.loginUrl} id={`zocial-${p.alias}`} className={clsx("zocial", p.providerId)}>
<span>{p.displayName}</span> <span>{p.displayName}</span>
</a> </a>
</li> </li>

View File

@ -1,27 +1,34 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n } & KcProps) => { export type LoginConfigTotpProps = KcProps & {
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext; kcContext: KcContextBase.LoginConfigTotp;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const { cx } = useCssAndCx(); const LoginConfigTotp = memo((props: LoginConfigTotpProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
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, ...kcProps }}
doFetchDefaultThemeResources={true}
headerNode={msg("loginTotpTitle")} headerNode={msg("loginTotpTitle")}
formNode={ formNode={
<> <>
@ -93,26 +100,26 @@ const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcCont
</li> </li>
</ol> </ol>
<form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post"> <form action={url.loginAction} className={clsx(kcProps.kcFormClass)} id="kc-totp-settings-form" method="post">
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<label htmlFor="totp" className={cx(props.kcLabelClass)}> <label htmlFor="totp" className={clsx(kcProps.kcLabelClass)}>
{msg("authenticatorCode")} {msg("authenticatorCode")}
</label>{" "} </label>{" "}
<span className="required">*</span> <span className="required">*</span>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="totp" id="totp"
name="totp" name="totp"
autoComplete="off" autoComplete="off"
className={cx(props.kcInputClass)} className={clsx(kcProps.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={clsx(kcProps.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("totp")} {messagesPerField.get("totp")}
</span> </span>
)} )}
@ -121,24 +128,24 @@ const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcCont
{mode && <input type="hidden" id="mode" value={mode} />} {mode && <input type="hidden" id="mode" value={mode} />}
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<label htmlFor="userLabel" className={cx(props.kcLabelClass)}> <label htmlFor="userLabel" className={clsx(kcProps.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>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="userLabel" id="userLabel"
name="userLabel" name="userLabel"
autoComplete="off" autoComplete="off"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
aria-invalid={messagesPerField.existsError("userLabel")} aria-invalid={messagesPerField.existsError("userLabel")}
/> />
{messagesPerField.existsError("userLabel") && ( {messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite"> <span id="input-error-otp-label" className={clsx(kcProps.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("userLabel")} {messagesPerField.get("userLabel")}
</span> </span>
)} )}
@ -149,17 +156,17 @@ const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcCont
<> <>
<input <input
type="submit" type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
id="saveTOTPBtn" id="saveTOTPBtn"
value={msgStr("doSubmit")} value={msgStr("doSubmit")}
/> />
<button <button
type="submit" type="submit"
className={cx( className={clsx(
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonDefaultClass, kcProps.kcButtonDefaultClass,
props.kcButtonLargeClass, kcProps.kcButtonLargeClass,
props.kcButtonLargeClass kcProps.kcButtonLargeClass
)} )}
id="cancelTOTPBtn" id="cancelTOTPBtn"
name="cancel-aia" name="cancel-aia"
@ -171,7 +178,7 @@ const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcCont
) : ( ) : (
<input <input
type="submit" type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
id="saveTOTPBtn" id="saveTOTPBtn"
value={msgStr("doSubmit")} value={msgStr("doSubmit")}
/> />

View File

@ -1,28 +1,40 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginIdpLinkConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n } & KcProps) => { export type LoginIdpLinkConfirmProps = KcProps & {
kcContext: KcContextBase.LoginIdpLinkConfirm;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginIdpLinkConfirm = memo((props: LoginIdpLinkConfirmProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, idpAlias } = kcContext; const { url, idpAlias } = kcContext;
const { msg } = i18n; const { msg } = i18n;
const { cx } = useCssAndCx();
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
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={clsx(kcProps.kcFormGroupClass)}>
<button <button
type="submit" type="submit"
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="submitAction" name="submitAction"
id="updateProfile" id="updateProfile"
value="updateProfile" value="updateProfile"
@ -31,7 +43,12 @@ const LoginIdpLinkConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: Kc
</button> </button>
<button <button
type="submit" type="submit"
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonDefaultClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="submitAction" name="submitAction"
id="linkAccount" id="linkAccount"
value="linkAccount" value="linkAccount"

View File

@ -1,18 +1,27 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 LoginIdpLinkEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail; i18n: I18n } & KcProps) => { export type LoginIdpLinkEmailProps = KcProps & {
kcContext: KcContextBase.LoginIdpLinkEmail;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginIdpLinkEmail = memo((props: LoginIdpLinkEmailProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, realm, brokerContext, idpAlias } = kcContext; const { url, realm, brokerContext, idpAlias } = kcContext;
const { msg } = i18n; const { msg } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
doFetchDefaultThemeResources={true}
headerNode={msg("emailLinkIdpTitle", idpAlias)} headerNode={msg("emailLinkIdpTitle", idpAlias)}
formNode={ formNode={
<> <>

View File

@ -1,16 +1,24 @@
import React, { useEffect, memo } from "react"; import React, { useEffect, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 { headInsert } from "../tools/headInsert"; import { headInsert } from "../tools/headInsert";
import { pathJoin } from "../../bin/tools/pathJoin"; import { pathJoin } from "../../bin/tools/pathJoin";
import { useCssAndCx } from "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginOtp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginOtp; i18n: I18n } & KcProps) => { export type LoginOtpProps = KcProps & {
const { otpLogin, url } = kcContext; kcContext: KcContextBase.LoginOtp;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const { cx } = useCssAndCx(); const LoginOtp = memo((props: LoginOtpProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { otpLogin, url } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
@ -33,46 +41,50 @@ const LoginOtp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
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={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
{otpLogin.userOtpCredentials.length > 1 && ( {otpLogin.userOtpCredentials.length > 1 && (
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
{otpLogin.userOtpCredentials.map(otpCredential => ( {otpLogin.userOtpCredentials.map(otpCredential => (
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}> <div key={otpCredential.id} className={clsx(kcProps.kcSelectOTPListClass)}>
<input type="hidden" value="${otpCredential.id}" /> <input type="hidden" value="${otpCredential.id}" />
<div className={cx(props.kcSelectOTPListItemClass)}> <div className={clsx(kcProps.kcSelectOTPListItemClass)}>
<span className={cx(props.kcAuthenticatorOtpCircleClass)} /> <span className={clsx(kcProps.kcAuthenticatorOtpCircleClass)} />
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2> <h2 className={clsx(kcProps.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
)} )}
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="otp" className={cx(props.kcLabelClass)}> <label htmlFor="otp" className={clsx(kcProps.kcLabelClass)}>
{msg("loginOtpOneTime")} {msg("loginOtpOneTime")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus /> <input id="otp" name="otp" autoComplete="off" type="text" className={clsx(kcProps.kcInputClass)} autoFocus />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} /> <div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="login" name="login"
id="kc-login" id="kc-login"
type="submit" type="submit"

View File

@ -1,18 +1,27 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 LoginPageExpired = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginPageExpired; i18n: I18n } & KcProps) => { export type LoginPageExpired = KcProps & {
kcContext: KcContextBase.LoginPageExpired;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginPageExpired = memo((props: LoginPageExpired) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url } = kcContext; const { url } = kcContext;
const { msg } = i18n; const { msg } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
doFetchDefaultThemeResources={true}
displayMessage={false} displayMessage={false}
headerNode={msg("pageExpiredTitle")} headerNode={msg("pageExpiredTitle")}
formNode={ formNode={

View File

@ -0,0 +1,97 @@
import React, { useState, memo } from "react";
import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { clsx } from "../tools/clsx";
import { useConstCallback } from "powerhooks/useConstCallback";
import type { FormEventHandler } from "react";
import type { I18n } from "../i18n";
export type LoginPasswordProps = KcProps & {
kcContext: KcContextBase.LoginPassword;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginPassword = memo((props: LoginPasswordProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { realm, url, login } = kcContext;
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
setIsLoginButtonDisabled(true);
const formElement = e.target as HTMLFormElement;
formElement.submit();
});
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form">
<div id="kc-form-wrapper">
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
<hr />
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={clsx(kcProps.kcInputClass)}
name="password"
type="password"
autoFocus={true}
autoComplete="on"
defaultValue={login.password ?? ""}
/>
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options" />
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
{realm.resetPasswordAllowed && (
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
{msg("doForgotPassword")}
</a>
</span>
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
</form>
</div>
</div>
}
/>
);
});
export default LoginPassword;

View File

@ -1,28 +1,35 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginResetPassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n } & KcProps) => { export type LoginResetPasswordProps = KcProps & {
kcContext: KcContextBase.LoginResetPassword;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginResetPassword = memo((props: LoginResetPasswordProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, realm, auth } = kcContext; const { url, realm, auth } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { cx } = useCssAndCx();
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
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={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
{!realm.loginWithEmailAllowed {!realm.loginWithEmailAllowed
? msg("username") ? msg("username")
: !realm.registrationEmailAsUsername : !realm.registrationEmailAsUsername
@ -30,29 +37,34 @@ const LoginResetPassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcC
: msg("email")} : msg("email")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="username" id="username"
name="username" name="username"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
autoFocus autoFocus
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined} defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
/> />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}> <div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<span> <span>
<a href={url.loginUrl}>{msg("backToLogin")}</a> <a href={url.loginUrl}>{msg("backToLogin")}</a>
</span> </span>
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit" type="submit"
value={msgStr("doSubmit")} value={msgStr("doSubmit")}
/> />

View File

@ -1,12 +1,20 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n } & KcProps) => { export type LoginUpdatePasswordProps = KcProps & {
const { cx } = useCssAndCx(); kcContext: KcContextBase.LoginUpdatePassword;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginUpdatePassword = memo((props: LoginUpdatePasswordProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
@ -14,11 +22,10 @@ const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: Kc
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
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={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<input <input
type="text" type="text"
id="username" id="username"
@ -30,44 +37,46 @@ const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: Kc
/> />
<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={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-new" className={cx(props.kcLabelClass)}> <label htmlFor="password-new" className={clsx(kcProps.kcLabelClass)}>
{msg("passwordNew")} {msg("passwordNew")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.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={clsx(kcProps.kcInputClass)}
/> />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}> <div
<div className={cx(props.kcLabelWrapperClass)}> className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass))}
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}> >
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}>
{msg("passwordConfirm")} {msg("passwordConfirm")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="password" type="password"
id="password-confirm" id="password-confirm"
name="password-confirm" name="password-confirm"
autoComplete="new-password" autoComplete="new-password"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
/> />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
{isAppInitiatedAction && ( {isAppInitiatedAction && (
<div className="checkbox"> <div className="checkbox">
<label> <label>
@ -79,16 +88,16 @@ const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: Kc
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
{isAppInitiatedAction ? ( {isAppInitiatedAction ? (
<> <>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
<button <button
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit" type="submit"
name="cancel-aia" name="cancel-aia"
value="true" value="true"
@ -98,11 +107,11 @@ const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: Kc
</> </>
) : ( ) : (
<input <input
className={cx( className={clsx(
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonPrimaryClass, kcProps.kcButtonPrimaryClass,
props.kcButtonBlockClass, kcProps.kcButtonBlockClass,
props.kcButtonLargeClass kcProps.kcButtonLargeClass
)} )}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}

View File

@ -1,12 +1,20 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n } & KcProps) => { export type LoginUpdateProfile = KcProps & {
const { cx } = useCssAndCx(); kcContext: KcContextBase.LoginUpdateProfile;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginUpdateProfile = memo((props: LoginUpdateProfile) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
@ -14,84 +22,89 @@ const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcC
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
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={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
{user.editUsernameAllowed && ( {user.editUsernameAllowed && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
{msg("username")} {msg("username")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="username" id="username"
name="username" name="username"
defaultValue={user.username ?? ""} defaultValue={user.username ?? ""}
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
/> />
</div> </div>
</div> </div>
)} )}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}> <label htmlFor="email" className={clsx(kcProps.kcLabelClass)}>
{msg("email")} {msg("email")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(props.kcInputClass)} /> <input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={clsx(kcProps.kcInputClass)} />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}> <label htmlFor="firstName" className={clsx(kcProps.kcLabelClass)}>
{msg("firstName")} {msg("firstName")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="firstName" id="firstName"
name="firstName" name="firstName"
defaultValue={user.firstName ?? ""} defaultValue={user.firstName ?? ""}
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
/> />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}> <label htmlFor="lastName" className={clsx(kcProps.kcLabelClass)}>
{msg("lastName")} {msg("lastName")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input type="text" id="lastName" name="lastName" defaultValue={user.lastName ?? ""} className={cx(props.kcInputClass)} /> <input
type="text"
id="lastName"
name="lastName"
defaultValue={user.lastName ?? ""}
className={clsx(kcProps.kcInputClass)}
/>
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} /> <div className={clsx(kcProps.kcFormOptionsWrapperClass)} />
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
{isAppInitiatedAction ? ( {isAppInitiatedAction ? (
<> <>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
<button <button
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit" type="submit"
name="cancel-aia" name="cancel-aia"
value="true" value="true"
@ -101,11 +114,11 @@ const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcC
</> </>
) : ( ) : (
<input <input
className={cx( className={clsx(
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonPrimaryClass, kcProps.kcButtonPrimaryClass,
props.kcButtonBlockClass, kcProps.kcButtonBlockClass,
props.kcButtonLargeClass kcProps.kcButtonLargeClass
)} )}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}

View File

@ -0,0 +1,169 @@
import React, { useState, memo } from "react";
import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { clsx } from "../tools/clsx";
import { useConstCallback } from "powerhooks/useConstCallback";
import type { FormEventHandler } from "react";
import type { I18n } from "../i18n";
export type LoginUsernameProps = KcProps & {
kcContext: KcContextBase.LoginUsername;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginUsername = memo((props: LoginUsernameProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { social, realm, url, usernameHidden, login, registrationDisabled } = kcContext;
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
setIsLoginButtonDisabled(true);
const formElement = e.target as HTMLFormElement;
//NOTE: Even if we login with email Keycloak expect username and password in
//the POST request.
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
formElement.submit();
});
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && kcProps.kcContentWrapperClass)}>
<div
id="kc-form-wrapper"
className={clsx(
realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
)}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={clsx(kcProps.kcFormGroupClass)}>
{!usernameHidden &&
(() => {
const label = !realm.loginWithEmailAllowed
? "username"
: realm.registrationEmailAsUsername
? "email"
: "usernameOrEmail";
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
return (
<>
<label htmlFor={autoCompleteHelper} className={clsx(kcProps.kcLabelClass)}>
{msg(label)}
</label>
<input
tabIndex={1}
id={autoCompleteHelper}
className={clsx(kcProps.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"
autoFocus={true}
autoComplete="off"
/>
</>
);
})()}
</div>
<div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options">
{realm.rememberMe && !usernameHidden && (
<div className="checkbox">
<label>
<input
tabIndex={3}
id="rememberMe"
name="rememberMe"
type="checkbox"
{...(login.rememberMe
? {
"checked": true
}
: {})}
/>
{msg("rememberMe")}
</label>
</div>
)}
</div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<input
tabIndex={4}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.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={clsx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
<ul
className={clsx(
kcProps.kcFormSocialAccountListClass,
social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={clsx(kcProps.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={clsx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</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 LoginUsername;

View File

@ -1,18 +1,27 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 LoginVerifyEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginVerifyEmail; i18n: I18n } & KcProps) => { export type LoginVerifyEmailProps = KcProps & {
kcContext: KcContextBase.LoginVerifyEmail;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const LoginVerifyEmail = memo((props: LoginVerifyEmailProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg } = i18n; const { msg } = i18n;
const { url, user } = kcContext; const { url, user } = kcContext;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
doFetchDefaultThemeResources={true}
displayMessage={false} displayMessage={false}
headerNode={msg("emailVerifyTitle")} headerNode={msg("emailVerifyTitle")}
formNode={ formNode={

View File

@ -1,21 +1,28 @@
import React, { memo } from "react"; import React, { memo } from "react";
import { useCssAndCx } from "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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) => { export type LogoutConfirmProps = KcProps & {
const { url, client, logoutConfirm } = kcContext; kcContext: KcContextBase.LogoutConfirm;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const { cx } = useCssAndCx(); const LogoutConfirm = memo((props: LogoutConfirmProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, client, logoutConfirm } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
doFetchDefaultThemeResources={true}
displayMessage={false} displayMessage={false}
headerNode={msg("logoutConfirmTitle")} headerNode={msg("logoutConfirmTitle")}
formNode={ formNode={
@ -24,18 +31,18 @@ const LogoutConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContex
<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={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options"> <div id="kc-form-options">
<div className={cx(props.kcFormOptionsWrapperClass)}></div> <div className={clsx(kcProps.kcFormOptionsWrapperClass)}></div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
<input <input
tabIndex={4} tabIndex={4}
className={cx( className={clsx(
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonPrimaryClass, kcProps.kcButtonPrimaryClass,
props.kcButtonBlockClass, kcProps.kcButtonBlockClass,
props.kcButtonLargeClass kcProps.kcButtonLargeClass
)} )}
name="confirmLogout" name="confirmLogout"
id="kc-logout" id="kc-logout"

View File

@ -1,69 +1,76 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Register; i18n: I18n } & KcProps) => { export type RegisterProps = KcProps & {
kcContext: KcContextBase.Register;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const Register = memo((props: RegisterProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext; const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { cx } = useCssAndCx();
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
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={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}> <label htmlFor="firstName" className={clsx(kcProps.kcLabelClass)}>
{msg("firstName")} {msg("firstName")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="firstName" id="firstName"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
name="firstName" name="firstName"
defaultValue={register.formData.firstName ?? ""} defaultValue={register.formData.firstName ?? ""}
/> />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}> <label htmlFor="lastName" className={clsx(kcProps.kcLabelClass)}>
{msg("lastName")} {msg("lastName")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="lastName" id="lastName"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
name="lastName" name="lastName"
defaultValue={register.formData.lastName ?? ""} defaultValue={register.formData.lastName ?? ""}
/> />
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}> <label htmlFor="email" className={clsx(kcProps.kcLabelClass)}>
{msg("email")} {msg("email")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="email" id="email"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
name="email" name="email"
defaultValue={register.formData.email ?? ""} defaultValue={register.formData.email ?? ""}
autoComplete="email" autoComplete="email"
@ -71,17 +78,17 @@ const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase
</div> </div>
</div> </div>
{!realm.registrationEmailAsUsername && ( {!realm.registrationEmailAsUsername && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}> <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
{msg("username")} {msg("username")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="username" id="username"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
name="username" name="username"
defaultValue={register.formData.username ?? ""} defaultValue={register.formData.username ?? ""}
autoComplete="username" autoComplete="username"
@ -91,17 +98,19 @@ const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase
)} )}
{passwordRequired && ( {passwordRequired && (
<> <>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}> <div
<div className={cx(props.kcLabelWrapperClass)}> className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}
<label htmlFor="password" className={cx(props.kcLabelClass)}> >
<div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
{msg("password")} {msg("password")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input <input
type="password" type="password"
id="password" id="password"
className={cx(props.kcInputClass)} className={clsx(kcProps.kcInputClass)}
name="password" name="password"
autoComplete="new-password" autoComplete="new-password"
/> />
@ -109,41 +118,46 @@ const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase
</div> </div>
<div <div
className={cx( className={clsx(
props.kcFormGroupClass, kcProps.kcFormGroupClass,
messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass) messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass)
)} )}
> >
<div className={cx(props.kcLabelWrapperClass)}> <div className={clsx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}> <label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}>
{msg("passwordConfirm")} {msg("passwordConfirm")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" /> <input type="password" id="password-confirm" className={clsx(kcProps.kcInputClass)} name="password-confirm" />
</div> </div>
</div> </div>
</> </>
)} )}
{recaptchaRequired && ( {recaptchaRequired && (
<div className="form-group"> <div className="form-group">
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div> <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
</div> </div>
</div> </div>
)} )}
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<span> <span>
<a href={url.loginUrl}>{msg("backToLogin")}</a> <a href={url.loginUrl}>{msg("backToLogin")}</a>
</span> </span>
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit" type="submit"
value={msgStr("doRegister")} value={msgStr("doRegister")}
/> />

View File

@ -1,59 +1,61 @@
import React, { useMemo, memo, useEffect, useState, Fragment } from "react"; import React, { memo, useState } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
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";
export type RegisterUserProfileProps = KcProps & {
kcContext: KcContextBase.RegisterUserProfile;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const RegisterUserProfile = memo((props: RegisterUserProfileProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps) => {
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext; const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n; 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); const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayMessage={messagesPerField.exists("global")} displayMessage={messagesPerField.exists("global")}
displayRequiredFields={true} displayRequiredFields={true}
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={clsx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} /> <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
{recaptchaRequired && ( {recaptchaRequired && (
<div className="form-group"> <div className="form-group">
<div className={cx(props.kcInputWrapperClass)}> <div className={clsx(kcProps.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} /> <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
</div> </div>
</div> </div>
)} )}
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(kcProps.kcFormGroupClass)} style={{ "marginBottom": 30 }}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
<span> <span>
<a href={url.loginUrl}>{msg("backToLogin")}</a> <a href={url.loginUrl}>{msg("backToLogin")}</a>
</span> </span>
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input <input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit" type="submit"
value={msgStr("doRegister")} value={msgStr("doRegister")}
disabled={!isFomSubmittable} disabled={!isFomSubmittable}
@ -66,155 +68,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; export default RegisterUserProfile;

View File

@ -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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
export type TemplateProps = { export type TemplateProps = {
@ -42,8 +42,6 @@ const Template = memo((props: TemplateProps) => {
doFetchDefaultThemeResources doFetchDefaultThemeResources
} = props; } = props;
const { cx } = useCssAndCx();
const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n; const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [string]) => changeLocale(kcLanguageTag)); const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [string]) => changeLocale(kcLanguageTag));
@ -96,7 +94,7 @@ const Template = memo((props: TemplateProps) => {
if (props.kcHtmlClass !== undefined) { if (props.kcHtmlClass !== undefined) {
const htmlClassList = document.getElementsByTagName("html")[0].classList; const htmlClassList = document.getElementsByTagName("html")[0].classList;
const tokens = cx(props.kcHtmlClass).split(" "); const tokens = clsx(props.kcHtmlClass).split(" ");
htmlClassList.add(...tokens); htmlClassList.add(...tokens);
@ -115,18 +113,18 @@ const Template = memo((props: TemplateProps) => {
} }
return ( return (
<div className={cx(props.kcLoginClass)}> <div className={clsx(props.kcLoginClass)}>
<div id="kc-header" className={cx(props.kcHeaderClass)}> <div id="kc-header" className={clsx(props.kcHeaderClass)}>
<div id="kc-header-wrapper" className={cx(props.kcHeaderWrapperClass)}> <div id="kc-header-wrapper" className={clsx(props.kcHeaderWrapperClass)}>
{msg("loginTitleHtml", realm.displayNameHtml)} {msg("loginTitleHtml", realm.displayNameHtml)}
</div> </div>
</div> </div>
<div className={cx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}> <div className={clsx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
<header className={cx(props.kcFormHeaderClass)}> <header className={clsx(props.kcFormHeaderClass)}>
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && ( {realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
<div id="kc-locale"> <div id="kc-locale">
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}> <div id="kc-locale-wrapper" className={clsx(props.kcLocaleWrapperClass)}>
<div className="kc-dropdown" id="kc-locale-dropdown"> <div className="kc-dropdown" id="kc-locale-dropdown">
<a href="#" id="kc-current-locale-link"> <a href="#" id="kc-current-locale-link">
{labelBySupportedLanguageTag[currentLanguageTag]} {labelBySupportedLanguageTag[currentLanguageTag]}
@ -146,8 +144,8 @@ const Template = memo((props: TemplateProps) => {
)} )}
{!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? ( {!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
displayRequiredFields ? ( displayRequiredFields ? (
<div className={cx(props.kcContentWrapperClass)}> <div className={clsx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}> <div className={clsx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle"> <span className="subtitle">
<span className="required">*</span> <span className="required">*</span>
{msg("requiredFields")} {msg("requiredFields")}
@ -161,20 +159,20 @@ const Template = memo((props: TemplateProps) => {
<h1 id="kc-page-title">{headerNode}</h1> <h1 id="kc-page-title">{headerNode}</h1>
) )
) : displayRequiredFields ? ( ) : displayRequiredFields ? (
<div className={cx(props.kcContentWrapperClass)}> <div className={clsx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}> <div className={clsx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle"> <span className="subtitle">
<span className="required">*</span> {msg("requiredFields")} <span className="required">*</span> {msg("requiredFields")}
</span> </span>
</div> </div>
<div className="col-md-10"> <div className="col-md-10">
{showUsernameNode} {showUsernameNode}
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(props.kcFormGroupClass)}>
<div id="kc-username"> <div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label> <label id="kc-attempted-username">{auth?.attemptedUsername}</label>
<a id="reset-login" href={url.loginRestartFlowUrl}> <a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip"> <div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i> <i className={clsx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span> <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div> </div>
</a> </a>
@ -185,12 +183,12 @@ const Template = memo((props: TemplateProps) => {
) : ( ) : (
<> <>
{showUsernameNode} {showUsernameNode}
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(props.kcFormGroupClass)}>
<div id="kc-username"> <div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label> <label id="kc-attempted-username">{auth?.attemptedUsername}</label>
<a id="reset-login" href={url.loginRestartFlowUrl}> <a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip"> <div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i> <i className={clsx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span> <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div> </div>
</a> </a>
@ -203,11 +201,11 @@ const Template = memo((props: TemplateProps) => {
<div id="kc-content-wrapper"> <div id="kc-content-wrapper">
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */} {/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
{displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && ( {displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
<div className={cx("alert", `alert-${message.type}`)}> <div className={clsx("alert", `alert-${message.type}`)}>
{message.type === "success" && <span className={cx(props.kcFeedbackSuccessIcon)}></span>} {message.type === "success" && <span className={clsx(props.kcFeedbackSuccessIcon)}></span>}
{message.type === "warning" && <span className={cx(props.kcFeedbackWarningIcon)}></span>} {message.type === "warning" && <span className={clsx(props.kcFeedbackWarningIcon)}></span>}
{message.type === "error" && <span className={cx(props.kcFeedbackErrorIcon)}></span>} {message.type === "error" && <span className={clsx(props.kcFeedbackErrorIcon)}></span>}
{message.type === "info" && <span className={cx(props.kcFeedbackInfoIcon)}></span>} {message.type === "info" && <span className={clsx(props.kcFeedbackInfoIcon)}></span>}
<span <span
className="kc-feedback-text" className="kc-feedback-text"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@ -222,10 +220,10 @@ const Template = memo((props: TemplateProps) => {
id="kc-select-try-another-way-form" id="kc-select-try-another-way-form"
action={url.loginAction} action={url.loginAction}
method="post" method="post"
className={cx(displayWide && props.kcContentWrapperClass)} className={clsx(displayWide && props.kcContentWrapperClass)}
> >
<div className={cx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}> <div className={clsx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}>
<div className={cx(props.kcFormGroupClass)}> <div className={clsx(props.kcFormGroupClass)}>
<input type="hidden" name="tryAnotherWay" value="on" /> <input type="hidden" name="tryAnotherWay" value="on" />
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}> <a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>
{msg("doTryAnotherWay")} {msg("doTryAnotherWay")}
@ -235,8 +233,8 @@ const Template = memo((props: TemplateProps) => {
</form> </form>
)} )}
{displayInfo && ( {displayInfo && (
<div id="kc-info" className={cx(props.kcSignUpClass)}> <div id="kc-info" className={clsx(props.kcSignUpClass)}>
<div id="kc-info-wrapper" className={cx(props.kcInfoAreaWrapperClass)}> <div id="kc-info-wrapper" className={clsx(props.kcInfoAreaWrapperClass)}>
{infoNode} {infoNode}
</div> </div>
</div> </div>

View File

@ -1,8 +1,9 @@
import React, { useEffect, memo } from "react"; import React, { useEffect, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } 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 "../tools/useCssAndCx"; import { clsx } from "../tools/clsx";
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 +12,8 @@ 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";
import type { Extends } from "tsafe";
export const evtTermMarkdown = Evt.create<string | undefined>(undefined); export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
@ -21,7 +24,7 @@ export type KcContextLike = {
}; };
}; };
assert<KcContextBase extends KcContextLike ? true : false>(); assert<Extends<KcContextBase, KcContextLike>>();
/** Allow to avoid bundling the terms and download it on demand*/ /** Allow to avoid bundling the terms and download it on demand*/
export function useDownloadTerms(params: { export function useDownloadTerms(params: {
@ -53,13 +56,20 @@ export function useDownloadTerms(params: {
}, []); }, []);
} }
const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Terms; i18n: I18n } & KcProps) => { export type TermsProps = KcProps & {
kcContext: KcContextBase.Terms;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const Terms = memo((props: TermsProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
useRerenderOnStateChange(evtTermMarkdown); useRerenderOnStateChange(evtTermMarkdown);
const { cx } = useCssAndCx();
const { url } = kcContext; const { url } = kcContext;
if (evtTermMarkdown.state === undefined) { if (evtTermMarkdown.state === undefined) {
@ -68,21 +78,20 @@ const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Te
return ( return (
<Template <Template
{...{ kcContext, i18n, ...props }} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
doFetchDefaultThemeResources={true}
displayMessage={false} displayMessage={false}
headerNode={msg("termsTitle")} headerNode={msg("termsTitle")}
formNode={ 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"> <form className="form-actions" action={url.loginAction} method="POST">
<input <input
className={cx( className={clsx(
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonPrimaryClass, kcProps.kcButtonPrimaryClass,
props.kcButtonLargeClass kcProps.kcButtonLargeClass
)} )}
name="accept" name="accept"
id="kc-accept" id="kc-accept"
@ -90,7 +99,7 @@ const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Te
value={msgStr("doAccept")} value={msgStr("doAccept")}
/> />
<input <input
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
name="cancel" name="cancel"
id="kc-decline" id="kc-decline"
type="submit" type="submit"

View File

@ -0,0 +1,78 @@
import React, { useState, memo } from "react";
import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { clsx } from "../tools/clsx";
import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons";
export type UpdateUserProfileProps = KcProps & {
kcContext: KcContextBase.UpdateUserProfile;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const UpdateUserProfile = memo((props: UpdateUserProfileProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n;
const { url, isAppInitiatedAction } = kcContext;
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
<div className={clsx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
<div className={clsx(kcProps.kcFormOptionsWrapperClass)}></div>
</div>
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
{isAppInitiatedAction ? (
<>
<input
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
type="submit"
value={msgStr("doSubmit")}
/>
<button
className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
formNoValidate
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
)}
</div>
</div>
</form>
}
/>
);
});
export default UpdateUserProfile;

View File

@ -0,0 +1,203 @@
import React, { useRef, useState, memo } from "react";
import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { clsx } from "../tools/clsx";
import type { I18n, MessageKeyBase } from "../i18n";
import { base64url } from "rfc4648";
import { useConstCallback } from "powerhooks/useConstCallback";
export type WebauthnAuthenticateProps = KcProps & {
kcContext: KcContextBase.WebauthnAuthenticate;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template?: (props: TemplateProps) => JSX.Element | null;
};
const WebauthnAuthenticate = memo((props: WebauthnAuthenticateProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url } = kcContext;
const { msg, msgStr } = i18n;
const { authenticators, challenge, shouldDisplayAuthenticators, userVerification, rpId } = kcContext;
const createTimeout = Number(kcContext.createTimeout);
const isUserIdentified = kcContext.isUserIdentified == "true";
const webAuthnAuthenticate = useConstCallback(async () => {
if (!isUserIdentified) {
return;
}
const allowCredentials = authenticators.authenticators.map(
authenticator =>
({
id: base64url.parse(authenticator.credentialId, { loose: true }),
type: "public-key"
} as PublicKeyCredentialDescriptor)
);
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
setError(msgStr("webauthn-unsupported-browser-text"));
submitForm();
return;
}
const publicKey: PublicKeyCredentialRequestOptions = {
rpId,
challenge: base64url.parse(challenge, { loose: true })
};
if (createTimeout !== 0) {
publicKey.timeout = createTimeout * 1000;
}
if (allowCredentials.length) {
publicKey.allowCredentials = allowCredentials;
}
if (userVerification !== "not specified") {
publicKey.userVerification = userVerification;
}
try {
const resultRaw = await navigator.credentials.get({ publicKey });
if (!resultRaw || resultRaw.type != "public-key") return;
const result = resultRaw as PublicKeyCredential;
if (!("authenticatorData" in result.response)) return;
const response = result.response as AuthenticatorAssertionResponse;
const clientDataJSON = response.clientDataJSON;
const authenticatorData = response.authenticatorData;
const signature = response.signature;
setClientDataJSON(base64url.stringify(new Uint8Array(clientDataJSON), { pad: false }));
setAuthenticatorData(base64url.stringify(new Uint8Array(authenticatorData), { pad: false }));
setSignature(base64url.stringify(new Uint8Array(signature), { pad: false }));
setCredentialId(result.id);
setUserHandle(base64url.stringify(new Uint8Array(response.userHandle!), { pad: false }));
submitForm();
} catch (err) {
setError(String(err));
submitForm();
}
});
const webAuthForm = useRef<HTMLFormElement>(null);
const submitForm = useConstCallback(() => {
webAuthForm.current!.submit();
});
const [clientDataJSON, setClientDataJSON] = useState("");
const [authenticatorData, setAuthenticatorData] = useState("");
const [signature, setSignature] = useState("");
const [credentialId, setCredentialId] = useState("");
const [userHandle, setUserHandle] = useState("");
const [error, setError] = useState("");
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("webauthn-login-title")}
formNode={
<div id="kc-form-webauthn" className={clsx(kcProps.kcFormClass)}>
<form id="webauth" action={url.loginAction} ref={webAuthForm} method="post">
<input type="hidden" id="clientDataJSON" name="clientDataJSON" value={clientDataJSON} />
<input type="hidden" id="authenticatorData" name="authenticatorData" value={authenticatorData} />
<input type="hidden" id="signature" name="signature" value={signature} />
<input type="hidden" id="credentialId" name="credentialId" value={credentialId} />
<input type="hidden" id="userHandle" name="userHandle" value={userHandle} />
<input type="hidden" id="error" name="error" value={error} />
</form>
<div className={clsx(kcProps.kcFormGroupClass)}>
{authenticators &&
(() => (
<form id="authn_select" className={clsx(kcProps.kcFormClass)}>
{authenticators.authenticators.map(authenticator => (
<input
type="hidden"
name="authn_use_chk"
value={authenticator.credentialId}
key={authenticator.credentialId}
/>
))}
</form>
))()}
{authenticators &&
shouldDisplayAuthenticators &&
(() => (
<>
{authenticators.authenticators.length > 1 && (
<p className={clsx(kcProps.kcSelectAuthListItemTitle)}>{msg("webauthn-available-authenticators")}</p>
)}
<div className={clsx(kcProps.kcFormClass)}>
{authenticators.authenticators.map(authenticator => (
<div id="kc-webauthn-authenticator" className={clsx(kcProps.kcSelectAuthListItemClass)}>
<div className={clsx(kcProps.kcSelectAuthListItemIconClass)}>
<i
className={clsx(
kcProps[authenticator.transports.iconClass] ?? kcProps.kcWebAuthnDefaultIcon,
kcProps.kcSelectAuthListItemIconPropertyClass
)}
/>
</div>
<div className={clsx(kcProps.kcSelectAuthListItemBodyClass)}>
<div
id="kc-webauthn-authenticator-label"
className={clsx(kcProps.kcSelectAuthListItemHeadingClass)}
>
{authenticator.label}
</div>
{authenticator.transports && authenticator.transports.displayNameProperties.length && (
<div
id="kc-webauthn-authenticator-transport"
className={clsx(kcProps.kcSelectAuthListItemDescriptionClass)}
>
{authenticator.transports.displayNameProperties.map(
(transport: MessageKeyBase, index: number) => (
<>
<span>{msg(transport)}</span>
{index < authenticator.transports.displayNameProperties.length - 1 && (
<span>{", "}</span>
)}
</>
)
)}
</div>
)}
<div className={clsx(kcProps.kcSelectAuthListItemDescriptionClass)}>
<span id="kc-webauthn-authenticator-created-label">{msg("webauthn-createdAt-label")}</span>
<span id="kc-webauthn-authenticator-created">{authenticator.createdAt}</span>
</div>
</div>
<div className={clsx(kcProps.kcSelectAuthListItemFillClass)} />
</div>
))}
</div>
</>
))()}
<div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
<input
id="authenticateWebAuthnButton"
type="button"
onClick={webAuthnAuthenticate}
autoFocus={true}
value={msgStr("webauthn-doAuthenticate")}
className={clsx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
/>
</div>
</div>
</div>
}
/>
);
});
export default WebauthnAuthenticate;

View File

@ -0,0 +1,173 @@
import React, { memo, useEffect, Fragment } from "react";
import type { KcProps } from "../KcProps";
import type { Attribute } from "../../getKcContext/KcContextBase";
import { clsx } from "../../tools/clsx";
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 { 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 = clsx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
return (
<Fragment key={i}>
{group !== currentGroup && (currentGroup = group) !== "" && (
<div className={formGroupClassName}>
<div className={clsx(props.kcContentWrapperClass)}>
<label id={`header-${group}`} className={clsx(props.kcFormGroupHeader)}>
{advancedMsg(groupDisplayHeader) || currentGroup}
</label>
</div>
{groupDisplayDescription !== "" && (
<div className={clsx(props.kcLabelWrapperClass)}>
<label id={`description-${group}`} className={`${clsx(props.kcLabelClass)}`}>
{advancedMsg(groupDisplayDescription)}
</label>
</div>
)}
</div>
)}
{BeforeField && <BeforeField attribute={attribute} />}
<div className={formGroupClassName}>
<div className={clsx(props.kcLabelWrapperClass)}>
<label htmlFor={attribute.name} className={clsx(props.kcLabelClass)}>
{advancedMsg(attribute.displayName ?? "")}
</label>
{attribute.required && <>*</>}
</div>
<div className={clsx(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={clsx(props.kcInputClass)}
aria-invalid={displayableErrors.length !== 0}
disabled={attribute.readOnly}
autoComplete={attribute.autocomplete}
onBlur={onBlurFactory(attribute.name)}
/>
);
})()}
{displayableErrors.length !== 0 &&
(() => {
const divId = `input-error-${attribute.name}`;
return (
<>
<style>{`#${divId} > span: { display: block; }`}</style>
<span
id={divId}
className={clsx(props.kcInputErrorMessageClass)}
style={{
"position": displayableErrors.length === 1 ? "absolute" : undefined
}}
aria-live="polite"
>
{displayableErrors.map(({ errorMessage }) => errorMessage)}
</span>
</>
);
})()}
</div>
</div>
{AfterField && <AfterField attribute={attribute} />}
</Fragment>
);
})}
</>
);
}
);

View File

@ -2,6 +2,7 @@ import type { PageId } from "../../bin/keycloakify/generateFtl";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { Equals } from "tsafe"; import type { Equals } from "tsafe";
import type { MessageKeyBase } from "../i18n"; import type { MessageKeyBase } from "../i18n";
import type { KcTemplateClassKey } from "../components/KcProps";
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never; type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
@ -19,13 +20,28 @@ export type KcContextBase =
| KcContextBase.LoginVerifyEmail | KcContextBase.LoginVerifyEmail
| KcContextBase.Terms | KcContextBase.Terms
| KcContextBase.LoginOtp | KcContextBase.LoginOtp
| KcContextBase.LoginUsername
| KcContextBase.WebauthnAuthenticate
| KcContextBase.LoginPassword
| KcContextBase.LoginUpdatePassword | KcContextBase.LoginUpdatePassword
| KcContextBase.LoginUpdateProfile | KcContextBase.LoginUpdateProfile
| KcContextBase.LoginIdpLinkConfirm | KcContextBase.LoginIdpLinkConfirm
| KcContextBase.LoginIdpLinkEmail | KcContextBase.LoginIdpLinkEmail
| KcContextBase.LoginPageExpired | KcContextBase.LoginPageExpired
| KcContextBase.LoginConfigTotp | KcContextBase.LoginConfigTotp
| KcContextBase.LogoutConfirm; | KcContextBase.LogoutConfirm
| KcContextBase.UpdateUserProfile
| KcContextBase.IdpReviewUserProfile;
export type WebauthnAuthenticator = {
credentialId: string;
transports: {
iconClass: KcTemplateClassKey;
displayNameProperties: MessageKeyBase[];
};
label: string;
createdAt: string;
};
export declare namespace KcContextBase { export declare namespace KcContextBase {
export type Common = { export type Common = {
@ -196,6 +212,77 @@ export declare namespace KcContextBase {
}; };
}; };
export type LoginUsername = Common & {
pageId: "login-username.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
loginWithEmailAllowed: boolean;
rememberMe: boolean;
password: boolean;
resetPasswordAllowed: boolean;
registrationAllowed: boolean;
};
registrationDisabled: boolean;
login: {
username?: string;
rememberMe?: boolean;
};
usernameHidden?: boolean;
social: {
displayInfo: boolean;
providers?: {
loginUrl: string;
alias: string;
providerId: string;
displayName: string;
}[];
};
};
export type LoginPassword = Common & {
pageId: "login-password.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
resetPasswordAllowed: boolean;
};
auth?: {
showUsername?: boolean;
showResetCredentials?: boolean;
showTryAnotherWayLink?: boolean;
attemptedUsername?: string;
};
social: {
displayInfo: boolean;
};
login: {
password?: string;
};
};
export type WebauthnAuthenticate = Common & {
pageId: "webauthn-authenticate.ftl";
authenticators: {
authenticators: WebauthnAuthenticator[];
};
challenge: string;
// I hate this:
userVerification: UserVerificationRequirement | "not specified";
rpId: string;
createTimeout: string;
isUserIdentified: "true" | "false";
shouldDisplayAuthenticators: boolean;
social: {
displayInfo: boolean;
};
login: {};
};
export type LoginUpdatePassword = Common & { export type LoginUpdatePassword = Common & {
pageId: "login-update-password.ftl"; pageId: "login-update-password.ftl";
username: string; username: string;
@ -270,6 +357,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 = {

View File

@ -10,6 +10,7 @@ import { getKcContextFromWindow } from "./getKcContextFromWindow";
import { pathJoin } from "../../bin/tools/pathJoin"; import { pathJoin } from "../../bin/tools/pathJoin";
import { pathBasename } from "../tools/pathBasename"; import { pathBasename } from "../tools/pathBasename";
import { mockTestingResourcesCommonPath } from "../../bin/mockTestingResourcesPath"; import { mockTestingResourcesCommonPath } from "../../bin/mockTestingResourcesPath";
import { symToStr } from "tsafe/symToStr";
export function getKcContext<KcContextExtended extends { pageId: string } = never>(params?: { export function getKcContext<KcContextExtended extends { pageId: string } = never>(params?: {
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"]; mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
@ -17,9 +18,19 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
}): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined } { }): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined } {
const { mockPageId, mockData } = params ?? {}; const { mockPageId, mockData } = params ?? {};
if (mockPageId !== undefined) { const realKcContext = getKcContextFromWindow<KcContextExtended>();
if (mockPageId !== undefined && realKcContext === undefined) {
//TODO maybe trow if no mock fo custom page //TODO maybe trow if no mock fo custom page
console.log(
[
`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`,
`If assets are missing make sure you have built your Keycloak theme at least once.`
].join(" "),
"background: red; color: yellow; font-size: medium"
);
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId); const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId); const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId);
@ -47,8 +58,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;
@ -60,8 +79,6 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
].filter(exclude(undefined)); ].filter(exclude(undefined));
attributes.forEach(attribute => { attributes.forEach(attribute => {
console.log("====>", attribute);
const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name); const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name);
const augmentedAttribute: Attribute = {} as any; const augmentedAttribute: Attribute = {} as any;
@ -100,13 +117,15 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
return { kcContext }; return { kcContext };
} }
const kcContext = getKcContextFromWindow<KcContextExtended>(); if (realKcContext === undefined) {
return { "kcContext": undefined };
}
if (kcContext !== undefined) { {
const { url } = kcContext; const { url } = realKcContext;
url.resourcesCommonPath = pathJoin(url.resourcesPath, pathBasename(mockTestingResourcesCommonPath)); url.resourcesCommonPath = pathJoin(url.resourcesPath, pathBasename(mockTestingResourcesCommonPath));
} }
return { kcContext }; return { "kcContext": realKcContext };
} }

View File

@ -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;
})()
} }
}) })
]; ];
@ -361,6 +359,61 @@ export const kcContextMocks: KcContextBase[] = [
] ]
} }
}), }),
id<KcContextBase.LoginUsername>({
...kcContextCommonMock,
"pageId": "login-username.ftl",
"url": loginUrl,
"realm": {
...kcContextCommonMock.realm,
"loginWithEmailAllowed": true,
"rememberMe": true,
"password": true,
"resetPasswordAllowed": true,
"registrationAllowed": true
},
"social": {
"displayInfo": true
},
"usernameHidden": false,
"login": {
"rememberMe": false
},
"registrationDisabled": false
}),
id<KcContextBase.LoginPassword>({
...kcContextCommonMock,
"pageId": "login-password.ftl",
"url": loginUrl,
"realm": {
...kcContextCommonMock.realm,
"resetPasswordAllowed": true
},
"social": {
"displayInfo": false
},
"login": {}
}),
id<KcContextBase.WebauthnAuthenticate>({
...kcContextCommonMock,
"pageId": "webauthn-authenticate.ftl",
"url": loginUrl,
"authenticators": {
"authenticators": []
},
"realm": {
...kcContextCommonMock.realm
},
"challenge": "",
"userVerification": "not specified",
"rpId": "",
"createTimeout": "0",
"isUserIdentified": "false",
"shouldDisplayAuthenticators": false,
"social": {
"displayInfo": false
},
"login": {}
}),
id<KcContextBase.LoginUpdatePassword>({ id<KcContextBase.LoginUpdatePassword>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-update-password.ftl", "pageId": "login-update-password.ftl",
@ -423,5 +476,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
}
}) })
]; ];

View File

@ -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, useRef } 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";
@ -83,8 +83,6 @@ export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params:
return; return;
} }
let isMounted = true;
refHasStartedFetching.current = true; refHasStartedFetching.current = true;
(async () => { (async () => {
@ -144,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": {
@ -180,10 +174,6 @@ export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params:
) )
}); });
})(); })();
return () => {
isMounted = false;
};
}, []); }, []);
return i18n ?? null; return i18n ?? null;
@ -244,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
); );

View File

@ -0,0 +1,3 @@
import Markdown from "react-markdown";
export { Markdown };

7
src/lib/tools/clsx.ts Normal file
View File

@ -0,0 +1,7 @@
import { classnames } from "tss-react/tools/classnames";
import type { Cx } from "tss-react";
/** Drop in replacement for https://www.npmjs.com/package/clsx */
export const clsx: Cx = (...args) => {
return classnames(args);
};

View File

@ -1,11 +1,12 @@
import { createMakeStyles } from "tss-react"; import { clsx as cx } from "./clsx";
const { useStyles } = createMakeStyles({
"useTheme": () => ({})
});
/**
* @deprecated: Use clsx instead.
* import { clsx } from "keycloakify/lib/tools/clsx";
* You can use clsx as cx.
* If you where using the css() function you can import
* it from @emotion/css: https://emotion.sh/docs/@emotion/css
*/
export function useCssAndCx() { export function useCssAndCx() {
const { css, cx } = useStyles(); return { cx };
return { css, cx };
} }

View File

@ -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. */

View File

@ -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
} }
}); });

View File

@ -103,13 +103,13 @@ 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"
} }
@ -119,7 +119,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
if( pd === undefined || pd.configurable ){ if( pd === undefined || pd.configurable ){
var p= ""; var p= "";
Object.defineProperty(__webpack_require__, "p", { Object.defineProperty(__webpack_require__, "p", {
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev" : p; }, get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
set: function (value){ p = value; } set: function (value){ p = value; }
}); });
} }
@ -137,7 +137,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
if( pd === undefined || pd.configurable ){ if( pd === undefined || pd.configurable ){
var p= ""; var p= "";
Object.defineProperty(t, "p", { Object.defineProperty(t, "p", {
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev" : p; }, get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
set: function (value){ p = value; } set: function (value){ p = value; }
}); });
} }

View File

@ -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
}); });
} }

366
yarn.lock
View File

@ -2,24 +2,125 @@
# yarn lockfile v1 # yarn lockfile v1
"@babel/code-frame@^7.0.0": "@ampproject/remapping@^2.1.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
dependencies:
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==
dependencies: dependencies:
"@babel/highlight" "^7.18.6" "@babel/highlight" "^7.18.6"
"@babel/helper-module-imports@^7.16.7": "@babel/compat-data@^7.19.3":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151"
integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw==
"@babel/core@^7.0.0":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c"
integrity sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.19.3"
"@babel/helper-compilation-targets" "^7.19.3"
"@babel/helper-module-transforms" "^7.19.0"
"@babel/helpers" "^7.19.0"
"@babel/parser" "^7.19.3"
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.19.3"
"@babel/types" "^7.19.3"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.1"
semver "^6.3.0"
"@babel/generator@^7.19.3":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.3.tgz#d7f4d1300485b4547cb6f94b27d10d237b42bf59"
integrity sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==
dependencies:
"@babel/types" "^7.19.3"
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
"@babel/helper-compilation-targets@^7.19.3":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca"
integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==
dependencies:
"@babel/compat-data" "^7.19.3"
"@babel/helper-validator-option" "^7.18.6"
browserslist "^4.21.3"
semver "^6.3.0"
"@babel/helper-environment-visitor@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
"@babel/helper-function-name@^7.19.0":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==
dependencies:
"@babel/template" "^7.18.10"
"@babel/types" "^7.19.0"
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==
dependencies: dependencies:
"@babel/types" "^7.18.6" "@babel/types" "^7.18.6"
"@babel/helper-module-transforms@^7.19.0":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30"
integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==
dependencies:
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-module-imports" "^7.18.6"
"@babel/helper-simple-access" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/helper-validator-identifier" "^7.18.6"
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.19.0"
"@babel/types" "^7.19.0"
"@babel/helper-plugin-utils@^7.18.6": "@babel/helper-plugin-utils@^7.18.6":
version "7.18.9" version "7.19.0"
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.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf"
integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==
"@babel/helper-simple-access@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea"
integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-split-export-declaration@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075"
integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-string-parser@^7.18.10": "@babel/helper-string-parser@^7.18.10":
version "7.18.10" version "7.18.10"
@ -31,6 +132,25 @@
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"
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
"@babel/helper-validator-identifier@^7.19.1":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==
"@babel/helpers@^7.19.0":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18"
integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==
dependencies:
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.19.0"
"@babel/types" "^7.19.0"
"@babel/highlight@^7.18.6": "@babel/highlight@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
@ -40,6 +160,11 @@
chalk "^2.0.0" chalk "^2.0.0"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/parser@^7.18.10", "@babel/parser@^7.19.3":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a"
integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==
"@babel/plugin-syntax-jsx@^7.17.12": "@babel/plugin-syntax-jsx@^7.17.12":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0"
@ -48,12 +173,46 @@
"@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6"
"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3": "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3":
version "7.18.9" version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/template@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/parser" "^7.18.10"
"@babel/types" "^7.18.10"
"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.3":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.3.tgz#3a3c5348d4988ba60884e8494b0592b2f15a04b4"
integrity sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.19.3"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.19.3"
"@babel/types" "^7.19.3"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.18.10", "@babel/types@^7.19.0", "@babel/types@^7.19.3":
version "7.19.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.3.tgz#fc420e6bbe54880bce6779ffaf315f5e43ec9624"
integrity sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==
dependencies:
"@babel/helper-string-parser" "^7.18.10"
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@babel/types@^7.18.6": "@babel/types@^7.18.6":
version "7.18.13" version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
@ -81,7 +240,7 @@
source-map "^0.5.7" source-map "^0.5.7"
stylis "4.0.13" stylis "4.0.13"
"@emotion/cache@*", "@emotion/cache@^11.10.0": "@emotion/cache@^11.10.0":
version "11.10.3" version "11.10.3"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87"
integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ== integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==
@ -102,7 +261,7 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f"
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
"@emotion/react@^11.4.1": "@emotion/react@^11.10.4":
version "11.10.4" version "11.10.4"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199"
integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA== integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==
@ -116,7 +275,7 @@
"@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"
"@emotion/serialize@*", "@emotion/serialize@^1.1.0": "@emotion/serialize@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.0.tgz#b1f97b1011b09346a40e9796c37a3397b4ea8ea8" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.0.tgz#b1f97b1011b09346a40e9796c37a3397b4ea8ea8"
integrity sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA== integrity sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==
@ -142,7 +301,7 @@
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df" 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== integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==
"@emotion/utils@*", "@emotion/utils@^1.2.0": "@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"
integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==
@ -152,6 +311,46 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
"@jridgewell/gen-mapping@^0.1.0":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==
dependencies:
"@jridgewell/set-array" "^1.0.0"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/gen-mapping@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.15"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774"
integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@octokit/auth-token@^2.4.4": "@octokit/auth-token@^2.4.4":
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36"
@ -265,6 +464,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"
@ -392,11 +596,26 @@ braces@^3.0.2:
dependencies: dependencies:
fill-range "^7.0.1" fill-range "^7.0.1"
browserslist@^4.21.3:
version "4.21.4"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987"
integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==
dependencies:
caniuse-lite "^1.0.30001400"
electron-to-chromium "^1.4.251"
node-releases "^2.0.6"
update-browserslist-db "^1.0.9"
callsites@^3.0.0: callsites@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
caniuse-lite@^1.0.30001400:
version "1.0.30001415"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001415.tgz#fd7ea96e9e94c181a7f56e7571efb43d92b860cc"
integrity sha512-ER+PfgCJUe8BqunLGWd/1EY4g8AzQcsDAVzdtMGKVtQEmKAwaFfU6vb7EAVIqTMYsqxBorYZi2+22Iouj/y7GQ==
chalk@^2.0.0: chalk@^2.0.0:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -545,6 +764,11 @@ concat-map@0.0.1:
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
convert-source-map@^1.5.0: convert-source-map@^1.5.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
convert-source-map@^1.7.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
@ -618,7 +842,7 @@ d@1, d@^1.0.1:
es5-ext "^0.10.50" es5-ext "^0.10.50"
type "^1.0.1" type "^1.0.1"
debug@^4.0.0, debug@^4.3.2: debug@^4.0.0, debug@^4.1.0, debug@^4.3.2:
version "4.3.4" version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@ -660,6 +884,11 @@ domutils@^3.0.1:
domelementtype "^2.3.0" domelementtype "^2.3.0"
domhandler "^5.0.1" domhandler "^5.0.1"
electron-to-chromium@^1.4.251:
version "1.4.271"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.271.tgz#2d9f04f6a53c70e1bb1acfaae9c39f07ca40d290"
integrity sha512-BCPBtK07xR1/uY2HFDtl3wK2De66AW4MSiPlLrnPNxKC/Qhccxd59W73654S3y6Rb/k3hmuGJOBnhjfoutetXA==
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@ -743,14 +972,14 @@ event-emitter@^0.3.5:
d "1" d "1"
es5-ext "~0.10.14" es5-ext "~0.10.14"
evt@^2.4.0: evt@^2.4.13:
version "2.4.0" version "2.4.13"
resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.0.tgz#5052d3a685bba8f7e7be699b358c1781bd4037bf" resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.13.tgz#5ef873159ce62e099d58801a3e4b8e0f5b648017"
integrity sha512-1V8FnExwqzX2fufT793mHfCnQay078kRdAEDHzJz863pgJK9aeDiWXZFs5aKmzJfIgi+svevtApVnleZXz4Jeg== integrity sha512-haTVOsmjzk+28zpzvVwan9Zw2rLQF2izgi7BKjAPRzZAfcv+8scL0TpM8MzvGNKFYHiy+Bq3r6FYIIUPl9kt3A==
dependencies: dependencies:
minimal-polyfills "^2.2.2" minimal-polyfills "^2.2.2"
run-exclusive "^2.2.16" run-exclusive "^2.2.18"
tsafe "^1.0.1" tsafe "^1.4.1"
execa@^5.1.1: execa@^5.1.1:
version "5.1.1" version "5.1.1"
@ -816,6 +1045,11 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.5: get-caller-file@^2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@ -843,6 +1077,11 @@ glob@^7.0.5, glob@^7.1.3:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
has-flag@^3.0.0: has-flag@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -1033,11 +1272,21 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json-parse-even-better-errors@^2.3.0: json-parse-even-better-errors@^2.3.0:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
lines-and-columns@^1.1.6: lines-and-columns@^1.1.6:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
@ -1188,6 +1437,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"
@ -1210,6 +1464,11 @@ node-fetch@^2.6.7:
dependencies: dependencies:
whatwg-url "^5.0.0" whatwg-url "^5.0.0"
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
noms@0.0.0: noms@0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
@ -1356,6 +1615,11 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.3.1: picomatch@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
@ -1375,15 +1639,15 @@ please-upgrade-node@^3.2.0:
dependencies: dependencies:
semver-compare "^1.0.0" semver-compare "^1.0.0"
powerhooks@^0.20.15: powerhooks@^0.20.32:
version "0.20.15" version "0.20.32"
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.15.tgz#e5093a11f3ed1badbc09944acf5a3fb15ac3f1d7" resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.32.tgz#c9ddff1dcd7d7bb250e54ea284a6c65d9c228e09"
integrity sha512-28xNjPTtjPPP8vyi9SWXfn0U9hjxm/UzuarD2uofIXa/CPWmkCeTtikyLoR6yuiE4pOfvdDXVfU5im40GuzJaQ== integrity sha512-lp0rW5sAl8xrenhtB5eZOCwMFcPTtm55pn5++jeEMT1KaO/Rvd7hjaai+PPpDdwIbi5TaF+Zf5XhVO6gvfmXZQ==
dependencies: dependencies:
evt "^2.4.0" evt "^2.4.13"
memoizee "^0.4.15" memoizee "^0.4.15"
resize-observer-polyfill "^1.5.1" resize-observer-polyfill "^1.5.1"
tsafe "^1.0.1" tsafe "^1.4.1"
prettier@^2.3.0: prettier@^2.3.0:
version "2.7.1" version "2.7.1"
@ -1463,9 +1727,9 @@ readable-stream@~2.3.6:
util-deprecate "~1.0.1" util-deprecate "~1.0.1"
regenerator-runtime@^0.13.4: regenerator-runtime@^0.13.4:
version "0.13.9" version "0.13.10"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==
remark-parse@^9.0.0: remark-parse@^9.0.0:
version "9.0.0" version "9.0.0"
@ -1506,6 +1770,11 @@ restore-cursor@^3.1.0:
onetime "^5.1.0" onetime "^5.1.0"
signal-exit "^3.0.2" signal-exit "^3.0.2"
rfc4648@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.2.tgz#cf5dac417dd83e7f4debf52e3797a723c1373383"
integrity sha512-tLOizhR6YGovrEBLatX1sdcuhoSCXddw3mqNVAcKxGJ+J0hFeJ+SjeWCv5UPA/WU3YzWPPuCVYgXBKZUPGpKtg==
rfdc@^1.3.0: rfdc@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
@ -1518,10 +1787,10 @@ rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
run-exclusive@^2.2.16: run-exclusive@^2.2.18:
version "2.2.16" version "2.2.18"
resolved "https://registry.yarnpkg.com/run-exclusive/-/run-exclusive-2.2.16.tgz#8fa30a23037760af296c47872a5f6b38f25accf0" resolved "https://registry.yarnpkg.com/run-exclusive/-/run-exclusive-2.2.18.tgz#ec930edc3a7044750dc827df9372bde8f610f586"
integrity sha512-cdYv2LDvaBCRnrqXrwDFs1SgzGTx0EIsiEReTpsprEDR6hRUVlSyjoMYu+rez4S1gpz6YbOQxcmYFMXJQknVnQ== integrity sha512-TXr1Gkl1iEAOCCpBTRm/2m0+1KGjORcWpZZ+VGGTe7dYX8E4y8/fMvrHk0zf+kclec2R//tpvdBxgG0bDgaJfw==
dependencies: dependencies:
minimal-polyfills "^2.2.1" minimal-polyfills "^2.2.1"
@ -1552,6 +1821,11 @@ semver-regex@^3.1.2:
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.4.tgz#13053c0d4aa11d070a2f2872b6b1e3ae1e1971b4" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.4.tgz#13053c0d4aa11d070a2f2872b6b1e3ae1e1971b4"
integrity sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA== integrity sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==
semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
shebang-command@^2.0.0: shebang-command@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@ -1723,24 +1997,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@^1.0.1: tsafe@^1.4.1:
version "1.0.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.0.1.tgz#c8c4eb2d75d1478418a4941307c5dd667fd76d23" resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.4.1.tgz#59cdad8ac41babf88e56dcd1a683ae2fb145d059"
integrity sha512-FgJ1a4rE7YbmW5QIzpsfFl4tsAp0x74FH2bVE6qODb2U8jSrwTr5/ckIazeylme5zXndVbtgKm4BZdqmoGhiPw== integrity sha512-3IDBalvf6SyvHFS14UiwCWzqdSdo+Q0k2J7DZyJYaHW/iraW9DJpaBKDJpry3yQs3o/t/A+oGaRW3iVt2lKxzA==
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@^4.1.1: tss-react@4.4.1-rc.0:
version "4.1.1" version "4.4.1-rc.0"
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-4.1.1.tgz#207220417e4b2f8eb26d8280ab9cdeb385063069" resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-4.4.1-rc.0.tgz#96f9c2edcb9208ae39e845e6bafc9a50c9b96c66"
integrity sha512-K1U2s/GGw+XycUjJGztJsLUhwm8KJWz5afL5WZU3SwMeQsA+gbETM6bSxVk2/DXBdw9uYLL9jkSYPAXh0tfYBw== integrity sha512-ozltdpdDrB/6Ml6UpaL/eGwGZzTXXcHnJxwwO7mxcFaZnLaetbbbQHNUerRmqr21i9vgA6HFBAzOv9pa2PUsww==
dependencies:
"@emotion/cache" "*"
"@emotion/serialize" "*"
"@emotion/utils" "*"
type-fest@^0.21.3: type-fest@^0.21.3:
version "0.21.3" version "0.21.3"
@ -1818,6 +2088,14 @@ untildify@^4.0.0:
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
update-browserslist-db@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18"
integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==
dependencies:
escalade "^3.1.1"
picocolors "^1.0.0"
util-deprecate@~1.0.1: util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"