diff --git a/README.md b/README.md index 8381213..034c88a 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ and remove unnecessary files. ```bash rm -r src/App -rm src/keycloak-theme/valuesTransferredOverUrl.ts +rm src/keycloak-theme/login/valuesTransferredOverUrl.ts mv src/keycloak-theme/* src/ rm -r src/keycloak-theme diff --git a/package.json b/package.json index a568c2d..281e663 100755 --- a/package.json +++ b/package.json @@ -20,6 +20,10 @@ "extraLoginPages": [ "my-extra-page-1.ftl", "my-extra-page-2.ftl" + ], + "extraAccountPages": [ + "my-extra-page-1.ftl", + "my-extra-page-2.ftl" ] }, "author": "u/garronej", diff --git a/src/App/App.tsx b/src/App/App.tsx index 68f1393..dccb8af 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -5,9 +5,12 @@ import { createOidcClientProvider, useOidcClient } from "./oidc"; import { addFooToQueryParams, addBarToQueryParams } from "../keycloak-theme/login/valuesTransferredOverUrl"; import jwt_decode from "jwt-decode"; +const keycloakUrl = "https://auth.code.gouv.fr" +const keycloakRealm = "keycloakify"; + const { OidcClientProvider } = createOidcClientProvider({ - url: "https://auth.code.gouv.fr/auth", - realm: "keycloakify", + url: `${keycloakUrl}/auth`, + realm: keycloakRealm, clientId: "starter", //This function will be called just before redirecting, //it should return the current langue. @@ -38,18 +41,20 @@ function ContextualizedApp() { return (
- { - oidcClient.isUserLoggedIn ? - <> -

You are authenticated !

-
{JSON.stringify(jwt_decode(oidcClient.getAccessToken()), null, 2)}
- - - : - <> - - - } + { + oidcClient.isUserLoggedIn ? + <> +

You are authenticated !

+ {/* On older Keycloak version its /auth/realms instead of /realms */} + Link to your Keycloak account +
{JSON.stringify(jwt_decode(oidcClient.getAccessToken()), null, 2)}
+ + + : + <> + + + } logo test_image

Hello world

diff --git a/src/index.tsx b/src/index.tsx index ba55bfe..fa7baab 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,11 @@ import { createRoot } from "react-dom/client"; import { StrictMode, lazy, Suspense } from "react"; import { kcContext as kcLoginThemeContext } from "./keycloak-theme/login/kcContext"; +import { kcContext as kcAccountThemeContext } from "./keycloak-theme/account/kcContext"; -const App = lazy(() => import("./App")); const KcLoginThemeApp = lazy(() => import("./keycloak-theme/login/KcApp")); +const KcAccountThemeApp = lazy(() => import("./keycloak-theme/account/KcApp")); +const App = lazy(() => import("./App")); createRoot(document.getElementById("root")!).render( @@ -14,6 +16,10 @@ createRoot(document.getElementById("root")!).render( return ; } + if( kcAccountThemeContext !== undefined ){ + return ; + } + return ; })()} diff --git a/src/keycloak-theme/account/KcApp.css b/src/keycloak-theme/account/KcApp.css new file mode 100644 index 0000000..1f110d9 --- /dev/null +++ b/src/keycloak-theme/account/KcApp.css @@ -0,0 +1,5 @@ + + +.my-root-class { + background: url(./assets/background.svg) no-repeat center center fixed; +} \ No newline at end of file diff --git a/src/keycloak-theme/account/KcApp.tsx b/src/keycloak-theme/account/KcApp.tsx new file mode 100644 index 0000000..d6d9e46 --- /dev/null +++ b/src/keycloak-theme/account/KcApp.tsx @@ -0,0 +1,41 @@ +import "./KcApp.css"; +import { lazy, Suspense } from "react"; +import Fallback, { type PageProps } from "keycloakify/account"; +import type { KcContext } from "./kcContext"; +import { useI18n } from "./i18n"; + +const Template = lazy(() => import("./Template")); +const DefaultTemplate = lazy(() => import("keycloakify/account/Template")); + +const Password = lazy(() => import("./pages/Password")); +const MyExtraPage1 = lazy(() => import("./pages/MyExtraPage1")); +const MyExtraPage2 = lazy(() => import("./pages/MyExtraPage2")); + +const classes: PageProps["classes"] = { + "kcBodyClass": "my-root-class" +}; + +export default function App(props: { kcContext: KcContext; }) { + + const { kcContext } = props; + + const i18n = useI18n({ kcContext }); + + if (i18n === null) { + return null; + } + + return ( + + {(() => { + switch (kcContext.pageId) { + case "password.ftl": return ; + case "my-extra-page-1.ftl": return ; + case "my-extra-page-2.ftl": return ; + default: return ; + } + })()} + + ); + +} diff --git a/src/keycloak-theme/account/Template.tsx b/src/keycloak-theme/account/Template.tsx new file mode 100644 index 0000000..283178b --- /dev/null +++ b/src/keycloak-theme/account/Template.tsx @@ -0,0 +1,132 @@ +// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/login/Template.tsx + +import { clsx } from "keycloakify/tools/clsx"; +import { usePrepareTemplate } from "keycloakify/lib/usePrepareTemplate"; +import { type TemplateProps } from "keycloakify/account/TemplateProps"; +import { useGetClassName } from "keycloakify/account/lib/useGetClassName"; +import type { KcContext } from "./kcContext"; +import type { I18n } from "./i18n"; +import { assert } from "keycloakify/tools/assert"; + +export default function Template(props: TemplateProps) { + const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props; + + const { getClassName } = useGetClassName({ doUseDefaultCss, classes }); + + const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n; + + const { locale, url, features, realm, message, referrer } = kcContext; + + const { isReady } = usePrepareTemplate({ + "doFetchDefaultThemeResources": doUseDefaultCss, + url, + "stylesCommon": ["node_modules/patternfly/dist/css/patternfly.min.css", "node_modules/patternfly/dist/css/patternfly-additions.min.css"], + "styles": ["css/account.css"], + "htmlClassName": undefined, + "bodyClassName": clsx("admin-console", "user", getClassName("kcBodyClass")) + }); + + if (!isReady) { + return null; + } + + return ( + <> +
+ +
+ +
+
+ +
+ +
+ {message !== undefined && ( +
+ {message.type === "success" && } + {message.type === "error" && } + {message.summary} +
+ )} + + {children} +
+
+ + ); +} diff --git a/src/keycloak-theme/account/assets/background.svg b/src/keycloak-theme/account/assets/background.svg new file mode 100644 index 0000000..0e1cada --- /dev/null +++ b/src/keycloak-theme/account/assets/background.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/keycloak-theme/account/i18n.ts b/src/keycloak-theme/account/i18n.ts new file mode 100644 index 0000000..45f75c4 --- /dev/null +++ b/src/keycloak-theme/account/i18n.ts @@ -0,0 +1,6 @@ +import { createUseI18n } from "keycloakify/account"; + +//NOTE: See src/login/i18n.ts for instructions on customization of i18n messages. +export const { useI18n } = createUseI18n({}); + +export type I18n = NonNullable>; diff --git a/src/keycloak-theme/account/kcContext.ts b/src/keycloak-theme/account/kcContext.ts new file mode 100644 index 0000000..f105263 --- /dev/null +++ b/src/keycloak-theme/account/kcContext.ts @@ -0,0 +1,17 @@ +import { getKcContext } from "keycloakify/account"; + +export type KcContextExtension = + | { pageId: "my-extra-page-1.ftl"; } + | { pageId: "my-extra-page-2.ftl"; someCustomValue: string; }; + +export const { kcContext } = getKcContext({ + //mockPageId: "password.ftl", + mockData: [ + { + pageId: "my-extra-page-2.ftl", + someCustomValue: "foo bar" + } + ] +}); + +export type KcContext = NonNullable; \ No newline at end of file diff --git a/src/keycloak-theme/account/pages/MyExtraPage1.tsx b/src/keycloak-theme/account/pages/MyExtraPage1.tsx new file mode 100644 index 0000000..649e4cb --- /dev/null +++ b/src/keycloak-theme/account/pages/MyExtraPage1.tsx @@ -0,0 +1,15 @@ +import type { PageProps } from "keycloakify/account/pages/PageProps"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; + +export default function MyExtraPage1(props: PageProps, I18n>) { + + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + + return ( + + ); + +} diff --git a/src/keycloak-theme/account/pages/MyExtraPage2.tsx b/src/keycloak-theme/account/pages/MyExtraPage2.tsx new file mode 100644 index 0000000..dc90e84 --- /dev/null +++ b/src/keycloak-theme/account/pages/MyExtraPage2.tsx @@ -0,0 +1,18 @@ +import type { PageProps } from "keycloakify/account/pages/PageProps"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; + +export default function MyExtraPage1(props: PageProps, I18n>) { + + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + + // someCustomValue is declared by you in ../kcContext.ts + console.log(`TODO: Do something with: ${kcContext.someCustomValue}`); + + return ( + + ); + +} diff --git a/src/keycloak-theme/account/pages/Password.tsx b/src/keycloak-theme/account/pages/Password.tsx new file mode 100644 index 0000000..c92b4d1 --- /dev/null +++ b/src/keycloak-theme/account/pages/Password.tsx @@ -0,0 +1,105 @@ +import { clsx } from "keycloakify/tools/clsx"; +import type { PageProps } from "keycloakify/account/pages/PageProps"; +import { useGetClassName } from "keycloakify/account/lib/useGetClassName"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; + +export default function LogoutConfirm(props: PageProps, I18n>) { + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + + const { getClassName } = useGetClassName({ + doUseDefaultCss, + "classes": { + ...classes, + "kcBodyClass": clsx(classes?.kcBodyClass, "password") + } + }); + + const { url, password, account } = kcContext; + + const { msg } = i18n; + + return ( + + ); +} diff --git a/src/keycloak-theme/login/Template.tsx b/src/keycloak-theme/login/Template.tsx index 1fd077a..3e732e4 100644 --- a/src/keycloak-theme/login/Template.tsx +++ b/src/keycloak-theme/login/Template.tsx @@ -1,9 +1,10 @@ -// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/Template.tsx +// Copy pasted from: https://github.com/InseeFrLab/keycloakify/blob/main/src/login/Template.tsx + import { assert } from "keycloakify/tools/assert"; import { clsx } from "keycloakify/tools/clsx"; import { usePrepareTemplate } from "keycloakify/lib/usePrepareTemplate"; -import { type TemplateProps, defaultTemplateClasses } from "keycloakify/login/TemplateProps"; -import { useGetClassName } from "keycloakify/lib/useGetClassName"; +import { type TemplateProps } from "keycloakify/login/TemplateProps"; +import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; import type { KcContext } from "./kcContext"; import type { I18n } from "./i18n"; @@ -24,10 +25,7 @@ export default function Template(props: TemplateProps) { children } = props; - const { getClassName } = useGetClassName({ - "defaultClasses": !doUseDefaultCss ? undefined : defaultTemplateClasses, - classes - }); + const { getClassName } = useGetClassName({ doUseDefaultCss, classes }); const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n; diff --git a/src/keycloak-theme/login/pages/Login.tsx b/src/keycloak-theme/login/pages/Login.tsx index f89c259..4d2de20 100644 --- a/src/keycloak-theme/login/pages/Login.tsx +++ b/src/keycloak-theme/login/pages/Login.tsx @@ -1,8 +1,9 @@ +// ejected using 'npx eject-keycloak-page' import { useState, type FormEventHandler } from "react"; import { clsx } from "keycloakify/tools/clsx"; import { useConstCallback } from "keycloakify/tools/useConstCallback"; -import { type PageProps, defaultClasses } from "keycloakify/login/pages/PageProps"; -import { useGetClassName } from "keycloakify/lib/useGetClassName"; +import type { PageProps } from "keycloakify/login/pages/PageProps"; +import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; @@ -10,7 +11,7 @@ export default function Login(props: PageProps