commit
8a1a9b1026
25
.eslintrc.cjs
Normal file
25
.eslintrc.cjs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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 },
|
||||||
|
],
|
||||||
|
'react-hooks/exhaustive-deps': 'off',
|
||||||
|
'@typescript-eslint/no-redeclare': 'off',
|
||||||
|
'no-labels': 'off',
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['**/*.stories.*'],
|
||||||
|
rules: {
|
||||||
|
'import/no-anonymous-default-export': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
|
@ -49,8 +49,8 @@ jobs:
|
||||||
- run: npx keycloakify
|
- run: npx keycloakify
|
||||||
env:
|
env:
|
||||||
XDG_CACHE_HOME: "/home/runner/.cache/yarn"
|
XDG_CACHE_HOME: "/home/runner/.cache/yarn"
|
||||||
- run: mv build_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
|
- run: mv dist_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
|
||||||
- run: mv build_keycloak/target/*.jar keycloak-theme.jar
|
- run: mv dist_keycloak/target/*.jar keycloak-theme.jar
|
||||||
- uses: softprops/action-gh-release@v1
|
- uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -52,6 +52,6 @@ jspm_packages
|
||||||
|
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
/build_keycloak
|
/dist_keycloak
|
||||||
/build
|
/build
|
||||||
/storybook-static
|
/storybook-static
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
"stories": [
|
|
||||||
"../src/**/*.stories.tsx",
|
|
||||||
],
|
|
||||||
"addons": [
|
|
||||||
"@storybook/addon-links",
|
|
||||||
"@storybook/addon-essentials",
|
|
||||||
"@storybook/addon-interactions",
|
|
||||||
"@storybook/preset-create-react-app"
|
|
||||||
],
|
|
||||||
"framework": "@storybook/react",
|
|
||||||
"core": {
|
|
||||||
"builder": "@storybook/builder-webpack5"
|
|
||||||
},
|
|
||||||
"staticDirs": ['../public']
|
|
||||||
}
|
|
20
.storybook/main.ts
Normal file
20
.storybook/main.ts
Normal file
|
@ -0,0 +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"]
|
||||||
|
};
|
||||||
|
export default config;
|
|
@ -1,13 +0,0 @@
|
||||||
export const parameters = {
|
|
||||||
actions: {argTypesRegex: "^on[A-Z].*"},
|
|
||||||
controls: {
|
|
||||||
matchers: {
|
|
||||||
color: /(background|color)$/i,
|
|
||||||
date: /Date$/,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
storySort: (a, b) =>
|
|
||||||
a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, {numeric: true}),
|
|
||||||
},
|
|
||||||
}
|
|
15
.storybook/preview.ts
Normal file
15
.storybook/preview.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import type { Preview } from "@storybook/react";
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
|
@ -8,6 +8,6 @@ RUN yarn build
|
||||||
|
|
||||||
# production environment
|
# production environment
|
||||||
FROM nginx:stable-alpine
|
FROM nginx:stable-alpine
|
||||||
COPY --from=build /app/build /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
COPY --from=build /app/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY --from=build /app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
CMD nginx -g 'daemon off;'
|
CMD nginx -g 'daemon off;'
|
13
README.md
13
README.md
|
@ -10,13 +10,10 @@
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
This repo constitutes an easily reusable setup for a Keycloak theme project OR for a SPA React App that generates a
|
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.
|
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](#standalone-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](#standalone-keycloak-theme).
|
||||||
|
|
||||||
> ❗️ WARNING ❗️: Don't waste time trying to port this setup to [Vite](https://vitejs.dev/).
|
|
||||||
> Vite support is comming in a matter of days. There will be a comprehensive migration guide.
|
|
||||||
|
|
||||||
# Quick start
|
# Quick start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -32,7 +29,7 @@ yarn storybook # Start Storybook
|
||||||
# You can create stories even for pages that you haven't explicitly overloaded. See src/keycloak-theme/login/pages/LoginResetPassword.stories.tsx
|
# 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
|
# See Keycloakify's storybook for if you need a starting point for your stories: https://github.com/keycloakify/keycloakify/tree/main/stories
|
||||||
|
|
||||||
yarn start # See the Hello World app
|
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
|
# 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)
|
# You can now see the login.ftl page with the mock data. (Don't forget to comment it back when you're done)
|
||||||
|
|
||||||
|
@ -53,7 +50,7 @@ npx initialize-email-theme # For initializing your email theme
|
||||||
# Note that Keycloakify does not feature React integration for email yet.
|
# Note that Keycloakify does not feature React integration for email yet.
|
||||||
|
|
||||||
npx download-builtin-keycloak-theme # For downloading the default theme (as a reference)
|
npx download-builtin-keycloak-theme # For downloading the default theme (as a reference)
|
||||||
# Look for the files in build_keycloak/src/main/resources/theme/{base,keycloak}
|
# Look for the files in dist_keycloak/src/main/resources/theme/{base,keycloak}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Theme variant
|
# Theme variant
|
||||||
|
@ -205,8 +202,8 @@ jobs:
|
||||||
- run: npx keycloakify
|
- run: npx keycloakify
|
||||||
env:
|
env:
|
||||||
XDG_CACHE_HOME: "/home/runner/.cache/yarn"
|
XDG_CACHE_HOME: "/home/runner/.cache/yarn"
|
||||||
- run: mv build_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
|
- run: mv dist_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
|
||||||
- run: mv build_keycloak/target/*.jar keycloak-theme.jar
|
- run: mv dist_keycloak/target/*.jar keycloak-theme.jar
|
||||||
- uses: softprops/action-gh-release@v1
|
- uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
name: Release v\${{ needs.check_if_version_upgraded.outputs.to_version }}
|
name: Release v\${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||||
|
|
76
index.html
Normal file
76
index.html
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Notice the use of %BASE_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%BASE_URL%favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
-->
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="%BASE_URL%favicon-32x32.png">
|
||||||
|
|
||||||
|
<title>Keycloakify starter</title>
|
||||||
|
|
||||||
|
<link rel="preload" href="%BASE_URL%fonts/WorkSans/worksans-bold-webfont.woff2" as="font" crossorigin="anonymous">
|
||||||
|
<link rel="preload" href="%BASE_URL%fonts/WorkSans/worksans-medium-webfont.woff2" as="font" crossorigin="anonymous">
|
||||||
|
<link rel="preload" href="%BASE_URL%fonts/WorkSans/worksans-regular-webfont.woff2" as="font" crossorigin="anonymous">
|
||||||
|
<link rel="preload" href="%BASE_URL%fonts/WorkSans/worksans-semibold-webfont.woff2" as="font" crossorigin="anonymous">
|
||||||
|
<!-- SEE: https://docs.keycloakify.dev/limitations#self-hosted-fonts -->
|
||||||
|
<!-- Don't forget to import your custom fonts in Storybook as well: https://github.com/keycloakify/keycloakify-starter/blob/bb019e66fb09166cb9af1e24e230994f59daa420/src/keycloak-theme/login/createPageStory.tsx#L21 -->
|
||||||
|
<style>
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Work Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
/*400*/
|
||||||
|
font-display: swap;
|
||||||
|
src: url("%BASE_URL%fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Work Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("%BASE_URL%fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Work Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("%BASE_URL%fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Work Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
/*700*/
|
||||||
|
font-display: swap;
|
||||||
|
src: url("%BASE_URL%fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<meta name="keycloakify-ignore-start">
|
||||||
|
<script>console.log("This is logged Only in the main app, stripped out in the theme")</script>
|
||||||
|
<meta name="keycloakify-ignore-end">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
146
package.json
146
package.json
|
@ -1,86 +1,64 @@
|
||||||
{
|
{
|
||||||
"name": "keycloakify-starter",
|
"name": "keycloakify-starter",
|
||||||
"homepage": "https://starter.keycloakify.dev",
|
"homepage": "https://starter.keycloakify.dev",
|
||||||
"version": "5.3.3",
|
"version": "5.2.0",
|
||||||
"description": "A starter/demo project for keycloakify",
|
"description": "A starter/demo project for keycloakify",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/codegouvfr/keycloakify-starter.git"
|
"url": "git://github.com/codegouvfr/keycloakify-starter.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"type": "module",
|
||||||
"start": "copy-keycloak-resources-to-public && react-scripts start",
|
"scripts": {
|
||||||
"storybook": "copy-keycloak-resources-to-public && start-storybook -p 6006",
|
"dev": "vite",
|
||||||
"build": "react-scripts build && rimraf build/keycloak-resources",
|
"build": "tsc && vite build",
|
||||||
"build-keycloak-theme": "yarn build && keycloakify"
|
"build-keycloak-theme": "yarn build && keycloakify",
|
||||||
},
|
"storybook": "storybook dev -p 6006",
|
||||||
"keycloakify": {
|
"build-storybook": "storybook build"
|
||||||
"themeName": "keycloakify-starter",
|
},
|
||||||
"extraThemeProperties": [
|
"keycloakify": {
|
||||||
"foo=bar"
|
"themeName": "keycloakify-starter",
|
||||||
]
|
"extraThemeProperties": [
|
||||||
},
|
"foo=bar"
|
||||||
"author": "u/garronej",
|
]
|
||||||
"license": "MIT",
|
},
|
||||||
"keywords": [],
|
"author": "u/garronej",
|
||||||
"dependencies": {
|
"license": "MIT",
|
||||||
"evt": "^2.5.7",
|
"keywords": [],
|
||||||
"oidc-spa": "^4.2.0",
|
"dependencies": {
|
||||||
"keycloakify": "^9.3.0",
|
"evt": "^2.5.7",
|
||||||
"powerhooks": "^1.0.8",
|
"keycloakify": "^9.4.1",
|
||||||
"react": "18.1.0",
|
"oidc-spa": "^4.2.1",
|
||||||
"react-dom": "18.1.0",
|
"powerhooks": "^1.0.8",
|
||||||
"tsafe": "^1.6.6",
|
"react": "^18.2.0",
|
||||||
"zod": "^3.22.4"
|
"react-dom": "^18.2.0",
|
||||||
},
|
"tsafe": "^1.6.6",
|
||||||
"devDependencies": {
|
"zod": "^3.22.4"
|
||||||
"@types/node": "^15.3.1",
|
},
|
||||||
"@types/react": "18.0.9",
|
"devDependencies": {
|
||||||
"@types/react-dom": "18.0.4",
|
"@storybook/addon-essentials": "^7.6.14",
|
||||||
"react-scripts": "5.0.1",
|
"@storybook/addon-interactions": "^7.6.14",
|
||||||
"typescript": "~4.7.0",
|
"@storybook/addon-links": "^7.6.14",
|
||||||
"@storybook/addon-actions": "^6.5.16",
|
"@storybook/addon-onboarding": "^1.0.11",
|
||||||
"@storybook/addon-essentials": "^6.5.16",
|
"@storybook/blocks": "^7.6.14",
|
||||||
"@storybook/addon-interactions": "^6.5.16",
|
"@storybook/react": "^7.6.14",
|
||||||
"@storybook/addon-links": "^6.5.16",
|
"@storybook/react-vite": "^7.6.14",
|
||||||
"@storybook/builder-webpack5": "^6.5.16",
|
"@storybook/test": "^7.6.14",
|
||||||
"@storybook/manager-webpack5": "^6.5.16",
|
"@types/react": "^18.2.43",
|
||||||
"@storybook/node-logger": "^6.5.16",
|
"@types/react-dom": "^18.2.17",
|
||||||
"@storybook/preset-create-react-app": "^4.1.2",
|
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||||
"@storybook/react": "^6.5.16",
|
"@typescript-eslint/parser": "^6.14.0",
|
||||||
"@storybook/testing-library": "^0.0.13",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"rimraf": "^5.0.5"
|
"eslint": "^8.55.0",
|
||||||
},
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslintConfig": {
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
"extends": [
|
"eslint-plugin-storybook": "^0.6.15",
|
||||||
"react-app",
|
"storybook": "^7.6.14",
|
||||||
"react-app/jest"
|
"typescript": "^5.2.2",
|
||||||
],
|
"vite": "^5.0.8",
|
||||||
"rules": {
|
"vite-plugin-commonjs": "^0.10.1"
|
||||||
"react-hooks/exhaustive-deps": "off",
|
},
|
||||||
"@typescript-eslint/no-redeclare": "off",
|
"_comment": "See https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092",
|
||||||
"no-labels": "off"
|
"resolutions": {
|
||||||
},
|
"jackspeak": "2.1.1"
|
||||||
"overrides": [
|
}
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"**/*.stories.*"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"import/no-anonymous-default-export": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,36 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-bold-webfont.woff2" as="font" crossorigin="anonymous">
|
This file is only meant to be used by Storybook
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-medium-webfont.woff2" as="font" crossorigin="anonymous">
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-regular-webfont.woff2" as="font" crossorigin="anonymous">
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-semibold-webfont.woff2" as="font" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/fonts/WorkSans/font.css">
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Work Sans";
|
font-family: "Work Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal; /*400*/
|
font-weight: normal; /*400*/
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("./worksans-regular-webfont.woff2") format("woff2");
|
src: url("./worksans-regular-webfont.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Work Sans";
|
font-family: "Work Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("./worksans-medium-webfont.woff2") format("woff2");
|
src: url("./worksans-medium-webfont.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Work Sans";
|
font-family: "Work Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("./worksans-semibold-webfont.woff2") format("woff2");
|
src: url("./worksans-semibold-webfont.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Work Sans";
|
font-family: "Work Sans";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: bold; /*700*/
|
font-weight: bold; /*700*/
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("./worksans-bold-webfont.woff2") format("woff2");
|
src: url("./worksans-bold-webfont.woff2") format("woff2");
|
||||||
}
|
}
|
|
@ -1,89 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
|
|
||||||
|
|
||||||
<title>Keycloakify starter</title>
|
|
||||||
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-bold-webfont.woff2" as="font" crossorigin="anonymous">
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-medium-webfont.woff2" as="font" crossorigin="anonymous">
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-regular-webfont.woff2" as="font" crossorigin="anonymous">
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/fonts/WorkSans/worksans-semibold-webfont.woff2" as="font" crossorigin="anonymous">
|
|
||||||
<!-- SEE: https://docs.keycloakify.dev/limitations#self-hosted-fonts -->
|
|
||||||
<!-- Don't forget to import your custom fonts in Storybook as well: https://github.com/keycloakify/keycloakify-starter/blob/bb019e66fb09166cb9af1e24e230994f59daa420/src/keycloak-theme/login/createPageStory.tsx#L21 -->
|
|
||||||
<style>
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Work Sans';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
/*400*/
|
|
||||||
font-display: swap;
|
|
||||||
src: url("%PUBLIC_URL%/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Work Sans';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("%PUBLIC_URL%/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Work Sans';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("%PUBLIC_URL%/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Work Sans';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: bold;
|
|
||||||
/*700*/
|
|
||||||
font-display: swap;
|
|
||||||
src: url("%PUBLIC_URL%/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<meta name="keycloakify-ignore-start">
|
|
||||||
<script>console.log("This is logged Only in the main app, stripped out in the theme")</script>
|
|
||||||
<meta name="keycloakify-ignore-end">
|
|
||||||
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -22,7 +22,7 @@ export const { OidcProvider, useOidc } = createReactOidc({
|
||||||
"ui_locales": "en",
|
"ui_locales": "en",
|
||||||
"my_custom_param": "value of foo transferred to login page"
|
"my_custom_param": "value of foo transferred to login page"
|
||||||
}),
|
}),
|
||||||
publicUrl: process.env.PUBLIC_URL,
|
publicUrl: import.meta.env.BASE_URL,
|
||||||
decodedIdTokenSchema: z.object({
|
decodedIdTokenSchema: z.object({
|
||||||
// Use https://jwt.io/ to tell what's in your idToken
|
// Use https://jwt.io/ to tell what's in your idToken
|
||||||
// It will depend of your Keycloak configuration.
|
// It will depend of your Keycloak configuration.
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
|
|
||||||
import { kcContext as kcLoginThemeContext } from "keycloak-theme/login/kcContext";
|
|
||||||
import { kcContext as kcAccountThemeContext } from "keycloak-theme/login/kcContext";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If you need to use process.env.PUBLIC_URL, use this variable instead.
|
|
||||||
* If you can, import your assets using the import statement.
|
|
||||||
*
|
|
||||||
* See: https://docs.keycloakify.dev/importing-assets#importing-custom-assets-that-arent-fonts
|
|
||||||
*/
|
|
||||||
export const PUBLIC_URL = (()=>{
|
|
||||||
|
|
||||||
const kcContext = (()=>{
|
|
||||||
|
|
||||||
if( kcLoginThemeContext !== undefined ){
|
|
||||||
return kcLoginThemeContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( kcAccountThemeContext !== undefined ){
|
|
||||||
return kcLoginThemeContext
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
return (kcContext === undefined || process.env.NODE_ENV === "development")
|
|
||||||
? process.env.PUBLIC_URL
|
|
||||||
: `${kcContext.url.resourcesPath}/build`;
|
|
||||||
|
|
||||||
})();
|
|
|
@ -10,9 +10,9 @@ const MyExtraPage1 = lazy(() => import("./pages/MyExtraPage1"));
|
||||||
const MyExtraPage2 = lazy(() => import("./pages/MyExtraPage2"));
|
const MyExtraPage2 = lazy(() => import("./pages/MyExtraPage2"));
|
||||||
const Fallback = lazy(()=> import("keycloakify/account"));
|
const Fallback = lazy(()=> import("keycloakify/account"));
|
||||||
|
|
||||||
const classes: PageProps<any, any>["classes"] = {
|
const classes = {
|
||||||
"kcBodyClass": "my-root-account-class"
|
"kcBodyClass": "my-root-account-class"
|
||||||
};
|
} satisfies PageProps["classes"];
|
||||||
|
|
||||||
export default function KcApp(props: { kcContext: KcContext; }) {
|
export default function KcApp(props: { kcContext: KcContext; }) {
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,13 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: {
|
||||||
storyPartialKcContext: params.kcContext
|
storyPartialKcContext: params.kcContext
|
||||||
});
|
});
|
||||||
|
|
||||||
return <KcApp kcContext={kcContext} />;
|
return (
|
||||||
|
<>
|
||||||
|
{/* If you import custom fonts in your index.html you have to import them in storybook as well*/}
|
||||||
|
<link rel="stylesheet" type="text/css" href={`${import.meta.env.BASE_URL}fonts/WorkSans/font.css`} />
|
||||||
|
<KcApp kcContext={kcContext} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
|
|
||||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { createPageStory } from "../createPageStory";
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
const { PageStory } = createPageStory({
|
const { PageStory } = createPageStory({
|
||||||
pageId: "password.ftl"
|
pageId: "password.ftl"
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
const meta = {
|
||||||
title: "account/Password",
|
title: "account/Password",
|
||||||
component: PageStory,
|
component: PageStory,
|
||||||
} as ComponentMeta<typeof PageStory>;
|
} satisfies Meta<typeof PageStory>;
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Default: ComponentStory<typeof PageStory> = () => <PageStory
|
export const Default: Story = {
|
||||||
kcContext={{
|
render: () => <PageStory
|
||||||
message: { type: "success", summary: "This is a test message" }
|
kcContext={{
|
||||||
}}
|
message: { type: "success", summary: "This is a test message" }
|
||||||
/>;
|
}}
|
||||||
|
/>
|
||||||
|
};
|
||||||
|
|
|
@ -16,11 +16,11 @@ const Info = lazy(() => import("keycloakify/login/pages/Info"));
|
||||||
|
|
||||||
// This is like adding classes to theme.properties
|
// This is like adding classes to theme.properties
|
||||||
// https://github.com/keycloak/keycloak/blob/11.0.3/themes/src/main/resources/theme/keycloak/login/theme.properties
|
// https://github.com/keycloak/keycloak/blob/11.0.3/themes/src/main/resources/theme/keycloak/login/theme.properties
|
||||||
const classes: PageProps<any, any>["classes"] = {
|
const classes = {
|
||||||
// NOTE: The classes are defined in ./KcApp.css
|
// NOTE: The classes are defined in ./KcApp.css
|
||||||
"kcHtmlClass": "my-root-class",
|
"kcHtmlClass": "my-root-class",
|
||||||
"kcHeaderWrapperClass": "my-color my-font"
|
"kcHeaderWrapperClass": "my-color my-font"
|
||||||
};
|
} satisfies PageProps["classes"];
|
||||||
|
|
||||||
export default function KcApp(props: { kcContext: KcContext; }) {
|
export default function KcApp(props: { kcContext: KcContext; }) {
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
import keycloakifyLogoPngUrl from "./assets/keycloakify-logo.png";
|
import keycloakifyLogoPngUrl from "./assets/keycloakify-logo.png";
|
||||||
import { PUBLIC_URL } from "../../PUBLIC_URL";
|
|
||||||
|
|
||||||
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
const {
|
const {
|
||||||
|
@ -61,12 +60,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
style={{ "fontFamily": '"Work Sans"' }}
|
style={{ "fontFamily": '"Work Sans"' }}
|
||||||
>
|
>
|
||||||
{/*
|
{/*
|
||||||
This is just to show you how it can be done but this is not the best option for importing assets.
|
Here we are referencing the `keycloakify-logo.png` in the `public` directory.
|
||||||
See: https://docs.keycloakify.dev/importing-assets#importing-custom-assets
|
When possible don't use this approach, instead ...
|
||||||
*/}
|
*/}
|
||||||
<img src={`${PUBLIC_URL}/keycloakify-logo.png`} alt="Keycloakify logo" width={50} />
|
<img src={`${import.meta.env.BASE_URL}keycloakify-logo.png`} alt="Keycloakify logo" width={50} />
|
||||||
{msg("loginTitleHtml", realm.displayNameHtml)}!!!
|
{msg("loginTitleHtml", realm.displayNameHtml)}!!!
|
||||||
{/* This is the preferred way to use assets */}
|
{/* ...rely on the bundler to import your assets, it's more efficient */}
|
||||||
<img src={keycloakifyLogoPngUrl} alt="Keycloakify logo" width={50} />
|
<img src={keycloakifyLogoPngUrl} alt="Keycloakify logo" width={50} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,14 +76,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
<div id="kc-locale">
|
<div id="kc-locale">
|
||||||
<div id="kc-locale-wrapper" className={getClassName("kcLocaleWrapperClass")}>
|
<div id="kc-locale-wrapper" className={getClassName("kcLocaleWrapperClass")}>
|
||||||
<div className="kc-dropdown" id="kc-locale-dropdown">
|
<div className="kc-dropdown" id="kc-locale-dropdown">
|
||||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
|
||||||
<a href="#" id="kc-current-locale-link">
|
<a href="#" id="kc-current-locale-link">
|
||||||
{labelBySupportedLanguageTag[currentLanguageTag]}
|
{labelBySupportedLanguageTag[currentLanguageTag]}
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
{locale.supported.map(({ languageTag }) => (
|
{locale.supported.map(({ languageTag }) => (
|
||||||
<li key={languageTag} className="kc-dropdown-item">
|
<li key={languageTag} className="kc-dropdown-item">
|
||||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
|
||||||
<a href="#" onClick={() => changeLocale(languageTag)}>
|
<a href="#" onClick={() => changeLocale(languageTag)}>
|
||||||
{labelBySupportedLanguageTag[languageTag]}
|
{labelBySupportedLanguageTag[languageTag]}
|
||||||
</a>
|
</a>
|
||||||
|
@ -182,7 +179,6 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
>
|
>
|
||||||
<div className={getClassName("kcFormGroupClass")}>
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
<input type="hidden" name="tryAnotherWay" value="on" />
|
<input type="hidden" name="tryAnotherWay" value="on" />
|
||||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
id="try-another-way"
|
id="try-another-way"
|
||||||
|
|
|
@ -18,7 +18,7 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* If you import custom fonts in your index.html you have to import them in storybook as well*/}
|
{/* If you import custom fonts in your index.html you have to import them in storybook as well*/}
|
||||||
<link rel="stylesheet" type="text/css" href="fonts/WorkSans/font.css" />
|
<link rel="stylesheet" type="text/css" href={`${import.meta.env.BASE_URL}fonts/WorkSans/font.css`} />
|
||||||
<KcApp kcContext={kcContext} />
|
<KcApp kcContext={kcContext} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,98 +1,83 @@
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { createPageStory } from "../createPageStory";
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
const { PageStory } = createPageStory({
|
const { PageStory } = createPageStory({
|
||||||
pageId: "login.ftl"
|
pageId: "login.ftl"
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
const meta = {
|
||||||
title: "login/Login",
|
title: "login/Login",
|
||||||
component: PageStory,
|
component: PageStory,
|
||||||
} as ComponentMeta<typeof PageStory>;
|
} satisfies Meta<typeof PageStory>;
|
||||||
|
|
||||||
export const Default: ComponentStory<typeof PageStory> = () => <PageStory />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const WithoutPasswordField: ComponentStory<typeof PageStory> = () => (
|
export const Default: Story = {
|
||||||
<PageStory
|
render: () => <PageStory />,
|
||||||
kcContext={{
|
};
|
||||||
realm: { password: false }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithoutRegistration: ComponentStory<typeof PageStory> = () => (
|
export const WithoutPasswordField: Story = {
|
||||||
<PageStory
|
render: () => <PageStory kcContext={{ realm: { password: false } }} />,
|
||||||
kcContext={{
|
};
|
||||||
realm: { registrationAllowed: false }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithoutRememberMe: ComponentStory<typeof PageStory> = () => (
|
export const WithoutRegistration: Story = {
|
||||||
<PageStory
|
render: () => <PageStory kcContext={{ realm: { registrationAllowed: false } }} />,
|
||||||
kcContext={{
|
};
|
||||||
realm: { rememberMe: false }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithoutPasswordReset: ComponentStory<typeof PageStory> = () => (
|
export const WithoutRememberMe: Story = {
|
||||||
<PageStory
|
render: () => <PageStory kcContext={{ realm: { rememberMe: false } }} />,
|
||||||
kcContext={{
|
};
|
||||||
realm: { resetPasswordAllowed: false }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithEmailAsUsername: ComponentStory<typeof PageStory> = () => (
|
export const WithoutPasswordReset: Story = {
|
||||||
<PageStory
|
render: () => <PageStory kcContext={{ realm: { resetPasswordAllowed: false } }} />,
|
||||||
kcContext={{
|
};
|
||||||
realm: { loginWithEmailAllowed: false }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithPresetUsername: ComponentStory<typeof PageStory> = () => (
|
export const WithEmailAsUsername: Story = {
|
||||||
<PageStory
|
render: () => <PageStory kcContext={{ realm: { loginWithEmailAllowed: false } }} />,
|
||||||
kcContext={{
|
};
|
||||||
login: { username: "max.mustermann@mail.com" }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithImmutablePresetUsername: ComponentStory<typeof PageStory> = () => (
|
export const WithPresetUsername: Story = {
|
||||||
<PageStory
|
render: () => <PageStory kcContext={{ login: { username: "max.mustermann@mail.com" } }} />,
|
||||||
kcContext={{
|
};
|
||||||
auth: {
|
|
||||||
attemptedUsername: "max.mustermann@mail.com",
|
|
||||||
showUsername: true
|
|
||||||
},
|
|
||||||
usernameHidden: true,
|
|
||||||
message: { type: "info", summary: "Please re-authenticate to continue" }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithSocialProviders: ComponentStory<typeof PageStory> = () => (
|
export const WithImmutablePresetUsername: Story = {
|
||||||
<PageStory
|
render: () => (
|
||||||
kcContext={{
|
<PageStory
|
||||||
social: {
|
kcContext={{
|
||||||
displayInfo: true, providers: [
|
auth: {
|
||||||
{ loginUrl: 'google', alias: 'google', providerId: 'google', displayName: 'Google' },
|
attemptedUsername: "max.mustermann@mail.com",
|
||||||
{ loginUrl: 'microsoft', alias: 'microsoft', providerId: 'microsoft', displayName: 'Microsoft' },
|
showUsername: true,
|
||||||
{ loginUrl: 'facebook', alias: 'facebook', providerId: 'facebook', displayName: 'Facebook' },
|
},
|
||||||
{ loginUrl: 'instagram', alias: 'instagram', providerId: 'instagram', displayName: 'Instagram' },
|
usernameHidden: true,
|
||||||
{ loginUrl: 'twitter', alias: 'twitter', providerId: 'twitter', displayName: 'Twitter' },
|
message: { type: "info", summary: "Please re-authenticate to continue" },
|
||||||
{ 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' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
//This is to show that you can create stories for pages that you haven't overloaded.
|
//This is to show that you can create stories for pages that you haven't overloaded.
|
||||||
|
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { createPageStory } from "../createPageStory";
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
const { PageStory } = createPageStory({
|
const { PageStory } = createPageStory({
|
||||||
pageId: "login-reset-password.ftl"
|
pageId: "login-reset-password.ftl"
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
const meta = {
|
||||||
title: "login/LoginResetPassword",
|
title: "login/LoginResetPassword",
|
||||||
component: PageStory,
|
component: PageStory,
|
||||||
} as ComponentMeta<typeof PageStory>;
|
} satisfies Meta<typeof PageStory>;
|
||||||
|
|
||||||
export const Default: ComponentStory<typeof PageStory> = () => <PageStory />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const WithEmailAsUsername: ComponentStory<typeof PageStory> = () => (
|
export const Default: Story = {
|
||||||
<PageStory
|
render: () => <PageStory />
|
||||||
kcContext={{
|
};
|
||||||
realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true }
|
|
||||||
}}
|
export const WithEmailAsUsername: Story = {
|
||||||
/>
|
render: () => (
|
||||||
);
|
<PageStory
|
||||||
|
kcContext={{
|
||||||
|
realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
|
@ -1,21 +1,28 @@
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { createPageStory } from "../createPageStory";
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
const { PageStory } = createPageStory({
|
const { PageStory } = createPageStory({
|
||||||
pageId: "my-extra-page-2.ftl"
|
pageId: "my-extra-page-2.ftl"
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
const meta = {
|
||||||
title: "login/MyExtraPage2",
|
title: "login/MyExtraPage2",
|
||||||
component: PageStory,
|
component: PageStory,
|
||||||
} as ComponentMeta<typeof PageStory>;
|
} satisfies Meta<typeof PageStory>;
|
||||||
|
|
||||||
export const Default: ComponentStory<typeof PageStory> = () => <PageStory />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const WitAbc: ComponentStory<typeof PageStory> = () => (
|
export const Default: Story = {
|
||||||
<PageStory
|
render: () => <PageStory />
|
||||||
kcContext={{
|
};
|
||||||
someCustomValue: "abc"
|
|
||||||
}}
|
export const WitAbc: Story = {
|
||||||
/>
|
render: () => (
|
||||||
);
|
<PageStory
|
||||||
|
kcContext={{
|
||||||
|
someCustomValue: "abc"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
||||||
import { createPageStory } from "../createPageStory";
|
|
||||||
|
|
||||||
const { PageStory } = createPageStory({
|
|
||||||
pageId: "terms.ftl"
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "login/Terms",
|
|
||||||
component: PageStory,
|
|
||||||
} as ComponentMeta<typeof PageStory>;
|
|
||||||
|
|
||||||
export const Primary: ComponentStory<typeof PageStory> = () => <PageStory />;
|
|
|
@ -1,13 +1,18 @@
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { createPageStory } from "../createPageStory";
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
const { PageStory } = createPageStory({
|
const { PageStory } = createPageStory({
|
||||||
pageId: "terms.ftl"
|
pageId: "terms.ftl"
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
const meta = {
|
||||||
title: "login/Terms",
|
title: "login/Terms",
|
||||||
component: PageStory,
|
component: PageStory,
|
||||||
} as ComponentMeta<typeof PageStory>;
|
} satisfies Meta<typeof PageStory>;
|
||||||
|
|
||||||
export const Primary: ComponentStory<typeof PageStory> = () => <PageStory />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
render: () => <PageStory />
|
||||||
|
};
|
||||||
|
|
|
@ -7,8 +7,6 @@ import { evtTermMarkdown } from "keycloakify/login/lib/useDownloadTerms";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
import { useDownloadTerms } from "keycloakify/login";
|
import { useDownloadTerms } from "keycloakify/login";
|
||||||
import tos_en_url from "../assets/tos_en.md";
|
|
||||||
import tos_fr_url from "../assets/tos_fr.md";
|
|
||||||
|
|
||||||
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {
|
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
@ -28,18 +26,11 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
|
||||||
|
|
||||||
const tos_url = (() => {
|
const tos_url = (() => {
|
||||||
switch (currentLanguageTag) {
|
switch (currentLanguageTag) {
|
||||||
case "fr": return tos_fr_url;
|
case "fr": return `${import.meta.env.BASE_URL}terms/fr.md`;
|
||||||
default: return tos_en_url;
|
default: return `${import.meta.env.BASE_URL}terms/en.md`;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
if ("__STORYBOOK_ADDONS" in window) {
|
|
||||||
// NOTE: In storybook, when you import a .md file you get the content of the file.
|
|
||||||
// In Create React App on the other hand you get an url to the file.
|
|
||||||
return tos_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const markdownString = await fetch(tos_url).then(response => response.text());
|
const markdownString = await fetch(tos_url).then(response => response.text());
|
||||||
|
|
||||||
return markdownString;
|
return markdownString;
|
||||||
|
|
|
@ -32,3 +32,4 @@ createRoot(document.getElementById("root")!).render(
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|
5
src/react-app-env.d.ts → src/vite-env.d.ts
vendored
5
src/react-app-env.d.ts → src/vite-env.d.ts
vendored
|
@ -1,5 +1,6 @@
|
||||||
/// <reference types="react-scripts" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
declare module "*.md" {
|
declare module "*.md" {
|
||||||
const src: string;
|
const src: string;
|
||||||
export default src;
|
export default src;
|
||||||
}
|
}
|
|
@ -1,21 +1,25 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "ES2020",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"useDefineForClassFields": true,
|
||||||
"baseUrl": "src",
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
"allowJs": true,
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
/* Bundler mode */
|
||||||
"strict": true,
|
"moduleResolution": "bundler",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"allowImportingTsExtensions": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"resolveJsonModule": true,
|
||||||
"module": "esnext",
|
"isolatedModules": true,
|
||||||
"moduleResolution": "node",
|
"noEmit": true,
|
||||||
"resolveJsonModule": true,
|
"jsx": "react-jsx",
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
/* Linting */
|
||||||
"jsx": "react-jsx"
|
"strict": true,
|
||||||
},
|
"noUnusedLocals": true,
|
||||||
"include": ["src"]
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
10
tsconfig.node.json
Normal file
10
tsconfig.node.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
15
vite.config.ts
Normal file
15
vite.config.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
// NOTE: This is just for the Keycloakify core contributors to be able to dynamically link
|
||||||
|
// to a local version of the keycloakify package. This is not needed for normal usage.
|
||||||
|
import commonjs from "vite-plugin-commonjs";
|
||||||
|
import { keycloakify } from "keycloakify/vite-plugin";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
commonjs(),
|
||||||
|
keycloakify()
|
||||||
|
]
|
||||||
|
})
|
Loading…
Reference in a new issue