2021-03-06 15:16:21 +01:00
|
|
|
import { useReducer, useEffect, memo } from "react";
|
2021-03-02 01:05:15 +01:00
|
|
|
import type { ReactNode } from "react";
|
2021-03-07 15:37:37 +01:00
|
|
|
import { useKcMessage } from "../i18n/useKcMessage";
|
2021-03-06 15:16:21 +01:00
|
|
|
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
|
2021-06-23 08:16:51 +02:00
|
|
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
2021-03-04 21:14:54 +01:00
|
|
|
import { assert } from "../tools/assert";
|
2021-03-02 23:48:31 +01:00
|
|
|
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
2021-03-06 15:16:21 +01:00
|
|
|
import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
|
2021-03-02 23:48:31 +01:00
|
|
|
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
2021-07-21 22:10:28 +02:00
|
|
|
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
2021-03-04 13:56:51 +01:00
|
|
|
import { appendHead } from "../tools/appendHead";
|
2021-03-02 01:05:15 +01:00
|
|
|
import { join as pathJoin } from "path";
|
2021-07-21 22:10:28 +02:00
|
|
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
2021-03-06 14:42:56 +01:00
|
|
|
import type { KcTemplateProps } from "./KcProps";
|
2021-08-20 17:03:50 +02:00
|
|
|
import { useCssAndCx } from "tss-react";
|
2021-03-02 12:17:24 +01:00
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
export type TemplateProps = {
|
2021-03-02 12:17:24 +01:00
|
|
|
displayInfo?: boolean;
|
2021-03-02 22:48:36 +01:00
|
|
|
displayMessage?: boolean;
|
|
|
|
displayRequiredFields?: boolean;
|
|
|
|
displayWide?: boolean;
|
|
|
|
showAnotherWayIfPresent?: boolean;
|
2021-03-02 01:05:15 +01:00
|
|
|
headerNode: ReactNode;
|
2021-03-04 18:15:48 +01:00
|
|
|
showUsernameNode?: ReactNode;
|
2021-03-02 01:05:15 +01:00
|
|
|
formNode: ReactNode;
|
2021-03-07 14:57:53 +01:00
|
|
|
infoNode?: ReactNode;
|
2021-06-23 18:27:41 +02:00
|
|
|
/** If you write your own page you probably want
|
|
|
|
* to avoid pulling the default theme assets.
|
|
|
|
*/
|
|
|
|
doFetchDefaultThemeResources: boolean;
|
2021-10-11 21:35:40 +02:00
|
|
|
} & { kcContext: KcContextBase } & KcTemplateProps;
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
export const Template = memo((props: TemplateProps) => {
|
2021-03-02 01:05:15 +01:00
|
|
|
const {
|
|
|
|
displayInfo = false,
|
|
|
|
displayMessage = true,
|
|
|
|
displayRequiredFields = false,
|
|
|
|
displayWide = false,
|
|
|
|
showAnotherWayIfPresent = true,
|
|
|
|
headerNode,
|
2021-03-04 18:15:48 +01:00
|
|
|
showUsernameNode = null,
|
2021-03-02 01:05:15 +01:00
|
|
|
formNode,
|
2021-03-08 00:09:52 +01:00
|
|
|
infoNode = null,
|
2021-06-23 18:27:41 +02:00
|
|
|
kcContext,
|
2021-10-11 21:35:40 +02:00
|
|
|
doFetchDefaultThemeResources,
|
2021-03-02 01:05:15 +01:00
|
|
|
} = props;
|
|
|
|
|
2021-08-20 17:03:50 +02:00
|
|
|
const { cx } = useCssAndCx();
|
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
useEffect(() => {
|
|
|
|
console.log("Rendering this page with react using keycloakify");
|
|
|
|
}, []);
|
2021-03-05 15:46:34 +01:00
|
|
|
|
2021-03-07 15:37:37 +01:00
|
|
|
const { msg } = useKcMessage();
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
|
2021-03-02 01:05:15 +01:00
|
|
|
|
|
|
|
const onChangeLanguageClickFactory = useCallbackFactory(
|
2021-10-11 21:35:40 +02:00
|
|
|
([languageTag]: [KcLanguageTag]) => setKcLanguageTag(languageTag),
|
2021-03-02 01:05:15 +01:00
|
|
|
);
|
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
const onTryAnotherWayClick = useConstCallback(
|
|
|
|
() => (
|
|
|
|
document.forms["kc-select-try-another-way-form" as never].submit(),
|
|
|
|
false
|
|
|
|
),
|
2021-03-06 15:16:21 +01:00
|
|
|
);
|
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
const { realm, locale, auth, url, message, isAppInitiatedAction } =
|
|
|
|
kcContext;
|
2021-03-06 15:16:21 +01:00
|
|
|
|
2021-03-21 22:10:33 +01:00
|
|
|
useEffect(() => {
|
|
|
|
if (!realm.internationalizationEnabled) {
|
2021-03-06 15:16:21 +01:00
|
|
|
return;
|
|
|
|
}
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-21 22:10:33 +01:00
|
|
|
assert(locale !== undefined);
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-21 22:10:33 +01:00
|
|
|
if (kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current)) {
|
2021-03-06 15:16:21 +01:00
|
|
|
return;
|
|
|
|
}
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
window.location.href = locale.supported.find(
|
|
|
|
({ languageTag }) => languageTag === kcLanguageTag,
|
|
|
|
)!.url;
|
2021-03-21 22:10:33 +01:00
|
|
|
}, [kcLanguageTag]);
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
|
|
|
|
2021-03-02 01:05:15 +01:00
|
|
|
useEffect(() => {
|
2021-06-23 18:27:41 +02:00
|
|
|
if (!doFetchDefaultThemeResources) {
|
|
|
|
setExtraCssLoaded();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
let isUnmounted = false;
|
2021-03-21 22:10:33 +01:00
|
|
|
const cleanups: (() => void)[] = [];
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-21 22:10:33 +01:00
|
|
|
const toArr = (x: string | readonly string[] | undefined) =>
|
2021-03-06 15:16:21 +01:00
|
|
|
typeof x === "string" ? x.split(" ") : x ?? [];
|
2021-03-06 14:42:56 +01:00
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
Promise.all(
|
|
|
|
[
|
2021-10-11 21:35:40 +02:00
|
|
|
...toArr(props.stylesCommon).map(relativePath =>
|
|
|
|
pathJoin(url.resourcesCommonPath, relativePath),
|
|
|
|
),
|
|
|
|
...toArr(props.styles).map(relativePath =>
|
|
|
|
pathJoin(url.resourcesPath, relativePath),
|
|
|
|
),
|
|
|
|
].map(href =>
|
|
|
|
appendHead({
|
|
|
|
"type": "css",
|
|
|
|
href,
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
).then(() => {
|
|
|
|
if (isUnmounted) {
|
|
|
|
return;
|
|
|
|
}
|
2021-03-04 13:56:51 +01:00
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
setExtraCssLoaded();
|
|
|
|
});
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
toArr(props.scripts).forEach(relativePath =>
|
|
|
|
appendHead({
|
2021-03-04 13:56:51 +01:00
|
|
|
"type": "javascript",
|
2021-10-11 21:35:40 +02:00
|
|
|
"src": pathJoin(url.resourcesPath, relativePath),
|
|
|
|
}),
|
2021-03-02 01:05:15 +01:00
|
|
|
);
|
|
|
|
|
2021-03-20 02:54:15 +01:00
|
|
|
if (props.kcHtmlClass !== undefined) {
|
2021-03-21 22:10:33 +01:00
|
|
|
const htmlClassList =
|
2021-10-11 21:35:40 +02:00
|
|
|
document.getElementsByTagName("html")[0].classList;
|
2021-03-21 22:10:33 +01:00
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
const tokens = cx(props.kcHtmlClass).split(" ");
|
2021-03-21 22:10:33 +01:00
|
|
|
|
|
|
|
htmlClassList.add(...tokens);
|
|
|
|
|
|
|
|
cleanups.push(() => htmlClassList.remove(...tokens));
|
2021-03-20 02:54:15 +01:00
|
|
|
}
|
2021-03-03 03:17:56 +01:00
|
|
|
|
2021-03-21 22:10:33 +01:00
|
|
|
return () => {
|
|
|
|
isUnmounted = true;
|
|
|
|
|
|
|
|
cleanups.forEach(f => f());
|
|
|
|
};
|
2021-03-21 22:25:47 +01:00
|
|
|
}, [props.kcHtmlClass]);
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
if (!isExtraCssLoaded) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-03-02 01:05:15 +01:00
|
|
|
return (
|
2021-03-06 14:42:56 +01:00
|
|
|
<div className={cx(props.kcLoginClass)}>
|
|
|
|
<div id="kc-header" className={cx(props.kcHeaderClass)}>
|
2021-10-11 21:35:40 +02:00
|
|
|
<div
|
|
|
|
id="kc-header-wrapper"
|
|
|
|
className={cx(props.kcHeaderWrapperClass)}
|
|
|
|
>
|
2021-03-07 15:37:37 +01:00
|
|
|
{msg("loginTitleHtml", realm.displayNameHtml)}
|
2021-03-02 01:05:15 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2021-10-11 21:35:40 +02:00
|
|
|
<div
|
|
|
|
className={cx(
|
|
|
|
props.kcFormCardClass,
|
|
|
|
displayWide && props.kcFormCardAccountClass,
|
|
|
|
)}
|
|
|
|
>
|
2021-03-06 14:42:56 +01:00
|
|
|
<header className={cx(props.kcFormHeaderClass)}>
|
2021-10-11 21:35:40 +02:00
|
|
|
{realm.internationalizationEnabled &&
|
|
|
|
(assert(locale !== undefined), true) &&
|
|
|
|
locale.supported.length > 1 && (
|
|
|
|
<div id="kc-locale">
|
|
|
|
<div
|
|
|
|
id="kc-locale-wrapper"
|
|
|
|
className={cx(props.kcLocaleWrapperClass)}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className="kc-dropdown"
|
|
|
|
id="kc-locale-dropdown"
|
|
|
|
>
|
|
|
|
<a href="#" id="kc-current-locale-link">
|
|
|
|
{getKcLanguageTagLabel(
|
|
|
|
kcLanguageTag,
|
|
|
|
)}
|
|
|
|
</a>
|
|
|
|
<ul>
|
|
|
|
{locale.supported.map(
|
|
|
|
({ languageTag }) => (
|
|
|
|
<li
|
|
|
|
key={languageTag}
|
|
|
|
className="kc-dropdown-item"
|
|
|
|
>
|
|
|
|
<a
|
|
|
|
href="#"
|
|
|
|
onClick={onChangeLanguageClickFactory(
|
|
|
|
languageTag,
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
{getKcLanguageTagLabel(
|
|
|
|
languageTag,
|
|
|
|
)}
|
2021-03-02 01:05:15 +01:00
|
|
|
</a>
|
|
|
|
</li>
|
2021-10-11 21:35:40 +02:00
|
|
|
),
|
|
|
|
)}
|
|
|
|
</ul>
|
|
|
|
</div>
|
2021-03-02 01:05:15 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-10-11 21:35:40 +02:00
|
|
|
)}
|
|
|
|
{!(
|
|
|
|
auth !== undefined &&
|
|
|
|
auth.showUsername &&
|
|
|
|
!auth.showResetCredentials
|
|
|
|
) ? (
|
|
|
|
displayRequiredFields ? (
|
|
|
|
<div className={cx(props.kcContentWrapperClass)}>
|
|
|
|
<div
|
|
|
|
className={cx(
|
|
|
|
props.kcLabelWrapperClass,
|
|
|
|
"subtitle",
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<span className="subtitle">
|
|
|
|
<span className="required">*</span>
|
|
|
|
{msg("requiredFields")}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div className="col-md-10">
|
|
|
|
<h1 id="kc-page-title">{headerNode}</h1>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<h1 id="kc-page-title">{headerNode}</h1>
|
|
|
|
)
|
|
|
|
) : displayRequiredFields ? (
|
|
|
|
<div className={cx(props.kcContentWrapperClass)}>
|
|
|
|
<div
|
|
|
|
className={cx(
|
|
|
|
props.kcLabelWrapperClass,
|
|
|
|
"subtitle",
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<span className="subtitle">
|
|
|
|
<span className="required">*</span>{" "}
|
|
|
|
{msg("requiredFields")}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div className="col-md-10">
|
|
|
|
{showUsernameNode}
|
|
|
|
<div className={cx(props.kcFormGroupClass)}>
|
|
|
|
<div id="kc-username">
|
|
|
|
<label id="kc-attempted-username">
|
|
|
|
{auth?.attemptedUsername}
|
|
|
|
</label>
|
|
|
|
<a
|
|
|
|
id="reset-login"
|
|
|
|
href={url.loginRestartFlowUrl}
|
|
|
|
>
|
|
|
|
<div className="kc-login-tooltip">
|
|
|
|
<i
|
|
|
|
className={cx(
|
|
|
|
props.kcResetFlowIcon,
|
|
|
|
)}
|
|
|
|
></i>
|
|
|
|
<span className="kc-tooltip-text">
|
|
|
|
{msg("restartLoginTooltip")}
|
2021-03-02 01:05:15 +01:00
|
|
|
</span>
|
|
|
|
</div>
|
2021-10-11 21:35:40 +02:00
|
|
|
</a>
|
2021-03-02 01:05:15 +01:00
|
|
|
</div>
|
2021-10-11 21:35:40 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
{showUsernameNode}
|
|
|
|
<div className={cx(props.kcFormGroupClass)}>
|
|
|
|
<div id="kc-username">
|
|
|
|
<label id="kc-attempted-username">
|
|
|
|
{auth?.attemptedUsername}
|
|
|
|
</label>
|
|
|
|
<a
|
|
|
|
id="reset-login"
|
|
|
|
href={url.loginRestartFlowUrl}
|
|
|
|
>
|
|
|
|
<div className="kc-login-tooltip">
|
|
|
|
<i
|
|
|
|
className={cx(
|
|
|
|
props.kcResetFlowIcon,
|
|
|
|
)}
|
|
|
|
></i>
|
|
|
|
<span className="kc-tooltip-text">
|
|
|
|
{msg("restartLoginTooltip")}
|
|
|
|
</span>
|
2021-03-20 02:54:15 +01:00
|
|
|
</div>
|
2021-10-11 21:35:40 +02:00
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)}
|
2021-03-02 01:05:15 +01:00
|
|
|
</header>
|
|
|
|
<div id="kc-content">
|
|
|
|
<div id="kc-content-wrapper">
|
|
|
|
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
2021-10-11 21:35:40 +02:00
|
|
|
{displayMessage &&
|
|
|
|
message !== undefined &&
|
|
|
|
(message.type !== "warning" ||
|
|
|
|
!isAppInitiatedAction) && (
|
|
|
|
<div
|
|
|
|
className={cx(
|
|
|
|
"alert",
|
|
|
|
`alert-${message.type}`,
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
{message.type === "success" && (
|
|
|
|
<span
|
|
|
|
className={cx(
|
|
|
|
props.kcFeedbackSuccessIcon,
|
|
|
|
)}
|
|
|
|
></span>
|
|
|
|
)}
|
|
|
|
{message.type === "warning" && (
|
|
|
|
<span
|
|
|
|
className={cx(
|
|
|
|
props.kcFeedbackWarningIcon,
|
|
|
|
)}
|
|
|
|
></span>
|
|
|
|
)}
|
|
|
|
{message.type === "error" && (
|
|
|
|
<span
|
|
|
|
className={cx(
|
|
|
|
props.kcFeedbackErrorIcon,
|
|
|
|
)}
|
|
|
|
></span>
|
|
|
|
)}
|
|
|
|
{message.type === "info" && (
|
|
|
|
<span
|
|
|
|
className={cx(
|
|
|
|
props.kcFeedbackInfoIcon,
|
|
|
|
)}
|
|
|
|
></span>
|
|
|
|
)}
|
|
|
|
<span
|
|
|
|
className="kc-feedback-text"
|
|
|
|
dangerouslySetInnerHTML={{
|
|
|
|
"__html": message.summary,
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
2021-03-02 01:05:15 +01:00
|
|
|
{formNode}
|
2021-10-11 21:35:40 +02:00
|
|
|
{auth !== undefined &&
|
|
|
|
auth.showTryAnotherWayLink &&
|
|
|
|
showAnotherWayIfPresent && (
|
|
|
|
<form
|
|
|
|
id="kc-select-try-another-way-form"
|
|
|
|
action={url.loginAction}
|
|
|
|
method="post"
|
|
|
|
className={cx(
|
|
|
|
displayWide &&
|
|
|
|
props.kcContentWrapperClass,
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className={cx(
|
|
|
|
displayWide && [
|
|
|
|
props.kcFormSocialAccountContentClass,
|
|
|
|
props.kcFormSocialAccountClass,
|
|
|
|
],
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className={cx(
|
|
|
|
props.kcFormGroupClass,
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<input
|
|
|
|
type="hidden"
|
|
|
|
name="tryAnotherWay"
|
|
|
|
value="on"
|
|
|
|
/>
|
|
|
|
<a
|
|
|
|
href="#"
|
|
|
|
id="try-another-way"
|
|
|
|
onClick={onTryAnotherWayClick}
|
|
|
|
>
|
|
|
|
{msg("doTryAnotherWay")}
|
|
|
|
</a>
|
|
|
|
</div>
|
2021-03-02 01:05:15 +01:00
|
|
|
</div>
|
2021-10-11 21:35:40 +02:00
|
|
|
</form>
|
|
|
|
)}
|
|
|
|
{displayInfo && (
|
|
|
|
<div
|
|
|
|
id="kc-info"
|
|
|
|
className={cx(props.kcSignUpClass)}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
id="kc-info-wrapper"
|
|
|
|
className={cx(props.kcInfoAreaWrapperClass)}
|
|
|
|
>
|
2021-03-07 14:57:53 +01:00
|
|
|
{infoNode}
|
2021-03-02 01:05:15 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-10-11 21:35:40 +02:00
|
|
|
)}
|
2021-03-02 23:48:31 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-03-02 01:05:15 +01:00
|
|
|
);
|
2021-03-02 22:48:36 +01:00
|
|
|
});
|