aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Joseph Garrone <[email protected]>2024-06-06 05:26:06 +0200
committerGravatar Joseph Garrone <[email protected]>2024-06-06 05:26:06 +0200
commit59008f5b87a208885e2351ff1e1cfae5aaae55ae (patch)
tree595a08e3efb372ee8dd176e1c811726b86e1da04
parentMigrate to keycloakify 10 (diff)
Migrate to Keycloakify v10
-rw-r--r--.devcontainer/devcontainer.json38
-rw-r--r--.eslintrc.cjs46
-rw-r--r--.github/workflows/ci.yaml110
-rw-r--r--.prettierignore6
-rw-r--r--.prettierrc.json24
-rw-r--r--.storybook/main.ts30
-rw-r--r--.storybook/preview-head.html3
-rw-r--r--.storybook/preview.ts16
-rw-r--r--README.md45
-rw-r--r--index.html21
-rwxr-xr-xpackage.json98
-rw-r--r--src/account/KcApp.tsx27
-rw-r--r--src/account/KcContext.ts5
-rw-r--r--src/account/PageStory.tsx19
-rw-r--r--src/account/Template.tsx159
-rw-r--r--src/account/pages/Account.stories.tsx19
-rw-r--r--src/account/pages/Password.stories.tsx29
-rw-r--r--src/login/KcApp.tsx40
-rw-r--r--src/login/KcContext.ts5
-rw-r--r--src/login/PageStory.tsx13
-rw-r--r--src/login/Template.tsx278
-rw-r--r--src/login/UserProfileFormFields.tsx699
-rw-r--r--src/login/pages/Register.stories.tsx1
-rw-r--r--src/main.tsx23
-rw-r--r--src/vite-env.d.ts9
-rw-r--r--tsconfig.json42
-rw-r--r--tsconfig.node.json16
-rw-r--r--vite.config.ts18
-rw-r--r--yarn.lock13
29 files changed, 412 insertions, 1440 deletions
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 608e85a..ba1457b 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,23 +1,23 @@
{
- "name": "Keycloakify Starter Devcontainer",
- "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm",
- "features": {
- "ghcr.io/devcontainers/features/docker-in-docker:2": {
- "moby": true,
- "installDockerBuildx": true,
- "version": "latest",
- "dockerDashComposeVersion": "none"
+ "name": "Keycloakify Starter Devcontainer",
+ "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm",
+ "features": {
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {
+ "moby": true,
+ "installDockerBuildx": true,
+ "version": "latest",
+ "dockerDashComposeVersion": "none"
+ },
+ "ghcr.io/devcontainers-contrib/features/maven-sdkman:2": {
+ "version": "latest",
+ "jdkVersion": "latest",
+ "jdkDistro": "ms"
+ }
},
- "ghcr.io/devcontainers-contrib/features/maven-sdkman:2": {
- "version": "latest",
- "jdkVersion": "latest",
- "jdkDistro": "ms"
+ "postCreateCommand": "yarn install",
+ "customizations": {
+ "vscode": {
+ "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
+ }
}
- },
- "postCreateCommand": "yarn install",
- "customizations": {
- "vscode": {
- "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
- }
- }
}
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index ff5edfe..1dc4447 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -1,25 +1,27 @@
module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parser: '@typescript-eslint/parser',
- plugins: ['react-refresh'],
- rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:react-hooks/recommended",
+ "plugin:storybook/recommended"
],
- 'react-hooks/exhaustive-deps': 'off',
- '@typescript-eslint/no-redeclare': 'off',
- 'no-labels': 'off',
- },
- overrides: [
- {
- files: ['**/*.stories.*'],
- rules: {
- 'import/no-anonymous-default-export': 'off',
- },
+ ignorePatterns: ["dist", ".eslintrc.cjs"],
+ parser: "@typescript-eslint/parser",
+ plugins: ["react-refresh"],
+ rules: {
+ "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
+ "react-hooks/exhaustive-deps": "off",
+ "@typescript-eslint/no-redeclare": "off",
+ "no-labels": "off"
},
- ],
-}
+ overrides: [
+ {
+ files: ["**/*.stories.*"],
+ rules: {
+ "import/no-anonymous-default-export": "off"
+ }
+ }
+ ]
+};
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 7f51c0f..894408d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,62 +1,60 @@
name: ci
on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ - uses: bahmutov/npm-install@v1
+ - run: yarn build
+ - run: npx keycloakify
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- - uses: bahmutov/npm-install@v1
- - run: yarn build
- - run: npx keycloakify
-
- check_if_version_upgraded:
- name: Check if version upgrade
- if: github.event_name == 'push'
- runs-on: ubuntu-latest
- needs: test
- outputs:
- from_version: ${{ steps.step1.outputs.from_version }}
- to_version: ${{ steps.step1.outputs.to_version }}
- is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }}
- steps:
- - uses: garronej/[email protected]
- id: step1
- with:
- action_name: is_package_json_version_upgraded
- branch: ${{ github.head_ref || github.ref }}
-
- create_github_release:
- runs-on: ubuntu-latest
- needs: check_if_version_upgraded
- # We create a release only if the version have been upgraded and we are on a default branch
- if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' && github.event_name == 'push'
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- - uses: bahmutov/npm-install@v1
- - run: yarn build
- - run: npx keycloakify
- - run: mv dist_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
- - run: mv dist_keycloak/target/*.jar keycloak-theme.jar
- - uses: softprops/action-gh-release@v1
- with:
- name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
- tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
- target_commitish: ${{ github.head_ref || github.ref }}
- generate_release_notes: true
- draft: false
- files: |
- retrocompat-keycloak-theme.jar
- keycloak-theme.jar
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ check_if_version_upgraded:
+ name: Check if version upgrade
+ if: github.event_name == 'push'
+ runs-on: ubuntu-latest
+ needs: test
+ outputs:
+ from_version: ${{ steps.step1.outputs.from_version }}
+ to_version: ${{ steps.step1.outputs.to_version }}
+ is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }}
+ steps:
+ - uses: garronej/[email protected]
+ id: step1
+ with:
+ action_name: is_package_json_version_upgraded
+ branch: ${{ github.head_ref || github.ref }}
+ create_github_release:
+ runs-on: ubuntu-latest
+ needs: check_if_version_upgraded
+ # We create a release only if the version have been upgraded and we are on a default branch
+ if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' && github.event_name == 'push'
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ - uses: bahmutov/npm-install@v1
+ - run: yarn build
+ - run: npx keycloakify
+ - run: mv dist_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
+ - run: mv dist_keycloak/target/*.jar keycloak-theme.jar
+ - uses: softprops/action-gh-release@v1
+ with:
+ name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
+ tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
+ target_commitish: ${{ github.head_ref || github.ref }}
+ generate_release_notes: true
+ draft: false
+ files: |
+ retrocompat-keycloak-theme.jar
+ keycloak-theme.jar
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..e2a3254
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+node_modules/
+/dist/
+/dist_keycloak/
+/public/keycloak-resources/
+/.vscode/
+/.yarn_home/ \ No newline at end of file
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..aaeb4f2
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,24 @@
+{
+ "printWidth": 90,
+ "tabWidth": 4,
+ "useTabs": false,
+ "semi": true,
+ "singleQuote": false,
+ "trailingComma": "none",
+ "bracketSpacing": true,
+ "arrowParens": "avoid",
+ "overrides": [
+ {
+ "files": [
+ "**/login/pages/*.tsx",
+ "**/account/pages/*.tsx",
+ "**/login/Template.tsx",
+ "**/account/Template.tsx",
+ "**/login/UserProfileFormFields.tsx"
+ ],
+ "options": {
+ "printWidth": 150
+ }
+ }
+ ]
+}
diff --git a/.storybook/main.ts b/.storybook/main.ts
index 305ca53..7da46a4 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -1,20 +1,20 @@
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
- stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
- addons: [
- "@storybook/addon-links",
- "@storybook/addon-essentials",
- "@storybook/addon-onboarding",
- "@storybook/addon-interactions",
- ],
- framework: {
- name: "@storybook/react-vite",
- options: {},
- },
- docs: {
- autodocs: "tag",
- },
- staticDirs: ["../public"]
+ stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
+ addons: [
+ "@storybook/addon-links",
+ "@storybook/addon-essentials",
+ "@storybook/addon-onboarding",
+ "@storybook/addon-interactions"
+ ],
+ framework: {
+ name: "@storybook/react-vite",
+ options: {}
+ },
+ docs: {
+ autodocs: "tag"
+ },
+ staticDirs: ["../public"]
};
export default config;
diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
new file mode 100644
index 0000000..17958c7
--- /dev/null
+++ b/.storybook/preview-head.html
@@ -0,0 +1,3 @@
+<script>
+ console.log("Hello world");
+</script>
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index 37914b1..9e8a01c 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -1,14 +1,14 @@
import type { Preview } from "@storybook/react";
const preview: Preview = {
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/i,
- },
- },
- },
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i
+ }
+ }
+ }
};
export default preview;
diff --git a/README.md b/README.md
index 963178d..e1015ae 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,12 @@
This repo constitutes an easily reusable setup for a Keycloak theme project OR for a Vite SPA React App that generates a
Keycloak theme that goes along with it.
-If you are only looking to create a Keycloak theme (and not a Keycloak theme and an App that share the same codebase) there are a lot of things that you can remove from this starter: [Please read this section of the README](#i-only-want-a-keycloak-theme).
+If you are only looking to create a Keycloak theme (and not a Keycloak theme and an App that share the same codebase) there are a lot of things that you can remove from this starter: [Please read this section of the README](#i-only-want-a-keycloak-theme).
-This starter is based on Vite. There is also [a Webpack based starter](https://github.com/keycloakify/keycloakify-starter-cra).
+This starter is based on Vite. There is also [a Webpack based starter](https://github.com/keycloakify/keycloakify-starter-cra).
> 📣 Looking for a library for redirecting your user to Keycloak when they click on the 'Login' button?
-> Check out [oidc-spa](https://oidc-spa.dev) It's made by us and it's used in the [src/App](https://github.com/keycloakify/keycloakify-starter/tree/main/src/App) of this starter.
+> Check out [oidc-spa](https://oidc-spa.dev) It's made by us and it's used in the [src/App](https://github.com/keycloakify/keycloakify-starter/tree/main/src/App) of this starter.
# Quick start
@@ -30,14 +30,14 @@ yarn # install dependencies (it's like npm install)
yarn storybook # Start Storybook
# This is by far the best way to develop your theme
- # This enable to quickly see your pages in isolation and in different states.
+ # This enable to quickly see your pages in isolation and in different states.
# You can create stories even for pages that you haven't explicitly overloaded. See src/keycloak-theme/login/pages/LoginResetPassword.stories.tsx
# See Keycloakify's storybook for if you need a starting point for your stories: https://github.com/keycloakify/keycloakify/tree/main/stories
yarn dev # See the Hello World app
# Uncomment line 97 of src/keycloak-theme/login/kcContext where it reads: `mockPageId: "login.ftl"`, reload https://localhost:3000
# You can now see the login.ftl page with the mock data. (Don't forget to comment it back when you're done)
-
+
# Install mvn (Maven) if not already done. On mac it's 'brew install maven', on Ubuntu/Debian it's 'sudo apt-get install maven'
yarn build-keycloak-theme # Actually build the theme (generates the .jar to be imported in Keycloak)
@@ -45,11 +45,11 @@ yarn build-keycloak-theme # Actually build the theme (generates the .jar to be i
# your theme on a real Keycloak instance.
npx eject-keycloak-page # Prompt that let you select the pages you want to customize
- # This CLI tools is not guaranty to work, you can always copy pase pages
+ # This CLI tools is not guaranty to work, you can always copy pase pages
# from the Keycloakify repo.
# After you ejected a page you need to edit the src/keycloak-theme/login(or admin)/KcApp.tsx file
- # You need to add a case in the switch for the page you just imported in your project.
- # Look how it's done for the Login page and replicate for your new page.
+ # You need to add a case in the switch for the page you just imported in your project.
+ # Look how it's done for the Login page and replicate for your new page.
npx initialize-email-theme # For initializing your email theme
# Note that Keycloakify does not feature React integration for email yet.
@@ -61,17 +61,17 @@ npx download-builtin-keycloak-theme # For downloading the default theme (as a re
## Using a development container
This starter supports [development containers](https://containers.dev/). You can customize the configuration file [`.devcontainer.json`](./.devcontainer/devcontainer.json) to your liking.
-Checkout [this video](https://www.youtube.com/watch?v=cB86HE_HIDc) to understand dev containers and how to set up your environment.
+Checkout [this video](https://www.youtube.com/watch?v=cB86HE_HIDc) to understand dev containers and how to set up your environment.
-# Theme variant
+# Theme variant
Keycloakify enables you to create different variant for a single theme.
-This enable you to have a single jar that embed two or more theme variant.
+This enable you to have a single jar that embed two or more theme variant.
-![Theme variant](https://content.gitbook.com/content/FcBKODbZbNDgm0rc6a9K/blobs/9iKgs2rv2Kfb2pbs4dRz/image.png)
+![Theme variant](https://content.gitbook.com/content/FcBKODbZbNDgm0rc6a9K/blobs/9iKgs2rv2Kfb2pbs4dRz/image.png)
You can enable this feature by providing multiple theme name in the Keycloakify build option.
-[See documentation](https://docs.keycloakify.dev/build-options#themename)
+[See documentation](https://docs.keycloakify.dev/build-options#themename)
# The CI workflow
@@ -89,23 +89,22 @@ You can enable this feature by providing multiple theme name in the Keycloakify
and when **releasing a new version**: `<org>/<repo>:latest` and `<org>/<repo>:X.Y.Z`
[See on DockerHub](https://hub.docker.com/r/codegouvfr/keycloakify-starter)
-![image](https://user-images.githubusercontent.com/6702424/229296422-9d522707-114e-4282-93f7-01ca38c3a1e0.png)
+![image](https://user-images.githubusercontent.com/6702424/229296422-9d522707-114e-4282-93f7-01ca38c3a1e0.png)
![image](https://user-images.githubusercontent.com/6702424/229296556-a69f2dc9-4653-475c-9c89-d53cf33dc05a.png)
+# The storybook
-# The storybook
-
-![image](https://github.com/keycloakify/keycloakify/assets/6702424/a18ac1ff-dcfd-4b8c-baed-dcda5aa1d762)
+![image](https://github.com/keycloakify/keycloakify/assets/6702424/a18ac1ff-dcfd-4b8c-baed-dcda5aa1d762)
```bash
yarn
yarn storybook
```
-# Docker
+# Docker
-Instructions for building and running the react app (`src/App`) that is collocated with our Keycloak theme.
+Instructions for building and running the react app (`src/App`) that is collocated with our Keycloak theme.
```bash
docker build -f Dockerfile -t keycloakify/keycloakify-starter:main .
@@ -115,8 +114,8 @@ docker run -it -dp 8083:80 keycloakify/keycloakify-starter:main
# I only want a Keycloak theme
-If you are only looking to create a Keycloak theme and not a Theme + a React app, you can run theses few commands to refactor the template
-and remove unnecessary files.
+If you are only looking to create a Keycloak theme and not a Theme + a React app, you can run theses few commands to refactor the template
+and remove unnecessary files.
```bash
cd path/to/keycloakify-starter
@@ -193,7 +192,7 @@ jobs:
steps:
- uses: garronej/[email protected]
id: step1
- with:
+ with:
action_name: is_package_json_version_upgraded
branch: \${{ github.head_ref || github.ref }}
@@ -226,4 +225,4 @@ jobs:
EOF
```
-You can also remove `oidc-spa`, `powerhooks`, `zod` and `tsafe` from your dependencies.
+You can also remove `oidc-spa`, `powerhooks`, `zod` and `tsafe` from your dependencies.
diff --git a/index.html b/index.html
index 6791f43..089aa15 100644
--- a/index.html
+++ b/index.html
@@ -1,15 +1,14 @@
<!doctype html>
-<html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
- </head>
-
- <body>
- <div id="root"></div>
- <script type="module" src="/src/main.tsx"></script>
- </body>
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
+ </head>
+ <body>
+ <div id="root"></div>
+ <script type="module" src="/src/main.tsx"></script>
+ </body>
</html>
diff --git a/package.json b/package.json
index 2cd6edb..3b4735d 100755
--- a/package.json
+++ b/package.json
@@ -1,50 +1,52 @@
{
- "name": "keycloakify-starter",
- "version": "6.1.10",
- "description": "Starter for Keycloakify 10",
- "repository": {
- "type": "git",
- "url": "git://github.com/codegouvfr/keycloakify-starter.git"
- },
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc && vite build",
- "build-keycloak-theme": "yarn build && keycloakify",
- "storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build"
- },
- "license": "MIT",
- "keywords": [],
- "dependencies": {
- "keycloakify": "10.0.0-rc.31",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@storybook/addon-essentials": "^8.0.2",
- "@storybook/addon-interactions": "^8.0.2",
- "@storybook/addon-links": "^8.0.2",
- "@storybook/addon-onboarding": "^8.0.2",
- "@storybook/blocks": "^8.0.2",
- "@storybook/react": "^8.0.2",
- "@storybook/react-vite": "^8.0.2",
- "@storybook/test": "^8.0.2",
- "@types/react": "^18.2.43",
- "@types/react-dom": "^18.2.17",
- "@typescript-eslint/eslint-plugin": "^6.14.0",
- "@typescript-eslint/parser": "^6.14.0",
- "@vitejs/plugin-react": "^4.2.1",
- "eslint": "^8.55.0",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-react-refresh": "^0.4.5",
- "eslint-plugin-storybook": "^0.8.0",
- "storybook": "^8.0.2",
- "typescript": "^5.2.2",
- "vite": "^5.0.8"
- },
- "_comment": "See https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092",
- "resolutions": {
- "jackspeak": "2.1.1"
- }
+ "name": "keycloakify-starter",
+ "version": "6.1.10",
+ "description": "Starter for Keycloakify 10",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/codegouvfr/keycloakify-starter.git"
+ },
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "build-keycloak-theme": "yarn build && keycloakify",
+ "storybook": "storybook dev -p 6006",
+ "build-storybook": "storybook build",
+ "format": "npx prettier . --write"
+ },
+ "license": "MIT",
+ "keywords": [],
+ "dependencies": {
+ "keycloakify": "10.0.0-rc.33",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@storybook/addon-essentials": "^8.0.2",
+ "@storybook/addon-interactions": "^8.0.2",
+ "@storybook/addon-links": "^8.0.2",
+ "@storybook/addon-onboarding": "^8.0.2",
+ "@storybook/blocks": "^8.0.2",
+ "@storybook/react": "^8.0.2",
+ "@storybook/react-vite": "^8.0.2",
+ "@storybook/test": "^8.0.2",
+ "@types/react": "^18.2.43",
+ "@types/react-dom": "^18.2.17",
+ "@typescript-eslint/eslint-plugin": "^6.14.0",
+ "@typescript-eslint/parser": "^6.14.0",
+ "@vitejs/plugin-react": "^4.2.1",
+ "eslint": "^8.55.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.5",
+ "eslint-plugin-storybook": "^0.8.0",
+ "prettier": "3.3.1",
+ "storybook": "^8.0.2",
+ "typescript": "^5.2.2",
+ "vite": "^5.0.8"
+ },
+ "_comment": "See https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092",
+ "resolutions": {
+ "jackspeak": "2.1.1"
+ }
}
diff --git a/src/account/KcApp.tsx b/src/account/KcApp.tsx
index 7920bba..ca2d8f3 100644
--- a/src/account/KcApp.tsx
+++ b/src/account/KcApp.tsx
@@ -1,9 +1,11 @@
import { Suspense, lazy } from "react";
-import type { KcContext } from "./kcContext";
+import type { PageProps } from "keycloakify/account";
+import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
-
+import Template from "keycloakify/account/Template";
const Fallback = lazy(() => import("keycloakify/account/Fallback"));
-const Template = lazy(() => import("./Template"));
+
+const classes = {} satisfies PageProps["classes"];
export default function KcApp(props: { kcContext: KcContext }) {
const { kcContext } = props;
@@ -19,14 +21,17 @@ export default function KcApp(props: { kcContext: KcContext }) {
{(() => {
switch (kcContext.pageId) {
default:
- return <Fallback
- {...{
- kcContext,
- i18n,
- Template,
- }}
- doUseDefaultCss={true}
- />
+ return (
+ <Fallback
+ {...{
+ kcContext,
+ i18n,
+ classes,
+ Template
+ }}
+ doUseDefaultCss={true}
+ />
+ );
}
})()}
</Suspense>
diff --git a/src/account/KcContext.ts b/src/account/KcContext.ts
index 985ea03..7072fbd 100644
--- a/src/account/KcContext.ts
+++ b/src/account/KcContext.ts
@@ -5,4 +5,7 @@ export type KcContextExtraProperties = {};
export type KcContextExtraPropertiesPerPage = {};
-export type KcContext = ExtendKcContext<KcContextExtraProperties, KcContextExtraPropertiesPerPage>;
+export type KcContext = ExtendKcContext<
+ KcContextExtraProperties,
+ KcContextExtraPropertiesPerPage
+>;
diff --git a/src/account/PageStory.tsx b/src/account/PageStory.tsx
index e249d58..65bb37a 100644
--- a/src/account/PageStory.tsx
+++ b/src/account/PageStory.tsx
@@ -1,10 +1,10 @@
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
-import type { KcContext } from "./kcContext";
+import type { KcContext } from "./KcContext";
import { createGetKcContextMock } from "keycloakify/account";
import type {
KcContextExtraProperties,
KcContextExtraPropertiesPerPage
-} from "./kcContext";
+} from "./KcContext";
import KcApp from "./KcApp";
const kcContextExtraProperties: KcContextExtraProperties = {};
@@ -17,10 +17,14 @@ export const { getKcContextMock } = createGetKcContextMock({
overridesPerPage: {}
});
-export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
+export function createPageStory<PageId extends KcContext["pageId"]>(params: {
+ pageId: PageId;
+}) {
const { pageId } = params;
- function PageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
+ function PageStory(props: {
+ kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
+ }) {
const { kcContext: overrides } = props;
const kcContextMock = getKcContextMock({
@@ -28,13 +32,8 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
overrides
});
- return (
- <>
- <KcApp kcContext={kcContextMock} />
- </>
- );
+ return <KcApp kcContext={kcContextMock} />;
}
return { PageStory };
}
-
diff --git a/src/account/Template.tsx b/src/account/Template.tsx
deleted file mode 100644
index 9e6cd0d..0000000
--- a/src/account/Template.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copy pasted from: https://github.com/keycloakify/keycloakify/blob/main/src/account/Template.tsx
-
-import { useEffect } from "react";
-import { assert } from "keycloakify/tools/assert";
-import { clsx } from "keycloakify/tools/clsx";
-import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
-import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
-import { useSetClassName } from "keycloakify/tools/useSetClassName";
-import type { TemplateProps } from "keycloakify/account/TemplateProps";
-import type { KcContext } from "./KcContext";
-import type { I18n } from "./i18n";
-
-export default function Template(props: TemplateProps<KcContext, I18n>) {
- const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props;
-
- const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
-
- const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
-
- const { locale, url, features, realm, message, referrer } = kcContext;
-
- useEffect(() => {
- document.title = msgStr("accountManagementTitle");
- }, []);
-
- useSetClassName({
- qualifiedName: "html",
- className: getClassName("kcHtmlClass")
- });
-
- useSetClassName({
- qualifiedName: "body",
- className: clsx("admin-console", "user", getClassName("kcBodyClass"))
- });
-
- useEffect(() => {
- const { currentLanguageTag } = locale ?? {};
-
- if (currentLanguageTag === undefined) {
- return;
- }
-
- const html = document.querySelector("html");
- assert(html !== null);
- html.lang = currentLanguageTag;
- }, []);
-
- const { areAllStyleSheetsLoaded } = useInsertLinkTags({
- componentOrHookName: "Template",
- hrefs: !doUseDefaultCss
- ? []
- : [
- `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`,
- `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`,
- `${url.resourcesPath}/css/account.css`
- ]
- });
-
- if (!areAllStyleSheetsLoaded) {
- return null;
- }
-
- return (
- <>
- <header className="navbar navbar-default navbar-pf navbar-main header">
- <nav className="navbar" role="navigation">
- <div className="navbar-header">
- <div className="container">
- <h1 className="navbar-title">Keycloak</h1>
- </div>
- </div>
- <div className="navbar-collapse navbar-collapse-1">
- <div className="container">
- <ul className="nav navbar-nav navbar-utility">
- {realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
- <li>
- <div className="kc-dropdown" id="kc-locale-dropdown">
- <a href="#" id="kc-current-locale-link">
- {labelBySupportedLanguageTag[currentLanguageTag]}
- </a>
- <ul>
- {locale.supported.map(({ languageTag }) => (
- <li key={languageTag} className="kc-dropdown-item">
- <a href={getChangeLocalUrl(languageTag)}>{labelBySupportedLanguageTag[languageTag]}</a>
- </li>
- ))}
- </ul>
- </div>
- </li>
- )}
- {referrer?.url && (
- <li>
- <a href={referrer.url} id="referrer">
- {msg("backTo", referrer.name)}
- </a>
- </li>
- )}
- <li>
- <a href={url.getLogoutUrl()}>{msg("doSignOut")}</a>
- </li>
- </ul>
- </div>
- </div>
- </nav>
- </header>
-
- <div className="container">
- <div className="bs-sidebar col-sm-3">
- <ul>
- <li className={clsx(active === "account" && "active")}>
- <a href={url.accountUrl}>{msg("account")}</a>
- </li>
- {features.passwordUpdateSupported && (
- <li className={clsx(active === "password" && "active")}>
- <a href={url.passwordUrl}>{msg("password")}</a>
- </li>
- )}
- <li className={clsx(active === "totp" && "active")}>
- <a href={url.totpUrl}>{msg("authenticator")}</a>
- </li>
- {features.identityFederation && (
- <li className={clsx(active === "social" && "active")}>
- <a href={url.socialUrl}>{msg("federatedIdentity")}</a>
- </li>
- )}
- <li className={clsx(active === "sessions" && "active")}>
- <a href={url.sessionsUrl}>{msg("sessions")}</a>
- </li>
- <li className={clsx(active === "applications" && "active")}>
- <a href={url.applicationsUrl}>{msg("applications")}</a>
- </li>
- {features.log && (
- <li className={clsx(active === "log" && "active")}>
- <a href={url.logUrl}>{msg("log")}</a>
- </li>
- )}
- {realm.userManagedAccessAllowed && features.authorization && (
- <li className={clsx(active === "authorization" && "active")}>
- <a href={url.resourceUrl}>{msg("myResources")}</a>
- </li>
- )}
- </ul>
- </div>
-
- <div className="col-sm-9 content-area">
- {message !== undefined && (
- <div className={clsx("alert", `alert-${message.type}`)}>
- {message.type === "success" && <span className="pficon pficon-ok"></span>}
- {message.type === "error" && <span className="pficon pficon-error-circle-o"></span>}
- <span className="kc-feedback-text">{message.summary}</span>
- </div>
- )}
-
- {children}
- </div>
- </div>
- </>
- );
-}
diff --git a/src/account/pages/Account.stories.tsx b/src/account/pages/Account.stories.tsx
new file mode 100644
index 0000000..86d2bab
--- /dev/null
+++ b/src/account/pages/Account.stories.tsx
@@ -0,0 +1,19 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { createPageStory } from "../PageStory";
+
+const pageId = "account.ftl";
+
+const { PageStory } = createPageStory({ pageId });
+
+const meta = {
+ title: `account/${pageId}`,
+ component: PageStory
+} satisfies Meta<typeof PageStory>;
+
+export default meta;
+
+type Story = StoryObj<typeof meta>;
+
+export const Default: Story = {
+ render: () => <PageStory />
+};
diff --git a/src/account/pages/Password.stories.tsx b/src/account/pages/Password.stories.tsx
new file mode 100644
index 0000000..a9b588d
--- /dev/null
+++ b/src/account/pages/Password.stories.tsx
@@ -0,0 +1,29 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { createPageStory } from "../PageStory";
+
+const pageId = "password.ftl";
+
+const { PageStory } = createPageStory({ pageId });
+
+const meta = {
+ title: `account/${pageId}`,
+ component: PageStory
+} satisfies Meta<typeof PageStory>;
+
+export default meta;
+
+type Story = StoryObj<typeof meta>;
+
+export const Default: Story = {
+ render: () => <PageStory />
+};
+
+export const WithMessage: Story = {
+ render: () => (
+ <PageStory
+ kcContext={{
+ message: { type: "success", summary: "This is a test message" }
+ }}
+ />
+ )
+};
diff --git a/src/login/KcApp.tsx b/src/login/KcApp.tsx
index 6ec7fc7..3dd158e 100644
--- a/src/login/KcApp.tsx
+++ b/src/login/KcApp.tsx
@@ -1,11 +1,13 @@
import { Suspense, lazy } from "react";
+import type { PageProps } from "keycloakify/login";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
import { useDownloadTerms } from "keycloakify/login";
-
+import Template from "keycloakify/login/Template";
const Fallback = lazy(() => import("keycloakify/login/Fallback"));
-const Template = lazy(() => import("./Template"));
-const UserProfileFormFields = lazy(() => import("./UserProfileFormFields"));
+const UserProfileFormFields = lazy(() => import("keycloakify/login/UserProfileFormFields"));
+
+const classes = {} satisfies PageProps["classes"];
export default function KcApp(props: { kcContext: KcContext }) {
const { kcContext } = props;
@@ -15,12 +17,14 @@ export default function KcApp(props: { kcContext: KcContext }) {
useDownloadTerms({
kcContext,
downloadTermMarkdown: async ({ currentLanguageTag }) => {
-
const termsFileName = (() => {
switch (currentLanguageTag) {
- case "fr": return "fr.md";
- case "es": return "es.md";
- default: return "en.md";
+ case "fr":
+ return "fr.md";
+ case "es":
+ return "es.md";
+ default:
+ return "en.md";
}
})();
@@ -28,7 +32,6 @@ export default function KcApp(props: { kcContext: KcContext }) {
const response = await fetch(`${import.meta.env}terms/${termsFileName}`);
return response.text();
-
}
});
@@ -41,15 +44,18 @@ export default function KcApp(props: { kcContext: KcContext }) {
{(() => {
switch (kcContext.pageId) {
default:
- return <Fallback
- {...{
- kcContext,
- i18n,
- Template,
- UserProfileFormFields
- }}
- doUseDefaultCss={true}
- />
+ return (
+ <Fallback
+ {...{
+ kcContext,
+ i18n,
+ classes,
+ Template,
+ UserProfileFormFields
+ }}
+ doUseDefaultCss={true}
+ />
+ );
}
})()}
</Suspense>
diff --git a/src/login/KcContext.ts b/src/login/KcContext.ts
index 1898a16..101f6b7 100644
--- a/src/login/KcContext.ts
+++ b/src/login/KcContext.ts
@@ -5,4 +5,7 @@ export type KcContextExtraProperties = {};
export type KcContextExtraPropertiesPerPage = {};
-export type KcContext = ExtendKcContext<KcContextExtraProperties, KcContextExtraPropertiesPerPage>;
+export type KcContext = ExtendKcContext<
+ KcContextExtraProperties,
+ KcContextExtraPropertiesPerPage
+>;
diff --git a/src/login/PageStory.tsx b/src/login/PageStory.tsx
index 1c89573..ca3388c 100644
--- a/src/login/PageStory.tsx
+++ b/src/login/PageStory.tsx
@@ -1,11 +1,11 @@
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
-import type { KcContext } from "./kcContext";
+import type { KcContext } from "./KcContext";
import KcApp from "./KcApp";
import { createGetKcContextMock } from "keycloakify/login";
import type {
KcContextExtraProperties,
KcContextExtraPropertiesPerPage
-} from "./kcContext";
+} from "./KcContext";
const kcContextExtraProperties: KcContextExtraProperties = {};
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
@@ -17,10 +17,14 @@ export const { getKcContextMock } = createGetKcContextMock({
overridesPerPage: {}
});
-export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
+export function createPageStory<PageId extends KcContext["pageId"]>(params: {
+ pageId: PageId;
+}) {
const { pageId } = params;
- function PageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
+ function PageStory(props: {
+ kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
+ }) {
const { kcContext: overrides } = props;
const kcContextMock = getKcContextMock({
@@ -37,4 +41,3 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
return { PageStory };
}
-
diff --git a/src/login/Template.tsx b/src/login/Template.tsx
deleted file mode 100644
index 5ac6965..0000000
--- a/src/login/Template.tsx
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copy pasted from: https://github.com/keycloakify/keycloakify/blob/main/src/login/Template.tsx
-
-import { useEffect } from "react";
-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 { 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";
-
-export default function Template(props: TemplateProps<KcContext, I18n>) {
- const {
- displayInfo = false,
- displayMessage = true,
- displayRequiredFields = false,
- headerNode,
- showUsernameNode = null,
- socialProvidersNode = null,
- infoNode = null,
- documentTitle,
- bodyClassName,
- kcContext,
- i18n,
- doUseDefaultCss,
- classes,
- children
- } = props;
-
- const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
-
- const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
-
- const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;
-
- useEffect(() => {
- document.title = documentTitle ?? msgStr("loginTitle", kcContext.realm.displayName);
- }, []);
-
- useSetClassName({
- qualifiedName: "html",
- className: getClassName("kcHtmlClass")
- });
-
- useSetClassName({
- qualifiedName: "body",
- className: bodyClassName ?? getClassName("kcBodyClass")
- });
-
- useEffect(() => {
- const { currentLanguageTag } = locale ?? {};
-
- if (currentLanguageTag === undefined) {
- return;
- }
-
- const html = document.querySelector("html");
- assert(html !== null);
- html.lang = currentLanguageTag;
- }, []);
-
- const { areAllStyleSheetsLoaded } = useInsertLinkTags({
- componentOrHookName: "Template",
- hrefs: !doUseDefaultCss
- ? []
- : [
- `${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`,
- `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`,
- `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`,
- `${url.resourcesCommonPath}/lib/pficon/pficon.css`,
- `${url.resourcesPath}/css/login.css`
- ]
- });
-
- const { insertScriptTags } = useInsertScriptTags({
- componentOrHookName: "Template",
- scriptTags: [
- {
- type: "module",
- src: `${url.resourcesPath}/js/menu-button-links.js`
- },
- ...(authenticationSession === undefined
- ? []
- : [
- {
- type: "module",
- textContent: [
- `import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";`,
- ``,
- `checkCookiesAndSetTimer(`,
- ` "${authenticationSession.authSessionId}",`,
- ` "${authenticationSession.tabId}",`,
- ` "${url.ssoLoginInOtherTabsUrl}"`,
- `);`
- ].join("\n")
- } as const
- ]),
- ...scripts.map(
- script =>
- ({
- type: "text/javascript",
- src: script
- }) as const
- )
- ]
- });
-
- useEffect(() => {
- if (areAllStyleSheetsLoaded) {
- insertScriptTags();
- }
- }, [areAllStyleSheetsLoaded]);
-
- if (!areAllStyleSheetsLoaded) {
- return null;
- }
-
- return (
- <div className={getClassName("kcLoginClass")}>
- <div id="kc-header" className={getClassName("kcHeaderClass")}>
- <div id="kc-header-wrapper" className={getClassName("kcHeaderWrapperClass")}>
- {msg("loginTitleHtml", realm.displayNameHtml)}
- </div>
- </div>
-
- <div className={getClassName("kcFormCardClass")}>
- <header className={getClassName("kcFormHeaderClass")}>
- {realm.internationalizationEnabled && (assert(locale !== undefined), locale.supported.length > 1) && (
- <div className={getClassName("kcLocaleMainClass")} id="kc-locale">
- <div id="kc-locale-wrapper" className={getClassName("kcLocaleWrapperClass")}>
- <div id="kc-locale-dropdown" className={clsx("menu-button-links", getClassName("kcLocaleDropDownClass"))}>
- <button
- tabIndex={1}
- id="kc-current-locale-link"
- aria-label={msgStr("languages")}
- aria-haspopup="true"
- aria-expanded="false"
- aria-controls="language-switch1"
- >
- {labelBySupportedLanguageTag[currentLanguageTag]}
- </button>
- <ul
- role="menu"
- tabIndex={-1}
- aria-labelledby="kc-current-locale-link"
- aria-activedescendant=""
- id="language-switch1"
- className={getClassName("kcLocaleListClass")}
- >
- {locale.supported.map(({ languageTag }, i) => (
- <li key={languageTag} className={getClassName("kcLocaleListItemClass")} role="none">
- <a
- role="menuitem"
- id={`language-${i + 1}`}
- className={getClassName("kcLocaleItemClass")}
- href={getChangeLocalUrl(languageTag)}
- >
- {labelBySupportedLanguageTag[languageTag]}
- </a>
- </li>
- ))}
- </ul>
- </div>
- </div>
- </div>
- )}
- {!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
- displayRequiredFields ? (
- <div className={getClassName("kcContentWrapperClass")}>
- <div className={clsx(getClassName("kcLabelWrapperClass"), "subtitle")}>
- <span className="subtitle">
- <span className="required">*</span>
- {msg("requiredFields")}
- </span>
- </div>
- <div className="col-md-10">
- <h1 id="kc-page-title">{headerNode}</h1>
- </div>
- </div>
- ) : (
- <h1 id="kc-page-title">{headerNode}</h1>
- )
- ) : displayRequiredFields ? (
- <div className={getClassName("kcContentWrapperClass")}>
- <div className={clsx(getClassName("kcLabelWrapperClass"), "subtitle")}>
- <span className="subtitle">
- <span className="required">*</span> {msg("requiredFields")}
- </span>
- </div>
- <div className="col-md-10">
- {showUsernameNode}
- <div id="kc-username" className={getClassName("kcFormGroupClass")}>
- <label id="kc-attempted-username">{auth.attemptedUsername}</label>
- <a id="reset-login" href={url.loginRestartFlowUrl} aria-label={msgStr("restartLoginTooltip")}>
- <div className="kc-login-tooltip">
- <i className={getClassName("kcResetFlowIcon")}></i>
- <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
- </div>
- </a>
- </div>
- </div>
- </div>
- ) : (
- <>
- {showUsernameNode}
- <div id="kc-username" className={getClassName("kcFormGroupClass")}>
- <label id="kc-attempted-username">{auth.attemptedUsername}</label>
- <a id="reset-login" href={url.loginRestartFlowUrl} aria-label={msgStr("restartLoginTooltip")}>
- <div className="kc-login-tooltip">
- <i className={getClassName("kcResetFlowIcon")}></i>
- <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
- </div>
- </a>
- </div>
- </>
- )}
- </header>
- <div id="kc-content">
- <div id="kc-content-wrapper">
- {/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
- {displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
- <div
- className={clsx(
- `alert-${message.type}`,
- getClassName("kcAlertClass"),
- `pf-m-${message?.type === "error" ? "danger" : message.type}`
- )}
- >
- <div className="pf-c-alert__icon">
- {message.type === "success" && <span className={getClassName("kcFeedbackSuccessIcon")}></span>}
- {message.type === "warning" && <span className={getClassName("kcFeedbackWarningIcon")}></span>}
- {message.type === "error" && <span className={getClassName("kcFeedbackErrorIcon")}></span>}
- {message.type === "info" && <span className={getClassName("kcFeedbackInfoIcon")}></span>}
- </div>
- <span
- className={getClassName("kcAlertTitleClass")}
- dangerouslySetInnerHTML={{
- __html: message.summary
- }}
- />
- </div>
- )}
- {children}
- {auth !== undefined && auth.showTryAnotherWayLink && (
- <form id="kc-select-try-another-way-form" action={url.loginAction} method="post">
- <div className={getClassName("kcFormGroupClass")}>
- <div className={getClassName("kcFormGroupClass")}>
- <input type="hidden" name="tryAnotherWay" value="on" />
- <a
- href="#"
- id="try-another-way"
- onClick={() => {
- document.forms["kc-select-try-another-way-form" as never].submit();
- return false;
- }}
- >
- {msg("doTryAnotherWay")}
- </a>
- </div>
- </div>
- </form>
- )}
- {socialProvidersNode}
- {displayInfo && (
- <div id="kc-info" className={getClassName("kcSignUpClass")}>
- <div id="kc-info-wrapper" className={getClassName("kcInfoAreaWrapperClass")}>
- {infoNode}
- </div>
- </div>
- )}
- </div>
- </div>
- </div>
- </div>
- );
-}
diff --git a/src/login/UserProfileFormFields.tsx b/src/login/UserProfileFormFields.tsx
deleted file mode 100644
index 2b6264f..0000000
--- a/src/login/UserProfileFormFields.tsx
+++ /dev/null
@@ -1,699 +0,0 @@
-// Copy pasted from: https://github.com/keycloakify/keycloakify/blob/main/src/login/UserProfileFormFields.tsx
-
-import { useEffect, useReducer, Fragment } from "react";
-import { assert } from "tsafe/assert";
-import type { ClassKey } from "keycloakify/login/TemplateProps";
-import {
- useUserProfileForm,
- getButtonToDisplayForMultivaluedAttributeField,
- type KcContextLike,
- type FormAction,
- type FormFieldError
-} from "keycloakify/login/lib/useUserProfileForm";
-import type { Attribute } from "keycloakify/login/KcContext";
-import type { I18n } from "./i18n";
-
-export type UserProfileFormFieldsProps = {
- kcContext: KcContextLike;
- i18n: I18n;
- getClassName: (classKey: ClassKey) => string;
- onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
- BeforeField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
- AfterField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
-};
-
-type BeforeAfterFieldProps = {
- attribute: Attribute;
- dispatchFormAction: React.Dispatch<FormAction>;
- displayableErrors: FormFieldError[];
- i18n: I18n;
- valueOrValues: string | string[];
-};
-
-// NOTE: Enabled by default but it's a UX best practice to set it to false.
-const doMakeUserConfirmPassword = true;
-
-export default function UserProfileFormFields(props: UserProfileFormFieldsProps) {
- const { kcContext, onIsFormSubmittableValueChange, i18n, getClassName, BeforeField, AfterField } = props;
-
- const { advancedMsg } = i18n;
-
- const {
- formState: { formFieldStates, isFormSubmittable },
- dispatchFormAction
- } = useUserProfileForm({
- kcContext,
- i18n,
- doMakeUserConfirmPassword
- });
-
- useEffect(() => {
- onIsFormSubmittableValueChange(isFormSubmittable);
- }, [isFormSubmittable]);
-
- const groupNameRef = { current: "" };
-
- return (
- <>
- {formFieldStates.map(({ attribute, displayableErrors, valueOrValues }) => {
- return (
- <Fragment key={attribute.name}>
- <GroupLabel attribute={attribute} getClassName={getClassName} i18n={i18n} groupNameRef={groupNameRef} />
- {BeforeField !== undefined && (
- <BeforeField
- attribute={attribute}
- dispatchFormAction={dispatchFormAction}
- displayableErrors={displayableErrors}
- i18n={i18n}
- valueOrValues={valueOrValues}
- />
- )}
- <div
- className={getClassName("kcFormGroupClass")}
- style={{
- display: attribute.name === "password-confirm" && !doMakeUserConfirmPassword ? "none" : undefined
- }}
- >
- <div className={getClassName("kcLabelWrapperClass")}>
- <label htmlFor={attribute.name} className={getClassName("kcLabelClass")}>
- {advancedMsg(attribute.displayName ?? "")}
- </label>
- {attribute.required && <>*</>}
- </div>
- <div className={getClassName("kcInputWrapperClass")}>
- {attribute.annotations.inputHelperTextBefore !== undefined && (
- <div
- className={getClassName("kcInputHelperTextBeforeClass")}
- id={`form-help-text-before-${attribute.name}`}
- aria-live="polite"
- >
- {advancedMsg(attribute.annotations.inputHelperTextBefore)}
- </div>
- )}
- <InputFiledByType
- attribute={attribute}
- valueOrValues={valueOrValues}
- displayableErrors={displayableErrors}
- formValidationDispatch={dispatchFormAction}
- getClassName={getClassName}
- i18n={i18n}
- />
- <FieldErrors
- attribute={attribute}
- getClassName={getClassName}
- displayableErrors={displayableErrors}
- fieldIndex={undefined}
- />
- {attribute.annotations.inputHelperTextAfter !== undefined && (
- <div
- className={getClassName("kcInputHelperTextAfterClass")}
- id={`form-help-text-after-${attribute.name}`}
- aria-live="polite"
- >
- {advancedMsg(attribute.annotations.inputHelperTextAfter)}
- </div>
- )}
-
- {AfterField !== undefined && (
- <AfterField
- attribute={attribute}
- dispatchFormAction={dispatchFormAction}
- displayableErrors={displayableErrors}
- i18n={i18n}
- valueOrValues={valueOrValues}
- />
- )}
- {/* NOTE: Downloading of html5DataAnnotations scripts is done in the useUserProfileForm hook */}
- </div>
- </div>
- </Fragment>
- );
- })}
- </>
- );
-}
-
-function GroupLabel(props: {
- attribute: Attribute;
- getClassName: UserProfileFormFieldsProps["getClassName"];
- i18n: I18n;
- groupNameRef: {
- current: string;
- };
-}) {
- const { attribute, getClassName, i18n, groupNameRef } = props;
-
- const { advancedMsg } = i18n;
-
- if (attribute.group?.name !== groupNameRef.current) {
- groupNameRef.current = attribute.group?.name ?? "";
-
- if (groupNameRef.current !== "") {
- assert(attribute.group !== undefined);
-
- return (
- <div
- className={getClassName("kcFormGroupClass")}
- {...Object.fromEntries(Object.entries(attribute.group.html5DataAnnotations).map(([key, value]) => [`data-${key}`, value]))}
- >
- {(() => {
- const groupDisplayHeader = attribute.group.displayHeader ?? "";
- const groupHeaderText = groupDisplayHeader !== "" ? advancedMsg(groupDisplayHeader) : attribute.group.name;
-
- return (
- <div className={getClassName("kcContentWrapperClass")}>
- <label id={`header-${attribute.group.name}`} className={getClassName("kcFormGroupHeader")}>
- {groupHeaderText}
- </label>
- </div>
- );
- })()}
- {(() => {
- const groupDisplayDescription = attribute.group.displayDescription ?? "";
-
- if (groupDisplayDescription !== "") {
- const groupDescriptionText = advancedMsg(groupDisplayDescription);
-
- return (
- <div className={getClassName("kcLabelWrapperClass")}>
- <label id={`description-${attribute.group.name}`} className={getClassName("kcLabelClass")}>
- {groupDescriptionText}
- </label>
- </div>
- );
- }
-
- return null;
- })()}
- </div>
- );
- }
- }
-
- return null;
-}
-
-function FieldErrors(props: {
- attribute: Attribute;
- getClassName: UserProfileFormFieldsProps["getClassName"];
- displayableErrors: FormFieldError[];
- fieldIndex: number | undefined;
-}) {
- const { attribute, getClassName, fieldIndex } = props;
-
- const displayableErrors = props.displayableErrors.filter(error => error.fieldIndex === fieldIndex);
-
- if (displayableErrors.length === 0) {
- return null;
- }
-
- return (
- <span
- id={`input-error-${attribute.name}${fieldIndex === undefined ? "" : `-${fieldIndex}`}`}
- className={getClassName("kcInputErrorMessageClass")}
- aria-live="polite"
- >
- {displayableErrors
- .filter(error => error.fieldIndex === fieldIndex)
- .map(({ errorMessage }, i, arr) => (
- <Fragment key={i}>
- <span key={i}>{errorMessage}</span>
- {arr.length - 1 !== i && <br />}
- </Fragment>
- ))}
- </span>
- );
-}
-
-type InputFiledByTypeProps = {
- attribute: Attribute;
- valueOrValues: string | string[];
- displayableErrors: FormFieldError[];
- formValidationDispatch: React.Dispatch<FormAction>;
- getClassName: UserProfileFormFieldsProps["getClassName"];
- i18n: I18n;
-};
-
-function InputFiledByType(props: InputFiledByTypeProps) {
- const { attribute, valueOrValues } = props;
-
- switch (attribute.annotations.inputType) {
- case "textarea":
- return <TextareaTag {...props} />;
- case "select":
- case "multiselect":
- return <SelectTag {...props} />;
- case "select-radiobuttons":
- case "multiselect-checkboxes":
- return <InputTagSelects {...props} />;
- default: {
- if (valueOrValues instanceof Array) {
- return (
- <>
- {valueOrValues.map((...[, i]) => (
- <InputTag key={i} {...props} fieldIndex={i} />
- ))}
- </>
- );
- }
-
- const inputNode = <InputTag {...props} fieldIndex={undefined} />;
-
- if (attribute.name === "password" || attribute.name === "password-confirm") {
- return (
- <PasswordWrapper getClassName={props.getClassName} i18n={props.i18n} passwordInputId={attribute.name}>
- {inputNode}
- </PasswordWrapper>
- );
- }
-
- return inputNode;
- }
- }
-}
-
-function PasswordWrapper(props: { getClassName: (classKey: ClassKey) => string; i18n: I18n; passwordInputId: string; children: JSX.Element }) {
- const { getClassName, i18n, passwordInputId, children } = props;
-
- const { msgStr } = i18n;
-
- const [isPasswordRevealed, toggleIsPasswordRevealed] = useReducer((isPasswordRevealed: boolean) => !isPasswordRevealed, false);
-
- useEffect(() => {
- const passwordInputElement = document.getElementById(passwordInputId);
-
- assert(passwordInputElement instanceof HTMLInputElement);
-
- passwordInputElement.type = isPasswordRevealed ? "text" : "password";
- }, [isPasswordRevealed]);
-
- return (
- <div className={getClassName("kcInputGroup")}>
- {children}
- <button
- type="button"
- className={getClassName("kcFormPasswordVisibilityButtonClass")}
- aria-label={msgStr(isPasswordRevealed ? "hidePassword" : "showPassword")}
- aria-controls={passwordInputId}
- onClick={toggleIsPasswordRevealed}
- >
- <i
- className={getClassName(isPasswordRevealed ? "kcFormPasswordVisibilityIconHide" : "kcFormPasswordVisibilityIconShow")}
- aria-hidden
- />
- </button>
- </div>
- );
-}
-
-function InputTag(props: InputFiledByTypeProps & { fieldIndex: number | undefined }) {
- const { attribute, fieldIndex, getClassName, formValidationDispatch, valueOrValues, i18n, displayableErrors } = props;
-
- return (
- <>
- <input
- type={(() => {
- const { inputType } = attribute.annotations;
-
- if (inputType?.startsWith("html5-")) {
- return inputType.slice(6);
- }
-
- return inputType ?? "text";
- })()}
- id={attribute.name}
- name={attribute.name}
- value={(() => {
- if (fieldIndex !== undefined) {
- assert(valueOrValues instanceof Array);
- return valueOrValues[fieldIndex];
- }
-
- assert(typeof valueOrValues === "string");
-
- return valueOrValues;
- })()}
- className={getClassName("kcInputClass")}
- aria-invalid={displayableErrors.find(error => error.fieldIndex === fieldIndex) !== undefined}
- disabled={attribute.readOnly}
- autoComplete={attribute.autocomplete}
- placeholder={attribute.annotations.inputTypePlaceholder}
- pattern={attribute.annotations.inputTypePattern}
- size={attribute.annotations.inputTypeSize === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeSize}`)}
- maxLength={
- attribute.annotations.inputTypeMaxlength === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeMaxlength}`)
- }
- minLength={
- attribute.annotations.inputTypeMinlength === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeMinlength}`)
- }
- max={attribute.annotations.inputTypeMax}
- min={attribute.annotations.inputTypeMin}
- step={attribute.annotations.inputTypeStep}
- {...Object.fromEntries(Object.entries(attribute.html5DataAnnotations ?? {}).map(([key, value]) => [`data-${key}`, value]))}
- onChange={event =>
- formValidationDispatch({
- action: "update",
- name: attribute.name,
- valueOrValues: (() => {
- if (fieldIndex !== undefined) {
- assert(valueOrValues instanceof Array);
-
- return valueOrValues.map((value, i) => {
- if (i === fieldIndex) {
- return event.target.value;
- }
-
- return value;
- });
- }
-
- return event.target.value;
- })()
- })
- }
- onBlur={() =>
- props.formValidationDispatch({
- action: "focus lost",
- name: attribute.name,
- fieldIndex: fieldIndex
- })
- }
- />
- {(() => {
- if (fieldIndex === undefined) {
- return null;
- }
-
- assert(valueOrValues instanceof Array);
-
- const values = valueOrValues;
-
- return (
- <>
- <FieldErrors
- attribute={attribute}
- getClassName={getClassName}
- displayableErrors={displayableErrors}
- fieldIndex={fieldIndex}
- />
- <AddRemoveButtonsMultiValuedAttribute
- attribute={attribute}
- values={values}
- fieldIndex={fieldIndex}
- dispatchFormAction={formValidationDispatch}
- i18n={i18n}
- />
- </>
- );
- })()}
- </>
- );
-}
-
-function AddRemoveButtonsMultiValuedAttribute(props: {
- attribute: Attribute;
- values: string[];
- fieldIndex: number;
- dispatchFormAction: React.Dispatch<Extract<FormAction, { action: "update" }>>;
- i18n: I18n;
-}) {
- const { attribute, values, fieldIndex, dispatchFormAction, i18n } = props;
-
- const { msg } = i18n;
-
- const { hasAdd, hasRemove } = getButtonToDisplayForMultivaluedAttributeField({ attribute, values, fieldIndex });
-
- const idPostfix = `-${attribute.name}-${fieldIndex + 1}`;
-
- return (
- <>
- {hasRemove && (
- <>
- <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 ? <>&nbsp;|&nbsp;</> : null}
- </>
- )}
- {hasAdd && (
- <button
- id={`kc-add${idPostfix}`}
- type="button"
- className="pf-c-button pf-m-inline pf-m-link"
- onClick={() =>
- dispatchFormAction({
- action: "update",
- name: attribute.name,
- valueOrValues: [...values, ""]
- })
- }
- >
- {msg("addValue")}
- </button>
- )}
- </>
- );
-}
-
-function InputTagSelects(props: InputFiledByTypeProps) {
- const { attribute, formValidationDispatch, getClassName, valueOrValues } = props;
-
- const { advancedMsg } = props.i18n;
-
- const { classDiv, classInput, classLabel, inputType } = (() => {
- const { inputType } = attribute.annotations;
-
- assert(inputType === "select-radiobuttons" || inputType === "multiselect-checkboxes");
-
- switch (inputType) {
- case "select-radiobuttons":
- return {
- inputType: "radio",
- classDiv: getClassName("kcInputClassRadio"),
- classInput: getClassName("kcInputClassRadioInput"),
- classLabel: getClassName("kcInputClassRadioLabel")
- };
- case "multiselect-checkboxes":
- return {
- inputType: "checkbox",
- classDiv: getClassName("kcInputClassCheckbox"),
- classInput: getClassName("kcInputClassCheckboxInput"),
- classLabel: getClassName("kcInputClassCheckboxLabel")
- };
- }
- })();
-
- const options = (() => {
- walk: {
- const { inputOptionsFromValidation } = attribute.annotations;
-
- if (inputOptionsFromValidation === undefined) {
- break walk;
- }
-
- const validator = (attribute.validators as Record<string, { options?: string[] }>)[inputOptionsFromValidation];
-
- if (validator === undefined) {
- break walk;
- }
-
- if (validator.options === undefined) {
- break walk;
- }
-
- return validator.options;
- }
-
- return attribute.validators.options?.options ?? [];
- })();
-
- return (
- <>
- {options.map(option => (
- <div key={option} className={classDiv}>
- <input
- type={inputType}
- id={`${attribute.name}-${option}`}
- name={attribute.name}
- value={option}
- className={classInput}
- aria-invalid={props.displayableErrors.length !== 0}
- disabled={attribute.readOnly}
- checked={valueOrValues instanceof Array ? valueOrValues.includes(option) : valueOrValues === option}
- onChange={event =>
- formValidationDispatch({
- action: "update",
- name: attribute.name,
- valueOrValues: (() => {
- const isChecked = event.target.checked;
-
- if (valueOrValues instanceof Array) {
- const newValues = [...valueOrValues];
-
- if (isChecked) {
- newValues.push(option);
- } else {
- newValues.splice(newValues.indexOf(option), 1);
- }
-
- return newValues;
- }
-
- return event.target.checked ? option : "";
- })()
- })
- }
- onBlur={() =>
- formValidationDispatch({
- action: "focus lost",
- name: attribute.name,
- fieldIndex: undefined
- })
- }
- />
- <label
- htmlFor={`${attribute.name}-${option}`}
- className={`${classLabel}${attribute.readOnly ? ` ${getClassName("kcInputClassRadioCheckboxLabelDisabled")}` : ""}`}
- >
- {advancedMsg(option)}
- </label>
- </div>
- ))}
- </>
- );
-}
-
-function TextareaTag(props: InputFiledByTypeProps) {
- const { attribute, formValidationDispatch, getClassName, displayableErrors, valueOrValues } = props;
-
- assert(typeof valueOrValues === "string");
-
- const value = valueOrValues;
-
- return (
- <textarea
- id={attribute.name}
- name={attribute.name}
- className={getClassName("kcInputClass")}
- aria-invalid={displayableErrors.length !== 0}
- disabled={attribute.readOnly}
- cols={attribute.annotations.inputTypeCols === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeCols}`)}
- rows={attribute.annotations.inputTypeRows === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeRows}`)}
- maxLength={attribute.annotations.inputTypeMaxlength === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeMaxlength}`)}
- value={value}
- onChange={event =>
- formValidationDispatch({
- action: "update",
- name: attribute.name,
- valueOrValues: event.target.value
- })
- }
- onBlur={() =>
- formValidationDispatch({
- action: "focus lost",
- name: attribute.name,
- fieldIndex: undefined
- })
- }
- />
- );
-}
-
-function SelectTag(props: InputFiledByTypeProps) {
- const { attribute, formValidationDispatch, getClassName, displayableErrors, i18n, valueOrValues } = props;
-
- const { advancedMsg } = i18n;
-
- const isMultiple = attribute.annotations.inputType === "multiselect";
-
- return (
- <select
- id={attribute.name}
- name={attribute.name}
- className={getClassName("kcInputClass")}
- aria-invalid={displayableErrors.length !== 0}
- disabled={attribute.readOnly}
- multiple={isMultiple}
- size={attribute.annotations.inputTypeSize === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeSize}`)}
- value={valueOrValues}
- onChange={event =>
- formValidationDispatch({
- action: "update",
- name: attribute.name,
- valueOrValues: (() => {
- if (isMultiple) {
- return Array.from(event.target.selectedOptions).map(option => option.value);
- }
-
- return event.target.value;
- })()
- })
- }
- onBlur={() =>
- formValidationDispatch({
- action: "focus lost",
- name: attribute.name,
- fieldIndex: undefined
- })
- }
- >
- {!isMultiple && <option value=""></option>}
- {(() => {
- const options = (() => {
- walk: {
- const { inputOptionsFromValidation } = attribute.annotations;
-
- if (inputOptionsFromValidation === undefined) {
- break walk;
- }
-
- assert(typeof inputOptionsFromValidation === "string");
-
- const validator = (attribute.validators as Record<string, { options?: string[] }>)[inputOptionsFromValidation];
-
- if (validator === undefined) {
- break walk;
- }
-
- if (validator.options === undefined) {
- break walk;
- }
-
- return validator.options;
- }
-
- return attribute.validators.options?.options ?? [];
- })();
-
- return options.map(option => (
- <option key={option} value={option}>
- {(() => {
- if (attribute.annotations.inputOptionLabels !== undefined) {
- const { inputOptionLabels } = attribute.annotations;
-
- return advancedMsg(inputOptionLabels[option] ?? option);
- }
-
- if (attribute.annotations.inputOptionLabelsI18nPrefix !== undefined) {
- return advancedMsg(`${attribute.annotations.inputOptionLabelsI18nPrefix}.${option}`);
- }
-
- return option;
- })()}
- </option>
- ));
- })()}
- </select>
- );
-}
diff --git a/src/login/pages/Register.stories.tsx b/src/login/pages/Register.stories.tsx
index 41e7cd0..86ca040 100644
--- a/src/login/pages/Register.stories.tsx
+++ b/src/login/pages/Register.stories.tsx
@@ -1,4 +1,3 @@
-
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory } from "../PageStory";
diff --git a/src/main.tsx b/src/main.tsx
index 1ba776a..895ed89 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,24 +1,33 @@
/* eslint-disable react-refresh/only-export-components */
import { createRoot } from "react-dom/client";
import { StrictMode, lazy, Suspense } from "react";
-//import { getKcContextMock } from "./login/PageStory";
-//const kcContext = getKcContextMock({ pageId: "register.ftl", overrides: {} });
-const { kcContext } = window;
const KcLoginThemeApp = lazy(() => import("./login/KcApp"));
const KcAccountThemeApp = lazy(() => import("./account/KcApp"));
+let { kcContext } = window;
+
+// NOTE: This is just to test a specific page when you run `yarn dev`
+// however the recommended way to develope is to use the Storybook
+if (kcContext === undefined) {
+ kcContext = (await import("./login/PageStory")).getKcContextMock({
+ pageId: "register.ftl"
+ });
+}
+
createRoot(document.getElementById("root")!).render(
<StrictMode>
<Suspense>
{(() => {
switch (kcContext?.themeType) {
- case "login": return <KcLoginThemeApp kcContext={kcContext} />;
- case "account": return <KcAccountThemeApp kcContext={kcContext} />;
- case undefined: return <h1>No Keycloak Context</h1>;
+ case "login":
+ return <KcLoginThemeApp kcContext={kcContext} />;
+ case "account":
+ return <KcAccountThemeApp kcContext={kcContext} />;
+ case undefined:
+ return <h1>No Keycloak Context</h1>;
}
})()}
</Suspense>
</StrictMode>
);
-
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index cbf61da..37e9404 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -3,9 +3,8 @@
import type { KcContext as KcContextLogin } from "./login/kcContext";
import type { KcContext as KcContextAccount } from "./account/kcContext";
-
declare global {
- interface Window {
- kcContext?: KcContextLogin | KcContextAccount;
- }
-} \ No newline at end of file
+ interface Window {
+ kcContext?: KcContextLogin | KcContextAccount;
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index a7fc6fb..30d6ff1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,25 +1,25 @@
{
- "compilerOptions": {
- "target": "ES2020",
- "useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx",
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
- },
- "include": ["src"],
- "references": [{ "path": "./tsconfig.node.json" }]
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 42872c5..26063d8 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -1,10 +1,10 @@
{
- "compilerOptions": {
- "composite": true,
- "skipLibCheck": true,
- "module": "ESNext",
- "moduleResolution": "bundler",
- "allowSyntheticDefaultImports": true
- },
- "include": ["vite.config.ts"]
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 108187e..38af6ad 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,15 +1,11 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
import { keycloakify } from "keycloakify/vite-plugin";
-
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [
- react(),
- keycloakify()
- ],
- build: {
- sourcemap: true
- }
-})
+ plugins: [react(), keycloakify()],
+ build: {
+ sourcemap: true
+ }
+});
diff --git a/yarn.lock b/yarn.lock
index 967a329..e1f1c94 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5011,10 +5011,10 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
- version "10.0.0-rc.31"
- resolved "https://registry.yarnpkg.com/keycloakify/-/keycloakify-10.0.0-rc.31.tgz#4ccd4887de0f759ff91f5765a9011c77fbc2230f"
- integrity sha512-UMDtVq4jxlihKPnp2OMo2FXTlAEl0PpdN8Bbk0yBxvxgvPuDXazWM2smi4tr48aTLGhx/fWdiyw1mvsOlcFvPA==
+ version "10.0.0-rc.33"
+ resolved "https://registry.yarnpkg.com/keycloakify/-/keycloakify-10.0.0-rc.33.tgz#2a522facaf3138e7c9b699e95ef45cfc73ab0296"
+ integrity sha512-rByUFHqsSQ1P9ZsnbCtB02rHfF38J4+dV0gr/oArAviLt6NauO2r3KoRKMtkeT/1OKCvkthvK7cloFWEjBDiBQ==
dependencies:
react-markdown "^5.0.3"
tsafe "^1.6.6"
@@ -5774,6 +5774,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.1.tgz#e68935518dd90bb7ec4821ba970e68f8de16e1ac"
+ integrity sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==
+
prettier@^3.1.1:
version "3.2.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"