Compare commits
36 Commits
v10.0.0-rc
...
v10.0.0-rc
Author | SHA1 | Date | |
---|---|---|---|
ef9c933ca8 | |||
0461190a67 | |||
06b3211b08 | |||
2033a9ce0c | |||
fca18d9209 | |||
4f99088449 | |||
b1da684008 | |||
89fb6de2d5 | |||
b665bae3bb | |||
0b5a7544ca | |||
183826ca0d | |||
e507aace6b | |||
43c93ef0b4 | |||
093e51e092 | |||
17e1655eaf | |||
6b570f2b9a | |||
f239d105a7 | |||
776d8378e3 | |||
dd770cd7c6 | |||
4b3de54e18 | |||
5741cd1b2b | |||
b780d7136e | |||
3c28a05746 | |||
57ac5badba | |||
e873eb5123 | |||
c1a63edd71 | |||
37a060c4db | |||
157e4ac485 | |||
ba4d9675a8 | |||
e011fb094c | |||
f55a934939 | |||
96a88fe865 | |||
6cdb83d730 | |||
95f06df45d | |||
ec52b357d5 | |||
d84546cd7d |
@ -34,7 +34,6 @@ export function DocsContainer({ children, context }) {
|
||||
.docblock-argstable-head th:nth-child(3), .docblock-argstable-body tr > td:nth-child(2) p {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
`}</style>
|
||||
<BaseContainer
|
||||
context={{
|
||||
@ -64,11 +63,6 @@ export function CanvasContainer({ children }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
`}</style>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
19
.storybook/preview-head.html
Normal file
19
.storybook/preview-head.html
Normal file
@ -0,0 +1,19 @@
|
||||
<style>
|
||||
body.sb-show-main.sb-main-padded {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body:not(.kcBodyClass) {
|
||||
background-color: #393939;
|
||||
}
|
||||
|
||||
|
||||
body.sb-show-preparing-docs > .sb-wrapper {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
body .sb-preparing-story {
|
||||
visibility: hidden;
|
||||
|
||||
}
|
||||
</style>
|
@ -116,10 +116,45 @@ const { getHardCodedWeight } = (() => {
|
||||
const orderedPagesPrefix = [
|
||||
"Introduction",
|
||||
"login/login.ftl",
|
||||
"login/register-user-profile.ftl",
|
||||
"login/register.ftl",
|
||||
"login/terms.ftl",
|
||||
"login/error.ftl",
|
||||
"login/code.ftl",
|
||||
"login/delete-account-confirm.ftl",
|
||||
"login/delete-credential.ftl",
|
||||
"login/frontchannel-logout.ftl",
|
||||
"login/idp-review-user-profile.ftl",
|
||||
"login/info.ftl",
|
||||
"login/login-config-totp.ftl",
|
||||
"login/login-idp-link-confirm.ftl",
|
||||
"login/login-idp-link-email.ftl",
|
||||
"login/login-oauth-grant.ftl",
|
||||
"login/login-otp.ftl",
|
||||
"login/login-page-expired.ftl",
|
||||
"login/login-password.ftl",
|
||||
"login/login-reset-otp.ftl",
|
||||
"login/login-reset-password.ftl",
|
||||
"login/login-update-password.ftl",
|
||||
"login/login-update-profile.ftl",
|
||||
"login/login-username.ftl",
|
||||
"login/login-verify-email.ftl",
|
||||
"login/login-x509-info.ftl",
|
||||
"login/logout-confirm.ftl",
|
||||
"login/saml-post-form.ftl",
|
||||
"login/select-authenticator.ftl",
|
||||
"login/update-email.ftl",
|
||||
"login/webauthn-authenticate.ftl",
|
||||
"login/webauthn-error.ftl",
|
||||
"login/webauthn-register.ftl",
|
||||
"login/login-oauth2-device-verify-user-code.ftl",
|
||||
"login/login-recovery-authn-code-config.ftl",
|
||||
"login/login-recovery-authn-code-input.ftl",
|
||||
"account/account.ftl",
|
||||
"account/password.ftl",
|
||||
"account/federatedIdentity.ftl",
|
||||
"account/log.ftl",
|
||||
"account/sessions.ftl",
|
||||
"account/totp.ftl",
|
||||
];
|
||||
|
||||
function getHardCodedWeight(kind) {
|
||||
|
49
.storybook/static/tos/tos_en.md
Normal file
49
.storybook/static/tos/tos_en.md
Normal file
@ -0,0 +1,49 @@
|
||||
## Overview
|
||||
|
||||
This Terms of Service document outlines the rules and regulations for the use of **Example Company's** Services.
|
||||
|
||||
## Acceptance of Terms
|
||||
|
||||
By accessing and using our services, you acknowledge that you have read, understood, and agree to be bound by these terms. If you do not accept these terms, you are not authorized to use our services.
|
||||
|
||||
## Description of Service
|
||||
|
||||
**Example Service** (hereinafter referred to as "the Service") is a web-based solution offered by **Example Company** (hereinafter referred to as "the Company"). Our service provides users with access to [documentation](https://example.com/docs) and support for managing their projects effectively.
|
||||
|
||||
## Modifications to the Terms of Service
|
||||
|
||||
The Company reserves the right to modify these terms at any time. Such modifications will be effective immediately upon posting the updated terms on our website. Your continued use of the Service after any such changes shall constitute your consent to such changes.
|
||||
|
||||
## Account Registration
|
||||
|
||||
You may be required to register with the Service to access certain features. When registering, you agree to provide accurate, current, and complete information about yourself as requested.
|
||||
|
||||
## User Responsibilities
|
||||
|
||||
- **Data Security**: Users are responsible for safeguarding their login credentials and should not disclose their passwords to any third party.
|
||||
- **Acceptable Use**: Users are expected to use the Service in a responsible manner that does not infringe upon the rights of others.
|
||||
- **Content Ownership**: Users retain all rights to the content they upload to the Service but grant the Company a license to use and distribute this content as part of the Service.
|
||||
|
||||
## Intellectual Property
|
||||
|
||||
All intellectual property rights related to the Service and its original content, features, and functionality are owned by the Company.
|
||||
|
||||
## Termination
|
||||
|
||||
The Company may terminate or suspend access to our Service immediately, without prior notice or liability, for any reason whatsoever, including, without limitation, breach of these Terms.
|
||||
|
||||
## Governing Law
|
||||
|
||||
These Terms shall be governed and construed in accordance with the laws of [Your Country], without regard to its conflict of law provisions.
|
||||
|
||||
## Contact Information
|
||||
|
||||
For any questions about these Terms, please contact us at [support@example.com](mailto:support@example.com) or visit our [FAQ page](https://example.com/faq).
|
||||
|
||||
## Changes to Terms of Service
|
||||
|
||||
We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material, we will provide at least 30 days' notice prior to any new terms taking effect.
|
||||
|
||||
## Effective Date
|
||||
|
||||
These terms are effective as of **[Insert Date]**.
|
49
.storybook/static/tos/tos_es.md
Normal file
49
.storybook/static/tos/tos_es.md
Normal file
@ -0,0 +1,49 @@
|
||||
## Resumen
|
||||
|
||||
Este documento de Términos de Servicio detalla las reglas y regulaciones para el uso de los servicios de **Empresa Ejemplo**.
|
||||
|
||||
## Aceptación de Términos
|
||||
|
||||
Al acceder y utilizar nuestros servicios, usted reconoce que ha leído, entendido y acepta estar vinculado por estos términos. Si no acepta estos términos, no está autorizado para usar nuestros servicios.
|
||||
|
||||
## Descripción del Servicio
|
||||
|
||||
**Servicio Ejemplo** (en adelante denominado "el Servicio") es una solución basada en la web ofrecida por **Empresa Ejemplo** (en adelante denominada "la Empresa"). Nuestro servicio proporciona a los usuarios acceso a [documentación](https://ejemplo.com/docs) y soporte para gestionar sus proyectos de manera efectiva.
|
||||
|
||||
## Modificaciones a los Términos de Servicio
|
||||
|
||||
La Empresa se reserva el derecho de modificar estos términos en cualquier momento. Dichas modificaciones entrarán en vigor inmediatamente después de la publicación de los términos actualizados en nuestro sitio web. Su uso continuado del Servicio después de tales cambios constituirá su consentimiento a dichos cambios.
|
||||
|
||||
## Registro de Cuenta
|
||||
|
||||
Puede ser necesario que se registre en el Servicio para acceder a ciertas características. Al registrarse, usted acepta proporcionar información precisa, actual y completa sobre sí mismo como se solicita.
|
||||
|
||||
## Responsabilidades del Usuario
|
||||
|
||||
- **Seguridad de Datos**: Los usuarios son responsables de salvaguardar sus credenciales de inicio de sesión y no deben divulgar sus contraseñas a terceros.
|
||||
- **Uso Aceptable**: Se espera que los usuarios utilicen el Servicio de manera responsable que no infrinja los derechos de otros.
|
||||
- **Propiedad del Contenido**: Los usuarios retienen todos los derechos sobre el contenido que cargan en el Servicio, pero otorgan a la Empresa una licencia para usar y distribuir este contenido como parte del Servicio.
|
||||
|
||||
## Propiedad Intelectual
|
||||
|
||||
Todos los derechos de propiedad intelectual relacionados con el Servicio y su contenido original, características y funcionalidad son propiedad de la Empresa.
|
||||
|
||||
## Terminación
|
||||
|
||||
La Empresa puede terminar o suspender su acceso a nuestro Servicio de inmediato, sin previo aviso ni responsabilidad, por cualquier motivo, incluido, entre otros, una violación de estos Términos.
|
||||
|
||||
## Ley Aplicable
|
||||
|
||||
Estos Términos se regirán e interpretarán de acuerdo con las leyes de [Su País], sin tener en cuenta sus disposiciones de conflicto de leyes.
|
||||
|
||||
## Información de Contacto
|
||||
|
||||
Para cualquier pregunta sobre estos Términos, contáctenos en [support@ejemplo.com](mailto:support@ejemplo.com) o visite nuestra [página de FAQ](https://ejemplo.com/faq).
|
||||
|
||||
## Cambios a los Términos de Servicio
|
||||
|
||||
Nos reservamos el derecho, a nuestra única discreción, de modificar o reemplazar estos Términos en cualquier momento. Si una revisión es material, proporcionaremos al menos 30 días de aviso antes de que los nuevos términos entren en vigor.
|
||||
|
||||
## Fecha de Efectividad
|
||||
|
||||
Estos términos son efectivos a partir del **[Insertar Fecha]**.
|
49
.storybook/static/tos/tos_fr.md
Normal file
49
.storybook/static/tos/tos_fr.md
Normal file
@ -0,0 +1,49 @@
|
||||
## Vue d'ensemble
|
||||
|
||||
Ce document des Conditions Générales d'Utilisation détaille les règles et réglementations pour l'utilisation des services de **l'Entreprise Exemple**.
|
||||
|
||||
## Acceptation des Conditions
|
||||
|
||||
En accédant et en utilisant nos services, vous reconnaissez avoir lu, compris et accepté d'être lié par ces conditions. Si vous n'acceptez pas ces termes, vous n'êtes pas autorisé à utiliser nos services.
|
||||
|
||||
## Description du Service
|
||||
|
||||
**Service Exemple** (ci-après dénommé "le Service") est une solution basée sur le web offerte par **l'Entreprise Exemple** (ci-après dénommée "l'Entreprise"). Notre service offre aux utilisateurs un accès à la [documentation](https://exemple.com/docs) et un support pour gérer efficacement leurs projets.
|
||||
|
||||
## Modifications des Conditions de Service
|
||||
|
||||
L'Entreprise se réserve le droit de modifier ces conditions à tout moment. De telles modifications entreront en vigueur immédiatement après la publication des termes mis à jour sur notre site web. Votre utilisation continue du Service après de tels changements constitue votre consentement à ces modifications.
|
||||
|
||||
## Inscription au Compte
|
||||
|
||||
Vous devrez peut-être vous inscrire au Service pour accéder à certaines fonctionnalités. Lors de l'inscription, vous acceptez de fournir des informations précises, actuelles et complètes vous concernant, comme demandé.
|
||||
|
||||
## Responsabilités des Utilisateurs
|
||||
|
||||
- **Sécurité des Données** : Les utilisateurs sont responsables de la sauvegarde de leurs identifiants de connexion et ne doivent divulguer leurs mots de passe à aucun tiers.
|
||||
- **Utilisation Acceptable** : Les utilisateurs sont censés utiliser le Service de manière responsable qui ne porte pas atteinte aux droits d'autrui.
|
||||
- **Propriété du Contenu** : Les utilisateurs conservent tous les droits sur le contenu qu'ils téléchargent sur le Service mais accordent à l'Entreprise une licence pour utiliser et distribuer ce contenu dans le cadre du Service.
|
||||
|
||||
## Propriété Intellectuelle
|
||||
|
||||
Tous les droits de propriété intellectuelle relatifs au Service et à son contenu original, fonctionnalités et fonctionnement sont détenus par l'Entreprise.
|
||||
|
||||
## Résiliation
|
||||
|
||||
L'Entreprise peut résilier ou suspendre votre accès à notre Service immédiatement, sans préavis ni responsabilité, pour quelque raison que ce soit, y compris, sans limitation, en cas de violation de ces Conditions.
|
||||
|
||||
## Loi Applicable
|
||||
|
||||
Ces Conditions seront régies et interprétées conformément aux lois de [Votre Pays], sans égard à ses dispositions de conflit de lois.
|
||||
|
||||
## Informations de Contact
|
||||
|
||||
Pour toute question concernant ces Conditions, veuillez nous contacter à [support@exemple.com](mailto:support@exemple.com) ou visitez notre [page FAQ](https://exemple.com/faq).
|
||||
|
||||
## Modifications des Conditions de Service
|
||||
|
||||
Nous nous réservons le droit, à notre seule discrétion, de modifier ou de remplacer ces Conditions à tout moment. Si une révision est importante, nous vous fournirons un préavis d'au moins 30 jours avant que les nouveaux termes prennent effet.
|
||||
|
||||
## Date d'Effet
|
||||
|
||||
Ces conditions sont effectives à partir du **[Insérer la Date]**.
|
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "10.0.0-rc.23",
|
||||
"version": "10.0.0-rc.26",
|
||||
"description": "Create Keycloak themes using React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -9,7 +9,7 @@
|
||||
"scripts": {
|
||||
"prepare": "patch-package && ts-node --skipProject scripts/generate-i18n-messages.ts",
|
||||
"build": "ts-node --skipProject scripts/build.ts",
|
||||
"storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006",
|
||||
"storybook": "ts-node --skipProject scripts/start-storybook.ts",
|
||||
"link-in-starter": "ts-node --skipProject scripts/link-in-starter.ts",
|
||||
"test": "yarn test:types && vitest run",
|
||||
"test:types": "tsc -p test/tsconfig.json --noEmit",
|
||||
@ -17,8 +17,7 @@
|
||||
"format": "yarn _format --write",
|
||||
"format:check": "yarn _format --list-different",
|
||||
"link-in-app": "ts-node --skipProject scripts/link-in-app.ts",
|
||||
"copy-keycloak-resources-to-storybook-static": "PUBLIC_DIR_PATH=.storybook/static node dist/bin/main.js copy-keycloak-resources-to-public",
|
||||
"build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook",
|
||||
"build-storybook": "ts-node --skipProject scripts/build-storybook.ts",
|
||||
"dump-keycloak-realm": "ts-node --skipProject scripts/dump-keycloak-realm.ts"
|
||||
},
|
||||
"bin": {
|
||||
@ -47,6 +46,7 @@
|
||||
"dist/bin/shared/constants.js.map",
|
||||
"!dist/vite-plugin/",
|
||||
"dist/vite-plugin/index.d.ts",
|
||||
"dist/vite-plugin/vite-plugin.d.ts",
|
||||
"dist/vite-plugin/index.js"
|
||||
],
|
||||
"keywords": [
|
||||
@ -116,7 +116,7 @@
|
||||
"ts-node": "^10.9.2",
|
||||
"tsc-alias": "^1.8.10",
|
||||
"tss-react": "^4.9.10",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript": "^4.9.1-beta",
|
||||
"vite": "^5.2.11",
|
||||
"vitest": "^0.29.8",
|
||||
"yauzl": "^2.10.0",
|
||||
|
19
scripts/build-storybook.ts
Normal file
19
scripts/build-storybook.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as child_process from "child_process";
|
||||
import { join } from "path";
|
||||
|
||||
run("yarn build");
|
||||
|
||||
run(`node ${join("dist", "bin", "main.js")} copy-keycloak-resources-to-public`, {
|
||||
env: {
|
||||
...process.env,
|
||||
PUBLIC_DIR_PATH: join(".storybook", "static")
|
||||
}
|
||||
});
|
||||
|
||||
run("npx build-storybook");
|
||||
|
||||
function run(command: string, options?: { env?: NodeJS.ProcessEnv }) {
|
||||
console.log(`$ ${command}`);
|
||||
|
||||
child_process.execSync(command, { stdio: "inherit", ...options });
|
||||
}
|
@ -1,11 +1,7 @@
|
||||
import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import { join } from "path";
|
||||
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
|
||||
import chokidar from "chokidar";
|
||||
import * as runExclusive from "run-exclusive";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
import chalk from "chalk";
|
||||
import { startRebuildOnSrcChange } from "./startRebuildOnSrcChange";
|
||||
|
||||
fs.rmSync("node_modules", { recursive: true, force: true });
|
||||
fs.rmSync("dist", { recursive: true, force: true });
|
||||
@ -23,35 +19,7 @@ run("yarn install", { cwd: join("..", "keycloakify-starter") });
|
||||
|
||||
run(`npx ts-node --skipProject ${join("scripts", "link-in-app.ts")} keycloakify-starter`);
|
||||
|
||||
const { waitForDebounce } = waitForDebounceFactory({ delay: 400 });
|
||||
|
||||
const runYarnBuild = runExclusive.build(async () => {
|
||||
console.log(chalk.green("Running `yarn build`"));
|
||||
|
||||
const dCompleted = new Deferred<void>();
|
||||
|
||||
const child = child_process.spawn("yarn", ["build"], {
|
||||
env: process.env
|
||||
});
|
||||
|
||||
child.stdout.on("data", data => process.stdout.write(data));
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
child.on("exit", () => dCompleted.resolve());
|
||||
|
||||
await dCompleted.pr;
|
||||
|
||||
console.log("\n\n");
|
||||
});
|
||||
|
||||
console.log(chalk.green("Watching for changes in src/"));
|
||||
|
||||
chokidar.watch("src", { ignoreInitial: true }).on("all", async () => {
|
||||
await waitForDebounce();
|
||||
|
||||
runYarnBuild();
|
||||
});
|
||||
startRebuildOnSrcChange();
|
||||
|
||||
function run(command: string, options?: { cwd: string }) {
|
||||
console.log(`$ ${command}`);
|
||||
|
31
scripts/start-storybook.ts
Normal file
31
scripts/start-storybook.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import { join } from "path";
|
||||
import { startRebuildOnSrcChange } from "./startRebuildOnSrcChange";
|
||||
|
||||
run("yarn build");
|
||||
|
||||
run(`node ${join("dist", "bin", "main.js")} copy-keycloak-resources-to-public`, {
|
||||
env: {
|
||||
...process.env,
|
||||
PUBLIC_DIR_PATH: join(".storybook", "static")
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
const child = child_process.spawn("npx", ["start-storybook", "-p", "6006"]);
|
||||
|
||||
child.stdout.on("data", data => process.stdout.write(data));
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
child.on("exit", process.exit.bind(process));
|
||||
}
|
||||
|
||||
startRebuildOnSrcChange();
|
||||
|
||||
function run(command: string, options?: { env?: NodeJS.ProcessEnv }) {
|
||||
console.log(`$ ${command}`);
|
||||
|
||||
child_process.execSync(command, { stdio: "inherit", ...options });
|
||||
}
|
36
scripts/startRebuildOnSrcChange.ts
Normal file
36
scripts/startRebuildOnSrcChange.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import * as child_process from "child_process";
|
||||
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
|
||||
import chokidar from "chokidar";
|
||||
import * as runExclusive from "run-exclusive";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
import chalk from "chalk";
|
||||
|
||||
export function startRebuildOnSrcChange() {
|
||||
const { waitForDebounce } = waitForDebounceFactory({ delay: 400 });
|
||||
|
||||
const runYarnBuild = runExclusive.build(async () => {
|
||||
console.log(chalk.green("Running `yarn build`"));
|
||||
|
||||
const dCompleted = new Deferred<void>();
|
||||
|
||||
const child = child_process.spawn("yarn", ["build"]);
|
||||
|
||||
child.stdout.on("data", data => process.stdout.write(data));
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
child.on("exit", () => dCompleted.resolve());
|
||||
|
||||
await dCompleted.pr;
|
||||
|
||||
console.log("\n\n");
|
||||
});
|
||||
|
||||
console.log(chalk.green("Watching for changes in src/"));
|
||||
|
||||
chokidar.watch("src", { ignoreInitial: true }).on("all", async () => {
|
||||
await waitForDebounce();
|
||||
|
||||
runYarnBuild();
|
||||
});
|
||||
}
|
@ -5,7 +5,7 @@ import {
|
||||
import { assert } from "tsafe/assert";
|
||||
|
||||
/**
|
||||
* This is an equivalent of process.env.PUBLIC_URL thay you can use in Webpack projects.
|
||||
* This is an equivalent of process.env.PUBLIC_URL that you can use in Webpack projects.
|
||||
* This works both in your main app and in your Keycloak theme.
|
||||
*/
|
||||
export const PUBLIC_URL = (() => {
|
||||
|
@ -2,14 +2,12 @@ import { useEffect } from "react";
|
||||
import { clsx } from "keycloakify/tools/clsx";
|
||||
import { type TemplateProps } from "keycloakify/account/TemplateProps";
|
||||
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
||||
import { createUseInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
||||
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
||||
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
||||
import type { KcContext } from "./kcContext";
|
||||
import type { I18n } from "./i18n";
|
||||
import { assert } from "keycloakify/tools/assert";
|
||||
|
||||
const { useInsertLinkTags } = createUseInsertLinkTags();
|
||||
|
||||
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props;
|
||||
|
||||
@ -46,6 +44,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
}, []);
|
||||
|
||||
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
||||
componentOrHookName: "Template",
|
||||
hrefs: !doUseDefaultCss
|
||||
? []
|
||||
: [
|
||||
|
@ -4,6 +4,7 @@ import fallbackMessages from "./baseMessages/en";
|
||||
import { getMessages } from "./baseMessages";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { KcContext } from "../kcContext/KcContext";
|
||||
import { Reflect } from "tsafe/Reflect";
|
||||
|
||||
export const fallbackLanguageTag = "en";
|
||||
|
||||
@ -137,7 +138,10 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
return i18n ?? null;
|
||||
}
|
||||
|
||||
return { useI18n };
|
||||
return {
|
||||
useI18n,
|
||||
ofTypeI18n: Reflect<GenericI18n<MessageKey | ExtraMessageKey>>()
|
||||
};
|
||||
}
|
||||
|
||||
function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
|
@ -1,10 +1,6 @@
|
||||
import Fallback from "keycloakify/account/Fallback";
|
||||
|
||||
export default Fallback;
|
||||
|
||||
export { getKcContext } from "keycloakify/account/kcContext/getKcContext";
|
||||
export { createGetKcContext } from "keycloakify/account/kcContext/createGetKcContext";
|
||||
export type { AccountThemePageId as PageId } from "keycloakify/bin/shared/constants";
|
||||
export { createUseI18n } from "keycloakify/account/i18n/i18n";
|
||||
export type { ExtendKcContext } from "keycloakify/account/kcContext";
|
||||
export { createGetKcContextMock } from "keycloakify/account/kcContext";
|
||||
|
||||
export type { PageProps } from "keycloakify/account/pages/PageProps";
|
||||
|
@ -1,6 +1,24 @@
|
||||
import type { ThemeType, AccountThemePageId } from "keycloakify/bin/shared/constants";
|
||||
import type { ValueOf } from "keycloakify/tools/ValueOf";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
import type { ThemeType, AccountThemePageId } from "keycloakify/bin/shared/constants";
|
||||
|
||||
export type ExtendKcContext<
|
||||
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
|
||||
KcContextExtraPropertiesPerPage extends Record<string, Record<string, unknown>>
|
||||
> = ValueOf<{
|
||||
[PageId in keyof KcContextExtraPropertiesPerPage | KcContext["pageId"]]: Extract<
|
||||
KcContext,
|
||||
{ pageId: PageId }
|
||||
> extends never
|
||||
? KcContext.Common &
|
||||
KcContextExtraProperties & {
|
||||
pageId: PageId;
|
||||
} & KcContextExtraPropertiesPerPage[PageId]
|
||||
: Extract<KcContext, { pageId: PageId }> &
|
||||
KcContextExtraProperties &
|
||||
KcContextExtraPropertiesPerPage[PageId];
|
||||
}>;
|
||||
|
||||
export type KcContext =
|
||||
| KcContext.Password
|
||||
|
@ -1,134 +0,0 @@
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||
import { isStorybook } from "keycloakify/lib/isStorybook";
|
||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||
import { symToStr } from "tsafe/symToStr";
|
||||
import {
|
||||
kcContextMocks,
|
||||
kcContextCommonMock
|
||||
} from "keycloakify/account/kcContext/kcContextMocks";
|
||||
|
||||
export function createGetKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
mockProperties?: Record<string, string>;
|
||||
}) {
|
||||
const { mockData, mockProperties } = params ?? {};
|
||||
|
||||
function getKcContext<
|
||||
PageId extends
|
||||
| ExtendKcContext<KcContextExtension>["pageId"]
|
||||
| undefined = undefined
|
||||
>(params?: {
|
||||
mockPageId?: PageId;
|
||||
storyPartialKcContext?: DeepPartial<
|
||||
Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>
|
||||
>;
|
||||
}): {
|
||||
kcContext: PageId extends undefined
|
||||
? ExtendKcContext<KcContextExtension> | undefined
|
||||
: Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>;
|
||||
} {
|
||||
const { mockPageId, storyPartialKcContext } = params ?? {};
|
||||
|
||||
const realKcContext = getKcContextFromWindow<KcContextExtension>();
|
||||
|
||||
if (mockPageId !== undefined && realKcContext === undefined) {
|
||||
//TODO maybe trow if no mock fo custom page
|
||||
|
||||
warn_that_mock_is_enbaled: {
|
||||
if (isStorybook) {
|
||||
break warn_that_mock_is_enbaled;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`%cKeycloakify: ${symToStr({
|
||||
mockPageId
|
||||
})} set to ${mockPageId}.`,
|
||||
"background: red; color: yellow; font-size: medium"
|
||||
);
|
||||
}
|
||||
|
||||
const kcContextDefaultMock = kcContextMocks.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
const partialKcContextCustomMock = (() => {
|
||||
const out: DeepPartial<ExtendKcContext<KcContextExtension>> = {};
|
||||
|
||||
const mockDataPick = mockData?.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
if (mockDataPick !== undefined) {
|
||||
deepAssign({
|
||||
target: out,
|
||||
source: mockDataPick
|
||||
});
|
||||
}
|
||||
|
||||
if (storyPartialKcContext !== undefined) {
|
||||
deepAssign({
|
||||
target: out,
|
||||
source: storyPartialKcContext
|
||||
});
|
||||
}
|
||||
|
||||
return Object.keys(out).length === 0 ? undefined : out;
|
||||
})();
|
||||
|
||||
if (
|
||||
kcContextDefaultMock === undefined &&
|
||||
partialKcContextCustomMock === undefined
|
||||
) {
|
||||
console.warn(
|
||||
[
|
||||
`WARNING: You declared the non build in page ${mockPageId} but you didn't `,
|
||||
`provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`,
|
||||
`Please check the documentation of the getKcContext function`
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
const kcContext: any = {};
|
||||
|
||||
deepAssign({
|
||||
target: kcContext,
|
||||
source:
|
||||
kcContextDefaultMock !== undefined
|
||||
? kcContextDefaultMock
|
||||
: { pageId: mockPageId, ...kcContextCommonMock }
|
||||
});
|
||||
|
||||
if (partialKcContextCustomMock !== undefined) {
|
||||
deepAssign({
|
||||
target: kcContext,
|
||||
source: partialKcContextCustomMock
|
||||
});
|
||||
}
|
||||
|
||||
if (mockProperties !== undefined) {
|
||||
deepAssign({
|
||||
target: kcContext.properties,
|
||||
source: mockProperties
|
||||
});
|
||||
}
|
||||
|
||||
return { kcContext };
|
||||
}
|
||||
|
||||
if (realKcContext === undefined) {
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
if (realKcContext.themeType !== "account") {
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
return { kcContext: realKcContext as any };
|
||||
}
|
||||
|
||||
return { getKcContext };
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { createGetKcContext } from "./createGetKcContext";
|
||||
|
||||
/** NOTE: We now recommend using createGetKcContext instead of this function to make storybook integration easier
|
||||
* See: https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/account/kcContext.ts
|
||||
*/
|
||||
export function getKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockPageId?: ExtendKcContext<KcContextExtension>["pageId"];
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
}): { kcContext: ExtendKcContext<KcContextExtension> | undefined } {
|
||||
const { mockPageId, mockData } = params ?? {};
|
||||
|
||||
const { getKcContext } = createGetKcContext({
|
||||
mockData
|
||||
});
|
||||
|
||||
const { kcContext } = getKcContext({ mockPageId });
|
||||
|
||||
return { kcContext };
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
||||
import { nameOfTheGlobal } from "keycloakify/bin/shared/constants";
|
||||
import type { KcContext } from "./KcContext";
|
||||
|
||||
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [
|
||||
KcContextExtension
|
||||
] extends [never]
|
||||
? KcContext
|
||||
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
|
||||
|
||||
export function getKcContextFromWindow<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(): ExtendKcContext<KcContextExtension> | undefined {
|
||||
return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal];
|
||||
}
|
80
src/account/kcContext/getKcContextMock.ts
Normal file
80
src/account/kcContext/getKcContextMock.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { ExtendKcContext, KcContext as KcContextBase } from "./KcContext";
|
||||
import type { AccountThemePageId } from "keycloakify/bin/shared/constants";
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
|
||||
export function createGetKcContextMock<
|
||||
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
|
||||
KcContextExtraPropertiesPerPage extends Record<
|
||||
`${string}.ftl`,
|
||||
Record<string, unknown>
|
||||
>
|
||||
>(params: {
|
||||
kcContextExtraProperties: KcContextExtraProperties;
|
||||
kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage;
|
||||
overrides?: DeepPartial<KcContextExtraProperties & KcContextBase.Common>;
|
||||
overridesPerPage?: {
|
||||
[PageId in
|
||||
| AccountThemePageId
|
||||
| keyof KcContextExtraPropertiesPerPage]?: DeepPartial<
|
||||
Extract<
|
||||
ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>,
|
||||
{ pageId: PageId }
|
||||
>
|
||||
>;
|
||||
};
|
||||
}) {
|
||||
const {
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage,
|
||||
overrides: overrides_global,
|
||||
overridesPerPage: overridesPerPage_global
|
||||
} = params;
|
||||
|
||||
type KcContext = ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>;
|
||||
|
||||
function getKcContextMock<
|
||||
PageId extends AccountThemePageId | keyof KcContextExtraPropertiesPerPage
|
||||
>(params: {
|
||||
pageId: PageId;
|
||||
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
|
||||
}): Extract<KcContext, { pageId: PageId }> {
|
||||
const { pageId, overrides } = params;
|
||||
|
||||
const kcContextMock = structuredCloneButFunctions(
|
||||
kcContextMocks.find(kcContextMock => kcContextMock.pageId === pageId) ?? {
|
||||
...kcContextCommonMock,
|
||||
pageId
|
||||
}
|
||||
);
|
||||
|
||||
[
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage[pageId],
|
||||
overrides_global,
|
||||
overridesPerPage_global?.[pageId],
|
||||
overrides
|
||||
]
|
||||
.filter(exclude(undefined))
|
||||
.forEach(overrides =>
|
||||
deepAssign({
|
||||
target: kcContextMock,
|
||||
source: overrides
|
||||
})
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
return kcContextMock;
|
||||
}
|
||||
|
||||
return { getKcContextMock };
|
||||
}
|
@ -1 +1,2 @@
|
||||
export type { KcContext } from "./KcContext";
|
||||
export type { ExtendKcContext, KcContext } from "./KcContext";
|
||||
export { createGetKcContextMock } from "./getKcContextMock";
|
||||
|
@ -40,98 +40,33 @@ export const kcContextCommonMock: KcContext.Common = {
|
||||
locale: {
|
||||
supported: [
|
||||
/* spell-checker: disable */
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
||||
label: "Deutsch",
|
||||
languageTag: "de"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||
label: "Norsk",
|
||||
languageTag: "no"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
label: "Русский",
|
||||
languageTag: "ru"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||
label: "Svenska",
|
||||
languageTag: "sv"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
||||
label: "Português (Brasil)",
|
||||
languageTag: "pt-BR"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
||||
label: "Lietuvių",
|
||||
languageTag: "lt"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||
label: "English",
|
||||
languageTag: "en"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||
label: "Italiano",
|
||||
languageTag: "it"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
||||
label: "Français",
|
||||
languageTag: "fr"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
||||
label: "中文简体",
|
||||
languageTag: "zh-CN"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
||||
label: "Español",
|
||||
languageTag: "es"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
||||
label: "Čeština",
|
||||
languageTag: "cs"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
label: "日本語",
|
||||
languageTag: "ja"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
||||
label: "Slovenčina",
|
||||
languageTag: "sk"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||
label: "Polski",
|
||||
languageTag: "pl"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||
label: "Català",
|
||||
languageTag: "ca"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||
label: "Nederlands",
|
||||
languageTag: "nl"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
||||
label: "Türkçe",
|
||||
languageTag: "tr"
|
||||
}
|
||||
["de", "Deutsch"],
|
||||
["no", "Norsk"],
|
||||
["ru", "Русский"],
|
||||
["sv", "Svenska"],
|
||||
["pt-BR", "Português (Brasil)"],
|
||||
["lt", "Lietuvių"],
|
||||
["en", "English"],
|
||||
["it", "Italiano"],
|
||||
["fr", "Français"],
|
||||
["zh-CN", "中文简体"],
|
||||
["es", "Español"],
|
||||
["cs", "Čeština"],
|
||||
["ja", "日本語"],
|
||||
["sk", "Slovenčina"],
|
||||
["pl", "Polski"],
|
||||
["ca", "Català"],
|
||||
["nl", "Nederlands"],
|
||||
["tr", "Türkçe"]
|
||||
/* spell-checker: enable */
|
||||
],
|
||||
].map(
|
||||
([languageTag, label]) =>
|
||||
({
|
||||
languageTag,
|
||||
label,
|
||||
url: "https://gist.github.com/garronej/52baaca1bb925f2296ab32741e062b8e"
|
||||
}) as const
|
||||
),
|
||||
currentLanguageTag: "en"
|
||||
},
|
||||
features: {
|
||||
|
@ -8,9 +8,6 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
const buildOptions = readBuildOptions({ cliCommandOptions });
|
||||
|
||||
await copyKeycloakResourcesToPublic({
|
||||
buildOptions: {
|
||||
...buildOptions,
|
||||
publicDirPath: buildOptions.reactAppRootDirPath
|
||||
}
|
||||
buildOptions
|
||||
});
|
||||
}
|
||||
|
@ -288,13 +288,11 @@ function decodeHtmlEntities(htmlStr){
|
||||
are_same_path(path, [])
|
||||
) || (
|
||||
<#-- attributesByName adds a lot of noise to the output and is not needed -->
|
||||
key == "attributesByName" &&
|
||||
(
|
||||
are_same_path(path, ["profile"]) ||
|
||||
are_same_path(path, ["register"])
|
||||
)
|
||||
) || (
|
||||
key == "attributes" &&
|
||||
are_same_path(path, ["profile"])
|
||||
) || (
|
||||
<#-- We already have the attributes in profile speedup the rendering by filtering it out from the register object -->
|
||||
(key == "attributes" || key == "attributesByName") &&
|
||||
are_same_path(path, ["register"])
|
||||
)
|
||||
>
|
||||
|
@ -14,6 +14,8 @@ export function getNpmWorkspaceRootDirPath(params: {
|
||||
pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")])
|
||||
);
|
||||
|
||||
assert(cwd !== pathSep, "NPM workspace not found");
|
||||
|
||||
try {
|
||||
child_process.execSync("npm config get", {
|
||||
cwd,
|
||||
@ -21,48 +23,46 @@ export function getNpmWorkspaceRootDirPath(params: {
|
||||
});
|
||||
} catch (error) {
|
||||
if (String(error).includes("ENOWORKSPACES")) {
|
||||
assert(cwd !== pathSep, "NPM workspace not found");
|
||||
|
||||
return callee(depth + 1);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { isExpectedDependencyFound } = (() => {
|
||||
const packageJsonFilePath = pathJoin(cwd, "package.json");
|
||||
const packageJsonFilePath = pathJoin(cwd, "package.json");
|
||||
|
||||
assert(fs.existsSync(packageJsonFilePath));
|
||||
if (!fs.existsSync(packageJsonFilePath)) {
|
||||
return callee(depth + 1);
|
||||
}
|
||||
|
||||
const parsedPackageJson = JSON.parse(
|
||||
fs.readFileSync(packageJsonFilePath).toString("utf8")
|
||||
);
|
||||
assert(fs.existsSync(packageJsonFilePath));
|
||||
|
||||
let isExpectedDependencyFound = false;
|
||||
const parsedPackageJson = JSON.parse(
|
||||
fs.readFileSync(packageJsonFilePath).toString("utf8")
|
||||
);
|
||||
|
||||
for (const dependenciesOrDevDependencies of [
|
||||
"dependencies",
|
||||
"devDependencies"
|
||||
] as const) {
|
||||
const dependencies = parsedPackageJson[dependenciesOrDevDependencies];
|
||||
let isExpectedDependencyFound = false;
|
||||
|
||||
if (dependencies === undefined) {
|
||||
continue;
|
||||
}
|
||||
for (const dependenciesOrDevDependencies of [
|
||||
"dependencies",
|
||||
"devDependencies"
|
||||
] as const) {
|
||||
const dependencies = parsedPackageJson[dependenciesOrDevDependencies];
|
||||
|
||||
assert(dependencies instanceof Object);
|
||||
|
||||
if (dependencies[dependencyExpected] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
isExpectedDependencyFound = true;
|
||||
if (dependencies === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return { isExpectedDependencyFound };
|
||||
})();
|
||||
assert(dependencies instanceof Object);
|
||||
|
||||
if (!isExpectedDependencyFound) {
|
||||
if (dependencies[dependencyExpected] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
isExpectedDependencyFound = true;
|
||||
}
|
||||
|
||||
if (!isExpectedDependencyFound && parsedPackageJson.name !== dependencyExpected) {
|
||||
return callee(depth + 1);
|
||||
}
|
||||
|
||||
|
@ -3,15 +3,12 @@ import { assert } from "keycloakify/tools/assert";
|
||||
import { clsx } from "keycloakify/tools/clsx";
|
||||
import { type TemplateProps } from "keycloakify/login/TemplateProps";
|
||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { createUseInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
||||
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
||||
import type { KcContext } from "./kcContext";
|
||||
import type { I18n } from "./i18n";
|
||||
|
||||
const { useInsertLinkTags } = createUseInsertLinkTags();
|
||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
||||
|
||||
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
const {
|
||||
displayInfo = false,
|
||||
@ -63,6 +60,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
}, []);
|
||||
|
||||
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
||||
componentOrHookName: "Template",
|
||||
hrefs: !doUseDefaultCss
|
||||
? []
|
||||
: [
|
||||
@ -75,6 +73,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
});
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
componentOrHookName: "Template",
|
||||
scriptTags: [
|
||||
{
|
||||
type: "module",
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { useEffect, useReducer, Fragment } from "react";
|
||||
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
||||
import { useUserProfileForm, type KcContextLike, type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm";
|
||||
import {
|
||||
useUserProfileForm,
|
||||
getButtonToDisplayForMultivaluedAttributeField,
|
||||
type KcContextLike,
|
||||
type FormAction,
|
||||
type FormFieldError
|
||||
} from "keycloakify/login/lib/useUserProfileForm";
|
||||
import type { Attribute } from "keycloakify/login/kcContext/KcContext";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { I18n } from "./i18n";
|
||||
@ -413,92 +419,34 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
|
||||
|
||||
const { msg } = i18n;
|
||||
|
||||
const hasRemove = (() => {
|
||||
if (values.length === 1) {
|
||||
return false;
|
||||
}
|
||||
const { hasAdd, hasRemove } = getButtonToDisplayForMultivaluedAttributeField({ attribute, values, fieldIndex });
|
||||
|
||||
const minCount = (() => {
|
||||
const { multivalued } = attribute.validators;
|
||||
|
||||
if (multivalued === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const minStr = multivalued.min;
|
||||
|
||||
if (minStr === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parseInt(`${minStr}`);
|
||||
})();
|
||||
|
||||
if (minCount === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (values.length === minCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
||||
const hasAdd = (() => {
|
||||
if (fieldIndex + 1 !== values.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxCount = (() => {
|
||||
const { multivalued } = attribute.validators;
|
||||
|
||||
if (multivalued === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const maxStr = multivalued.max;
|
||||
|
||||
if (maxStr === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parseInt(`${maxStr}`);
|
||||
})();
|
||||
|
||||
if (maxCount === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.length === maxCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})();
|
||||
const idPostfix = `-${attribute.name}-${fieldIndex + 1}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasRemove && (
|
||||
<button
|
||||
id={`kc-remove-${attribute.name}-${fieldIndex + 1}`}
|
||||
type="button"
|
||||
className="pf-c-button pf-m-inline pf-m-link"
|
||||
onClick={() =>
|
||||
dispatchFormAction({
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: values.filter((_, i) => i !== fieldIndex)
|
||||
})
|
||||
}
|
||||
>
|
||||
{msg("remove")}
|
||||
{hasRemove ? <> | </> : null}
|
||||
</button>
|
||||
<>
|
||||
<button
|
||||
id={`kc-remove${idPostfix}`}
|
||||
type="button"
|
||||
className="pf-c-button pf-m-inline pf-m-link"
|
||||
onClick={() =>
|
||||
dispatchFormAction({
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: values.filter((_, i) => i !== fieldIndex)
|
||||
})
|
||||
}
|
||||
>
|
||||
{msg("remove")}
|
||||
</button>
|
||||
{hasAdd ? <> | </> : null}
|
||||
</>
|
||||
)}
|
||||
{hasAdd && (
|
||||
<button
|
||||
id="kc-add-titles-1"
|
||||
id={`kc-add${idPostfix}`}
|
||||
type="button"
|
||||
className="pf-c-button pf-m-inline pf-m-link"
|
||||
onClick={() =>
|
||||
@ -580,7 +528,7 @@ function InputTagSelects(props: InputFiledByTypeProps) {
|
||||
className={classInput}
|
||||
aria-invalid={props.displayableErrors.length !== 0}
|
||||
disabled={attribute.readOnly}
|
||||
checked={valueOrValues.includes(option)}
|
||||
checked={valueOrValues instanceof Array ? valueOrValues.includes(option) : valueOrValues === option}
|
||||
onChange={event =>
|
||||
formValidationDispatch({
|
||||
action: "update",
|
||||
|
@ -4,6 +4,7 @@ import fallbackMessages from "./baseMessages/en";
|
||||
import { getMessages } from "./baseMessages";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { KcContext } from "../kcContext/KcContext";
|
||||
import { Reflect } from "tsafe/Reflect";
|
||||
|
||||
export const fallbackLanguageTag = "en";
|
||||
|
||||
@ -139,7 +140,10 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
return i18n ?? null;
|
||||
}
|
||||
|
||||
return { useI18n };
|
||||
return {
|
||||
useI18n,
|
||||
ofTypeI18n: Reflect<GenericI18n<MessageKey | ExtraMessageKey>>()
|
||||
};
|
||||
}
|
||||
|
||||
function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
|
@ -1,11 +1,10 @@
|
||||
import Fallback from "keycloakify/login/Fallback";
|
||||
|
||||
export default Fallback;
|
||||
|
||||
export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
|
||||
export { getKcContext } from "keycloakify/login/kcContext/getKcContext";
|
||||
export { createGetKcContext } from "keycloakify/login/kcContext/createGetKcContext";
|
||||
export type { LoginThemePageId as PageId } from "keycloakify/bin/shared/constants";
|
||||
export { createUseI18n } from "keycloakify/login/i18n/i18n";
|
||||
export type {
|
||||
ExtendKcContext,
|
||||
Attribute,
|
||||
PasswordPolicies
|
||||
} from "keycloakify/login/kcContext";
|
||||
export { createGetKcContextMock } from "keycloakify/login/kcContext";
|
||||
|
||||
export type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
|
@ -3,14 +3,28 @@ import type {
|
||||
LoginThemePageId,
|
||||
nameOfTheLocalizationRealmOverridesUserProfileProperty
|
||||
} from "keycloakify/bin/shared/constants";
|
||||
import type { ExtractAfterStartingWith } from "keycloakify/tools/ExtractAfterStartingWith";
|
||||
import type { ValueOf } from "keycloakify/tools/ValueOf";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
import type { MessageKey } from "../i18n/i18n";
|
||||
|
||||
type ExtractAfterStartingWith<
|
||||
Prefix extends string,
|
||||
StrEnum
|
||||
> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||
export type ExtendKcContext<
|
||||
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
|
||||
KcContextExtraPropertiesPerPage extends Record<string, Record<string, unknown>>
|
||||
> = ValueOf<{
|
||||
[PageId in keyof KcContextExtraPropertiesPerPage | KcContext["pageId"]]: Extract<
|
||||
KcContext,
|
||||
{ pageId: PageId }
|
||||
> extends never
|
||||
? KcContext.Common &
|
||||
KcContextExtraProperties & {
|
||||
pageId: PageId;
|
||||
} & KcContextExtraPropertiesPerPage[PageId]
|
||||
: Extract<KcContext, { pageId: PageId }> &
|
||||
KcContextExtraProperties &
|
||||
KcContextExtraPropertiesPerPage[PageId];
|
||||
}>;
|
||||
|
||||
/** Take theses type definition with a grain of salt.
|
||||
* Some values might be undefined on some pages.
|
||||
@ -138,12 +152,12 @@ export declare namespace KcContext {
|
||||
|
||||
getFirstError: (...fieldNames: string[]) => string;
|
||||
};
|
||||
properties: Record<string, string | undefined>;
|
||||
authenticationSession?: {
|
||||
authSessionId: string;
|
||||
tabId: string;
|
||||
ssoLoginInOtherTabsUrl: string;
|
||||
};
|
||||
properties: {};
|
||||
__localizationRealmOverridesUserProfile?: Record<string, string>;
|
||||
};
|
||||
|
||||
@ -585,7 +599,7 @@ export declare namespace KcContext {
|
||||
}
|
||||
|
||||
export type UserProfile = {
|
||||
attributes: Attribute[];
|
||||
attributesByName: Record<string, Attribute>;
|
||||
html5DataAnnotations?: Record<string, string>;
|
||||
};
|
||||
|
||||
@ -683,31 +697,31 @@ export type Attribute = {
|
||||
| "photo";
|
||||
};
|
||||
|
||||
export type Validators = Partial<{
|
||||
length: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
integer: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
email: Validators.DoIgnoreEmpty;
|
||||
pattern: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
|
||||
options: Validators.Options;
|
||||
multivalued: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
export type Validators = {
|
||||
length?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
integer?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
email?: Validators.DoIgnoreEmpty;
|
||||
pattern?: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
|
||||
options?: Validators.Options;
|
||||
multivalued?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
// NOTE: Following are the validators for which we don't implement client side validation yet
|
||||
// or for which the validation can't be performed on the client side.
|
||||
/*
|
||||
double: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
"up-immutable-attribute": {};
|
||||
"up-attribute-required-by-metadata-value": {};
|
||||
"up-username-has-value": {};
|
||||
"up-duplicate-username": {};
|
||||
"up-username-mutation": {};
|
||||
"up-email-exists-as-username": {};
|
||||
"up-blank-attribute-value": Validators.ErrorMessage & { "fail-on-null": boolean; };
|
||||
"up-duplicate-email": {};
|
||||
"local-date": Validators.DoIgnoreEmpty;
|
||||
"person-name-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
uri: Validators.DoIgnoreEmpty;
|
||||
"username-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
double?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
"up-immutable-attribute"?: {};
|
||||
"up-attribute-required-by-metadata-value"?: {};
|
||||
"up-username-has-value"?: {};
|
||||
"up-duplicate-username"?: {};
|
||||
"up-username-mutation"?: {};
|
||||
"up-email-exists-as-username"?: {};
|
||||
"up-blank-attribute-value"?: Validators.ErrorMessage & { "fail-on-null": boolean; };
|
||||
"up-duplicate-email"?: {};
|
||||
"local-date"?: Validators.DoIgnoreEmpty;
|
||||
"person-name-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
uri?: Validators.DoIgnoreEmpty;
|
||||
"username-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
*/
|
||||
}>;
|
||||
};
|
||||
|
||||
export declare namespace Validators {
|
||||
export type DoIgnoreEmpty = {
|
||||
|
@ -1,199 +0,0 @@
|
||||
import type { KcContext, Attribute } from "./KcContext";
|
||||
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||
import { isStorybook } from "keycloakify/lib/isStorybook";
|
||||
import { id } from "tsafe/id";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||
import { symToStr } from "tsafe/symToStr";
|
||||
|
||||
export function createGetKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
mockProperties?: Record<string, string>;
|
||||
}) {
|
||||
const { mockData, mockProperties } = params ?? {};
|
||||
|
||||
function getKcContext<
|
||||
PageId extends
|
||||
| ExtendKcContext<KcContextExtension>["pageId"]
|
||||
| undefined = undefined
|
||||
>(params?: {
|
||||
mockPageId?: PageId;
|
||||
storyPartialKcContext?: DeepPartial<
|
||||
Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>
|
||||
>;
|
||||
}): {
|
||||
kcContext: PageId extends undefined
|
||||
? ExtendKcContext<KcContextExtension> | undefined
|
||||
: Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>;
|
||||
} {
|
||||
const { mockPageId, storyPartialKcContext } = params ?? {};
|
||||
|
||||
const realKcContext = getKcContextFromWindow<KcContextExtension>();
|
||||
|
||||
if (mockPageId !== undefined && realKcContext === undefined) {
|
||||
//TODO maybe trow if no mock fo custom page
|
||||
|
||||
warn_that_mock_is_enabled: {
|
||||
if (isStorybook) {
|
||||
break warn_that_mock_is_enabled;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`%cKeycloakify: ${symToStr({
|
||||
mockPageId
|
||||
})} set to ${mockPageId}.`,
|
||||
"background: red; color: yellow; font-size: medium"
|
||||
);
|
||||
}
|
||||
|
||||
const kcContextDefaultMock = kcContextMocks.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
const partialKcContextCustomMock = (() => {
|
||||
const out: DeepPartial<ExtendKcContext<KcContextExtension>> = {};
|
||||
|
||||
const mockDataPick = mockData?.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
if (mockDataPick !== undefined) {
|
||||
deepAssign({
|
||||
target: out,
|
||||
source: mockDataPick
|
||||
});
|
||||
}
|
||||
|
||||
if (storyPartialKcContext !== undefined) {
|
||||
deepAssign({
|
||||
target: out,
|
||||
source: storyPartialKcContext
|
||||
});
|
||||
}
|
||||
|
||||
return Object.keys(out).length === 0 ? undefined : out;
|
||||
})();
|
||||
|
||||
if (
|
||||
kcContextDefaultMock === undefined &&
|
||||
partialKcContextCustomMock === undefined
|
||||
) {
|
||||
console.warn(
|
||||
[
|
||||
`WARNING: You declared the non build in page ${mockPageId} but you didn't `,
|
||||
`provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`,
|
||||
`Please check the documentation of the getKcContext function`
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
const kcContext: any = {};
|
||||
|
||||
deepAssign({
|
||||
target: kcContext,
|
||||
source:
|
||||
kcContextDefaultMock !== undefined
|
||||
? kcContextDefaultMock
|
||||
: { pageId: mockPageId, ...kcContextCommonMock }
|
||||
});
|
||||
|
||||
if (partialKcContextCustomMock !== undefined) {
|
||||
deepAssign({
|
||||
target: kcContext,
|
||||
source: partialKcContextCustomMock
|
||||
});
|
||||
|
||||
if ("profile" in partialKcContextCustomMock) {
|
||||
assert(
|
||||
kcContextDefaultMock !== undefined &&
|
||||
"profile" in kcContextDefaultMock
|
||||
);
|
||||
|
||||
const { attributes } = kcContextDefaultMock.profile;
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes = [];
|
||||
|
||||
const partialAttributes = [
|
||||
...((
|
||||
partialKcContextCustomMock as DeepPartial<KcContext.Register>
|
||||
).profile?.attributes ?? [])
|
||||
].filter(exclude(undefined));
|
||||
|
||||
attributes.forEach(attribute => {
|
||||
const partialAttribute = partialAttributes.find(
|
||||
({ name }) => name === attribute.name
|
||||
);
|
||||
|
||||
const augmentedAttribute: Attribute = {} as any;
|
||||
|
||||
deepAssign({
|
||||
target: augmentedAttribute,
|
||||
source: attribute
|
||||
});
|
||||
|
||||
if (partialAttribute !== undefined) {
|
||||
partialAttributes.splice(
|
||||
partialAttributes.indexOf(partialAttribute),
|
||||
1
|
||||
);
|
||||
|
||||
deepAssign({
|
||||
target: augmentedAttribute,
|
||||
source: partialAttribute
|
||||
});
|
||||
}
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes.push(
|
||||
augmentedAttribute
|
||||
);
|
||||
});
|
||||
|
||||
partialAttributes
|
||||
.map(partialAttribute => ({
|
||||
validators: {},
|
||||
...partialAttribute
|
||||
}))
|
||||
.forEach(partialAttribute => {
|
||||
const { name } = partialAttribute;
|
||||
|
||||
assert(
|
||||
name !== undefined,
|
||||
"If you define a mock attribute it must have at least a name"
|
||||
);
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes.push(
|
||||
partialAttribute as any
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (mockProperties !== undefined) {
|
||||
deepAssign({
|
||||
target: kcContext.properties,
|
||||
source: mockProperties
|
||||
});
|
||||
}
|
||||
|
||||
return { kcContext };
|
||||
}
|
||||
|
||||
if (realKcContext === undefined) {
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
if (realKcContext.themeType !== "login") {
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
return { kcContext: realKcContext as any };
|
||||
}
|
||||
|
||||
return { getKcContext };
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { createGetKcContext } from "./createGetKcContext";
|
||||
|
||||
/** NOTE: We now recommend using createGetKcContext instead of this function to make storybook integration easier
|
||||
* See: https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/account/kcContext.ts
|
||||
*/
|
||||
export function getKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockPageId?: ExtendKcContext<KcContextExtension>["pageId"];
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
}): { kcContext: ExtendKcContext<KcContextExtension> | undefined } {
|
||||
const { mockPageId, mockData } = params ?? {};
|
||||
|
||||
const { getKcContext } = createGetKcContext<KcContextExtension>({
|
||||
mockData
|
||||
});
|
||||
|
||||
const { kcContext } = getKcContext({ mockPageId });
|
||||
|
||||
return { kcContext };
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import type { KcContext } from "./KcContext";
|
||||
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
||||
import { nameOfTheGlobal } from "keycloakify/bin/shared/constants";
|
||||
|
||||
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [
|
||||
KcContextExtension
|
||||
] extends [never]
|
||||
? KcContext
|
||||
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
|
||||
|
||||
export function getKcContextFromWindow<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(): ExtendKcContext<KcContextExtension> | undefined {
|
||||
return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal];
|
||||
}
|
80
src/login/kcContext/getKcContextMock.ts
Normal file
80
src/login/kcContext/getKcContextMock.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { ExtendKcContext, KcContext as KcContextBase } from "./KcContext";
|
||||
import type { LoginThemePageId } from "keycloakify/bin/shared/constants";
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
|
||||
export function createGetKcContextMock<
|
||||
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
|
||||
KcContextExtraPropertiesPerPage extends Record<
|
||||
`${string}.ftl`,
|
||||
Record<string, unknown>
|
||||
>
|
||||
>(params: {
|
||||
kcContextExtraProperties: KcContextExtraProperties;
|
||||
kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage;
|
||||
overrides?: DeepPartial<KcContextExtraProperties & KcContextBase.Common>;
|
||||
overridesPerPage?: {
|
||||
[PageId in
|
||||
| LoginThemePageId
|
||||
| keyof KcContextExtraPropertiesPerPage]?: DeepPartial<
|
||||
Extract<
|
||||
ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>,
|
||||
{ pageId: PageId }
|
||||
>
|
||||
>;
|
||||
};
|
||||
}) {
|
||||
const {
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage,
|
||||
overrides: overrides_global,
|
||||
overridesPerPage: overridesPerPage_global
|
||||
} = params;
|
||||
|
||||
type KcContext = ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>;
|
||||
|
||||
function getKcContextMock<
|
||||
PageId extends LoginThemePageId | keyof KcContextExtraPropertiesPerPage
|
||||
>(params: {
|
||||
pageId: PageId;
|
||||
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
|
||||
}): Extract<KcContext, { pageId: PageId }> {
|
||||
const { pageId, overrides } = params;
|
||||
|
||||
const kcContextMock = structuredCloneButFunctions(
|
||||
kcContextMocks.find(kcContextMock => kcContextMock.pageId === pageId) ?? {
|
||||
...kcContextCommonMock,
|
||||
pageId
|
||||
}
|
||||
);
|
||||
|
||||
[
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage[pageId],
|
||||
overrides_global,
|
||||
overridesPerPage_global?.[pageId],
|
||||
overrides
|
||||
]
|
||||
.filter(exclude(undefined))
|
||||
.forEach(overrides =>
|
||||
deepAssign({
|
||||
target: kcContextMock,
|
||||
source: overrides
|
||||
})
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
return kcContextMock;
|
||||
}
|
||||
|
||||
return { getKcContextMock };
|
||||
}
|
@ -1 +1,7 @@
|
||||
export type { KcContext } from "./KcContext";
|
||||
export type {
|
||||
ExtendKcContext,
|
||||
KcContext,
|
||||
Attribute,
|
||||
PasswordPolicies
|
||||
} from "./KcContext";
|
||||
export { createGetKcContextMock } from "./getKcContextMock";
|
||||
|
@ -9,71 +9,72 @@ import { id } from "tsafe/id";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
||||
|
||||
const attributes: Attribute[] = [
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
"ignore.empty.value": true,
|
||||
min: "3",
|
||||
max: "255"
|
||||
}
|
||||
},
|
||||
displayName: "${username}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "username",
|
||||
readOnly: false,
|
||||
name: "username",
|
||||
value: "xxxx"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
const attributesByName = Object.fromEntries(
|
||||
id<Attribute[]>([
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
"ignore.empty.value": true,
|
||||
min: "3",
|
||||
max: "255"
|
||||
}
|
||||
},
|
||||
email: {
|
||||
"ignore.empty.value": true
|
||||
displayName: "${username}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "username",
|
||||
readOnly: false,
|
||||
name: "username"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
},
|
||||
email: {
|
||||
"ignore.empty.value": true
|
||||
},
|
||||
pattern: {
|
||||
"ignore.empty.value": true,
|
||||
pattern: "gmail\\.com$"
|
||||
}
|
||||
},
|
||||
pattern: {
|
||||
"ignore.empty.value": true,
|
||||
pattern: "gmail\\.com$"
|
||||
}
|
||||
displayName: "${email}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "email",
|
||||
readOnly: false,
|
||||
name: "email"
|
||||
},
|
||||
displayName: "${email}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "email",
|
||||
readOnly: false,
|
||||
name: "email"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
displayName: "${firstName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "firstName"
|
||||
},
|
||||
displayName: "${firstName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "firstName"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
displayName: "${lastName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "lastName"
|
||||
}
|
||||
];
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
displayName: "${lastName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "lastName"
|
||||
}
|
||||
]).map(attribute => [attribute.name, attribute])
|
||||
);
|
||||
|
||||
const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`;
|
||||
|
||||
@ -112,98 +113,34 @@ export const kcContextCommonMock: KcContext.Common = {
|
||||
locale: {
|
||||
supported: [
|
||||
/* spell-checker: disable */
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
||||
label: "Deutsch",
|
||||
languageTag: "de"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||
label: "Norsk",
|
||||
languageTag: "no"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
label: "Русский",
|
||||
languageTag: "ru"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||
label: "Svenska",
|
||||
languageTag: "sv"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
||||
label: "Português (Brasil)",
|
||||
languageTag: "pt-BR"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
||||
label: "Lietuvių",
|
||||
languageTag: "lt"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||
label: "English",
|
||||
languageTag: "en"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||
label: "Italiano",
|
||||
languageTag: "it"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
||||
label: "Français",
|
||||
languageTag: "fr"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
||||
label: "中文简体",
|
||||
languageTag: "zh-CN"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
||||
label: "Español",
|
||||
languageTag: "es"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
||||
label: "Čeština",
|
||||
languageTag: "cs"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
label: "日本語",
|
||||
languageTag: "ja"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
||||
label: "Slovenčina",
|
||||
languageTag: "sk"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||
label: "Polski",
|
||||
languageTag: "pl"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||
label: "Català",
|
||||
languageTag: "ca"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||
label: "Nederlands",
|
||||
languageTag: "nl"
|
||||
},
|
||||
{
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
||||
label: "Türkçe",
|
||||
languageTag: "tr"
|
||||
}
|
||||
["de", "Deutsch"],
|
||||
["no", "Norsk"],
|
||||
["ru", "Русский"],
|
||||
["sv", "Svenska"],
|
||||
["pt-BR", "Português (Brasil)"],
|
||||
["lt", "Lietuvių"],
|
||||
["en", "English"],
|
||||
["it", "Italiano"],
|
||||
["fr", "Français"],
|
||||
["zh-CN", "中文简体"],
|
||||
["es", "Español"],
|
||||
["cs", "Čeština"],
|
||||
["ja", "日本語"],
|
||||
["sk", "Slovenčina"],
|
||||
["pl", "Polski"],
|
||||
["ca", "Català"],
|
||||
["nl", "Nederlands"],
|
||||
["tr", "Türkçe"]
|
||||
/* spell-checker: enable */
|
||||
],
|
||||
].map(
|
||||
([languageTag, label]) =>
|
||||
({
|
||||
languageTag,
|
||||
label,
|
||||
url: "https://gist.github.com/garronej/52baaca1bb925f2296ab32741e062b8e"
|
||||
}) as const
|
||||
),
|
||||
|
||||
currentLanguageTag: "en"
|
||||
},
|
||||
auth: {
|
||||
@ -265,7 +202,7 @@ export const kcContextMocks = [
|
||||
recaptchaRequired: false,
|
||||
pageId: "register.ftl",
|
||||
profile: {
|
||||
attributes
|
||||
attributesByName
|
||||
},
|
||||
scripts: [
|
||||
//"https://www.google.com/recaptcha/api.js"
|
||||
@ -416,7 +353,7 @@ export const kcContextMocks = [
|
||||
...kcContextCommonMock,
|
||||
pageId: "login-update-profile.ftl",
|
||||
profile: {
|
||||
attributes
|
||||
attributesByName
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginIdpLinkConfirm>({
|
||||
@ -472,14 +409,16 @@ export const kcContextMocks = [
|
||||
...kcContextCommonMock,
|
||||
pageId: "idp-review-user-profile.ftl",
|
||||
profile: {
|
||||
attributes
|
||||
attributesByName
|
||||
}
|
||||
}),
|
||||
id<KcContext.UpdateEmail>({
|
||||
...kcContextCommonMock,
|
||||
pageId: "update-email.ftl",
|
||||
profile: {
|
||||
attributes: attributes.filter(attribute => attribute.name === "email")
|
||||
attributesByName: {
|
||||
email: attributesByName["email"]
|
||||
}
|
||||
}
|
||||
}),
|
||||
id<KcContext.SelectAuthenticator>({
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { useEffect } from "react";
|
||||
import { memoize } from "keycloakify/tools/memoize";
|
||||
import { fallbackLanguageTag } from "keycloakify/login/i18n/i18n";
|
||||
import { useConst } from "keycloakify/tools/useConst";
|
||||
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
||||
import { assert } from "tsafe/assert";
|
||||
import {
|
||||
createStatefulObservable,
|
||||
useRerenderOnChange
|
||||
} from "keycloakify/tools/StatefulObservable";
|
||||
import { KcContext } from "../kcContext";
|
||||
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
|
||||
|
||||
const obsTermsMarkdown = createStatefulObservable<string | undefined>(() => undefined);
|
||||
|
||||
@ -27,29 +24,18 @@ export function useDownloadTerms(params: {
|
||||
kcContext: KcContextLike;
|
||||
downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string>;
|
||||
}) {
|
||||
const { kcContext } = params;
|
||||
const { kcContext, downloadTermMarkdown } = params;
|
||||
|
||||
const { downloadTermMarkdownMemoized } = (function useClosure() {
|
||||
const { downloadTermMarkdown } = params;
|
||||
|
||||
const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown);
|
||||
|
||||
const downloadTermMarkdownMemoized = useConst(() =>
|
||||
memoize((currentLanguageTag: string) =>
|
||||
downloadTermMarkdownConst({ currentLanguageTag })
|
||||
)
|
||||
);
|
||||
|
||||
return { downloadTermMarkdownMemoized };
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
useOnFistMount(async () => {
|
||||
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
||||
downloadTermMarkdownMemoized(
|
||||
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
|
||||
).then(thermMarkdown => (obsTermsMarkdown.current = thermMarkdown));
|
||||
const termsMarkdown = await downloadTermMarkdown({
|
||||
currentLanguageTag:
|
||||
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
|
||||
});
|
||||
|
||||
obsTermsMarkdown.current = termsMarkdown;
|
||||
}
|
||||
}, []);
|
||||
});
|
||||
}
|
||||
|
||||
export function useTermsMarkdown() {
|
||||
|
@ -3,6 +3,7 @@ import type { ClassKey } from "keycloakify/login/TemplateProps";
|
||||
|
||||
export const { useGetClassName } = createUseClassName<ClassKey>({
|
||||
defaultClasses: {
|
||||
kcHtmlClass: "login-pf",
|
||||
kcBodyClass: undefined,
|
||||
kcHeaderWrapperClass: undefined,
|
||||
kcLocaleWrapperClass: undefined,
|
||||
@ -54,7 +55,6 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
||||
kcLogoLink: "http://www.keycloak.org",
|
||||
kcContainerClass: "container-fluid",
|
||||
kcSelectAuthListItemTitle: "select-auth-box-paragraph",
|
||||
kcHtmlClass: "login-pf",
|
||||
kcLoginOTPListItemTitleClass: "pf-c-tile__title",
|
||||
"kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift",
|
||||
kcWebAuthnUnknownIcon: "pficon pficon-key unknown-transport-class",
|
||||
|
@ -8,7 +8,8 @@ import { emailRegexp } from "keycloakify/tools/emailRegExp";
|
||||
import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/KcContext";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { formatNumber } from "keycloakify/tools/formatNumber";
|
||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export type FormFieldError = {
|
||||
@ -67,7 +68,7 @@ export type FormAction =
|
||||
export type KcContextLike = {
|
||||
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
||||
profile: {
|
||||
attributes: Attribute[];
|
||||
attributesByName: Record<string, Attribute>;
|
||||
html5DataAnnotations?: Record<string, string>;
|
||||
};
|
||||
passwordRequired?: boolean;
|
||||
@ -102,12 +103,11 @@ namespace internal {
|
||||
};
|
||||
}
|
||||
|
||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
||||
|
||||
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
|
||||
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
componentOrHookName: "useUserProfileForm",
|
||||
scriptTags: Object.keys(kcContext.profile?.html5DataAnnotations ?? {})
|
||||
.filter(key => key !== "kcMultivalued" && key !== "kcNumberFormat") // NOTE: Keycloakify handles it.
|
||||
.map(key => ({
|
||||
@ -136,7 +136,11 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
|
||||
const attributes = (() => {
|
||||
retrocompat_patch: {
|
||||
if ("profile" in kcContext && "attributes" in kcContext.profile && kcContext.profile.attributes.length !== 0) {
|
||||
if (
|
||||
"profile" in kcContext &&
|
||||
"attributesByName" in kcContext.profile &&
|
||||
Object.keys(kcContext.profile.attributesByName).length !== 0
|
||||
) {
|
||||
break retrocompat_patch;
|
||||
}
|
||||
|
||||
@ -216,7 +220,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
assert(false, "Unable to mock user profile from the current kcContext");
|
||||
}
|
||||
|
||||
return kcContext.profile.attributes.map(attribute_pre_group_patch => {
|
||||
return Object.values(kcContext.profile.attributesByName).map(attribute_pre_group_patch => {
|
||||
if (typeof attribute_pre_group_patch.group === "string" && attribute_pre_group_patch.group !== "") {
|
||||
const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } =
|
||||
attribute_pre_group_patch as Attribute & {
|
||||
@ -242,7 +246,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
})();
|
||||
|
||||
for (const attribute of attributes) {
|
||||
syntheticAttributes.push(attribute);
|
||||
syntheticAttributes.push(structuredCloneButFunctions(attribute));
|
||||
|
||||
add_password_and_password_confirm: {
|
||||
if (!kcContext.passwordRequired) {
|
||||
@ -284,6 +288,21 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Consistency patch
|
||||
syntheticAttributes.forEach(attribute => {
|
||||
if (getIsMultivaluedSingleField({ attribute })) {
|
||||
attribute.multivalued = true;
|
||||
}
|
||||
|
||||
if (attribute.multivalued) {
|
||||
attribute.values ??= attribute.value !== undefined ? [attribute.value] : [];
|
||||
delete attribute.value;
|
||||
} else {
|
||||
attribute.value ??= attribute.values?.[0];
|
||||
delete attribute.values;
|
||||
}
|
||||
});
|
||||
|
||||
return syntheticAttributes;
|
||||
})();
|
||||
|
||||
@ -299,10 +318,10 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
break handle_multi_valued_attribute;
|
||||
}
|
||||
|
||||
const values = attribute.values ?? [""];
|
||||
const values = attribute.values?.length ? attribute.values : [""];
|
||||
|
||||
apply_validator_min_range: {
|
||||
if (attribute.annotations.inputType?.startsWith("multiselect")) {
|
||||
if (getIsMultivaluedSingleField({ attribute })) {
|
||||
break apply_validator_min_range;
|
||||
}
|
||||
|
||||
@ -349,7 +368,8 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
attributeName: attribute.name,
|
||||
formFieldStates: initialFormFieldState
|
||||
}),
|
||||
hasLostFocusAtLeastOnce: valueOrValues instanceof Array ? valueOrValues.map(() => false) : false,
|
||||
hasLostFocusAtLeastOnce:
|
||||
valueOrValues instanceof Array && !getIsMultivaluedSingleField({ attribute }) ? valueOrValues.map(() => false) : false,
|
||||
valueOrValues: valueOrValues
|
||||
}))
|
||||
};
|
||||
@ -543,7 +563,7 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
|
||||
server_side_error: {
|
||||
if (attribute.multivalued) {
|
||||
const defaultValues = attribute.values ?? [""];
|
||||
const defaultValues = attribute.values?.length ? attribute.values : [""];
|
||||
|
||||
assert(valueOrValues instanceof Array);
|
||||
|
||||
@ -595,7 +615,7 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
break handle_multi_valued_multi_fields;
|
||||
}
|
||||
|
||||
if (attribute.annotations.inputType?.startsWith("multiselect")) {
|
||||
if (getIsMultivaluedSingleField({ attribute })) {
|
||||
break handle_multi_valued_multi_fields;
|
||||
}
|
||||
|
||||
@ -674,7 +694,7 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
break handle_multi_valued_single_field;
|
||||
}
|
||||
|
||||
if (!attribute.annotations.inputType?.startsWith("multiselect")) {
|
||||
if (!getIsMultivaluedSingleField({ attribute })) {
|
||||
break handle_multi_valued_single_field;
|
||||
}
|
||||
|
||||
@ -913,6 +933,10 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
return valueOrValues;
|
||||
})();
|
||||
|
||||
if (usernameValue === "") {
|
||||
break check_password_policy_x;
|
||||
}
|
||||
|
||||
if (value !== usernameValue) {
|
||||
break check_password_policy_x;
|
||||
}
|
||||
@ -950,6 +974,10 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
{
|
||||
const emailValue = emailFormFieldState.valueOrValues;
|
||||
|
||||
if (emailValue === "") {
|
||||
break check_password_policy_x;
|
||||
}
|
||||
|
||||
if (value !== emailValue) {
|
||||
break check_password_policy_x;
|
||||
}
|
||||
@ -1239,3 +1267,79 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
|
||||
return { getErrors };
|
||||
}
|
||||
|
||||
function getIsMultivaluedSingleField(params: { attribute: Attribute }) {
|
||||
const { attribute } = params;
|
||||
|
||||
return attribute.annotations.inputType?.startsWith("multiselect") ?? false;
|
||||
}
|
||||
|
||||
export function getButtonToDisplayForMultivaluedAttributeField(params: { attribute: Attribute; values: string[]; fieldIndex: number }) {
|
||||
const { attribute, values, fieldIndex } = params;
|
||||
|
||||
const hasRemove = (() => {
|
||||
if (values.length === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const minCount = (() => {
|
||||
const { multivalued } = attribute.validators;
|
||||
|
||||
if (multivalued === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const minStr = multivalued.min;
|
||||
|
||||
if (minStr === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parseInt(`${minStr}`);
|
||||
})();
|
||||
|
||||
if (minCount === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (values.length === minCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
||||
const hasAdd = (() => {
|
||||
if (fieldIndex + 1 !== values.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxCount = (() => {
|
||||
const { multivalued } = attribute.validators;
|
||||
|
||||
if (multivalued === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const maxStr = multivalued.max;
|
||||
|
||||
if (maxStr === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parseInt(`${maxStr}`);
|
||||
})();
|
||||
|
||||
if (maxCount === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.length === maxCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
||||
return { hasRemove, hasAdd };
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
||||
|
||||
<ul id="kc-totp-supported-apps">
|
||||
{totp.supportedApplications.map(app => (
|
||||
<li>{advancedMsg(app)}</li>
|
||||
<li key={app}>{advancedMsg(app)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
|
@ -2,12 +2,10 @@ import { useEffect } from "react";
|
||||
import { clsx } from "keycloakify/tools/clsx";
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import type { KcContext } from "../kcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
||||
|
||||
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>, I18n>) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
|
||||
@ -21,6 +19,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
componentOrHookName: "LoginRecoveryAuthnCodeConfig",
|
||||
scriptTags: [
|
||||
{
|
||||
type: "text/javascript",
|
||||
@ -31,7 +30,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
||||
var tmpTextarea = document.createElement("textarea");
|
||||
var codes = document.getElementById("kc-recovery-codes-list").getElementsByTagName("li");
|
||||
for (i = 0; i < codes.length; i++) {
|
||||
tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\n";
|
||||
tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\\n";
|
||||
}
|
||||
document.body.appendChild(tmpTextarea);
|
||||
tmpTextarea.select();
|
||||
@ -65,7 +64,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
||||
|
||||
for (var i = 0; i < recoveryCodes.length; i++) {
|
||||
var recoveryCodeLiElement = recoveryCodes[i].innerText;
|
||||
recoveryCodeList += recoveryCodeLiElement + "\r\n";
|
||||
recoveryCodeList += recoveryCodeLiElement + "\\r\\n";
|
||||
}
|
||||
|
||||
return recoveryCodeList;
|
||||
@ -84,9 +83,9 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
||||
};
|
||||
|
||||
return fileBodyContent =
|
||||
"${msgStr("recovery-codes-download-file-header")}\n\n" +
|
||||
recoveryCodeList + "\n" +
|
||||
"${msgStr("recovery-codes-download-file-description")}\n\n" +
|
||||
"${msgStr("recovery-codes-download-file-header")}\\n\\n" +
|
||||
recoveryCodeList + "\\n" +
|
||||
"${msgStr("recovery-codes-download-file-description")}\\n\\n" +
|
||||
"${msgStr("recovery-codes-download-file-date")} " + formatCurrentDateTime();
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import type { KcContext } from "../kcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
||||
|
||||
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
|
||||
@ -31,6 +29,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
||||
const { msg, msgStr, advancedMsg } = i18n;
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
componentOrHookName: "WebauthnAuthenticate",
|
||||
scriptTags: [
|
||||
{
|
||||
type: "text/javascript",
|
||||
|
@ -3,12 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import type { KcContext } from "../kcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
||||
|
||||
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>, I18n>) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
|
||||
@ -35,6 +33,7 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
componentOrHookName: "WebauthnRegister",
|
||||
scriptTags: [
|
||||
{
|
||||
type: "text/javascript",
|
||||
|
@ -1,31 +0,0 @@
|
||||
export type AndByDiscriminatingKey<
|
||||
DiscriminatingKey extends string,
|
||||
U1 extends Record<DiscriminatingKey, string>,
|
||||
U2 extends Record<DiscriminatingKey, string>
|
||||
> = AndByDiscriminatingKey.Tf1<DiscriminatingKey, U1, U1, U2>;
|
||||
|
||||
export declare namespace AndByDiscriminatingKey {
|
||||
export type Tf1<
|
||||
DiscriminatingKey extends string,
|
||||
U1,
|
||||
U1Again extends Record<DiscriminatingKey, string>,
|
||||
U2 extends Record<DiscriminatingKey, string>
|
||||
> =
|
||||
U1 extends Pick<U2, DiscriminatingKey>
|
||||
? Tf2<DiscriminatingKey, U1, U2, U1Again>
|
||||
: U1Again[DiscriminatingKey] & U2[DiscriminatingKey] extends never
|
||||
? U1 | U2
|
||||
: U1;
|
||||
|
||||
export type Tf2<
|
||||
DiscriminatingKey extends string,
|
||||
SingletonU1 extends Record<DiscriminatingKey, string>,
|
||||
U2,
|
||||
U1 extends Record<DiscriminatingKey, string>
|
||||
> =
|
||||
U2 extends Pick<SingletonU1, DiscriminatingKey>
|
||||
? U2 & SingletonU1
|
||||
: U2 extends Pick<U1, DiscriminatingKey>
|
||||
? never
|
||||
: U2;
|
||||
}
|
4
src/tools/ExtractAfterStartingWith.ts
Normal file
4
src/tools/ExtractAfterStartingWith.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type ExtractAfterStartingWith<
|
||||
Prefix extends string,
|
||||
StrEnum
|
||||
> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
@ -5,15 +5,15 @@ import type { StatefulObservable } from "../StatefulObservable";
|
||||
/**
|
||||
* Equivalent of https://docs.evt.land/api/react-hooks
|
||||
* */
|
||||
export function useRerenderOnChange($: StatefulObservable<unknown>): void {
|
||||
export function useRerenderOnChange(obs: StatefulObservable<unknown>): void {
|
||||
//NOTE: We use function in case the state is a function
|
||||
const [, setCurrent] = useState(() => $.current);
|
||||
const [, setCurrent] = useState(() => obs.current);
|
||||
|
||||
useObservable(
|
||||
({ registerSubscription }) => {
|
||||
const subscription = $.subscribe(current => setCurrent(() => current));
|
||||
const subscription = obs.subscribe(current => setCurrent(() => current));
|
||||
registerSubscription(subscription);
|
||||
},
|
||||
[$]
|
||||
[obs]
|
||||
);
|
||||
}
|
||||
|
2
src/tools/ValueOf.ts
Normal file
2
src/tools/ValueOf.ts
Normal file
@ -0,0 +1,2 @@
|
||||
/** Pendant of `keyof T` */
|
||||
export type ValueOf<T> = T[keyof T];
|
@ -1,45 +1,61 @@
|
||||
import { assert } from "tsafe/assert";
|
||||
import { is } from "tsafe/is";
|
||||
import { deepClone } from "./deepClone";
|
||||
import { structuredCloneButFunctions } from "./structuredCloneButFunctions";
|
||||
|
||||
//Warning: Be mindful that because of array this is not idempotent.
|
||||
/** NOTE: Array a copied over, not merged. */
|
||||
export function deepAssign(params: {
|
||||
target: Record<string, unknown>;
|
||||
source: Record<string, unknown>;
|
||||
}) {
|
||||
const { target } = params;
|
||||
|
||||
const source = deepClone(params.source);
|
||||
}): void {
|
||||
const { target, source } = params;
|
||||
|
||||
Object.keys(source).forEach(key => {
|
||||
var dereferencedSource = source[key];
|
||||
|
||||
if (dereferencedSource === undefined) {
|
||||
delete target[key];
|
||||
return;
|
||||
}
|
||||
|
||||
if (dereferencedSource instanceof Date) {
|
||||
assign({
|
||||
target,
|
||||
key,
|
||||
value: new Date(dereferencedSource.getTime())
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dereferencedSource instanceof Array) {
|
||||
assign({
|
||||
target,
|
||||
key,
|
||||
value: structuredCloneButFunctions(dereferencedSource)
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
target[key] === undefined ||
|
||||
dereferencedSource instanceof Function ||
|
||||
!(dereferencedSource instanceof Object)
|
||||
) {
|
||||
Object.defineProperty(target, key, {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
assign({
|
||||
target,
|
||||
key,
|
||||
value: dereferencedSource
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const dereferencedTarget = target[key];
|
||||
|
||||
if (dereferencedSource instanceof Array) {
|
||||
assert(is<unknown[]>(dereferencedTarget));
|
||||
assert(is<unknown[]>(dereferencedSource));
|
||||
|
||||
dereferencedSource.forEach(entry => dereferencedTarget.push(entry));
|
||||
|
||||
return;
|
||||
if (!(target[key] instanceof Object)) {
|
||||
target[key] = {};
|
||||
}
|
||||
|
||||
const dereferencedTarget = target[key];
|
||||
|
||||
assert(is<Record<string, unknown>>(dereferencedTarget));
|
||||
assert(is<Record<string, unknown>>(dereferencedSource));
|
||||
|
||||
@ -49,3 +65,18 @@ export function deepAssign(params: {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assign(params: {
|
||||
target: Record<string, unknown>;
|
||||
key: string;
|
||||
value: unknown;
|
||||
}): void {
|
||||
const { target, key, value } = params;
|
||||
|
||||
Object.defineProperty(target, key, {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value
|
||||
});
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
|
||||
export function deepClone<T>(o: T): T {
|
||||
if (!(o instanceof Object)) {
|
||||
return o;
|
||||
}
|
||||
|
||||
if (typeof o === "function") {
|
||||
return o;
|
||||
}
|
||||
|
||||
if (o instanceof Array) {
|
||||
return o.map(deepClone) as any;
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(o).map(([key, value]) => [key, deepClone(value)])
|
||||
) as any;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
type SimpleType = number | string | boolean | null | undefined;
|
||||
type FuncWithSimpleParams<T extends SimpleType[], R> = (...args: T) => R;
|
||||
|
||||
export function memoize<T extends SimpleType[], R>(
|
||||
fn: FuncWithSimpleParams<T, R>,
|
||||
options?: {
|
||||
argsLength?: number;
|
||||
max?: number;
|
||||
}
|
||||
): FuncWithSimpleParams<T, R> {
|
||||
const cache = new Map<string, ReturnType<FuncWithSimpleParams<T, R>>>();
|
||||
|
||||
const { argsLength = fn.length, max = Infinity } = options ?? {};
|
||||
|
||||
return ((...args: Parameters<FuncWithSimpleParams<T, R>>) => {
|
||||
const key = JSON.stringify(
|
||||
args
|
||||
.slice(0, argsLength)
|
||||
.map(v => {
|
||||
if (v === null) {
|
||||
return "null";
|
||||
}
|
||||
if (v === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
switch (typeof v) {
|
||||
case "number":
|
||||
return `number-${v}`;
|
||||
case "string":
|
||||
return `string-${v}`;
|
||||
case "boolean":
|
||||
return `boolean-${v ? "true" : "false"}`;
|
||||
}
|
||||
})
|
||||
.join("-sIs9sAslOdeWlEdIos3-")
|
||||
);
|
||||
|
||||
if (cache.has(key)) {
|
||||
return cache.get(key);
|
||||
}
|
||||
|
||||
if (max === cache.size) {
|
||||
for (const key of cache.keys()) {
|
||||
cache.delete(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const value = fn(...args);
|
||||
|
||||
cache.set(key, value);
|
||||
|
||||
return value;
|
||||
}) as any;
|
||||
}
|
24
src/tools/structuredCloneButFunctions.ts
Normal file
24
src/tools/structuredCloneButFunctions.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
|
||||
/**
|
||||
* Functionally equivalent to structuredClone but
|
||||
* functions are not cloned but kept as is.
|
||||
* (as opposed to structuredClone that chokes if it encounters a function)
|
||||
*/
|
||||
export function structuredCloneButFunctions<T>(o: T): T {
|
||||
if (!(o instanceof Object)) {
|
||||
return o;
|
||||
}
|
||||
|
||||
if (typeof o === "function") {
|
||||
return o;
|
||||
}
|
||||
|
||||
if (o instanceof Array) {
|
||||
return o.map(structuredCloneButFunctions) as any;
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(o).map(([key, value]) => [key, structuredCloneButFunctions(value)])
|
||||
) as any;
|
||||
}
|
@ -1,95 +1,86 @@
|
||||
import { useReducer, useEffect } from "react";
|
||||
import { useEffect, useReducer } from "react";
|
||||
import { useConst } from "keycloakify/tools/useConst";
|
||||
import { id } from "tsafe/id";
|
||||
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
|
||||
|
||||
export function createUseInsertLinkTags() {
|
||||
let linkTagsContext:
|
||||
| {
|
||||
styleSheetHrefs: string[];
|
||||
prAreAllStyleSheetsLoaded: Promise<void>;
|
||||
remove: () => void;
|
||||
}
|
||||
| undefined = undefined;
|
||||
const alreadyMountedComponentOrHookNames = new Set<string>();
|
||||
|
||||
/** NOTE: The hrefs can't changes. There should be only one one call on this. */
|
||||
function useInsertLinkTags(params: { hrefs: string[] }) {
|
||||
const { hrefs } = params;
|
||||
/**
|
||||
* NOTE: The component that use this hook can only be mounded once!
|
||||
* And can't rerender with different hrefs.
|
||||
* If it's mounted again the page will be reloaded.
|
||||
* This simulates the behavior of a server rendered page that imports css stylesheet in the head.
|
||||
*/
|
||||
export function useInsertLinkTags(params: {
|
||||
componentOrHookName: string;
|
||||
hrefs: string[];
|
||||
}) {
|
||||
const { hrefs, componentOrHookName } = params;
|
||||
|
||||
const [areAllStyleSheetsLoaded, setAllStyleSheetLoaded] = useReducer(
|
||||
() => true,
|
||||
hrefs.length === 0
|
||||
);
|
||||
useOnFistMount(() => {
|
||||
const isAlreadyMounted =
|
||||
alreadyMountedComponentOrHookNames.has(componentOrHookName);
|
||||
|
||||
useEffect(() => {
|
||||
let isActive = true;
|
||||
if (isAlreadyMounted) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
mount_link_tags: {
|
||||
if (linkTagsContext !== undefined) {
|
||||
if (
|
||||
JSON.stringify(linkTagsContext.styleSheetHrefs) ===
|
||||
JSON.stringify(hrefs)
|
||||
) {
|
||||
break mount_link_tags;
|
||||
}
|
||||
alreadyMountedComponentOrHookNames.add(componentOrHookName);
|
||||
});
|
||||
|
||||
linkTagsContext.remove();
|
||||
const [areAllStyleSheetsLoaded, setAllStyleSheetsLoaded] = useReducer(
|
||||
() => true,
|
||||
false
|
||||
);
|
||||
|
||||
linkTagsContext = undefined;
|
||||
const refPrAllStyleSheetLoaded = useConst(() => ({
|
||||
current: id<Promise<void> | undefined>(undefined)
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
let isActive = true;
|
||||
|
||||
(refPrAllStyleSheetLoaded.current ??= (async () => {
|
||||
let lastMountedHtmlElement: HTMLLinkElement | undefined = undefined;
|
||||
|
||||
const prs: Promise<void>[] = [];
|
||||
|
||||
for (const href of hrefs) {
|
||||
const htmlElement = document.createElement("link");
|
||||
|
||||
prs.push(
|
||||
new Promise<void>(resolve =>
|
||||
htmlElement.addEventListener("load", () => resolve())
|
||||
)
|
||||
);
|
||||
|
||||
htmlElement.rel = "stylesheet";
|
||||
|
||||
htmlElement.href = href;
|
||||
|
||||
if (lastMountedHtmlElement !== undefined) {
|
||||
lastMountedHtmlElement.insertAdjacentElement("afterend", htmlElement);
|
||||
} else {
|
||||
document.head.prepend(htmlElement);
|
||||
}
|
||||
|
||||
let lastMountedHtmlElement: HTMLLinkElement | undefined = undefined;
|
||||
|
||||
const prs: Promise<void>[] = [];
|
||||
const removeFns: (() => void)[] = [];
|
||||
|
||||
for (const href of hrefs) {
|
||||
const htmlElement = document.createElement("link");
|
||||
|
||||
prs.push(
|
||||
new Promise<void>(resolve =>
|
||||
htmlElement.addEventListener("load", () => resolve())
|
||||
)
|
||||
);
|
||||
|
||||
htmlElement.rel = "stylesheet";
|
||||
|
||||
htmlElement.href = href;
|
||||
|
||||
if (lastMountedHtmlElement !== undefined) {
|
||||
lastMountedHtmlElement.insertAdjacentElement(
|
||||
"afterend",
|
||||
htmlElement
|
||||
);
|
||||
} else {
|
||||
document.head.prepend(htmlElement);
|
||||
}
|
||||
|
||||
removeFns.push(() => {
|
||||
htmlElement.remove();
|
||||
});
|
||||
|
||||
lastMountedHtmlElement = htmlElement;
|
||||
}
|
||||
|
||||
linkTagsContext = {
|
||||
styleSheetHrefs: hrefs,
|
||||
prAreAllStyleSheetsLoaded: Promise.all(prs).then(() => undefined),
|
||||
remove: () => removeFns.forEach(fn => fn())
|
||||
};
|
||||
lastMountedHtmlElement = htmlElement;
|
||||
}
|
||||
|
||||
linkTagsContext.prAreAllStyleSheetsLoaded.then(() => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
setAllStyleSheetLoaded();
|
||||
});
|
||||
await Promise.all(prs);
|
||||
})()).then(() => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
return () => {
|
||||
isActive = false;
|
||||
};
|
||||
}, []);
|
||||
setAllStyleSheetsLoaded();
|
||||
});
|
||||
|
||||
return { areAllStyleSheetsLoaded };
|
||||
}
|
||||
return () => {
|
||||
isActive = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { useInsertLinkTags };
|
||||
return { areAllStyleSheetsLoaded };
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import { useConst } from "keycloakify/tools/useConst";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
|
||||
|
||||
export type ScriptTag = ScriptTag.TextContent | ScriptTag.Src;
|
||||
|
||||
@ -17,95 +17,86 @@ export namespace ScriptTag {
|
||||
};
|
||||
}
|
||||
|
||||
export function createUseInsertScriptTags() {
|
||||
const alreadyMountedComponentOrHookNames = new Set<string>();
|
||||
|
||||
/**
|
||||
* NOTE: The component that use this hook can only be mounded once!
|
||||
* And can't rerender with different scriptTags.
|
||||
* If it's mounted again the page will be reloaded.
|
||||
* This simulates the behavior of a server rendered page that imports javascript in the head.
|
||||
*
|
||||
* The returned function is supposed to be called in a useEffect and
|
||||
* will not download the scripts multiple times event if called more than once (react strict mode).
|
||||
*
|
||||
*/
|
||||
export function useInsertScriptTags(params: {
|
||||
componentOrHookName: string;
|
||||
scriptTags: ScriptTag[];
|
||||
}) {
|
||||
const { scriptTags, componentOrHookName } = params;
|
||||
|
||||
useOnFistMount(() => {
|
||||
const isAlreadyMounted =
|
||||
alreadyMountedComponentOrHookNames.has(componentOrHookName);
|
||||
|
||||
if (isAlreadyMounted) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
alreadyMountedComponentOrHookNames.add(componentOrHookName);
|
||||
});
|
||||
|
||||
let areScriptsInserted = false;
|
||||
|
||||
function useInsertScriptTags(params: { scriptTags: ScriptTag[] }) {
|
||||
const { scriptTags } = params;
|
||||
const insertScriptTags = useCallback(() => {
|
||||
if (areScriptsInserted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentScriptTagsRef = useConst(() => ({
|
||||
current: scriptTags
|
||||
}));
|
||||
|
||||
currentScriptTagsRef.current = scriptTags;
|
||||
|
||||
const insertScriptTags = useCallback(() => {
|
||||
scriptTags.forEach(scriptTag => {
|
||||
// NOTE: Avoid loading same script twice. (Like jQuery for example)
|
||||
{
|
||||
const getFingerprint = (scriptTags: ScriptTag[]) =>
|
||||
scriptTags
|
||||
.map((scriptTag): string => {
|
||||
if ("textContent" in scriptTag) {
|
||||
return scriptTag.textContent;
|
||||
}
|
||||
if ("src" in scriptTag) {
|
||||
return scriptTag.src;
|
||||
}
|
||||
assert(false);
|
||||
})
|
||||
.join("---");
|
||||
|
||||
if (
|
||||
getFingerprint(scriptTags) !==
|
||||
getFingerprint(currentScriptTagsRef.current)
|
||||
) {
|
||||
// NOTE: This is for when the scripts imported in the Template have changed switching
|
||||
// from one page to another in storybook.
|
||||
window.location.reload();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (areScriptsInserted) {
|
||||
return;
|
||||
}
|
||||
|
||||
scriptTags.forEach(scriptTag => {
|
||||
// NOTE: Avoid loading same script twice. (Like jQuery for example)
|
||||
{
|
||||
const scripts = document.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
const script = scripts[i];
|
||||
if ("textContent" in scriptTag) {
|
||||
if (script.textContent === scriptTag.textContent) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ("src" in scriptTag) {
|
||||
if (script.getAttribute("src") === scriptTag.src) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
const htmlElement = document.createElement("script");
|
||||
|
||||
htmlElement.type = scriptTag.type;
|
||||
|
||||
(() => {
|
||||
const scripts = document.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
const script = scripts[i];
|
||||
if ("textContent" in scriptTag) {
|
||||
htmlElement.textContent = scriptTag.textContent;
|
||||
return;
|
||||
if (script.textContent === scriptTag.textContent) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ("src" in scriptTag) {
|
||||
htmlElement.src = scriptTag.src;
|
||||
return;
|
||||
if (script.getAttribute("src") === scriptTag.src) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
assert(false);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
document.head.appendChild(htmlElement);
|
||||
});
|
||||
const htmlElement = document.createElement("script");
|
||||
|
||||
areScriptsInserted = true;
|
||||
}, []);
|
||||
htmlElement.type = scriptTag.type;
|
||||
|
||||
return { insertScriptTags };
|
||||
}
|
||||
(() => {
|
||||
if ("textContent" in scriptTag) {
|
||||
htmlElement.textContent = scriptTag.textContent;
|
||||
return;
|
||||
}
|
||||
if ("src" in scriptTag) {
|
||||
htmlElement.src = scriptTag.src;
|
||||
return;
|
||||
}
|
||||
assert(false);
|
||||
})();
|
||||
|
||||
return { useInsertScriptTags };
|
||||
document.head.appendChild(htmlElement);
|
||||
});
|
||||
|
||||
areScriptsInserted = true;
|
||||
}, []);
|
||||
|
||||
return { insertScriptTags };
|
||||
}
|
||||
|
18
src/tools/useOnFirstMount.ts
Normal file
18
src/tools/useOnFirstMount.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { useEffect } from "react";
|
||||
import { useConst } from "powerhooks/useConst";
|
||||
import { id } from "tsafe/id";
|
||||
|
||||
/** Callback is guaranteed to be call only once per component mount event in strict mode */
|
||||
export function useOnFistMount(callback: () => void) {
|
||||
const refHasCallbackBeenCalled = useConst(() => ({ current: id<boolean>(false) }));
|
||||
|
||||
useEffect(() => {
|
||||
if (refHasCallbackBeenCalled.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
|
||||
refHasCallbackBeenCalled.current = true;
|
||||
}, []);
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import React, { lazy, Suspense } from "react";
|
||||
import React from "react";
|
||||
import Fallback from "../../dist/account";
|
||||
import type { KcContext } from "./kcContext";
|
||||
import { useI18n } from "./i18n";
|
||||
|
||||
const DefaultTemplate = lazy(() => import("../../dist/account/Template"));
|
||||
import Template from "../../dist/account/Template";
|
||||
|
||||
export default function KcApp(props: { kcContext: KcContext }) {
|
||||
const { kcContext } = props;
|
||||
@ -14,14 +13,5 @@ export default function KcApp(props: { kcContext: KcContext }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
{(() => {
|
||||
switch (kcContext.pageId) {
|
||||
default:
|
||||
return <Fallback {...{ kcContext, i18n }} Template={DefaultTemplate} doUseDefaultCss={true} />;
|
||||
}
|
||||
})()}
|
||||
</Suspense>
|
||||
);
|
||||
return <Fallback {...{ kcContext, i18n }} Template={Template} doUseDefaultCss={true} />;
|
||||
}
|
||||
|
@ -1,19 +1,35 @@
|
||||
import React from "react";
|
||||
import { getKcContext, type KcContext } from "./kcContext";
|
||||
import type { KcContext } from "./kcContext";
|
||||
import { getKcContextMock } from "./kcContextMock";
|
||||
import KcApp from "./KcApp";
|
||||
import type { DeepPartial } from "../../dist/tools/DeepPartial";
|
||||
|
||||
export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
|
||||
const { pageId } = params;
|
||||
|
||||
function PageStory(params: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
|
||||
const { kcContext } = getKcContext({
|
||||
mockPageId: pageId,
|
||||
storyPartialKcContext: params.kcContext
|
||||
function PageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
|
||||
const { kcContext: overrides } = props;
|
||||
|
||||
const kcContextMock = getKcContextMock({
|
||||
pageId,
|
||||
overrides
|
||||
});
|
||||
|
||||
return <KcApp kcContext={kcContext} />;
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<KcApp kcContext={kcContextMock} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
return { PageStory };
|
||||
}
|
||||
|
||||
export const parameters = {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { createGetKcContext } from "../../dist/account";
|
||||
import type { ExtendKcContext } from "../../dist/account";
|
||||
|
||||
export const { getKcContext } = createGetKcContext();
|
||||
export type KcContextExtraProperties = {};
|
||||
|
||||
const { kcContext } = getKcContext();
|
||||
export type KcContextExtraPropertiesPerPage = {};
|
||||
|
||||
export type KcContext = NonNullable<typeof kcContext>;
|
||||
export type KcContext = ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>;
|
||||
|
13
stories/account/kcContextMock.ts
Normal file
13
stories/account/kcContextMock.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createGetKcContextMock } from "../../dist/account";
|
||||
import type {
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
} from "./kcContext";
|
||||
|
||||
const kcContextExtraProperties: KcContextExtraProperties = {};
|
||||
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
|
||||
|
||||
export const { getKcContextMock } = createGetKcContextMock({
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage
|
||||
});
|
@ -1,15 +1,17 @@
|
||||
import React from "react";
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "account.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: "account/Account",
|
||||
component: PageStory
|
||||
title: `account/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
@ -1,173 +0,0 @@
|
||||
import React from "react";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
|
||||
const { PageStory } = createPageStory({
|
||||
pageId: "totp.ftl"
|
||||
});
|
||||
|
||||
const meta = {
|
||||
title: "account/Authenticator",
|
||||
component: PageStory
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
export default meta;
|
||||
|
||||
export const Default = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: false,
|
||||
totpSecretEncoded: "HE4W MSTC OBKU CY2M ONXF OV3Q NYYU I3SH",
|
||||
totpSecret: "99fJbpUAcLsnWWpn1DnG",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACk0lEQVR4Xu2YQY6DMAxFjViw5AjcBC6GBBIXozfJEbpkger53wEKqOpmFvaikQYNeVRyHPvbiejXscp95jp+/D5zHT9+n7kO8qeIFDqKzjJo9dC1wUSPP7yG4IPq41lq9ZK+keLZSwXDGwMhOCZgdX4sBVD1qld+GYg/h6ScreBuIDo5FKfVM7Z8aWs9PB2E2/73DdOlwUrK9Ck+HDnzB7ziR8fjlD/OPI8pVQwCi899TkNw2M+tp9XSLFKPIq2UySIhBB906fCQTicFwiv1EUG6+d+bl4zPIYnUk5oIcS69/evPYStUp6P0dJhD/mhauijcth76mOsfw+GFrbfXKJx7LW2N15kijuWIMCYicLQOCEimDp1c0L8PzCLTs3/d+ZQLyl6VqeSIT9nz25szf2ZybHgC31yrXEQIbqaPjX0k9GqWy0N/nLkagsHWNXR0LZwsR357c0pjC6fm+meu5f6f6oszz/qj7GpYCdHf0LVH/gTgtJ/5bVavPJ9svwnBS9qaqwoHOh3G7Ln++HIIDgpKYpFW00dlkX7ruz836THBWQpzd23/xeDsFVroz15fRjsfMyaC8JX2Y8PZf+VIoKff+uTO6WSIUIfSkrl9/rbfnbPr30R8hnMtXA/98ea5lx4ZlSMgQlMsEnb73XnP+yNl/SuR3/lzTSZHMTirMpMcXjWr0U5Mp/rnzmk/TsXkC2/iKEJ5TRG4DZ5KrP/C0RiVmkp+5I8zN1uh2vv9Vs+bzJ4947Y+bz6wl6ZIcv87ZaU2+6PwnoKdb7VYmrf9Z02MxCmNdmparbVJtrA4nA+e9LgIS6dzfvly7j+4XWIuPJp8iE9PbvkzJHYNabt/o5MP+535t/Hj95nr+PH7zHX8m/8B+RAnloz5pi4AAAAASUVORK5CYII=",
|
||||
policy: {
|
||||
type: "totp"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: []
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "ihTeSAMfNsobnPjYiktV8DY-5T4sVzVdrEZRdwfMm8Y",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithTotpEnabled = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: true,
|
||||
totpSecretEncoded: "G55E MZKC JFUD MQLT MFIF EVSB JFLG M6SO",
|
||||
totpSecret: "7zFeBIh6AsaPRVAIVfzN",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACo0lEQVR4Xu2YPY6DMBCFJ6JwyRF8k+RiSCBxsXATjkDpAmX2vTGBwErbbDFTZIps4s8rmfl9RvRPW+W6crYvv66c7cuvK2cjX0Tkho9yW/q5PHSc5QYA62PwXnWqmzrRSUdNL+mygRC8kzQZWhqVO1CRds3YHopnfUkzp2c7ZAY+GIdXywOb0qsdJMXiFn9serYrncxNv/PDkdfUzObk/eNaX368mnl1kML8RH1vFoGzargA1DM/VeWhOpf9+by5iL5Q0NaEUETslHiSIz+dOc4q0tqBrcg7IsnpnZ8BeLmjqjFa4Fps4vlR3484nFHH6OP8o1cTc4I/Q3D4Uqw1TjpkeHqc2R/Rjvb89OUUDAL/CpycOf/o6fUjP505/phrOf8wn+tolsxyD8GZnzyrJSScrNyEcXhHJwrBh2yj2fShPlFB2PQxn935aK1HIB1G1nczm8+P+nbmC7si+zell53a4i97fnhz5Gddxc9iSgLPpPifGn9vDqN0YBL0lpozdx7nd+dDHSiFXkV+NlZO85Efzvzda8yrwkylvlEbhxE4bTJpiCEIkWNHbxD/w/++fJMOVX8p5Q70F0V2EI4LsUWd+ov6Wtgu5aM/OXNIf6jWbKq6zmekA77t88WZr5lXO6vvWaj6kbNo4nv/ceaon0TpYPqrmNJhue/x9+ZKLchbO+cLPrb+aI09BLeob1en2nqkKsUYfOvatSGa/ircmD7i78rNmJoYzXwIKh228z3+ztzef+Cb6S/lSxoWOXM2CO/ZuvlqARtLvX8u1Ie6+d+bd/X9pdS3lrrF/8jPCPytv9AVIbfvddxE4iNFLKL+hH/xCNudKgTvGX/r33ars/y062gQjljfWN8cyKm+f2NPOvqTL//Lvvy6crYvv66c7d/8B/9RFjk6Tp30AAAAAElFTkSuQmCC",
|
||||
policy: {
|
||||
type: "totp",
|
||||
algorithm: "HmacSHA1"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: [
|
||||
{
|
||||
id: "7afaaf7d-f2d5-44f5-a966-e5297f0b2b7a",
|
||||
userLabel: "mobile"
|
||||
}
|
||||
]
|
||||
},
|
||||
message: {
|
||||
summary: "Mobile authenticator configured.",
|
||||
type: "success"
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "0UvyCNJHRJXmdahtRmn0tTPCU2nwLtWBUfPaaX1qb4g",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithManualMode = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
mode: "manual",
|
||||
totp: {
|
||||
enabled: false,
|
||||
totpSecretEncoded: "KZ5H CYTW GBVV ASDE JRXG MMCK HAZU E6TX",
|
||||
totpSecret: "Vzqbv0kPHdLnf0J83Bzw",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACpklEQVR4Xu2YQa6rMAxFXTFgyBLYSbsxJJC6sb6dZAkdMkDNv8cBStHTm/yBM2ikIpqTgePY1w6W/xyLnWc+x5efZz7Hl59nPgf8aWaXPFl+2ZhbzfWaGPTT3yr4mPPPs8nty4ZeKxfzRQ6q4IO1P8zq0c/iffvqtIlLTfw5psxsK3f7JirjTHDqWpQ3T9fC/fytn2956u32bNJv8RHIyZ/n0MvJh8cpvwJ5GffkQaBNYPo2auCyv30YVmtitm4yu1qT5mtXCR9svsqXeih1/I1IbZHLKniTskxPOvCGSB3Wud2/0Vz+5YH9uHZAvzORUAlXaXmY9FHxyZuWI0L5sfs3lkt1vDTbtVtM8bmovrCT26o/0bxozVAWIY3IuTLpsvk3mDNeRv9QqrJWEp+25Xc01/uMVudHpySiE3PXklN1cLSm8yCgKmuWICUIxip4vqM6Y+kalNX3hJNtz+9orgOXQ60noZPrd/H5u74E86I/pfXXm/obXPvOr2juVW8o9nsTS77T5Ix18CZ71sh+qQ7n3+LzY32J5WptXt291Bdaf8tcVw76Hcvpqr31R3CUOri7Q79r4ap61+5O12XoT1leOrFK+HZ/asga/sr0tz5F85wozWq4aMKcP1DK3f54Ttfv+a0iqG1wCU2H/iGWl156IionQYWmngTpan84H9aGy+8nl7I8J5ejOnjP0SNCC/0/lVpydKyPwZz7u/Xef80ouaRHHt7PP5j74BJFfBpJ3vLp460/wdxtxX5KM6XPMvktJ6/7i+YjvfRS/Gs3za3218LJH5qwzKKf7fzd3fXwEWmkf5WTKS3JN1YRTxKhiY9IC6mzUKmP/g3knL8cqoeUiKvJL/EZyT1/sJ/vg+X7G07e7Q/mf40vP898ji8/z3yO/+b/ANUwOXCzdQgqAAAAAElFTkSuQmCC",
|
||||
policy: {
|
||||
type: "totp",
|
||||
algorithm: "HmacSHA1"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: []
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "HiBl2ADzLwKwQS813LOEig1Ymm4xpEu_NacYtWJIuHU",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual"
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const MoreThanOneTotpProviders = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: true,
|
||||
totpSecretEncoded: "G55E MZKC JFUD MQLT MFIF EVSB JFLG M6SO",
|
||||
totpSecret: "7zFeBIh6AsaPRVAIVfzN",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACo0lEQVR4Xu2YPY6DMBCFJ6JwyRF8k+RiSCBxsXATjkDpAmX2vTGBwErbbDFTZIps4s8rmfl9RvRPW+W6crYvv66c7cuvK2cjX0Tkho9yW/q5PHSc5QYA62PwXnWqmzrRSUdNL+mygRC8kzQZWhqVO1CRds3YHopnfUkzp2c7ZAY+GIdXywOb0qsdJMXiFn9serYrncxNv/PDkdfUzObk/eNaX368mnl1kML8RH1vFoGzargA1DM/VeWhOpf9+by5iL5Q0NaEUETslHiSIz+dOc4q0tqBrcg7IsnpnZ8BeLmjqjFa4Fps4vlR3484nFHH6OP8o1cTc4I/Q3D4Uqw1TjpkeHqc2R/Rjvb89OUUDAL/CpycOf/o6fUjP505/phrOf8wn+tolsxyD8GZnzyrJSScrNyEcXhHJwrBh2yj2fShPlFB2PQxn935aK1HIB1G1nczm8+P+nbmC7si+zell53a4i97fnhz5Gddxc9iSgLPpPifGn9vDqN0YBL0lpozdx7nd+dDHSiFXkV+NlZO85Efzvzda8yrwkylvlEbhxE4bTJpiCEIkWNHbxD/w/++fJMOVX8p5Q70F0V2EI4LsUWd+ov6Wtgu5aM/OXNIf6jWbKq6zmekA77t88WZr5lXO6vvWaj6kbNo4nv/ceaon0TpYPqrmNJhue/x9+ZKLchbO+cLPrb+aI09BLeob1en2nqkKsUYfOvatSGa/ircmD7i78rNmJoYzXwIKh228z3+ztzef+Cb6S/lSxoWOXM2CO/ZuvlqARtLvX8u1Ie6+d+bd/X9pdS3lrrF/8jPCPytv9AVIbfvddxE4iNFLKL+hH/xCNudKgTvGX/r33ars/y062gQjljfWN8cyKm+f2NPOvqTL//Lvvy6crYvv66c7d/8B/9RFjk6Tp30AAAAAElFTkSuQmCC",
|
||||
policy: {
|
||||
type: "totp",
|
||||
algorithm: "HmacSHA1"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: [
|
||||
{
|
||||
id: "7afaaf7d-f2d5-44f5-a966-e5297f0b2b7a",
|
||||
userLabel: "Samsung S23"
|
||||
},
|
||||
{
|
||||
id: "fbe22500-d979-45a3-9666-84c99e27958e",
|
||||
userLabel: "Apple Iphone 15"
|
||||
}
|
||||
]
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "0UvyCNJHRJXmdahtRmn0tTPCU2nwLtWBUfPaaX1qb4g",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
);
|
@ -1,34 +1,41 @@
|
||||
import React from "react";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "federatedIdentity.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: "account/FederatedIdentity",
|
||||
component: PageStory
|
||||
title: `account/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const NotConnected = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
pageId: "federatedIdentity.ftl",
|
||||
federatedIdentity: {
|
||||
identities: [
|
||||
{
|
||||
providerId: "google",
|
||||
displayName: "keycloak-oidc",
|
||||
connected: false
|
||||
}
|
||||
],
|
||||
removeLinkPossible: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const NotConnected: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
pageId: "federatedIdentity.ftl",
|
||||
federatedIdentity: {
|
||||
identities: [
|
||||
{
|
||||
providerId: "google",
|
||||
displayName: "keycloak-oidc",
|
||||
connected: false
|
||||
}
|
||||
],
|
||||
removeLinkPossible: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "log.ftl";
|
||||
|
||||
@ -9,346 +9,350 @@ const { PageStory } = createPageStory({
|
||||
});
|
||||
|
||||
const meta = {
|
||||
title: "account/Log",
|
||||
title: `account/${pageId}`,
|
||||
component: PageStory
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
log: {
|
||||
events: [
|
||||
{
|
||||
date: "2024-04-26T12:29:08Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T12:10:56Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:57:34Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:57:21Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:56Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "remove totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:55Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "remove totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:41Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:36Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:32:54Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:54Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:52Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:40Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:09Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "false",
|
||||
key: "remember_me"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:24:17Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [],
|
||||
event: "logout"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:54Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:50Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:47Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:15Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [],
|
||||
event: "logout"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:06Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:22:53Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [],
|
||||
event: "logout"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:21:29Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "false",
|
||||
key: "remember_me"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:17:32Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-18T11:19:09Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-18T11:18:50Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-18T11:18:24Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
log: {
|
||||
events: [
|
||||
{
|
||||
date: "2024-04-26T12:29:08Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T12:10:56Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:57:34Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:57:21Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:56Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "remove totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:55Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "remove totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:41Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:56:36Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [],
|
||||
event: "update totp"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T11:32:54Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:54Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:52Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:40Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:42:09Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "false",
|
||||
key: "remember_me"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:24:17Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [],
|
||||
event: "logout"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:54Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:50Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:47Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:15Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [],
|
||||
event: "logout"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:23:06Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:22:53Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [],
|
||||
event: "logout"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:21:29Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "false",
|
||||
key: "remember_me"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-26T09:17:32Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-18T11:19:09Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-18T11:18:50Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "keycloakify-frontend",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
},
|
||||
{
|
||||
date: "2024-04-18T11:18:24Z",
|
||||
ipAddress: "127.0.0.1",
|
||||
client: "account",
|
||||
details: [
|
||||
{
|
||||
value: "openid-connect",
|
||||
key: "auth_method"
|
||||
},
|
||||
{
|
||||
value: "john.doe",
|
||||
key: "username"
|
||||
}
|
||||
],
|
||||
event: "login"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,24 +1,31 @@
|
||||
import React from "react";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "password.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: "account/Password",
|
||||
component: PageStory
|
||||
title: `account/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const WithMessage = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: { type: "success", summary: "This is a test message" }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const WithMessage: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: { type: "success", summary: "This is a test message" }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,55 +1,62 @@
|
||||
import React from "react";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "sessions.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: "account/Sessions",
|
||||
component: PageStory
|
||||
title: `account/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
sessions: {
|
||||
sessions: [
|
||||
{
|
||||
expires: "2024-04-26T18:14:19Z",
|
||||
clients: ["account"],
|
||||
ipAddress: "172.20.0.1",
|
||||
started: "2024-04-26T08:14:19Z",
|
||||
lastAccess: "2024-04-26T08:30:54Z",
|
||||
id: "af835e30-4821-43b1-b4f7-e732d3cc15d2"
|
||||
},
|
||||
{
|
||||
expires: "2024-04-26T18:14:09Z",
|
||||
clients: ["security-admin-console", "account"],
|
||||
ipAddress: "172.20.0.1",
|
||||
started: "2024-04-26T08:14:09Z",
|
||||
lastAccess: "2024-04-26T08:15:14Z",
|
||||
id: "60a9d8b8-617d-441e-8643-08c3fe30e231"
|
||||
}
|
||||
]
|
||||
},
|
||||
stateChecker: "xQ7EOgFrLi4EvnJ8dbXKhwFGWk_bkOp0X89mhilt1os"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const WithError = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
url: { passwordUrl: "/auth/realms/keycloakify/account/password" },
|
||||
stateChecker: "xQ7EOgFrLi4EvnJ8dbXKhwFGWk_bkOp0X89mhilt1os",
|
||||
message: {
|
||||
summary: "Invalid existing password.",
|
||||
type: "error"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
sessions: {
|
||||
sessions: [
|
||||
{
|
||||
expires: "2024-04-26T18:14:19Z",
|
||||
clients: ["account"],
|
||||
ipAddress: "172.20.0.1",
|
||||
started: "2024-04-26T08:14:19Z",
|
||||
lastAccess: "2024-04-26T08:30:54Z",
|
||||
id: "af835e30-4821-43b1-b4f7-e732d3cc15d2"
|
||||
},
|
||||
{
|
||||
expires: "2024-04-26T18:14:09Z",
|
||||
clients: ["security-admin-console", "account"],
|
||||
ipAddress: "172.20.0.1",
|
||||
started: "2024-04-26T08:14:09Z",
|
||||
lastAccess: "2024-04-26T08:15:14Z",
|
||||
id: "60a9d8b8-617d-441e-8643-08c3fe30e231"
|
||||
}
|
||||
]
|
||||
},
|
||||
stateChecker: "xQ7EOgFrLi4EvnJ8dbXKhwFGWk_bkOp0X89mhilt1os"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
url: { passwordUrl: "/auth/realms/keycloakify/account/password" },
|
||||
stateChecker: "xQ7EOgFrLi4EvnJ8dbXKhwFGWk_bkOp0X89mhilt1os",
|
||||
message: {
|
||||
summary: "Invalid existing password.",
|
||||
type: "error"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,105 +1,187 @@
|
||||
import React from "react";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "totp.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
const { PageStory } = createPageStory({
|
||||
pageId
|
||||
});
|
||||
|
||||
const meta = {
|
||||
title: "account/Authenticator",
|
||||
component: PageStory
|
||||
title: `account/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: false,
|
||||
totpSecretEncoded: "HB2W ESCK KJKF K5DC GJQX S5RQ I5AX CZ2U",
|
||||
totpSecret: "8ubHJRTUtb2ayv0GAqgT",
|
||||
manualUrl: "http://localhost:8080/realms/master/account/totp?mode=manual",
|
||||
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACoUlEQVR4Xu2YPW7DMAxGGWTImCP4JvbFAtiAL+bcxEfI6MEI+z3Kzl+BLh2oIRwEV08FJIr8SMX8T1vtc+bdvvxz5t2+/HPm3eA3Mzvc+jm+fDoPzXIQkPV18N79ejvOWnRpTlcf/XTVV4Aq+MU0uzZaZI12rZVml4YzVcQHDTrEYufBKuQaZEfX1TvDWhEv98+ty79dGX7HRx438ofQfB04Th08jNS+us+n+1l/XbfZKrgcumj/trRnpfak0Dw54Xp/nC+Xy5bOB7x6dDxt1sq5j/tP52vkd5Ee+Xc1JfnKmergxKdcOyOSCgLik5xXEtXBtXVVvTFcC+pdV6+YqIVT+rpNf4hKjqMgXdo9frO5ldAM1dFJfA7+1O9srqiM4W6otuYQIZ0pvivg8mWUvo6q14VImuTocf/JXFq4cP8lPifld/jXidQqOL0CX0V66L9a6Y9Pu34nc7XW8qoQQ9GfcghVwiq4kStqDdl1hGZpZ3f/ZnN9qyCHkTrWq4nl/l/8n8tVmieL1lpFhiDw0uQ84jeXl4ahp+tay/1r6/ai3/kchxKQI6njyCX64zg8n2s4RZEhIDkJ5ZD9b/mTzZnVq2mmMFP/RjJpwNObf7M5qa0Lj9K837/kvKuEu1ov6p9EiEXkd8ei+57f+VwJPSCSfSnNWkR8PvefytFvK/1riPiIEkXM1sGl39qrWlcEm9K3OXnXn2wO4qcFhWZ02uFk2dO/uTwMJx9ostn6Q4mq4LzvDmoSqXqzE5+8BHiOVsJ7arH661ZFuixCGp/+z+ZsWF+ROuR3I9faS39YBS82F9d2rpWkUzWchqFFdWitS5C+9F+5vC/3T/9Fkl8Y+H3BN/9mc/KHRwrxGa9iePnHGvhf9uWfM+/25Z8z7/Zv/gPV7u6J7fyCcQAAAABJRU5ErkJggg==",
|
||||
qrUrl: "http://localhost:8080/realms/master/account/totp?mode=qr",
|
||||
otpCredentials: []
|
||||
},
|
||||
url: {
|
||||
resourcesPath: "/resources/ueycc/account/keycloakify-starter",
|
||||
resourceUrl: "http://localhost:8080/realms/master/account/resource",
|
||||
resourcesCommonPath: "/resources/ueycc/account/keycloakify-starter/resources-common",
|
||||
logUrl: "http://localhost:8080/realms/master/account/log",
|
||||
socialUrl: "http://localhost:8080/realms/master/account/identity",
|
||||
accountUrl: "http://localhost:8080/realms/master/account/",
|
||||
sessionsUrl: "http://localhost:8080/realms/master/account/sessions",
|
||||
totpUrl: "http://localhost:8080/realms/master/account/totp",
|
||||
applicationsUrl: "http://localhost:8080/realms/master/account/applications",
|
||||
passwordUrl: "http://localhost:8080/realms/master/account/password"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const WithTotpEnabled = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: true,
|
||||
totpSecretEncoded: "HB2W ESCK KJKF K5DC GJQX S5RQ I5AX CZ2U",
|
||||
totpSecret: "8ubHJRTUtb2ayv0GAqgT",
|
||||
manualUrl: "http://localhost:8080/realms/master/account/totp?mode=manual",
|
||||
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACoUlEQVR4Xu2YPW7DMAxGGWTImCP4JvbFAtiAL+bcxEfI6MEI+z3Kzl+BLh2oIRwEV08FJIr8SMX8T1vtc+bdvvxz5t2+/HPm3eA3Mzvc+jm+fDoPzXIQkPV18N79ejvOWnRpTlcf/XTVV4Aq+MU0uzZaZI12rZVml4YzVcQHDTrEYufBKuQaZEfX1TvDWhEv98+ty79dGX7HRx438ofQfB04Th08jNS+us+n+1l/XbfZKrgcumj/trRnpfak0Dw54Xp/nC+Xy5bOB7x6dDxt1sq5j/tP52vkd5Ee+Xc1JfnKmergxKdcOyOSCgLik5xXEtXBtXVVvTFcC+pdV6+YqIVT+rpNf4hKjqMgXdo9frO5ldAM1dFJfA7+1O9srqiM4W6otuYQIZ0pvivg8mWUvo6q14VImuTocf/JXFq4cP8lPifld/jXidQqOL0CX0V66L9a6Y9Pu34nc7XW8qoQQ9GfcghVwiq4kStqDdl1hGZpZ3f/ZnN9qyCHkTrWq4nl/l/8n8tVmieL1lpFhiDw0uQ84jeXl4ahp+tay/1r6/ai3/kchxKQI6njyCX64zg8n2s4RZEhIDkJ5ZD9b/mTzZnVq2mmMFP/RjJpwNObf7M5qa0Lj9K837/kvKuEu1ov6p9EiEXkd8ei+57f+VwJPSCSfSnNWkR8PvefytFvK/1riPiIEkXM1sGl39qrWlcEm9K3OXnXn2wO4qcFhWZ02uFk2dO/uTwMJx9ostn6Q4mq4LzvDmoSqXqzE5+8BHiOVsJ7arH661ZFuixCGp/+z+ZsWF+ROuR3I9faS39YBS82F9d2rpWkUzWchqFFdWitS5C+9F+5vC/3T/9Fkl8Y+H3BN/9mc/KHRwrxGa9iePnHGvhf9uWfM+/25Z8z7/Zv/gPV7u6J7fyCcQAAAABJRU5ErkJggg==",
|
||||
qrUrl: "http://localhost:8080/realms/master/account/totp?mode=qr",
|
||||
otpCredentials: []
|
||||
},
|
||||
url: {
|
||||
resourcesPath: "/resources/ueycc/account/keycloakify-starter",
|
||||
resourceUrl: "http://localhost:8080/realms/master/account/resource",
|
||||
resourcesCommonPath: "/resources/ueycc/account/keycloakify-starter/resources-common",
|
||||
logUrl: "http://localhost:8080/realms/master/account/log",
|
||||
socialUrl: "http://localhost:8080/realms/master/account/identity",
|
||||
accountUrl: "http://localhost:8080/realms/master/account/",
|
||||
sessionsUrl: "http://localhost:8080/realms/master/account/sessions",
|
||||
totpUrl: "http://localhost:8080/realms/master/account/totp",
|
||||
applicationsUrl: "http://localhost:8080/realms/master/account/applications",
|
||||
passwordUrl: "http://localhost:8080/realms/master/account/password"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: false,
|
||||
totpSecretEncoded: "HE4W MSTC OBKU CY2M ONXF OV3Q NYYU I3SH",
|
||||
totpSecret: "99fJbpUAcLsnWWpn1DnG",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACk0lEQVR4Xu2YQY6DMAxFjViw5AjcBC6GBBIXozfJEbpkger53wEKqOpmFvaikQYNeVRyHPvbiejXscp95jp+/D5zHT9+n7kO8qeIFDqKzjJo9dC1wUSPP7yG4IPq41lq9ZK+keLZSwXDGwMhOCZgdX4sBVD1qld+GYg/h6ScreBuIDo5FKfVM7Z8aWs9PB2E2/73DdOlwUrK9Ck+HDnzB7ziR8fjlD/OPI8pVQwCi899TkNw2M+tp9XSLFKPIq2UySIhBB906fCQTicFwiv1EUG6+d+bl4zPIYnUk5oIcS69/evPYStUp6P0dJhD/mhauijcth76mOsfw+GFrbfXKJx7LW2N15kijuWIMCYicLQOCEimDp1c0L8PzCLTs3/d+ZQLyl6VqeSIT9nz25szf2ZybHgC31yrXEQIbqaPjX0k9GqWy0N/nLkagsHWNXR0LZwsR357c0pjC6fm+meu5f6f6oszz/qj7GpYCdHf0LVH/gTgtJ/5bVavPJ9svwnBS9qaqwoHOh3G7Ln++HIIDgpKYpFW00dlkX7ruz836THBWQpzd23/xeDsFVroz15fRjsfMyaC8JX2Y8PZf+VIoKff+uTO6WSIUIfSkrl9/rbfnbPr30R8hnMtXA/98ea5lx4ZlSMgQlMsEnb73XnP+yNl/SuR3/lzTSZHMTirMpMcXjWr0U5Mp/rnzmk/TsXkC2/iKEJ5TRG4DZ5KrP/C0RiVmkp+5I8zN1uh2vv9Vs+bzJ4947Y+bz6wl6ZIcv87ZaU2+6PwnoKdb7VYmrf9Z02MxCmNdmparbVJtrA4nA+e9LgIS6dzfvly7j+4XWIuPJp8iE9PbvkzJHYNabt/o5MP+535t/Hj95nr+PH7zHX8m/8B+RAnloz5pi4AAAAASUVORK5CYII=",
|
||||
policy: {
|
||||
type: "totp"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: []
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "ihTeSAMfNsobnPjYiktV8DY-5T4sVzVdrEZRdwfMm8Y",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithManualMode = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
mode: "manual",
|
||||
totp: {
|
||||
enabled: false,
|
||||
totpSecretEncoded: "HB2W ESCK KJKF K5DC GJQX S5RQ I5AX CZ2U",
|
||||
totpSecret: "8ubHJRTUtb2ayv0GAqgT",
|
||||
manualUrl: "http://localhost:8080/realms/master/account/totp?mode=manual",
|
||||
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACoUlEQVR4Xu2YPW7DMAxGGWTImCP4JvbFAtiAL+bcxEfI6MEI+z3Kzl+BLh2oIRwEV08FJIr8SMX8T1vtc+bdvvxz5t2+/HPm3eA3Mzvc+jm+fDoPzXIQkPV18N79ejvOWnRpTlcf/XTVV4Aq+MU0uzZaZI12rZVml4YzVcQHDTrEYufBKuQaZEfX1TvDWhEv98+ty79dGX7HRx438ofQfB04Th08jNS+us+n+1l/XbfZKrgcumj/trRnpfak0Dw54Xp/nC+Xy5bOB7x6dDxt1sq5j/tP52vkd5Ee+Xc1JfnKmergxKdcOyOSCgLik5xXEtXBtXVVvTFcC+pdV6+YqIVT+rpNf4hKjqMgXdo9frO5ldAM1dFJfA7+1O9srqiM4W6otuYQIZ0pvivg8mWUvo6q14VImuTocf/JXFq4cP8lPifld/jXidQqOL0CX0V66L9a6Y9Pu34nc7XW8qoQQ9GfcghVwiq4kStqDdl1hGZpZ3f/ZnN9qyCHkTrWq4nl/l/8n8tVmieL1lpFhiDw0uQ84jeXl4ahp+tay/1r6/ai3/kchxKQI6njyCX64zg8n2s4RZEhIDkJ5ZD9b/mTzZnVq2mmMFP/RjJpwNObf7M5qa0Lj9K837/kvKuEu1ov6p9EiEXkd8ei+57f+VwJPSCSfSnNWkR8PvefytFvK/1riPiIEkXM1sGl39qrWlcEm9K3OXnXn2wO4qcFhWZ02uFk2dO/uTwMJx9ostn6Q4mq4LzvDmoSqXqzE5+8BHiOVsJ7arH661ZFuixCGp/+z+ZsWF+ROuR3I9faS39YBS82F9d2rpWkUzWchqFFdWitS5C+9F+5vC/3T/9Fkl8Y+H3BN/9mc/KHRwrxGa9iePnHGvhf9uWfM+/25Z8z7/Zv/gPV7u6J7fyCcQAAAABJRU5ErkJggg==",
|
||||
qrUrl: "http://localhost:8080/realms/master/account/totp?mode=qr",
|
||||
otpCredentials: []
|
||||
},
|
||||
url: {
|
||||
resourcesPath: "/resources/ueycc/account/keycloakify-starter",
|
||||
resourceUrl: "http://localhost:8080/realms/master/account/resource",
|
||||
resourcesCommonPath: "/resources/ueycc/account/keycloakify-starter/resources-common",
|
||||
logUrl: "http://localhost:8080/realms/master/account/log",
|
||||
socialUrl: "http://localhost:8080/realms/master/account/identity",
|
||||
accountUrl: "http://localhost:8080/realms/master/account/",
|
||||
sessionsUrl: "http://localhost:8080/realms/master/account/sessions",
|
||||
totpUrl: "http://localhost:8080/realms/master/account/totp",
|
||||
applicationsUrl: "http://localhost:8080/realms/master/account/applications",
|
||||
passwordUrl: "http://localhost:8080/realms/master/account/password"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithTotpEnabled: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: true,
|
||||
totpSecretEncoded: "G55E MZKC JFUD MQLT MFIF EVSB JFLG M6SO",
|
||||
totpSecret: "7zFeBIh6AsaPRVAIVfzN",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACo0lEQVR4Xu2YPY6DMBCFJ6JwyRF8k+RiSCBxsXATjkDpAmX2vTGBwErbbDFTZIps4s8rmfl9RvRPW+W6crYvv66c7cuvK2cjX0Tkho9yW/q5PHSc5QYA62PwXnWqmzrRSUdNL+mygRC8kzQZWhqVO1CRds3YHopnfUkzp2c7ZAY+GIdXywOb0qsdJMXiFn9serYrncxNv/PDkdfUzObk/eNaX368mnl1kML8RH1vFoGzargA1DM/VeWhOpf9+by5iL5Q0NaEUETslHiSIz+dOc4q0tqBrcg7IsnpnZ8BeLmjqjFa4Fps4vlR3484nFHH6OP8o1cTc4I/Q3D4Uqw1TjpkeHqc2R/Rjvb89OUUDAL/CpycOf/o6fUjP505/phrOf8wn+tolsxyD8GZnzyrJSScrNyEcXhHJwrBh2yj2fShPlFB2PQxn935aK1HIB1G1nczm8+P+nbmC7si+zell53a4i97fnhz5Gddxc9iSgLPpPifGn9vDqN0YBL0lpozdx7nd+dDHSiFXkV+NlZO85Efzvzda8yrwkylvlEbhxE4bTJpiCEIkWNHbxD/w/++fJMOVX8p5Q70F0V2EI4LsUWd+ov6Wtgu5aM/OXNIf6jWbKq6zmekA77t88WZr5lXO6vvWaj6kbNo4nv/ceaon0TpYPqrmNJhue/x9+ZKLchbO+cLPrb+aI09BLeob1en2nqkKsUYfOvatSGa/ircmD7i78rNmJoYzXwIKh228z3+ztzef+Cb6S/lSxoWOXM2CO/ZuvlqARtLvX8u1Ie6+d+bd/X9pdS3lrrF/8jPCPytv9AVIbfvddxE4iNFLKL+hH/xCNudKgTvGX/r33ars/y062gQjljfWN8cyKm+f2NPOvqTL//Lvvy6crYvv66c7d/8B/9RFjk6Tp30AAAAAElFTkSuQmCC",
|
||||
policy: {
|
||||
type: "totp",
|
||||
algorithm: "HmacSHA1"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: [
|
||||
{
|
||||
id: "7afaaf7d-f2d5-44f5-a966-e5297f0b2b7a",
|
||||
userLabel: "mobile"
|
||||
}
|
||||
]
|
||||
},
|
||||
message: {
|
||||
summary: "Mobile authenticator configured.",
|
||||
type: "success"
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "0UvyCNJHRJXmdahtRmn0tTPCU2nwLtWBUfPaaX1qb4g",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithManualMode: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
mode: "manual",
|
||||
totp: {
|
||||
enabled: false,
|
||||
totpSecretEncoded: "KZ5H CYTW GBVV ASDE JRXG MMCK HAZU E6TX",
|
||||
totpSecret: "Vzqbv0kPHdLnf0J83Bzw",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACpklEQVR4Xu2YQa6rMAxFXTFgyBLYSbsxJJC6sb6dZAkdMkDNv8cBStHTm/yBM2ikIpqTgePY1w6W/xyLnWc+x5efZz7Hl59nPgf8aWaXPFl+2ZhbzfWaGPTT3yr4mPPPs8nty4ZeKxfzRQ6q4IO1P8zq0c/iffvqtIlLTfw5psxsK3f7JirjTHDqWpQ3T9fC/fytn2956u32bNJv8RHIyZ/n0MvJh8cpvwJ5GffkQaBNYPo2auCyv30YVmtitm4yu1qT5mtXCR9svsqXeih1/I1IbZHLKniTskxPOvCGSB3Wud2/0Vz+5YH9uHZAvzORUAlXaXmY9FHxyZuWI0L5sfs3lkt1vDTbtVtM8bmovrCT26o/0bxozVAWIY3IuTLpsvk3mDNeRv9QqrJWEp+25Xc01/uMVudHpySiE3PXklN1cLSm8yCgKmuWICUIxip4vqM6Y+kalNX3hJNtz+9orgOXQ60noZPrd/H5u74E86I/pfXXm/obXPvOr2juVW8o9nsTS77T5Ix18CZ71sh+qQ7n3+LzY32J5WptXt291Bdaf8tcVw76Hcvpqr31R3CUOri7Q79r4ap61+5O12XoT1leOrFK+HZ/asga/sr0tz5F85wozWq4aMKcP1DK3f54Ttfv+a0iqG1wCU2H/iGWl156IionQYWmngTpan84H9aGy+8nl7I8J5ejOnjP0SNCC/0/lVpydKyPwZz7u/Xef80ouaRHHt7PP5j74BJFfBpJ3vLp460/wdxtxX5KM6XPMvktJ6/7i+YjvfRS/Gs3za3218LJH5qwzKKf7fzd3fXwEWmkf5WTKS3JN1YRTxKhiY9IC6mzUKmP/g3knL8cqoeUiKvJL/EZyT1/sJ/vg+X7G07e7Q/mf40vP898ji8/z3yO/+b/ANUwOXCzdQgqAAAAAElFTkSuQmCC",
|
||||
policy: {
|
||||
type: "totp",
|
||||
algorithm: "HmacSHA1"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: []
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "HiBl2ADzLwKwQS813LOEig1Ymm4xpEu_NacYtWJIuHU",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual"
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const MoreThanOneTotpProviders: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
totp: {
|
||||
enabled: true,
|
||||
totpSecretEncoded: "G55E MZKC JFUD MQLT MFIF EVSB JFLG M6SO",
|
||||
totpSecret: "7zFeBIh6AsaPRVAIVfzN",
|
||||
manualUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=manual",
|
||||
supportedApplications: ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
totpSecretQrCode:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACo0lEQVR4Xu2YPY6DMBCFJ6JwyRF8k+RiSCBxsXATjkDpAmX2vTGBwErbbDFTZIps4s8rmfl9RvRPW+W6crYvv66c7cuvK2cjX0Tkho9yW/q5PHSc5QYA62PwXnWqmzrRSUdNL+mygRC8kzQZWhqVO1CRds3YHopnfUkzp2c7ZAY+GIdXywOb0qsdJMXiFn9serYrncxNv/PDkdfUzObk/eNaX368mnl1kML8RH1vFoGzargA1DM/VeWhOpf9+by5iL5Q0NaEUETslHiSIz+dOc4q0tqBrcg7IsnpnZ8BeLmjqjFa4Fps4vlR3484nFHH6OP8o1cTc4I/Q3D4Uqw1TjpkeHqc2R/Rjvb89OUUDAL/CpycOf/o6fUjP505/phrOf8wn+tolsxyD8GZnzyrJSScrNyEcXhHJwrBh2yj2fShPlFB2PQxn935aK1HIB1G1nczm8+P+nbmC7si+zell53a4i97fnhz5Gddxc9iSgLPpPifGn9vDqN0YBL0lpozdx7nd+dDHSiFXkV+NlZO85Efzvzda8yrwkylvlEbhxE4bTJpiCEIkWNHbxD/w/++fJMOVX8p5Q70F0V2EI4LsUWd+ov6Wtgu5aM/OXNIf6jWbKq6zmekA77t88WZr5lXO6vvWaj6kbNo4nv/ceaon0TpYPqrmNJhue/x9+ZKLchbO+cLPrb+aI09BLeob1en2nqkKsUYfOvatSGa/ircmD7i78rNmJoYzXwIKh228z3+ztzef+Cb6S/lSxoWOXM2CO/ZuvlqARtLvX8u1Ie6+d+bd/X9pdS3lrrF/8jPCPytv9AVIbfvddxE4iNFLKL+hH/xCNudKgTvGX/r33ars/y062gQjljfWN8cyKm+f2NPOvqTL//Lvvy6crYvv66c7d/8B/9RFjk6Tp30AAAAAElFTkSuQmCC",
|
||||
policy: {
|
||||
type: "totp",
|
||||
algorithm: "HmacSHA1"
|
||||
},
|
||||
qrUrl: "http://localhost:8080/realms/myrealm/account/totp?mode=qr",
|
||||
otpCredentials: [
|
||||
{
|
||||
id: "7afaaf7d-f2d5-44f5-a966-e5297f0b2b7a",
|
||||
userLabel: "Samsung S23"
|
||||
},
|
||||
{
|
||||
id: "fbe22500-d979-45a3-9666-84c99e27958e",
|
||||
userLabel: "Apple Iphone 15"
|
||||
}
|
||||
]
|
||||
},
|
||||
url: {
|
||||
totpUrl: "http://localhost:8080/realms/myrealm/account/totp"
|
||||
},
|
||||
messagesPerField: {},
|
||||
stateChecker: "0UvyCNJHRJXmdahtRmn0tTPCU2nwLtWBUfPaaX1qb4g",
|
||||
realm: {
|
||||
userManagedAccessAllowed: true,
|
||||
internationalizationEnabled: false
|
||||
},
|
||||
keycloakifyVersion: "9.6.1",
|
||||
themeVersion: "1.0.10",
|
||||
themeType: "account",
|
||||
themeName: "keycloakify",
|
||||
pageId: "totp.ftl"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
9
stories/global.d.ts
vendored
9
stories/global.d.ts
vendored
@ -1,9 +0,0 @@
|
||||
declare module "*.png" {
|
||||
const _default: string;
|
||||
export default _default;
|
||||
}
|
||||
|
||||
declare module "*.md" {
|
||||
const _default: string;
|
||||
export default _default;
|
||||
}
|
@ -21,14 +21,10 @@ import { KeycloakifyRotatingLogo } from "./KeycloakifyRotatingLogo";
|
||||
<h1><a href="#">Keycloakify </a> Storybook</h1>
|
||||
|
||||
<p>
|
||||
This website showcases all the Keycloak user-facing pages that can be customized using Keycloakify.
|
||||
The storybook serves as a comprehensive reference to help you determine which pages you would like to personalize.
|
||||
Keep in mind that customizing the <a href="https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/login/Template.tsx"><code>Template</code></a> component alone will already cover 90% of your customization needs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you discover that a page you wish to customize is not currently supported by Keycloakify, don't worry.
|
||||
Simply refer to <a href="https://docs.keycloakify.dev/limitations#i-have-established-that-a-page-that-i-need-isnt-supported-out-of-the-box-by-keycloakify-now-what">this documentation page</a> for further assistance.
|
||||
This website showcases all the Keycloak user-facing pages of the login and account theme.
|
||||
The storybook serves as a reference to help you determine which pages you would like to personalize.
|
||||
These pages are a direct React adaptation of the [built-in FreeMarker Keycloak pages](https://github.com/keycloak/keycloak/tree/24.0.4/themes/src/main/resources/theme/base).
|
||||
You may notice some visual bugs on certain pages; these issues were not introduced by Keycloakiy and are also present in the default Keycloak 24 theme.
|
||||
</p>
|
||||
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
import React, { lazy, Suspense } from "react";
|
||||
import React from "react";
|
||||
import Fallback from "../../dist/login";
|
||||
import type { KcContext } from "./kcContext";
|
||||
import { useI18n } from "./i18n";
|
||||
import { useDownloadTerms } from "../../dist/login/lib/useDownloadTerms";
|
||||
import tos_en_url from "./tos_en.md";
|
||||
import tos_fr_url from "./tos_fr.md";
|
||||
import Template from "../../dist/login/Template";
|
||||
import UserProfileFormFields from "../../dist/login/UserProfileFormFields";
|
||||
|
||||
@ -14,20 +12,19 @@ export default function KcApp(props: { kcContext: KcContext }) {
|
||||
const i18n = useI18n({ kcContext });
|
||||
|
||||
useDownloadTerms({
|
||||
kcContext: kcContext as any,
|
||||
kcContext,
|
||||
downloadTermMarkdown: async ({ currentLanguageTag }) => {
|
||||
const resource = (() => {
|
||||
switch (currentLanguageTag) {
|
||||
case "fr":
|
||||
return tos_fr_url;
|
||||
return "/tos/tos_fr.md";
|
||||
case "es":
|
||||
return "/tos/tos_es.md";
|
||||
default:
|
||||
return tos_en_url;
|
||||
return "/tos/tos_en.md";
|
||||
}
|
||||
})();
|
||||
|
||||
// webpack5 (used via storybook) loads markdown as string, not url
|
||||
if (resource.includes("\n")) return resource;
|
||||
|
||||
const response = await fetch(resource);
|
||||
return response.text();
|
||||
}
|
||||
@ -38,23 +35,14 @@ export default function KcApp(props: { kcContext: KcContext }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
{(() => {
|
||||
switch (kcContext.pageId) {
|
||||
default:
|
||||
return (
|
||||
<Fallback
|
||||
{...{
|
||||
kcContext,
|
||||
i18n,
|
||||
Template,
|
||||
UserProfileFormFields
|
||||
}}
|
||||
doUseDefaultCss={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
</Suspense>
|
||||
<Fallback
|
||||
{...{
|
||||
kcContext,
|
||||
i18n,
|
||||
Template,
|
||||
UserProfileFormFields
|
||||
}}
|
||||
doUseDefaultCss={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,35 @@
|
||||
import React from "react";
|
||||
import { getKcContext, type KcContext } from "./kcContext";
|
||||
import type { KcContext } from "./kcContext";
|
||||
import { getKcContextMock } from "./kcContextMock";
|
||||
import KcApp from "./KcApp";
|
||||
import type { DeepPartial } from "../../dist/tools/DeepPartial";
|
||||
|
||||
export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
|
||||
const { pageId } = params;
|
||||
|
||||
function PageStory(params: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
|
||||
const { kcContext } = getKcContext({
|
||||
mockPageId: pageId,
|
||||
storyPartialKcContext: params.kcContext
|
||||
function PageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
|
||||
const { kcContext: overrides } = props;
|
||||
|
||||
const kcContextMock = getKcContextMock({
|
||||
pageId,
|
||||
overrides
|
||||
});
|
||||
|
||||
return <KcApp kcContext={kcContext} />;
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<KcApp kcContext={kcContextMock} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
return { PageStory };
|
||||
}
|
||||
|
||||
export const parameters = {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { createGetKcContext } from "../../dist/login";
|
||||
import type { ExtendKcContext } from "../../dist/login";
|
||||
|
||||
export const { getKcContext } = createGetKcContext();
|
||||
export type KcContextExtraProperties = {};
|
||||
|
||||
const { kcContext } = getKcContext();
|
||||
export type KcContextExtraPropertiesPerPage = {};
|
||||
|
||||
export type KcContext = NonNullable<typeof kcContext>;
|
||||
export type KcContext = ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>;
|
||||
|
13
stories/login/kcContextMock.ts
Normal file
13
stories/login/kcContextMock.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createGetKcContextMock } from "../../dist/login";
|
||||
import type {
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
} from "./kcContext";
|
||||
|
||||
const kcContextExtraProperties: KcContextExtraProperties = {};
|
||||
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
|
||||
|
||||
export const { getKcContextMock } = createGetKcContextMock({
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage
|
||||
});
|
21
stories/login/pages/Code.stories.tsx
Normal file
21
stories/login/pages/Code.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "code.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
21
stories/login/pages/DeleteAccountConfirm.stories.tsx
Normal file
21
stories/login/pages/DeleteAccountConfirm.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "delete-account-confirm.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
21
stories/login/pages/DeleteCredential.stories.tsx
Normal file
21
stories/login/pages/DeleteCredential.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "delete-credential.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
@ -1,32 +1,31 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "error.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const WithAnotherMessage = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: { summary: "With another error message" }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const WithAnotherMessage: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: { summary: "With another error message" }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
21
stories/login/pages/FrontchannelLogout.stories.tsx
Normal file
21
stories/login/pages/FrontchannelLogout.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "frontchannel-logout.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "idp-review-user-profile.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,33 +1,55 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "info.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "This is the server message",
|
||||
type: "info"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "Server info message"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithLinkBack: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "Server message"
|
||||
},
|
||||
actionUri: undefined
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithRequiredActions: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "Server message"
|
||||
},
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PROFILE", "VERIFY_EMAIL"]
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,172 +1,185 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const WithoutPasswordField = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { password: false }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const WithoutRegistration = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { registrationAllowed: false }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithoutRegistration: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { registrationAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutRememberMe = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { rememberMe: false }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithoutRememberMe: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { rememberMe: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutPasswordReset = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { resetPasswordAllowed: false }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithoutPasswordReset: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { resetPasswordAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithEmailAsUsername = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { loginWithEmailAllowed: false }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { loginWithEmailAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithPresetUsername = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
login: { username: "max.mustermann@mail.com" }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithPresetUsername: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
login: { username: "max.mustermann@mail.com" }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithImmutablePresetUsername = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
auth: {
|
||||
attemptedUsername: "max.mustermann@mail.com",
|
||||
showUsername: true
|
||||
},
|
||||
usernameHidden: true,
|
||||
message: {
|
||||
type: "info",
|
||||
summary: "Please re-authenticate to continue"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithImmutablePresetUsername: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
auth: {
|
||||
attemptedUsername: "max.mustermann@mail.com",
|
||||
showUsername: true
|
||||
},
|
||||
usernameHidden: true,
|
||||
message: {
|
||||
type: "info",
|
||||
summary: "Please re-authenticate to continue"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithSocialProviders = () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft"
|
||||
},
|
||||
{
|
||||
loginUrl: "facebook",
|
||||
alias: "facebook",
|
||||
providerId: "facebook",
|
||||
displayName: "Facebook"
|
||||
},
|
||||
{
|
||||
loginUrl: "instagram",
|
||||
alias: "instagram",
|
||||
providerId: "instagram",
|
||||
displayName: "Instagram"
|
||||
},
|
||||
{
|
||||
loginUrl: "twitter",
|
||||
alias: "twitter",
|
||||
providerId: "twitter",
|
||||
displayName: "Twitter"
|
||||
},
|
||||
{
|
||||
loginUrl: "linkedin",
|
||||
alias: "linkedin",
|
||||
providerId: "linkedin",
|
||||
displayName: "LinkedIn"
|
||||
},
|
||||
{
|
||||
loginUrl: "stackoverflow",
|
||||
alias: "stackoverflow",
|
||||
providerId: "stackoverflow",
|
||||
displayName: "Stackoverflow"
|
||||
},
|
||||
{
|
||||
loginUrl: "github",
|
||||
alias: "github",
|
||||
providerId: "github",
|
||||
displayName: "Github"
|
||||
},
|
||||
{
|
||||
loginUrl: "gitlab",
|
||||
alias: "gitlab",
|
||||
providerId: "gitlab",
|
||||
displayName: "Gitlab"
|
||||
},
|
||||
{
|
||||
loginUrl: "bitbucket",
|
||||
alias: "bitbucket",
|
||||
providerId: "bitbucket",
|
||||
displayName: "Bitbucket"
|
||||
},
|
||||
{
|
||||
loginUrl: "paypal",
|
||||
alias: "paypal",
|
||||
providerId: "paypal",
|
||||
displayName: "PayPal"
|
||||
},
|
||||
{
|
||||
loginUrl: "openshift",
|
||||
alias: "openshift",
|
||||
providerId: "openshift",
|
||||
displayName: "OpenShift"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
export const WithSocialProviders: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft"
|
||||
},
|
||||
{
|
||||
loginUrl: "facebook",
|
||||
alias: "facebook",
|
||||
providerId: "facebook",
|
||||
displayName: "Facebook"
|
||||
},
|
||||
{
|
||||
loginUrl: "instagram",
|
||||
alias: "instagram",
|
||||
providerId: "instagram",
|
||||
displayName: "Instagram"
|
||||
},
|
||||
{
|
||||
loginUrl: "twitter",
|
||||
alias: "twitter",
|
||||
providerId: "twitter",
|
||||
displayName: "Twitter"
|
||||
},
|
||||
{
|
||||
loginUrl: "linkedin",
|
||||
alias: "linkedin",
|
||||
providerId: "linkedin",
|
||||
displayName: "LinkedIn"
|
||||
},
|
||||
{
|
||||
loginUrl: "stackoverflow",
|
||||
alias: "stackoverflow",
|
||||
providerId: "stackoverflow",
|
||||
displayName: "Stackoverflow"
|
||||
},
|
||||
{
|
||||
loginUrl: "github",
|
||||
alias: "github",
|
||||
providerId: "github",
|
||||
displayName: "Github"
|
||||
},
|
||||
{
|
||||
loginUrl: "gitlab",
|
||||
alias: "gitlab",
|
||||
providerId: "gitlab",
|
||||
displayName: "Gitlab"
|
||||
},
|
||||
{
|
||||
loginUrl: "bitbucket",
|
||||
alias: "bitbucket",
|
||||
providerId: "bitbucket",
|
||||
displayName: "Bitbucket"
|
||||
},
|
||||
{
|
||||
loginUrl: "paypal",
|
||||
alias: "paypal",
|
||||
providerId: "paypal",
|
||||
displayName: "PayPal"
|
||||
},
|
||||
{
|
||||
loginUrl: "openshift",
|
||||
alias: "openshift",
|
||||
providerId: "openshift",
|
||||
displayName: "OpenShift"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutPasswordField: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: { password: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,24 +1,46 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-config-totp.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const WithManualSetUp: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
mode: "manual"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
messagesPerField: {
|
||||
get: (fieldName: string) => (fieldName === "totp" ? "Invalid TOTP" : undefined),
|
||||
exists: (fieldName: string) => fieldName === "totp",
|
||||
existsError: (fieldName: string) => fieldName === "totp",
|
||||
printIfExists: <T,>(fieldName: string, x: T) => (fieldName === "totp" ? x : undefined)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-oauth2-device-verify-user-code.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-idp-link-confirm.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-idp-link-email.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-oauth2-device-verify-user-code.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-oauth-grant.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-otp.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-page-expired.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-password.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
21
stories/login/pages/LoginRecoveryAuthnCodeConfig.stories.tsx
Normal file
21
stories/login/pages/LoginRecoveryAuthnCodeConfig.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-recovery-authn-code-config.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
21
stories/login/pages/LoginRecoveryAuthnCodeInput.stories.tsx
Normal file
21
stories/login/pages/LoginRecoveryAuthnCodeInput.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-recovery-authn-code-input.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
21
stories/login/pages/LoginResetOtp.stories.tsx
Normal file
21
stories/login/pages/LoginResetOtp.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-reset-otp.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
@ -1,24 +1,34 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-reset-password.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: {
|
||||
loginWithEmailAllowed: true,
|
||||
registrationEmailAsUsername: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-update-password.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-update-profile.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,34 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-username.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: {
|
||||
loginWithEmailAllowed: true,
|
||||
registrationEmailAsUsername: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-verify-email.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
21
stories/login/pages/LoginX509Info.stories.tsx
Normal file
21
stories/login/pages/LoginX509Info.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "login-x509-info.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "logout-confirm.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
@ -1,24 +1,117 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "register.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
||||
export const WithFieldError: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
profile: {
|
||||
attributesByName: {
|
||||
email: {
|
||||
value: "max.mustermann@gmail.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
messagesPerField: {
|
||||
existsError: (fieldName: string) => fieldName === "email",
|
||||
exists: (fieldName: string) => fieldName === "email",
|
||||
get: (fieldName: string) => (fieldName === "email" ? "I don't like your email address" : undefined),
|
||||
printIfExists: <T,>(fieldName: string, x: T) => (fieldName === "email" ? x : undefined)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
realm: {
|
||||
registrationEmailAsUsername: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutPassword: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
passwordRequired: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithRecaptcha: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
scripts: ["https://www.google.com/recaptcha/api.js?hl=en"],
|
||||
recaptchaRequired: true,
|
||||
recaptchaSiteKey: "6LfQHvApAAAAAE73SYTd5vS0lB1Xr7zdiQ-6iBVa"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithRecaptchaFrench: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
locale: {
|
||||
currentLanguageTag: "fr"
|
||||
},
|
||||
scripts: ["https://www.google.com/recaptcha/api.js?hl=fr"],
|
||||
recaptchaRequired: true,
|
||||
recaptchaSiteKey: "6LfQHvApAAAAAE73SYTd5vS0lB1Xr7zdiQ-6iBVa"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithPresets: Story = {
|
||||
render: () => (
|
||||
<PageStory
|
||||
kcContext={{
|
||||
profile: {
|
||||
attributesByName: {
|
||||
firstName: {
|
||||
value: "Max"
|
||||
},
|
||||
lastName: {
|
||||
value: "Mustermann"
|
||||
},
|
||||
email: {
|
||||
value: "max.mustermann@gmail.com"
|
||||
},
|
||||
username: {
|
||||
value: "max.mustermann"
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,24 +1,21 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createPageStory, parameters } from "../createPageStory";
|
||||
|
||||
const pageId = "saml-post-form.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
const meta = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
parameters
|
||||
} satisfies Meta<typeof PageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <PageStory />
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user