310 lines
13 KiB
TypeScript
Raw Normal View History

2021-03-02 01:05:15 +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";
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
2021-03-08 00:09:52 +01:00
import type { KcContext } from "../KcContext";
2021-03-04 21:14:54 +01:00
import { assert } from "../tools/assert";
2021-03-02 01:05:15 +01:00
import { cx } from "tss-react";
2021-03-02 23:48:31 +01:00
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
2021-03-02 23:48:31 +01:00
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
2021-03-02 01:05:15 +01:00
import { useCallbackFactory } from "powerhooks";
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";
import { useConstCallback } from "powerhooks";
2021-03-06 14:42:56 +01:00
import type { KcTemplateProps } from "./KcProps";
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;
} & { kcContext: KcContext; } & 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,
kcContext
2021-03-02 01:05:15 +01:00
} = props;
useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []);
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
2021-03-02 01:05:15 +01:00
const onChangeLanguageClickFactory = useCallbackFactory(
([languageTag]: [KcLanguageTag]) =>
2021-03-02 23:48:31 +01:00
setKcLanguageTag(languageTag)
2021-03-02 01:05:15 +01:00
);
const onTryAnotherWayClick = useConstCallback(() =>
(document.forms["kc-select-try-another-way-form" as never].submit(), false)
);
const {
realm, locale, auth,
url, message, isAppInitiatedAction
} = kcContext;
useEffect(() => {
if (!realm.internationalizationEnabled) {
return;
}
2021-03-02 01:05:15 +01:00
assert(locale !== undefined);
2021-03-02 01:05:15 +01:00
if (kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current)) {
return;
}
2021-03-02 01:05:15 +01:00
window.location.href =
locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
}, [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-03-04 13:56:51 +01:00
let isUnmounted = false;
const cleanups: (() => void)[] = [];
2021-03-02 01:05:15 +01:00
const toArr = (x: string | readonly string[] | undefined) =>
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-03-06 14:42:56 +01:00
...toArr(props.stylesCommon).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)),
...toArr(props.styles).map(relativePath => pathJoin(url.resourcesPath, relativePath))
2021-03-04 13:56:51 +01:00
].map(href => appendHead({
"type": "css",
href
}))).then(() => {
if (isUnmounted) {
return;
}
setExtraCssLoaded();
});
2021-03-02 01:05:15 +01:00
2021-03-06 14:42:56 +01:00
toArr(props.scripts).forEach(
2021-03-04 13:56:51 +01:00
relativePath => appendHead({
"type": "javascript",
"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) {
const htmlClassList =
document.getElementsByTagName("html")[0]
.classList;
const tokens = cx(props.kcHtmlClass).split(" ")
htmlClassList.add(...tokens);
cleanups.push(() => htmlClassList.remove(...tokens));
2021-03-20 02:54:15 +01:00
}
2021-03-03 03:17:56 +01:00
return () => {
isUnmounted = true;
cleanups.forEach(f => f());
};
2021-03-02 01:05:15 +01:00
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)}>
2021-03-02 01:05:15 +01:00
2021-03-06 14:42:56 +01:00
<div id="kc-header" className={cx(props.kcHeaderClass)}>
<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-03-06 14:42:56 +01:00
<div className={cx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
<header className={cx(props.kcFormHeaderClass)}>
2021-03-02 01:05:15 +01:00
{
(
realm.internationalizationEnabled &&
(assert(locale !== undefined), true) &&
locale.supported.length > 1
) &&
<div id="kc-locale">
2021-03-06 14:42:56 +01:00
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
2021-03-02 01:05:15 +01:00
<div className="kc-dropdown" id="kc-locale-dropdown">
<a href="#" id="kc-current-locale-link">
2021-03-02 23:48:31 +01:00
{getKcLanguageTagLabel(kcLanguageTag)}
2021-03-02 01:05:15 +01:00
</a>
<ul>
{
locale.supported.map(
2021-03-20 02:54:15 +01:00
({ languageTag }) =>
2021-03-08 00:09:52 +01:00
<li key={languageTag} className="kc-dropdown-item">
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
2021-03-02 23:48:31 +01:00
{getKcLanguageTagLabel(languageTag)}
2021-03-02 01:05:15 +01:00
</a>
</li>
)
}
</ul>
</div>
</div>
</div>
}
{
2021-03-04 13:56:51 +01:00
!(
2021-03-02 01:05:15 +01:00
auth !== undefined &&
auth.showUsername &&
!auth.showResetCredentials
) ?
(
displayRequiredFields ?
(
2021-03-06 14:42:56 +01:00
<div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
2021-03-02 01:05:15 +01:00
<span className="subtitle">
<span className="required">*</span>
2021-03-07 15:37:37 +01:00
{msg("requiredFields")}
2021-03-02 01:05:15 +01:00
</span>
</div>
<div className="col-md-10">
<h1 id="kc-page-title">{headerNode}</h1>
</div>
</div>
)
:
(
<h1 id="kc-page-title">{headerNode}</h1>
)
) : (
displayRequiredFields ? (
2021-03-06 14:42:56 +01:00
<div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
2021-03-07 15:37:37 +01:00
<span className="subtitle"><span className="required">*</span> {msg("requiredFields")}</span>
2021-03-02 01:05:15 +01:00
</div>
<div className="col-md-10">
{showUsernameNode}
2021-03-06 14:42:56 +01:00
<div className={cx(props.kcFormGroupClass)}>
2021-03-02 01:05:15 +01:00
<div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
<a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip">
2021-03-06 14:42:56 +01:00
<i className={cx(props.kcResetFlowIcon)}></i>
2021-03-07 15:37:37 +01:00
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
2021-03-02 01:05:15 +01:00
</div>
</a>
</div>
</div>
</div>
</div>
) : (
2021-03-20 02:54:15 +01:00
<>
{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>
</div>
</a>
2021-03-02 01:05:15 +01:00
</div>
2021-03-20 02:54:15 +01:00
</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. */}
{
(
displayMessage &&
message !== undefined &&
(
message.type !== "warning" ||
!isAppInitiatedAction
)
) &&
<div className={cx("alert", `alert-${message.type}`)}>
2021-03-06 14:42:56 +01:00
{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 }}
/>
2021-03-02 01:05:15 +01:00
</div>
}
{formNode}
{
(
auth !== undefined &&
auth.showTryAnotherWayLink &&
showAnotherWayIfPresent
) &&
2021-03-06 14:42:56 +01:00
<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)}>
2021-03-02 01:05:15 +01:00
<input type="hidden" name="tryAnotherWay" value="on" />
2021-03-07 15:37:37 +01:00
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{msg("doTryAnotherWay")}</a>
2021-03-02 01:05:15 +01:00
</div>
</div >
</form>
}
{
displayInfo &&
2021-03-06 14:42:56 +01:00
<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-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
});