diff options
| author | 2023-03-21 17:19:01 +0100 | |
|---|---|---|
| committer | 2023-03-21 17:19:01 +0100 | |
| commit | cee8ea7ecb4a341d46e6728af8a43a4b0af2ca49 (patch) | |
| tree | fbe79cecb6ef951bb03ca3adaa25232e776bef81 | |
| parent | Working with v7 (without account theme) (diff) | |
Update setup
| -rw-r--r-- | README.md | 2 | ||||
| -rwxr-xr-x | package.json | 4 | ||||
| -rw-r--r-- | src/App/App.tsx | 33 | ||||
| -rw-r--r-- | src/index.tsx | 8 | ||||
| -rw-r--r-- | src/keycloak-theme/account/KcApp.css | 5 | ||||
| -rw-r--r-- | src/keycloak-theme/account/KcApp.tsx | 41 | ||||
| -rw-r--r-- | src/keycloak-theme/account/Template.tsx | 132 | ||||
| -rw-r--r-- | src/keycloak-theme/account/assets/background.svg | 132 | ||||
| -rw-r--r-- | src/keycloak-theme/account/i18n.ts | 6 | ||||
| -rw-r--r-- | src/keycloak-theme/account/kcContext.ts | 17 | ||||
| -rw-r--r-- | src/keycloak-theme/account/pages/MyExtraPage1.tsx | 15 | ||||
| -rw-r--r-- | src/keycloak-theme/account/pages/MyExtraPage2.tsx | 18 | ||||
| -rw-r--r-- | src/keycloak-theme/account/pages/Password.tsx | 105 | ||||
| -rw-r--r-- | src/keycloak-theme/login/Template.tsx | 12 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/Login.tsx | 7 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/Register.tsx | 7 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/RegisterUserProfile.tsx | 7 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/Terms.tsx | 7 | ||||
| -rw-r--r-- | src/keycloak-theme/login/pages/shared/UserProfileCommons.tsx | 2 |
19 files changed, 524 insertions, 36 deletions
@@ -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 ( <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"; |