2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
import { useState, useReducer ,useEffect, memo } from "react";
|
2021-03-02 01:05:15 +01:00
|
|
|
import type { ReactNode } from "react";
|
2021-03-02 23:48:31 +01:00
|
|
|
import { useKcTranslation } from "../i18n/useKcTranslation";
|
|
|
|
import { kcContext } from "../kcContext";
|
2021-03-02 01:05:15 +01:00
|
|
|
import { assert } from "evt/tools/typeSafety/assert";
|
|
|
|
import { cx } from "tss-react";
|
2021-03-02 23:48:31 +01:00
|
|
|
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
|
|
|
|
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
|
|
|
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-02 23:48:31 +01:00
|
|
|
import type { KcTemplateProperties } from "./KcProperties";
|
|
|
|
import { defaultKcTemplateProperties } from "./KcProperties";
|
2021-03-02 12:17:24 +01:00
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
export type TemplateProps = {
|
|
|
|
kcProperties: KcTemplateProperties;
|
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-04 18:15:48 +01:00
|
|
|
displayInfoNode?: ReactNode;
|
2021-03-02 01:05:15 +01:00
|
|
|
};
|
|
|
|
|
2021-03-02 22:48:36 +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,
|
2021-03-02 23:48:31 +01:00
|
|
|
kcProperties = {},
|
2021-03-02 01:05:15 +01:00
|
|
|
headerNode,
|
2021-03-04 18:15:48 +01:00
|
|
|
showUsernameNode = null,
|
2021-03-02 01:05:15 +01:00
|
|
|
formNode,
|
2021-03-04 18:15:48 +01:00
|
|
|
displayInfoNode = null
|
2021-03-02 01:05:15 +01:00
|
|
|
} = props;
|
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
const { t } = useKcTranslation();
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
Object.assign(kcProperties, defaultKcTemplateProperties);
|
2021-03-02 22:48:36 +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-03-02 23:48:31 +01:00
|
|
|
([languageTag]: [KcLanguageTag]) =>
|
|
|
|
setKcLanguageTag(languageTag)
|
2021-03-02 01:05:15 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
const onTryAnotherWayClick = useConstCallback(() => {
|
|
|
|
|
|
|
|
document.forms["kc-select-try-another-way-form" as never].submit();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
const [{ realm, locale, auth, url, message, isAppInitiatedAction }] = useState(() => (
|
|
|
|
assert(kcContext !== undefined, "App is not currently being served by KeyCloak"),
|
|
|
|
kcContext
|
|
|
|
));
|
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;
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
Promise.all(
|
|
|
|
[
|
|
|
|
...(kcProperties.stylesCommon ?? []).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)),
|
|
|
|
...(kcProperties.styles ?? []).map(relativePath => pathJoin(url.resourcesPath, relativePath))
|
|
|
|
].map(href => appendHead({
|
|
|
|
"type": "css",
|
|
|
|
href
|
|
|
|
}))).then(() => {
|
|
|
|
|
|
|
|
if (isUnmounted) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setExtraCssLoaded();
|
|
|
|
|
|
|
|
});
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
kcProperties.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-03 03:17:56 +01:00
|
|
|
document.getElementsByTagName("html")[0]
|
|
|
|
.classList
|
|
|
|
.add(cx(kcProperties.kcHtmlClass));
|
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
return () => { isUnmounted = true; };
|
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-02 23:48:31 +01:00
|
|
|
<div className={cx(kcProperties.kcLoginClass)}>
|
2021-03-02 01:05:15 +01:00
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
<div id="kc-header" className={cx(kcProperties.kcHeaderClass)}>
|
|
|
|
<div id="kc-header-wrapper" className={cx(kcProperties.kcHeaderWrapperClass)}>
|
2021-03-02 01:05:15 +01:00
|
|
|
{t("loginTitleHtml", realm.displayNameHtml)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2021-03-04 13:56:51 +01:00
|
|
|
<div className={cx(kcProperties.kcFormCardClass, displayWide && kcProperties.kcFormCardAccountClass)}>
|
2021-03-02 23:48:31 +01:00
|
|
|
<header className={cx(kcProperties.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-02 23:48:31 +01:00
|
|
|
<div id="kc-locale-wrapper" className={cx(kcProperties.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(
|
|
|
|
({ languageTag }) =>
|
|
|
|
<li 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-02 23:48:31 +01:00
|
|
|
<div className={cx(kcProperties.kcContentWrapperClass)}>
|
|
|
|
<div className={cx(kcProperties.kcLabelWrapperClass, "subtitle")}>
|
2021-03-02 01:05:15 +01:00
|
|
|
<span className="subtitle">
|
|
|
|
<span className="required">*</span>
|
|
|
|
{t("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 ? (
|
2021-03-02 23:48:31 +01:00
|
|
|
<div className={cx(kcProperties.kcContentWrapperClass)}>
|
|
|
|
<div className={cx(kcProperties.kcLabelWrapperClass, "subtitle")}>
|
2021-03-02 01:05:15 +01:00
|
|
|
<span className="subtitle"><span className="required">*</span> {t("requiredFields")}</span>
|
|
|
|
</div>
|
|
|
|
<div className="col-md-10">
|
|
|
|
{showUsernameNode}
|
2021-03-02 23:48:31 +01:00
|
|
|
<div className={cx(kcProperties.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-02 23:48:31 +01:00
|
|
|
<i className={cx(kcProperties.kcResetFlowIcon)}></i>
|
2021-03-02 01:05:15 +01:00
|
|
|
<span className="kc-tooltip-text">{t("restartLoginTooltip")}</span>
|
|
|
|
</div>
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
{showUsernameNode}
|
2021-03-02 23:48:31 +01:00
|
|
|
<div className={cx(kcProperties.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-02 23:48:31 +01:00
|
|
|
<i className={cx(kcProperties.kcResetFlowIcon)}></i>
|
2021-03-02 01:05:15 +01:00
|
|
|
<span className="kc-tooltip-text">{t("restartLoginTooltip")}</span>
|
|
|
|
</div>
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
</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-02 23:48:31 +01:00
|
|
|
{message.type === "success" && <span className={cx(kcProperties.kcFeedbackSuccessIcon)}></span>}
|
|
|
|
{message.type === "warning" && <span className={cx(kcProperties.kcFeedbackWarningIcon)}></span>}
|
|
|
|
{message.type === "error" && <span className={cx(kcProperties.kcFeedbackErrorIcon)}></span>}
|
|
|
|
{message.type === "info" && <span className={cx(kcProperties.kcFeedbackInfoIcon)}></span>}
|
2021-03-02 01:05:15 +01:00
|
|
|
<span className="kc-feedback-text">{message.summary}</span>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
{formNode}
|
|
|
|
{
|
|
|
|
(
|
|
|
|
auth !== undefined &&
|
|
|
|
auth.showTryAnotherWayLink &&
|
|
|
|
showAnotherWayIfPresent
|
|
|
|
) &&
|
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post" className={cx(displayWide && kcProperties.kcContentWrapperClass)} >
|
|
|
|
<div className={cx(displayWide && [kcProperties.kcFormSocialAccountContentClass, kcProperties.kcFormSocialAccountClass])} >
|
|
|
|
<div className={cx(kcProperties.kcFormGroupClass)}>
|
2021-03-02 01:05:15 +01:00
|
|
|
<input type="hidden" name="tryAnotherWay" value="on" />
|
|
|
|
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{t("doTryAnotherWay")}</a>
|
|
|
|
</div>
|
|
|
|
</div >
|
|
|
|
</form>
|
|
|
|
}
|
|
|
|
{
|
|
|
|
displayInfo &&
|
|
|
|
|
2021-03-02 23:48:31 +01:00
|
|
|
<div id="kc-info" className={cx(kcProperties.kcSignUpClass)}>
|
|
|
|
<div id="kc-info-wrapper" className={cx(kcProperties.kcInfoAreaWrapperClass)}>
|
2021-03-02 01:05:15 +01:00
|
|
|
{displayInfoNode}
|
|
|
|
</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
|
|
|
});
|