diff --git a/src/login/Fallback.tsx b/src/login/Fallback.tsx index 89a9cb75..864cac1f 100644 --- a/src/login/Fallback.tsx +++ b/src/login/Fallback.tsx @@ -5,7 +5,6 @@ import type { I18n } from "./i18n"; import type { KcContext } from "./kcContext"; import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields"; -import type { TermsAcceptanceProps } from "keycloakify/login/TermsAcceptance"; const Login = lazy(() => import("keycloakify/login/pages/Login")); const Register = lazy(() => import("keycloakify/login/pages/Register")); @@ -35,7 +34,6 @@ const DeleteCredential = lazy(() => import("keycloakify/login/pages/DeleteCreden type FallbackProps = PageProps & { UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>; - TermsAcceptance: LazyOrNot<(props: TermsAcceptanceProps) => JSX.Element | null>; }; export default function Fallback(props: FallbackProps) { diff --git a/src/login/TermsAcceptance.tsx b/src/login/TermsAcceptance.tsx deleted file mode 100644 index 4b417ebe..00000000 --- a/src/login/TermsAcceptance.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import type { ClassKey } from "keycloakify/login/TemplateProps"; -import { useRerenderOnStateChange } from "evt/hooks"; -import { Markdown } from "keycloakify/tools/Markdown"; -import { evtTermMarkdown } from "keycloakify/login/lib/useDownloadTerms"; -import type { KcContext } from "keycloakify/login/kcContext/KcContext"; -import type { I18n } from "./i18n"; - -export type TermsAcceptanceProps = { - kcContext: KcContextLike; - i18n: I18n; - getClassName: (classKey: ClassKey) => string; -}; - -type KcContextLike = { - termsAcceptanceRequired?: boolean; - messagesPerField: Pick; -}; - -export function TermsAcceptance(props: TermsAcceptanceProps) { - const { - kcContext: { termsAcceptanceRequired = false } - } = props; - - if (!termsAcceptanceRequired) { - return null; - } - - return ; -} - -export function TermsAcceptanceEnabled(props: TermsAcceptanceProps) { - const { - i18n, - getClassName, - kcContext: { messagesPerField } - } = props; - - const { msg } = i18n; - - useRerenderOnStateChange(evtTermMarkdown); - - const termMarkdown = evtTermMarkdown.state; - - if (termMarkdown === undefined) { - return null; - } - - return ( - <> -
-
- {msg("termsTitle")} -
- {termMarkdown} -
-
-
-
-
- - -
- {messagesPerField.existsError("termsAccepted") && ( -
- - {messagesPerField.get("termsAccepted")} - -
- )} -
- - ); -} diff --git a/src/login/kcContext/KcContext.ts b/src/login/kcContext/KcContext.ts index 7950c189..8e85db6d 100644 --- a/src/login/kcContext/KcContext.ts +++ b/src/login/kcContext/KcContext.ts @@ -187,6 +187,7 @@ export declare namespace KcContext { * A Keycloak Java extension used as dependency in Keycloakify. */ passwordPolicies?: PasswordPolicies; + termsAcceptanceRequired?: boolean; }; export type Info = Common & { diff --git a/src/login/lib/useDownloadTerms.ts b/src/login/lib/useDownloadTerms.ts index dae393d7..9c968d42 100644 --- a/src/login/lib/useDownloadTerms.ts +++ b/src/login/lib/useDownloadTerms.ts @@ -5,9 +5,10 @@ import { useConst } from "keycloakify/tools/useConst"; import { useConstCallback } from "keycloakify/tools/useConstCallback"; import { assert } from "tsafe/assert"; import { Evt } from "evt"; +import { useRerenderOnStateChange } from "evt/hooks/useRerenderOnStateChange"; import { KcContext } from "../kcContext"; -export const evtTermMarkdown = Evt.create(undefined); +const evtTermsMarkdown = Evt.create(undefined); export type KcContextLike = { pageId: string; @@ -41,8 +42,16 @@ export function useDownloadTerms(params: { useEffect(() => { if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) { downloadTermMarkdownMemoized(kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag).then( - thermMarkdown => (evtTermMarkdown.state = thermMarkdown) + thermMarkdown => (evtTermsMarkdown.state = thermMarkdown) ); } }, []); } + +export function useTermsMarkdown() { + useRerenderOnStateChange(evtTermsMarkdown); + + const termsMarkdown = evtTermsMarkdown.state; + + return { termsMarkdown }; +} diff --git a/src/login/pages/Register.tsx b/src/login/pages/Register.tsx index 2f521b89..d1e65d9d 100644 --- a/src/login/pages/Register.tsx +++ b/src/login/pages/Register.tsx @@ -2,26 +2,26 @@ import { useState } from "react"; import { clsx } from "keycloakify/tools/clsx"; import type { PageProps } from "keycloakify/login/pages/PageProps"; import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; +import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; +import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms"; +import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields"; +import { Markdown } from "keycloakify/tools/Markdown"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; -import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; -import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields"; -import type { TermsAcceptanceProps } from "../TermsAcceptance"; type RegisterProps = PageProps, I18n> & { UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>; - TermsAcceptance: LazyOrNot<(props: TermsAcceptanceProps) => JSX.Element | null>; }; export default function Register(props: RegisterProps) { - const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields, TermsAcceptance } = props; + const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields } = props; const { getClassName } = useGetClassName({ doUseDefaultCss, classes }); - const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext; + const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey, termsAcceptanceRequired } = kcContext; const { msg, msgStr } = i18n; @@ -39,7 +39,15 @@ export default function Register(props: RegisterProps) { }} onIsFormSubmittableValueChange={setIsFormSubmittable} /> - + {termsAcceptanceRequired && ( + + )} {recaptchaRequired && (
@@ -73,3 +81,54 @@ export default function Register(props: RegisterProps) { ); } + +function TermsAcceptance(props: { + i18n: I18n; + getClassName: ReturnType["getClassName"]; + messagesPerField: Pick; +}) { + const { i18n, getClassName, messagesPerField } = props; + + const { msg } = i18n; + + // NOTE: Refer to https://docs.keycloakify.dev/terms-and-conditions to load your terms and conditions. + const { termsMarkdown } = useTermsMarkdown(); + + if (termsMarkdown === undefined) { + return null; + } + + return ( + <> +
+
+ {msg("termsTitle")} +
+ {termsMarkdown} +
+
+
+
+
+ + +
+ {messagesPerField.existsError("termsAccepted") && ( +
+ + {messagesPerField.get("termsAccepted")} + +
+ )} +
+ + ); +} diff --git a/src/login/pages/Terms.tsx b/src/login/pages/Terms.tsx index fe0149a5..723d3ed3 100644 --- a/src/login/pages/Terms.tsx +++ b/src/login/pages/Terms.tsx @@ -1,9 +1,8 @@ import { clsx } from "keycloakify/tools/clsx"; -import { useRerenderOnStateChange } from "evt/hooks"; import { Markdown } from "keycloakify/tools/Markdown"; import type { PageProps } from "keycloakify/login/pages/PageProps"; import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; -import { evtTermMarkdown } from "keycloakify/login/lib/useDownloadTerms"; +import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms"; import type { KcContext } from "../kcContext"; import type { I18n } from "../i18n"; @@ -17,20 +16,18 @@ export default function Terms(props: PageProps
- {termMarkdown} + {termsMarkdown}