Refactor terms
This commit is contained in:
parent
fdead071e7
commit
54b129630e
@ -5,7 +5,6 @@ import type { I18n } from "./i18n";
|
|||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
||||||
import type { TermsAcceptanceProps } from "keycloakify/login/TermsAcceptance";
|
|
||||||
|
|
||||||
const Login = lazy(() => import("keycloakify/login/pages/Login"));
|
const Login = lazy(() => import("keycloakify/login/pages/Login"));
|
||||||
const Register = lazy(() => import("keycloakify/login/pages/Register"));
|
const Register = lazy(() => import("keycloakify/login/pages/Register"));
|
||||||
@ -35,7 +34,6 @@ const DeleteCredential = lazy(() => import("keycloakify/login/pages/DeleteCreden
|
|||||||
|
|
||||||
type FallbackProps = PageProps<KcContext, I18n> & {
|
type FallbackProps = PageProps<KcContext, I18n> & {
|
||||||
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
||||||
TermsAcceptance: LazyOrNot<(props: TermsAcceptanceProps) => JSX.Element | null>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Fallback(props: FallbackProps) {
|
export default function Fallback(props: FallbackProps) {
|
||||||
|
@ -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<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function TermsAcceptance(props: TermsAcceptanceProps) {
|
|
||||||
const {
|
|
||||||
kcContext: { termsAcceptanceRequired = false }
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
if (!termsAcceptanceRequired) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <TermsAcceptanceEnabled {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<div className="form-group">
|
|
||||||
<div className={getClassName("kcInputWrapperClass")}>
|
|
||||||
{msg("termsTitle")}
|
|
||||||
<div id="kc-registration-terms-text">
|
|
||||||
<Markdown>{termMarkdown}</Markdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<div className={getClassName("kcLabelWrapperClass")}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="termsAccepted"
|
|
||||||
name="termsAccepted"
|
|
||||||
className={getClassName("kcCheckboxInputClass")}
|
|
||||||
aria-invalid={messagesPerField.existsError("termsAccepted")}
|
|
||||||
/>
|
|
||||||
<label htmlFor="termsAccepted" className={getClassName("kcLabelClass")}>
|
|
||||||
{msg("acceptTerms")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{messagesPerField.existsError("termsAccepted") && (
|
|
||||||
<div className={getClassName("kcLabelWrapperClass")}>
|
|
||||||
<span id="input-error-terms-accepted" className={getClassName("kcInputErrorMessageClass")} aria-live="polite">
|
|
||||||
{messagesPerField.get("termsAccepted")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -187,6 +187,7 @@ export declare namespace KcContext {
|
|||||||
* A Keycloak Java extension used as dependency in Keycloakify.
|
* A Keycloak Java extension used as dependency in Keycloakify.
|
||||||
*/
|
*/
|
||||||
passwordPolicies?: PasswordPolicies;
|
passwordPolicies?: PasswordPolicies;
|
||||||
|
termsAcceptanceRequired?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Info = Common & {
|
export type Info = Common & {
|
||||||
|
@ -5,9 +5,10 @@ import { useConst } from "keycloakify/tools/useConst";
|
|||||||
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { Evt } from "evt";
|
import { Evt } from "evt";
|
||||||
|
import { useRerenderOnStateChange } from "evt/hooks/useRerenderOnStateChange";
|
||||||
import { KcContext } from "../kcContext";
|
import { KcContext } from "../kcContext";
|
||||||
|
|
||||||
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
const evtTermsMarkdown = Evt.create<string | undefined>(undefined);
|
||||||
|
|
||||||
export type KcContextLike = {
|
export type KcContextLike = {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
@ -41,8 +42,16 @@ export function useDownloadTerms(params: {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
||||||
downloadTermMarkdownMemoized(kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag).then(
|
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 };
|
||||||
|
}
|
||||||
|
@ -2,26 +2,26 @@ import { useState } from "react";
|
|||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
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 { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
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<Extract<KcContext, { pageId: "register.ftl" | "register-user-profile.ftl" }>, I18n> & {
|
type RegisterProps = PageProps<Extract<KcContext, { pageId: "register.ftl" | "register-user-profile.ftl" }>, I18n> & {
|
||||||
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
||||||
TermsAcceptance: LazyOrNot<(props: TermsAcceptanceProps) => JSX.Element | null>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Register(props: RegisterProps) {
|
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({
|
const { getClassName } = useGetClassName({
|
||||||
doUseDefaultCss,
|
doUseDefaultCss,
|
||||||
classes
|
classes
|
||||||
});
|
});
|
||||||
|
|
||||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey, termsAcceptanceRequired } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
@ -39,7 +39,15 @@ export default function Register(props: RegisterProps) {
|
|||||||
}}
|
}}
|
||||||
onIsFormSubmittableValueChange={setIsFormSubmittable}
|
onIsFormSubmittableValueChange={setIsFormSubmittable}
|
||||||
/>
|
/>
|
||||||
<TermsAcceptance {...{ kcContext, i18n, getClassName }} />
|
{termsAcceptanceRequired && (
|
||||||
|
<TermsAcceptance
|
||||||
|
{...{
|
||||||
|
i18n,
|
||||||
|
getClassName,
|
||||||
|
messagesPerField
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{recaptchaRequired && (
|
{recaptchaRequired && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className={getClassName("kcInputWrapperClass")}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
@ -73,3 +81,54 @@ export default function Register(props: RegisterProps) {
|
|||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TermsAcceptance(props: {
|
||||||
|
i18n: I18n;
|
||||||
|
getClassName: ReturnType<typeof useGetClassName>["getClassName"];
|
||||||
|
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
||||||
|
}) {
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div className="form-group">
|
||||||
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
|
{msg("termsTitle")}
|
||||||
|
<div id="kc-registration-terms-text">
|
||||||
|
<Markdown>{termsMarkdown}</Markdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="termsAccepted"
|
||||||
|
name="termsAccepted"
|
||||||
|
className={getClassName("kcCheckboxInputClass")}
|
||||||
|
aria-invalid={messagesPerField.existsError("termsAccepted")}
|
||||||
|
/>
|
||||||
|
<label htmlFor="termsAccepted" className={getClassName("kcLabelClass")}>
|
||||||
|
{msg("acceptTerms")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{messagesPerField.existsError("termsAccepted") && (
|
||||||
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
|
<span id="input-error-terms-accepted" className={getClassName("kcInputErrorMessageClass")} aria-live="polite">
|
||||||
|
{messagesPerField.get("termsAccepted")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import { useRerenderOnStateChange } from "evt/hooks";
|
|
||||||
import { Markdown } from "keycloakify/tools/Markdown";
|
import { Markdown } from "keycloakify/tools/Markdown";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
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 { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
@ -17,20 +16,18 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
|
|||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
useRerenderOnStateChange(evtTermMarkdown);
|
|
||||||
|
|
||||||
const { url } = kcContext;
|
const { url } = kcContext;
|
||||||
|
|
||||||
const termMarkdown = evtTermMarkdown.state;
|
const { termsMarkdown } = useTermsMarkdown();
|
||||||
|
|
||||||
if (termMarkdown === undefined) {
|
if (termsMarkdown === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("termsTitle")}>
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("termsTitle")}>
|
||||||
<div id="kc-terms-text">
|
<div id="kc-terms-text">
|
||||||
<Markdown>{termMarkdown}</Markdown>
|
<Markdown>{termsMarkdown}</Markdown>
|
||||||
</div>
|
</div>
|
||||||
<form className="form-actions" action={url.loginAction} method="POST">
|
<form className="form-actions" action={url.loginAction} method="POST">
|
||||||
<input
|
<input
|
||||||
|
Loading…
x
Reference in New Issue
Block a user