Merge pull request #15 from keycloakify/vite

Vite
This commit is contained in:
Joseph Garrone 2024-02-13 02:07:38 +01:00 committed by GitHub
commit 8a1a9b1026
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 4327 additions and 11620 deletions

25
.eslintrc.cjs Normal file
View 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',
},
},
],
}

View file

@ -49,8 +49,8 @@ jobs:
- run: npx keycloakify
env:
XDG_CACHE_HOME: "/home/runner/.cache/yarn"
- run: mv build_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
- run: mv build_keycloak/target/*.jar keycloak-theme.jar
- 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 }}

2
.gitignore vendored
View file

@ -52,6 +52,6 @@ jspm_packages
/dist
/build_keycloak
/dist_keycloak
/build
/storybook-static

View file

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

View file

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

View file

@ -8,6 +8,6 @@ RUN yarn build
# production environment
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
CMD nginx -g 'daemon off;'

View file

@ -10,13 +10,10 @@
# 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.
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
```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
# 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
# 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.
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
@ -205,8 +202,8 @@ jobs:
- run: npx keycloakify
env:
XDG_CACHE_HOME: "/home/runner/.cache/yarn"
- run: mv build_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
- run: mv build_keycloak/target/*.jar keycloak-theme.jar
- 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 }}

76
index.html Normal file
View 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>

View file

@ -1,86 +1,64 @@
{
"name": "keycloakify-starter",
"homepage": "https://starter.keycloakify.dev",
"version": "5.3.3",
"description": "A starter/demo project for keycloakify",
"repository": {
"type": "git",
"url": "git://github.com/codegouvfr/keycloakify-starter.git"
},
"scripts": {
"start": "copy-keycloak-resources-to-public && react-scripts start",
"storybook": "copy-keycloak-resources-to-public && start-storybook -p 6006",
"build": "react-scripts build && rimraf build/keycloak-resources",
"build-keycloak-theme": "yarn build && keycloakify"
},
"keycloakify": {
"themeName": "keycloakify-starter",
"extraThemeProperties": [
"foo=bar"
]
},
"author": "u/garronej",
"license": "MIT",
"keywords": [],
"dependencies": {
"evt": "^2.5.7",
"oidc-spa": "^4.2.0",
"keycloakify": "^9.3.0",
"powerhooks": "^1.0.8",
"react": "18.1.0",
"react-dom": "18.1.0",
"tsafe": "^1.6.6",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^15.3.1",
"@types/react": "18.0.9",
"@types/react-dom": "18.0.4",
"react-scripts": "5.0.1",
"typescript": "~4.7.0",
"@storybook/addon-actions": "^6.5.16",
"@storybook/addon-essentials": "^6.5.16",
"@storybook/addon-interactions": "^6.5.16",
"@storybook/addon-links": "^6.5.16",
"@storybook/builder-webpack5": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.16",
"@storybook/node-logger": "^6.5.16",
"@storybook/preset-create-react-app": "^4.1.2",
"@storybook/react": "^6.5.16",
"@storybook/testing-library": "^0.0.13",
"rimraf": "^5.0.5"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/no-redeclare": "off",
"no-labels": "off"
},
"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"
]
}
"name": "keycloakify-starter",
"homepage": "https://starter.keycloakify.dev",
"version": "5.2.0",
"description": "A starter/demo project for keycloakify",
"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"
},
"keycloakify": {
"themeName": "keycloakify-starter",
"extraThemeProperties": [
"foo=bar"
]
},
"author": "u/garronej",
"license": "MIT",
"keywords": [],
"dependencies": {
"evt": "^2.5.7",
"keycloakify": "^9.4.1",
"oidc-spa": "^4.2.1",
"powerhooks": "^1.0.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsafe": "^1.6.6",
"zod": "^3.22.4"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.6.14",
"@storybook/addon-interactions": "^7.6.14",
"@storybook/addon-links": "^7.6.14",
"@storybook/addon-onboarding": "^1.0.11",
"@storybook/blocks": "^7.6.14",
"@storybook/react": "^7.6.14",
"@storybook/react-vite": "^7.6.14",
"@storybook/test": "^7.6.14",
"@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.6.15",
"storybook": "^7.6.14",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"vite-plugin-commonjs": "^0.10.1"
},
"_comment": "See https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092",
"resolutions": {
"jackspeak": "2.1.1"
}
}

View file

@ -1,39 +1,36 @@
/*
<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">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/fonts/WorkSans/font.css">
This file is only meant to be used by Storybook
*/
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: normal; /*400*/
font-display: swap;
src: url("./worksans-regular-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("./worksans-medium-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("./worksans-semibold-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: bold; /*700*/
font-display: swap;
src: url("./worksans-bold-webfont.woff2") format("woff2");
}
font-family: "Work Sans";
font-style: normal;
font-weight: normal; /*400*/
font-display: swap;
src: url("./worksans-regular-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("./worksans-medium-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("./worksans-semibold-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: bold; /*700*/
font-display: swap;
src: url("./worksans-bold-webfont.woff2") format("woff2");
}

View file

@ -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>

View file

@ -22,7 +22,7 @@ export const { OidcProvider, useOidc } = createReactOidc({
"ui_locales": "en",
"my_custom_param": "value of foo transferred to login page"
}),
publicUrl: process.env.PUBLIC_URL,
publicUrl: import.meta.env.BASE_URL,
decodedIdTokenSchema: z.object({
// Use https://jwt.io/ to tell what's in your idToken
// It will depend of your Keycloak configuration.

View file

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

View file

@ -10,9 +10,9 @@ const MyExtraPage1 = lazy(() => import("./pages/MyExtraPage1"));
const MyExtraPage2 = lazy(() => import("./pages/MyExtraPage2"));
const Fallback = lazy(()=> import("keycloakify/account"));
const classes: PageProps<any, any>["classes"] = {
const classes = {
"kcBodyClass": "my-root-account-class"
};
} satisfies PageProps["classes"];
export default function KcApp(props: { kcContext: KcContext; }) {

View file

@ -15,7 +15,13 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: {
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} />
</>
);
}

View file

@ -1,18 +1,22 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({
pageId: "password.ftl"
});
export default {
const meta = {
title: "account/Password",
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
kcContext={{
message: { type: "success", summary: "This is a test message" }
}}
/>;
export const Default: Story = {
render: () => <PageStory
kcContext={{
message: { type: "success", summary: "This is a test message" }
}}
/>
};

View file

@ -16,11 +16,11 @@ const Info = lazy(() => import("keycloakify/login/pages/Info"));
// 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
const classes: PageProps<any, any>["classes"] = {
const classes = {
// NOTE: The classes are defined in ./KcApp.css
"kcHtmlClass": "my-root-class",
"kcHeaderWrapperClass": "my-color my-font"
};
} satisfies PageProps["classes"];
export default function KcApp(props: { kcContext: KcContext; }) {

View file

@ -9,7 +9,6 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "./kcContext";
import type { I18n } from "./i18n";
import keycloakifyLogoPngUrl from "./assets/keycloakify-logo.png";
import { PUBLIC_URL } from "../../PUBLIC_URL";
export default function Template(props: TemplateProps<KcContext, I18n>) {
const {
@ -61,12 +60,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
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.
See: https://docs.keycloakify.dev/importing-assets#importing-custom-assets
Here we are referencing the `keycloakify-logo.png` in the `public` directory.
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)}!!!
{/* 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} />
</div>
</div>
@ -77,14 +76,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
<div id="kc-locale">
<div id="kc-locale-wrapper" className={getClassName("kcLocaleWrapperClass")}>
<div className="kc-dropdown" id="kc-locale-dropdown">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" id="kc-current-locale-link">
{labelBySupportedLanguageTag[currentLanguageTag]}
</a>
<ul>
{locale.supported.map(({ languageTag }) => (
<li key={languageTag} className="kc-dropdown-item">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" onClick={() => changeLocale(languageTag)}>
{labelBySupportedLanguageTag[languageTag]}
</a>
@ -182,7 +179,6 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
>
<div className={getClassName("kcFormGroupClass")}>
<input type="hidden" name="tryAnotherWay" value="on" />
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a
href="#"
id="try-another-way"

View file

@ -18,7 +18,7 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: {
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="fonts/WorkSans/font.css" />
<link rel="stylesheet" type="text/css" href={`${import.meta.env.BASE_URL}fonts/WorkSans/font.css`} />
<KcApp kcContext={kcContext} />
</>
);

View file

@ -1,98 +1,83 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({
pageId: "login.ftl"
});
export default {
const meta = {
title: "login/Login",
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> = () => (
<PageStory
kcContext={{
realm: { password: false }
}}
/>
);
export const Default: Story = {
render: () => <PageStory />,
};
export const WithoutRegistration: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { registrationAllowed: false }
}}
/>
);
export const WithoutPasswordField: Story = {
render: () => <PageStory kcContext={{ realm: { password: false } }} />,
};
export const WithoutRememberMe: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { rememberMe: false }
}}
/>
);
export const WithoutRegistration: Story = {
render: () => <PageStory kcContext={{ realm: { registrationAllowed: false } }} />,
};
export const WithoutPasswordReset: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { resetPasswordAllowed: false }
}}
/>
);
export const WithoutRememberMe: Story = {
render: () => <PageStory kcContext={{ realm: { rememberMe: false } }} />,
};
export const WithEmailAsUsername: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { loginWithEmailAllowed: false }
}}
/>
);
export const WithoutPasswordReset: Story = {
render: () => <PageStory kcContext={{ realm: { resetPasswordAllowed: false } }} />,
};
export const WithPresetUsername: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
login: { username: "max.mustermann@mail.com" }
}}
/>
);
export const WithEmailAsUsername: Story = {
render: () => <PageStory kcContext={{ realm: { loginWithEmailAllowed: false } }} />,
};
export const WithImmutablePresetUsername: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
auth: {
attemptedUsername: "max.mustermann@mail.com",
showUsername: true
},
usernameHidden: true,
message: { type: "info", summary: "Please re-authenticate to continue" }
}}
/>
);
export const WithPresetUsername: Story = {
render: () => <PageStory kcContext={{ login: { username: "max.mustermann@mail.com" } }} />,
};
export const WithSocialProviders: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
social: {
displayInfo: true, providers: [
{ loginUrl: 'google', alias: 'google', providerId: 'google', displayName: 'Google' },
{ loginUrl: 'microsoft', alias: 'microsoft', providerId: 'microsoft', displayName: 'Microsoft' },
{ loginUrl: 'facebook', alias: 'facebook', providerId: 'facebook', displayName: 'Facebook' },
{ loginUrl: 'instagram', alias: 'instagram', providerId: 'instagram', displayName: 'Instagram' },
{ loginUrl: 'twitter', alias: 'twitter', providerId: 'twitter', displayName: 'Twitter' },
{ loginUrl: 'linkedin', alias: 'linkedin', providerId: 'linkedin', displayName: 'LinkedIn' },
{ loginUrl: 'stackoverflow', alias: 'stackoverflow', providerId: 'stackoverflow', displayName: 'Stackoverflow' },
{ loginUrl: 'github', alias: 'github', providerId: 'github', displayName: 'Github' },
{ loginUrl: 'gitlab', alias: 'gitlab', providerId: 'gitlab', displayName: 'Gitlab' },
{ loginUrl: 'bitbucket', alias: 'bitbucket', providerId: 'bitbucket', displayName: 'Bitbucket' },
{ loginUrl: 'paypal', alias: 'paypal', providerId: 'paypal', displayName: 'PayPal' },
{ loginUrl: 'openshift', alias: 'openshift', providerId: 'openshift', displayName: 'OpenShift' },
export const WithImmutablePresetUsername: Story = {
render: () => (
<PageStory
kcContext={{
auth: {
attemptedUsername: "max.mustermann@mail.com",
showUsername: true,
},
usernameHidden: true,
message: { type: "info", summary: "Please re-authenticate to continue" },
}}
/>
),
};
]
}
}}
/>
);
export const WithSocialProviders: 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' },
],
},
}}
/>
),
};

View file

@ -1,23 +1,30 @@
//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";
const { PageStory } = createPageStory({
pageId: "login-reset-password.ftl"
});
export default {
const meta = {
title: "login/LoginResetPassword",
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> = () => (
<PageStory
kcContext={{
realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true }
}}
/>
);
export const Default: Story = {
render: () => <PageStory />
};
export const WithEmailAsUsername: Story = {
render: () => (
<PageStory
kcContext={{
realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true }
}}
/>
)
};

View file

@ -1,21 +1,28 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({
pageId: "my-extra-page-2.ftl"
});
export default {
const meta = {
title: "login/MyExtraPage2",
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> = () => (
<PageStory
kcContext={{
someCustomValue: "abc"
}}
/>
);
export const Default: Story = {
render: () => <PageStory />
};
export const WitAbc: Story = {
render: () => (
<PageStory
kcContext={{
someCustomValue: "abc"
}}
/>
)
};

View file

@ -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 />;

View file

@ -1,13 +1,18 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({
pageId: "terms.ftl"
});
export default {
const meta = {
title: "login/Terms",
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 />
};

View file

@ -7,8 +7,6 @@ import { evtTermMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
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>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@ -28,18 +26,11 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
const tos_url = (() => {
switch (currentLanguageTag) {
case "fr": return tos_fr_url;
default: return tos_en_url;
case "fr": return `${import.meta.env.BASE_URL}terms/fr.md`;
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());
return markdownString;

View file

@ -32,3 +32,4 @@ createRoot(document.getElementById("root")!).render(
</Suspense>
</StrictMode>
);

View file

@ -1,5 +1,6 @@
/// <reference types="react-scripts" />
/// <reference types="vite/client" />
declare module "*.md" {
const src: string;
export default src;
}
}

View file

@ -1,21 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": "src",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
"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",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
tsconfig.node.json Normal file
View 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
View 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()
]
})

15061
yarn.lock

File diff suppressed because it is too large Load diff