aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App/App.tsx33
-rw-r--r--src/index.tsx8
-rw-r--r--src/keycloak-theme/account/KcApp.css5
-rw-r--r--src/keycloak-theme/account/KcApp.tsx41
-rw-r--r--src/keycloak-theme/account/Template.tsx132
-rw-r--r--src/keycloak-theme/account/assets/background.svg132
-rw-r--r--src/keycloak-theme/account/i18n.ts6
-rw-r--r--src/keycloak-theme/account/kcContext.ts17
-rw-r--r--src/keycloak-theme/account/pages/MyExtraPage1.tsx15
-rw-r--r--src/keycloak-theme/account/pages/MyExtraPage2.tsx18
-rw-r--r--src/keycloak-theme/account/pages/Password.tsx105
-rw-r--r--src/keycloak-theme/login/Template.tsx12
-rw-r--r--src/keycloak-theme/login/pages/Login.tsx7
-rw-r--r--src/keycloak-theme/login/pages/Register.tsx7
-rw-r--r--src/keycloak-theme/login/pages/RegisterUserProfile.tsx7
-rw-r--r--src/keycloak-theme/login/pages/Terms.tsx7
-rw-r--r--src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx2
17 files changed, 519 insertions, 35 deletions
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 (
<div className="App">
<header className="App-header">
- {
- oidcClient.isUserLoggedIn ?
- <>
- <h1>You are authenticated !</h1>
- <pre style={{ textAlign: "left" }}>{JSON.stringify(jwt_decode(oidcClient.getAccessToken()), null, 2)}</pre>
- <button onClick={() => oidcClient.logout({ redirectTo: "home" })}>Logout</button>
- </>
- :
- <>
- <button onClick={() => oidcClient.login({ doesCurrentHrefRequiresAuth: false })}>Login</button>
- </>
- }
+ {
+ oidcClient.isUserLoggedIn ?
+ <>
+ <h1>You are authenticated !</h1>
+ {/* On older Keycloak version its /auth/realms instead of /realms */}
+ <a href={`${keycloakUrl}/realms/${keycloakRealm}/account`} target="_blank">Link to your Keycloak account</a>
+ <pre style={{ textAlign: "left" }}>{JSON.stringify(jwt_decode(oidcClient.getAccessToken()), null, 2)}</pre>
+ <button onClick={() => oidcClient.logout({ redirectTo: "home" })}>Logout</button>
+ </>
+ :
+ <>
+ <button onClick={() => oidcClient.login({ doesCurrentHrefRequiresAuth: false })}>Login</button>
+ </>
+ }
<img src={logo} className="App-logo" alt="logo" />
<img src={myimg} alt="test_image" />
<p style={{ "fontFamily": '"Work Sans"' }}>Hello world</p>
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(
<StrictMode>
@@ -14,6 +16,10 @@ createRoot(document.getElementById("root")!).render(
return <KcLoginThemeApp kcContext={kcLoginThemeContext} />;
}
+ if( kcAccountThemeContext !== undefined ){
+ return <KcAccountThemeApp kcContext={kcAccountThemeContext} />;
+ }
+
return <App />;
})()}
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<any, any>["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 (
+ <Suspense>
+ {(() => {
+ switch (kcContext.pageId) {
+ case "password.ftl": return <Password {...{ kcContext, i18n, Template, classes }} doUseDefaultCss={true} />;
+ case "my-extra-page-1.ftl": return <MyExtraPage1 {...{ kcContext, i18n, Template, classes }} doUseDefaultCss={true} />;
+ case "my-extra-page-2.ftl": return <MyExtraPage2 {...{ kcContext, i18n, Template, classes }} doUseDefaultCss={true} />;
+ default: return <Fallback {...{ kcContext, i18n, classes }} Template={DefaultTemplate} doUseDefaultCss={true} />;
+ }
+ })()}
+ </Suspense>
+ );
+
+}
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<KcContext, I18n>) {
+ 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 (
+ <>
+ <header className="navbar navbar-default navbar-pf navbar-main header">
+ <nav className="navbar" role="navigation">
+ <div className="navbar-header">
+ <div className="container">
+ <h1 className="navbar-title">Keycloak</h1>
+ </div>
+ </div>
+ <div className="navbar-collapse navbar-collapse-1">
+ <div className="container">
+ <ul className="nav navbar-nav navbar-utility">
+ {realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
+ <li>
+ <div className="kc-dropdown" id="kc-locale-dropdown">
+ <a href="#" id="kc-current-locale-link">
+ {labelBySupportedLanguageTag[currentLanguageTag]}
+ </a>
+ <ul>
+ {locale.supported.map(({ languageTag }) => (
+ <li key={languageTag} className="kc-dropdown-item">
+ {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
+ <a href="#" onClick={() => changeLocale(languageTag)}>
+ {labelBySupportedLanguageTag[languageTag]}
+ </a>
+ </li>
+ ))}
+ </ul>
+ </div>
+ </li>
+ )}
+ {referrer?.url !== undefined && (
+ <li>
+ <a href={referrer.url} id="referrer">
+ {msg("backTo", referrer.name)}
+ </a>
+ </li>
+ )}
+ <li>
+ <a href={url.getLogoutUrl()}>{msg("doSignOut")}</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+ </header>
+
+ <div className="container">
+ <div className="bs-sidebar col-sm-3">
+ <ul>
+ <li className={clsx(active === "account" && "active")}>
+ <a href={url.accountUrl}>{msg("account")}</a>
+ </li>
+ {features.passwordUpdateSupported && (
+ <li className={clsx(active === "password" && "active")}>
+ <a href={url.passwordUrl}>{msg("password")}</a>
+ </li>
+ )}
+ <li className={clsx(active === "totp" && "active")}>
+ <a href={url.totpUrl}>{msg("authenticator")}</a>
+ </li>
+ {features.identityFederation && (
+ <li className={clsx(active === "social" && "active")}>
+ <a href={url.socialUrl}>{msg("federatedIdentity")}</a>
+ </li>
+ )}
+ <li className={clsx(active === "sessions" && "active")}>
+ <a href={url.sessionsUrl}>{msg("sessions")}</a>
+ </li>
+ <li className={clsx(active === "applications" && "active")}>
+ <a href={url.applicationsUrl}>{msg("applications")}</a>
+ </li>
+ {features.log && (
+ <li className={clsx(active === "log" && "active")}>
+ <a href={url.logUrl}>{msg("log")}</a>
+ </li>
+ )}
+ {realm.userManagedAccessAllowed && features.authorization && (
+ <li className={clsx(active === "authorization" && "active")}>
+ <a href={url.resourceUrl}>{msg("myResources")}</a>
+ </li>
+ )}
+ </ul>
+ </div>
+
+ <div className="col-sm-9 content-area">
+ {message !== undefined && (
+ <div className={clsx("alert", `alert-${message.type}`)}>
+ {message.type === "success" && <span className="pficon pficon-ok"></span>}
+ {message.type === "error" && <span className="pficon pficon-error-circle-o"></span>}
+ <span className="kc-feedback-text">{message.summary}</span>
+ </div>
+ )}
+
+ {children}
+ </div>
+ </div>
+ </>
+ );
+}
diff --git a/src/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 @@
+<svg width="1521" height="961" viewBox="0 0 1521 961" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g opacity="0.4">
+<g filter="url(#filter0_dd)">
+<path d="M289.342 250.792L427.47 389.611C471.444 433.805 542.707 433.805 586.621 389.611L724.749 250.792L507.046 32L289.342 250.792Z" fill="#EFEEEE"/>
+<path d="M586.267 389.258L586.267 389.258C542.548 433.256 471.603 433.256 427.824 389.258L290.047 250.792L507.046 32.7089L724.044 250.792L586.267 389.258Z" stroke="white" stroke-opacity="0.01"/>
+</g>
+<g filter="url(#filter1_dd)">
+<path d="M32 509.755L170.128 648.573C214.103 692.767 285.365 692.767 329.28 648.573L467.408 509.755L249.704 290.962L32 509.755Z" fill="#EFEEEE"/>
+<path d="M328.925 648.221L328.925 648.221C285.206 692.218 214.262 692.219 170.483 648.221L32.7054 509.755L249.704 291.671L466.702 509.755L328.925 648.221Z" stroke="white" stroke-opacity="0.01"/>
+</g>
+<g filter="url(#filter2_dd)">
+<path d="M289.281 767.036L427.409 905.854C471.384 950.048 542.646 950.048 586.561 905.854L724.689 767.036L506.985 548.243L289.281 767.036Z" fill="#EFEEEE"/>
+<path d="M586.206 905.502L586.206 905.502C542.487 949.499 471.543 949.5 427.764 905.502L289.986 767.036L506.985 548.952L723.983 767.036L586.206 905.502Z" stroke="white" stroke-opacity="0.01"/>
+</g>
+<g filter="url(#filter3_dd)">
+<path d="M546.562 509.755L684.69 648.573C728.665 692.767 799.927 692.767 843.842 648.573L981.97 509.755L764.266 290.962L546.562 509.755Z" fill="#EFEEEE"/>
+<path d="M843.487 648.221L843.487 648.221C799.768 692.218 728.824 692.219 685.044 648.221L547.267 509.755L764.266 291.671L981.264 509.755L843.487 648.221Z" stroke="white" stroke-opacity="0.01"/>
+</g>
+<g filter="url(#filter4_dd)">
+<path d="M803.843 250.792L941.971 389.611C985.945 433.805 1057.21 433.805 1101.12 389.611L1239.25 250.792L1021.55 32L803.843 250.792Z" fill="#EFEEEE"/>
+<path d="M1100.77 389.258L1100.77 389.258C1057.05 433.256 986.105 433.256 942.325 389.258L804.548 250.792L1021.55 32.7089L1238.55 250.792L1100.77 389.258Z" stroke="white" stroke-opacity="0.01"/>
+</g>
+<g filter="url(#filter5_dd)">
+<path d="M1062.81 509.755L1200.93 648.573C1244.91 692.767 1316.17 692.767 1360.08 648.573L1498.21 509.755L1280.51 290.962L1062.81 509.755Z" fill="#EFEEEE"/>
+<path d="M1359.73 648.221L1359.73 648.221C1316.01 692.218 1245.07 692.219 1201.29 648.221L1063.51 509.755L1280.51 291.671L1497.51 509.755L1359.73 648.221Z" stroke="white" stroke-opacity="0.01"/>
+</g>
+<g filter="url(#filter6_dd)">
+<path d="M805.524 767.036L943.653 905.854C987.627 950.048 1058.89 950.048 1102.8 905.854L1240.93 767.036L1023.23 548.243L805.524 767.036Z" fill="#EFEEEE"/>
+<path d="M1102.45 905.502L1102.45 905.502C1058.73 949.499 987.786 949.5 944.007 905.502L806.23 767.036L1023.23 548.952L1240.23 767.036L1102.45 905.502Z" stroke="white" stroke-opacity="0.01"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_dd" x="257.342" y="0" width="489.408" height="444.757" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="6" dy="6"/>
+<feGaussianBlur stdDeviation="8"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.75 0 0 0 0 0.71011 0 0 0 0 0.653125 0 0 0 0.51 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="-6" dy="-6"/>
+<feGaussianBlur stdDeviation="13"/>
+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.83 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+</filter>
+<filter id="filter1_dd" x="0" y="258.962" width="489.408" height="444.757" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="6" dy="6"/>
+<feGaussianBlur stdDeviation="8"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.75 0 0 0 0 0.71011 0 0 0 0 0.653125 0 0 0 0.51 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="-6" dy="-6"/>
+<feGaussianBlur stdDeviation="13"/>
+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.83 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+</filter>
+<filter id="filter2_dd" x="257.281" y="516.243" width="489.408" height="444.757" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="6" dy="6"/>
+<feGaussianBlur stdDeviation="8"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.75 0 0 0 0 0.71011 0 0 0 0 0.653125 0 0 0 0.51 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="-6" dy="-6"/>
+<feGaussianBlur stdDeviation="13"/>
+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.83 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+</filter>
+<filter id="filter3_dd" x="514.562" y="258.962" width="489.408" height="444.757" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="6" dy="6"/>
+<feGaussianBlur stdDeviation="8"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.75 0 0 0 0 0.71011 0 0 0 0 0.653125 0 0 0 0.51 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="-6" dy="-6"/>
+<feGaussianBlur stdDeviation="13"/>
+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.83 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+</filter>
+<filter id="filter4_dd" x="771.843" y="0" width="489.408" height="444.757" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="6" dy="6"/>
+<feGaussianBlur stdDeviation="8"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.75 0 0 0 0 0.71011 0 0 0 0 0.653125 0 0 0 0.51 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="-6" dy="-6"/>
+<feGaussianBlur stdDeviation="13"/>
+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.83 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+</filter>
+<filter id="filter5_dd" x="1030.81" y="258.962" width="489.408" height="444.757" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="6" dy="6"/>
+<feGaussianBlur stdDeviation="8"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.75 0 0 0 0 0.71011 0 0 0 0 0.653125 0 0 0 0.51 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="-6" dy="-6"/>
+<feGaussianBlur stdDeviation="13"/>
+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.83 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+</filter>
+<filter id="filter6_dd" x="773.524" y="516.243" width="489.408" height="444.757" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="6" dy="6"/>
+<feGaussianBlur stdDeviation="8"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0.75 0 0 0 0 0.71011 0 0 0 0 0.653125 0 0 0 0.51 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
+<feOffset dx="-6" dy="-6"/>
+<feGaussianBlur stdDeviation="13"/>
+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.83 0"/>
+<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
+</filter>
+</defs>
+</svg>
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<ReturnType<typeof useI18n>>;
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<KcContextExtension>({
+ //mockPageId: "password.ftl",
+ mockData: [
+ {
+ pageId: "my-extra-page-2.ftl",
+ someCustomValue: "foo bar"
+ }
+ ]
+});
+
+export type KcContext = NonNullable<typeof kcContext>; \ 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<Extract<KcContext, { pageId: "my-extra-page-1.ftl"; }>, I18n>) {
+
+ const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
+
+ return (
+ <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="my-extra-page-1" >
+ <h1>Hello world 1</h1>
+ </Template>
+ );
+
+}
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<Extract<KcContext, { pageId: "my-extra-page-2.ftl"; }>, 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 (
+ <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="my-extra-page-2" >
+ <h1>Hello world 2</h1>
+ </Template>
+ );
+
+}
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<Extract<KcContext, { pageId: "password.ftl" }>, 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 (
+ <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="password">
+ <div className="row">
+ <div className="col-md-10">
+ <h2>{msg("changePasswordHtmlTitle")}</h2>
+ </div>
+ <div className="col-md-2 subtitle">
+ <span className="subtitle">${msg("allFieldsRequired")}</span>
+ </div>
+ </div>
+
+ <form action={url.passwordUrl} className="form-horizontal" method="post">
+ <input
+ type="text"
+ id="username"
+ name="username"
+ value={account.username ?? ""}
+ autoComplete="username"
+ readOnly
+ style={{ "display": "none;" }}
+ />
+
+ {password.passwordSet && (
+ <div className="form-group">
+ <div className="col-sm-2 col-md-2">
+ <label htmlFor="password" className="control-label">
+ {msg("password")}
+ </label>
+ </div>
+
+ <div className="col-sm-10 col-md-10">
+ <input type="password" className="form-control" id="password" name="password" autoFocus autoComplete="current-password" />
+ </div>
+ </div>
+ )}
+
+ <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}" />
+
+ <div className="form-group">
+ <div className="col-sm-2 col-md-2">
+ <label htmlFor="password-new" className="control-label">
+ {msg("passwordNew")}
+ </label>
+ </div>
+
+ <div className="col-sm-10 col-md-10">
+ <input type="password" className="form-control" id="password-new" name="password-new" autoComplete="new-password" />
+ </div>
+ </div>
+
+ <div className="form-group">
+ <div className="col-sm-2 col-md-2">
+ <label htmlFor="password-confirm" className="control-label two-lines">
+ {msg("passwordConfirm")}
+ </label>
+ </div>
+
+ <div className="col-sm-10 col-md-10">
+ <input type="password" className="form-control" id="password-confirm" name="password-confirm" autoComplete="new-password" />
+ </div>
+ </div>
+
+ <div className="form-group">
+ <div id="kc-form-buttons" className="col-md-offset-2 col-md-10 submit">
+ <div>
+ <button
+ type="submit"
+ className={clsx(
+ getClassName("kcButtonClass"),
+ getClassName("kcButtonPrimaryClass"),
+ getClassName("kcButtonLargeClass")
+ )}
+ name="submitAction"
+ value="Save"
+ >
+ {msg("doSave")}
+ </button>
+ </div>
+ </div>
+ </div>
+ </form>
+ </Template>
+ );
+}
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<KcContext, I18n>) {
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<Extract<KcContext, { pageId: "log
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
- "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
+ doUseDefaultCss,
classes
});
diff --git a/src/keycloak-theme/login/pages/Register.tsx b/src/keycloak-theme/login/pages/Register.tsx
index 20fbed0..9275a09 100644
--- a/src/keycloak-theme/login/pages/Register.tsx
+++ b/src/keycloak-theme/login/pages/Register.tsx
@@ -1,6 +1,7 @@
+// ejected using 'npx eject-keycloak-page'
import { clsx } from "keycloakify/tools/clsx";
-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";
@@ -8,7 +9,7 @@ export default function Register(props: PageProps<Extract<KcContext, { pageId: "
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
- "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
+ doUseDefaultCss,
classes
});
diff --git a/src/keycloak-theme/login/pages/RegisterUserProfile.tsx b/src/keycloak-theme/login/pages/RegisterUserProfile.tsx
index 1955845..1071091 100644
--- a/src/keycloak-theme/login/pages/RegisterUserProfile.tsx
+++ b/src/keycloak-theme/login/pages/RegisterUserProfile.tsx
@@ -1,8 +1,9 @@
+// ejected using 'npx eject-keycloak-page'
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { UserProfileFormFields } from "./shared/UserProfileCommons";
-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 RegisterUserProfile(props: PageProps<Extract<KcContext,
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
- "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
+ doUseDefaultCss,
classes
});
diff --git a/src/keycloak-theme/login/pages/Terms.tsx b/src/keycloak-theme/login/pages/Terms.tsx
index 819db53..7b44f45 100644
--- a/src/keycloak-theme/login/pages/Terms.tsx
+++ b/src/keycloak-theme/login/pages/Terms.tsx
@@ -1,8 +1,9 @@
+// ejected using 'npx eject-keycloak-page'
import { clsx } from "keycloakify/tools/clsx";
import { useRerenderOnStateChange } from "evt/hooks";
import { Markdown } from "keycloakify/tools/Markdown";
-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 { evtTermMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
@@ -11,7 +12,7 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
- "defaultClasses": !doUseDefaultCss ? undefined : defaultClasses,
+ doUseDefaultCss,
classes
});
diff --git a/src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx b/src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx
index 0a50bfd..3b2b7a4 100644
--- a/src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx
+++ b/src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx
@@ -1,5 +1,5 @@
import { useEffect, Fragment } from "react";
-import type { ClassKey } from "keycloakify/login/pages/PageProps";
+import type { ClassKey } from "keycloakify/login/TemplateProps";
import { clsx } from "keycloakify/tools/clsx";
import { useFormValidation } from "keycloakify/login/lib/useFormValidation";
import type { Attribute } from "keycloakify/login/kcContext/KcContext";