diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..ff5edfe --- /dev/null +++ b/.eslintrc.cjs @@ -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', + }, + }, + ], +} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2e3aa48..a3ac8b3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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 }} diff --git a/.gitignore b/.gitignore index f96d6f1..ca0cce9 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,6 @@ jspm_packages /dist -/build_keycloak +/dist_keycloak /build /storybook-static diff --git a/.storybook/main.js b/.storybook/main.js deleted file mode 100644 index b7e6606..0000000 --- a/.storybook/main.js +++ /dev/null @@ -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'] -} \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..305ca53 --- /dev/null +++ b/.storybook/main.ts @@ -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; diff --git a/.storybook/preview.js b/.storybook/preview.js deleted file mode 100644 index b6de5f6..0000000 --- a/.storybook/preview.js +++ /dev/null @@ -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}), - }, -} \ No newline at end of file diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 0000000..ff58bbd --- /dev/null +++ b/.storybook/preview.ts @@ -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; diff --git a/Dockerfile b/Dockerfile index d506992..7056216 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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;' \ No newline at end of file diff --git a/README.md b/README.md index 45dcdf7..8d979a3 100644 --- a/README.md +++ b/README.md @@ -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 }} diff --git a/index.html b/index.html new file mode 100644 index 0000000..fc452ec --- /dev/null +++ b/index.html @@ -0,0 +1,76 @@ + + + + + + + + + + Keycloakify starter + + + + + + + + + + + + + + + + +
+ + + + diff --git a/package.json b/package.json index d7e8a0e..febe2ca 100755 --- a/package.json +++ b/package.json @@ -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" + } } diff --git a/public/fonts/WorkSans/font.css b/public/fonts/WorkSans/font.css index f4e3265..29f5e14 100644 --- a/public/fonts/WorkSans/font.css +++ b/public/fonts/WorkSans/font.css @@ -1,39 +1,36 @@ + /* - - - - - +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"); -} \ No newline at end of file + 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"); + } \ No newline at end of file diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 0361923..0000000 --- a/public/index.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - Keycloakify starter - - - - - - - - - - - - - - - - - - -
- - - - diff --git a/src/keycloak-theme/login/assets/tos_en.md b/public/terms/en.md similarity index 100% rename from src/keycloak-theme/login/assets/tos_en.md rename to public/terms/en.md diff --git a/src/keycloak-theme/login/assets/tos_fr.md b/public/terms/fr.md similarity index 100% rename from src/keycloak-theme/login/assets/tos_fr.md rename to public/terms/fr.md diff --git a/src/App/oidc.ts b/src/App/oidc.ts index 97cb66b..dcfdc16 100644 --- a/src/App/oidc.ts +++ b/src/App/oidc.ts @@ -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. diff --git a/src/PUBLIC_URL.ts b/src/PUBLIC_URL.ts deleted file mode 100644 index cc9d316..0000000 --- a/src/PUBLIC_URL.ts +++ /dev/null @@ -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`; - -})(); \ No newline at end of file diff --git a/src/keycloak-theme/account/KcApp.tsx b/src/keycloak-theme/account/KcApp.tsx index 030663a..3f88a88 100644 --- a/src/keycloak-theme/account/KcApp.tsx +++ b/src/keycloak-theme/account/KcApp.tsx @@ -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["classes"] = { +const classes = { "kcBodyClass": "my-root-account-class" -}; +} satisfies PageProps["classes"]; export default function KcApp(props: { kcContext: KcContext; }) { diff --git a/src/keycloak-theme/account/createPageStory.tsx b/src/keycloak-theme/account/createPageStory.tsx index cdf570f..cecb034 100644 --- a/src/keycloak-theme/account/createPageStory.tsx +++ b/src/keycloak-theme/account/createPageStory.tsx @@ -15,7 +15,13 @@ export function createPageStory(params: { storyPartialKcContext: params.kcContext }); - return ; + return ( + <> + {/* If you import custom fonts in your index.html you have to import them in storybook as well*/} + + + + ); } diff --git a/src/keycloak-theme/account/pages/Password.stories.tsx b/src/keycloak-theme/account/pages/Password.stories.tsx index 3f4c695..1ce118d 100644 --- a/src/keycloak-theme/account/pages/Password.stories.tsx +++ b/src/keycloak-theme/account/pages/Password.stories.tsx @@ -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; +} satisfies Meta; +export default meta; +type Story = StoryObj; -export const Default: ComponentStory = () => ; +export const Default: Story = { + render: () => +}; diff --git a/src/keycloak-theme/login/KcApp.tsx b/src/keycloak-theme/login/KcApp.tsx index 2433444..5366ad6 100644 --- a/src/keycloak-theme/login/KcApp.tsx +++ b/src/keycloak-theme/login/KcApp.tsx @@ -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["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; }) { diff --git a/src/keycloak-theme/login/Template.tsx b/src/keycloak-theme/login/Template.tsx index 7af5ad4..4f8857d 100644 --- a/src/keycloak-theme/login/Template.tsx +++ b/src/keycloak-theme/login/Template.tsx @@ -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) { const { @@ -61,12 +60,12 @@ export default function Template(props: TemplateProps) { 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 ... */} - Keycloakify logo + Keycloakify logo {msg("loginTitleHtml", realm.displayNameHtml)}!!! - {/* This is the preferred way to use assets */} + {/* ...rely on the bundler to import your assets, it's more efficient */} Keycloakify logo @@ -77,14 +76,12 @@ export default function Template(props: TemplateProps) {
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} {labelBySupportedLanguageTag[currentLanguageTag]}