Implement login
This commit is contained in:
parent
6a93c6e894
commit
e5e39d036d
@ -55,6 +55,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.5",
|
||||||
"evt": "^1.9.12",
|
"evt": "^1.9.12",
|
||||||
|
"minimal-polyfills": "^2.1.6",
|
||||||
"powerhooks": "^0.0.14",
|
"powerhooks": "^0.0.14",
|
||||||
"tss-react": "^0.0.9"
|
"tss-react": "^0.0.9"
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,19 @@
|
|||||||
"loginAction": "${url.loginAction}",
|
"loginAction": "${url.loginAction}",
|
||||||
"resourcesPath": "${url.resourcesPath}",
|
"resourcesPath": "${url.resourcesPath}",
|
||||||
"resourcesCommonPath": "${url.resourcesCommonPath}",
|
"resourcesCommonPath": "${url.resourcesCommonPath}",
|
||||||
"loginRestartFlowUrl": "${url.loginRestartFlowUrl}"
|
"loginRestartFlowUrl": "${url.loginRestartFlowUrl}",
|
||||||
|
"loginResetCredentialsUrl": "${url.loginResetCredentialsUrl}",
|
||||||
|
"registrationUrl": "${url.registrationUrl}"
|
||||||
},
|
},
|
||||||
"realm": {
|
"realm": {
|
||||||
"displayName": "${realm.displayName!''}" || undefined,
|
"displayName": "${realm.displayName!''}" || undefined,
|
||||||
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
|
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
|
||||||
"internationalizationEnabled": ${realm.internationalizationEnabled?c}
|
"internationalizationEnabled": ${realm.internationalizationEnabled?c},
|
||||||
|
"password": ${realm.password?c},
|
||||||
|
"loginWithEmailAllowed": ${realm.loginWithEmailAllowed?c},
|
||||||
|
"registrationEmailAsUsername": ${realm.registrationEmailAsUsername?c},
|
||||||
|
"rememberMe": ${realm.rememberMe?c},
|
||||||
|
"resetPasswordAllowed": ${realm.resetPasswordAllowed?c}
|
||||||
},
|
},
|
||||||
"locale": (function (){
|
"locale": (function (){
|
||||||
|
|
||||||
@ -54,6 +61,7 @@
|
|||||||
"showUsername": ${auth.showUsername()?c},
|
"showUsername": ${auth.showUsername()?c},
|
||||||
"showResetCredentials": ${auth.showResetCredentials()?c},
|
"showResetCredentials": ${auth.showResetCredentials()?c},
|
||||||
"showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c}
|
"showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c}
|
||||||
|
"selectedCredential": "${auth.selectedCredential!''}" || undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
<#if auth.showUsername() && !auth.showResetCredentials()>
|
<#if auth.showUsername() && !auth.showResetCredentials()>
|
||||||
@ -79,7 +87,7 @@
|
|||||||
|
|
||||||
<#if scripts??>
|
<#if scripts??>
|
||||||
<#list scripts as script>
|
<#list scripts as script>
|
||||||
out.push("${script}");
|
out.push("${script}");
|
||||||
</#list>
|
</#list>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
@ -107,7 +115,59 @@
|
|||||||
</#if>
|
</#if>
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
})()
|
})(),
|
||||||
|
"social": {
|
||||||
|
"displayInfo": ${social.displayInfo?c},
|
||||||
|
"providers": (()=>{
|
||||||
|
|
||||||
|
<#if social.providers??>
|
||||||
|
|
||||||
|
var out= [];
|
||||||
|
|
||||||
|
<#list social.providers as p>
|
||||||
|
out.push({
|
||||||
|
"loginUrl": "${p.loginUrl}",
|
||||||
|
"alias": "${p.alias}",
|
||||||
|
"providerId": "${p.providerId}",
|
||||||
|
"displayName": "${p.displayName}"
|
||||||
|
});
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
return out;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
"usernameEditDisabled": (function () {
|
||||||
|
|
||||||
|
<#if usernameEditDisabled??>
|
||||||
|
return true;
|
||||||
|
</#if>
|
||||||
|
return false;
|
||||||
|
|
||||||
|
})(),
|
||||||
|
"login": {
|
||||||
|
"username": "${login.username!''}" || undefined,
|
||||||
|
"rememberMe": (function (){
|
||||||
|
|
||||||
|
<#if login.rememberMe??>
|
||||||
|
return true;
|
||||||
|
</#if>
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
"registrationDisabled": (function (){
|
||||||
|
|
||||||
|
<#if registrationDisabled??>
|
||||||
|
return true;
|
||||||
|
</#if>
|
||||||
|
return false;
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -1,18 +1,154 @@
|
|||||||
|
|
||||||
/*
|
import { useState, memo } from "react";
|
||||||
import { useState, memo } from "react";
|
import { allPropertiesValuesToUndefined } from "./tools/allPropertiesValuesToUndefined";
|
||||||
import { KcProperties, Template } from "./Template";
|
import { Template, defaultKcTemplateProperties } from "./Template";
|
||||||
import { assert } from "evt/tools/typeSafety/assert";
|
import type { KcTemplateProperties, KcClasses } from "./Template";
|
||||||
import { keycloakPagesContext } from "./keycloakFtlValues";
|
import { assert } from "evt/tools/typeSafety/assert";
|
||||||
|
import { keycloakPagesContext } from "./keycloakFtlValues";
|
||||||
|
import { useKeycloakThemeTranslation } from "./i18n/useKeycloakTranslation";
|
||||||
|
import { cx } from "tss-react";
|
||||||
|
import { useConstCallback } from "powerhooks";
|
||||||
|
|
||||||
export type Props = {
|
export type KcLoginPageProperties = KcTemplateProperties & KcClasses<
|
||||||
properties: KcProperties;
|
"kcLogoLink" |
|
||||||
|
"kcLogoClass" |
|
||||||
|
"kcContainerClass" |
|
||||||
|
"kcContentClass" |
|
||||||
|
"kcFeedbackAreaClass" |
|
||||||
|
"kcLocaleClass" |
|
||||||
|
"kcAlertIconClasserror" |
|
||||||
|
"kcFormAreaClass" |
|
||||||
|
"kcFormSocialAccountListClass" |
|
||||||
|
"kcFormSocialAccountDoubleListClass" |
|
||||||
|
"kcFormSocialAccountListLinkClass" |
|
||||||
|
"kcWebAuthnKeyIcon" |
|
||||||
|
"kcFormClass" |
|
||||||
|
"kcFormGroupErrorClass" |
|
||||||
|
"kcLabelClass" |
|
||||||
|
"kcInputClass" |
|
||||||
|
"kcInputWrapperClass" |
|
||||||
|
"kcFormOptionsClass" |
|
||||||
|
"kcFormButtonsClass" |
|
||||||
|
"kcFormSettingClass" |
|
||||||
|
"kcTextareaClass" |
|
||||||
|
"kcInfoAreaClass" |
|
||||||
|
"kcButtonClass" |
|
||||||
|
"kcButtonPrimaryClass" |
|
||||||
|
"kcButtonDefaultClass" |
|
||||||
|
"kcButtonLargeClass" |
|
||||||
|
"kcButtonBlockClass" |
|
||||||
|
"kcInputLargeClass" |
|
||||||
|
"kcSrOnlyClass" |
|
||||||
|
"kcSelectAuthListClass" |
|
||||||
|
"kcSelectAuthListItemClass" |
|
||||||
|
"kcSelectAuthListItemInfoClass" |
|
||||||
|
"kcSelectAuthListItemLeftClass" |
|
||||||
|
"kcSelectAuthListItemBodyClass" |
|
||||||
|
"kcSelectAuthListItemDescriptionClass" |
|
||||||
|
"kcSelectAuthListItemHeadingClass" |
|
||||||
|
"kcSelectAuthListItemHelpTextClass" |
|
||||||
|
"kcAuthenticatorDefaultClass" |
|
||||||
|
"kcAuthenticatorPasswordClass" |
|
||||||
|
"kcAuthenticatorOTPClass" |
|
||||||
|
"kcAuthenticatorWebAuthnClass" |
|
||||||
|
"kcAuthenticatorWebAuthnPasswordlessClass" |
|
||||||
|
"kcSelectOTPListClass" |
|
||||||
|
"kcSelectOTPListItemClass" |
|
||||||
|
"kcAuthenticatorOtpCircleClass" |
|
||||||
|
"kcSelectOTPItemHeadingClass" |
|
||||||
|
"kcFormOptionsWrapperClass"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const defaultKcLoginPageProperties: KcLoginPageProperties = {
|
||||||
|
...defaultKcTemplateProperties,
|
||||||
|
"kcLogoLink": "http://www.keycloak.org",
|
||||||
|
"kcLogoClass": "login-pf-brand",
|
||||||
|
"kcContainerClass": "container-fluid",
|
||||||
|
"kcContentClass": "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3",
|
||||||
|
"kcFeedbackAreaClass": "col-md-12",
|
||||||
|
"kcLocaleClass": "col-xs-12 col-sm-1",
|
||||||
|
"kcAlertIconClasserror": "pficon pficon-error-circle-o",
|
||||||
|
|
||||||
|
"kcFormAreaClass": "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2",
|
||||||
|
"kcFormSocialAccountListClass": "login-pf-social list-unstyled login-pf-social-all",
|
||||||
|
"kcFormSocialAccountDoubleListClass": "login-pf-social-double-col",
|
||||||
|
"kcFormSocialAccountListLinkClass": "login-pf-social-link",
|
||||||
|
"kcWebAuthnKeyIcon": "pficon pficon-key",
|
||||||
|
|
||||||
|
"kcFormClass": "form-horizontal",
|
||||||
|
"kcFormGroupErrorClass": "has-error",
|
||||||
|
"kcLabelClass": "control-label",
|
||||||
|
"kcInputClass": "form-control",
|
||||||
|
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcFormSettingClass": "login-pf-settings",
|
||||||
|
"kcTextareaClass": "form-control",
|
||||||
|
|
||||||
|
"kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details",
|
||||||
|
|
||||||
|
// css classes for form buttons main class used for all buttons
|
||||||
|
"kcButtonClass": "btn",
|
||||||
|
// classes defining priority of the button - primary or default (there is typically only one priority button for the form)
|
||||||
|
"kcButtonPrimaryClass": "btn-primary",
|
||||||
|
"kcButtonDefaultClass": "btn-default",
|
||||||
|
// classes defining size of the button
|
||||||
|
"kcButtonLargeClass": "btn-lg",
|
||||||
|
"kcButtonBlockClass": "btn-block",
|
||||||
|
|
||||||
|
// css classes for input
|
||||||
|
"kcInputLargeClass": "input-lg",
|
||||||
|
|
||||||
|
// css classes for form accessability
|
||||||
|
"kcSrOnlyClass": "sr-only",
|
||||||
|
|
||||||
|
// css classes for select-authenticator form
|
||||||
|
"kcSelectAuthListClass": "list-group list-view-pf",
|
||||||
|
"kcSelectAuthListItemClass": "list-group-item list-view-pf-stacked",
|
||||||
|
"kcSelectAuthListItemInfoClass": "list-view-pf-main-info",
|
||||||
|
"kcSelectAuthListItemLeftClass": "list-view-pf-left",
|
||||||
|
"kcSelectAuthListItemBodyClass": "list-view-pf-body",
|
||||||
|
"kcSelectAuthListItemDescriptionClass": "list-view-pf-description",
|
||||||
|
"kcSelectAuthListItemHeadingClass": "list-group-item-heading",
|
||||||
|
"kcSelectAuthListItemHelpTextClass": "list-group-item-text",
|
||||||
|
|
||||||
|
// css classes for the authenticators
|
||||||
|
"kcAuthenticatorDefaultClass": "fa list-view-pf-icon-lg",
|
||||||
|
"kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg",
|
||||||
|
"kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg",
|
||||||
|
"kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg",
|
||||||
|
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg",
|
||||||
|
|
||||||
|
//css classes for the OTP Login Form
|
||||||
|
"kcSelectOTPListClass": "card-pf card-pf-view card-pf-view-select card-pf-view-single-select",
|
||||||
|
"kcSelectOTPListItemClass": "card-pf-body card-pf-top-element",
|
||||||
|
"kcAuthenticatorOtpCircleClass": "fa fa-mobile card-pf-icon-circle",
|
||||||
|
"kcSelectOTPItemHeadingClass": "card-pf-title text-center"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LoginPage = memo((props: Props)=>{
|
|
||||||
|
|
||||||
|
|
||||||
const [{ }] = useState(() => {
|
/** Tu use if you don't want any default */
|
||||||
|
export const allClearKcLoginPageProperties =
|
||||||
|
allPropertiesValuesToUndefined(defaultKcLoginPageProperties);
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
properties?: KcLoginPageProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoginPage = memo((props: Props) => {
|
||||||
|
|
||||||
|
const { properties = {} } = props;
|
||||||
|
|
||||||
|
const { t, tStr } = useKeycloakThemeTranslation();
|
||||||
|
|
||||||
|
Object.assign(properties, defaultKcLoginPageProperties);
|
||||||
|
|
||||||
|
const [{
|
||||||
|
social, realm, url,
|
||||||
|
usernameEditDisabled, login,
|
||||||
|
auth, registrationDisabled
|
||||||
|
}] = useState(() => {
|
||||||
|
|
||||||
assert(keycloakPagesContext !== undefined);
|
assert(keycloakPagesContext !== undefined);
|
||||||
|
|
||||||
@ -20,12 +156,145 @@ export const LoginPage = memo((props: Props)=>{
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
||||||
|
|
||||||
return (
|
const onSubmit = useConstCallback(() =>
|
||||||
<Template/>
|
(setIsLoginButtonDisabled(true), true)
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
export {};
|
return (
|
||||||
|
<Template
|
||||||
|
displayInfo={social.displayInfo}
|
||||||
|
displayWide={realm.password && social.providers !== undefined}
|
||||||
|
properties={properties}
|
||||||
|
headerNode={t("doLogIn")}
|
||||||
|
showUsernameNode={null}
|
||||||
|
formNode={
|
||||||
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="kc-form"
|
||||||
|
className={cx(realm.password && social.providers !== undefined && properties.kcContentWrapperClass)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="kc-form-wrapper"
|
||||||
|
className={cx(realm.password && social.providers && [properties.kcFormSocialAccountContentClass, properties.kcFormSocialAccountClass])}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
realm.password &&
|
||||||
|
(
|
||||||
|
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
|
||||||
|
<div className={cx(properties.kcFormGroupClass)}>
|
||||||
|
<label htmlFor="username" className={cx(properties.kcLabelClass)}>
|
||||||
|
{
|
||||||
|
!realm.loginWithEmailAllowed ?
|
||||||
|
t("username")
|
||||||
|
:
|
||||||
|
(
|
||||||
|
!realm.registrationEmailAsUsername ?
|
||||||
|
t("usernameOrEmail") :
|
||||||
|
t("email")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
tabIndex={1}
|
||||||
|
id="username"
|
||||||
|
className={cx(properties.kcInputClass)}
|
||||||
|
name="username"
|
||||||
|
value={login.username ?? ''}
|
||||||
|
type="text"
|
||||||
|
{...(usernameEditDisabled ? { "disabled": true } : { "autofocus": true, "autocomplete": "off" })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(properties.kcFormGroupClass)}>
|
||||||
|
<label htmlFor="password" className={cx(properties.kcLabelClass)}>
|
||||||
|
{t("password")}
|
||||||
|
</label>
|
||||||
|
<input tabIndex={2} id="password" className={cx(properties.kcInputClass)} name="password" type="password" autoComplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(properties.kcFormGroupClass, properties.kcFormSettingClass)}>
|
||||||
|
<div id="kc-form-options">
|
||||||
|
{
|
||||||
|
(
|
||||||
|
realm.rememberMe &&
|
||||||
|
!usernameEditDisabled
|
||||||
|
) &&
|
||||||
|
<div className="checkbox">
|
||||||
|
<label>
|
||||||
|
<input tabIndex={3} id="rememberMe" name="rememberMe" type="checkbox" {...(login.rememberMe ? { "checked": true } : {})}> {t("rememberMe")}</input>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={cx(properties.kcFormOptionsWrapperClass)}>
|
||||||
|
{
|
||||||
|
realm.resetPasswordAllowed &&
|
||||||
|
<span>
|
||||||
|
<a tabIndex={5} href={url.loginResetCredentialsUrl}>{t("doForgotPassword")}</a>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" className={cx(properties.kcFormGroupClass)}>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
id="id-hidden-input"
|
||||||
|
name="credentialId"
|
||||||
|
{...(auth?.selectedCredential !== undefined ? { "value": auth.selectedCredential } : {})}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
tabIndex={4}
|
||||||
|
className={cx(properties.kcButtonClass, properties.kcButtonPrimaryClass, properties.kcButtonBlockClass, properties.kcButtonLargeClass)} name="login" id="kc-login" type="submit"
|
||||||
|
value={tStr("doLogIn")}
|
||||||
|
disabled={isLoginButtonDisabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
(realm.password && social.providers !== undefined) &&
|
||||||
|
<div id="kc-social-providers" className={cx(properties.kcFormSocialAccountContentClass, properties.kcFormSocialAccountClass)}>
|
||||||
|
<ul className={cx(properties.kcFormSocialAccountListClass, social.providers.length > 4 && properties.kcFormSocialAccountDoubleListClass)}>
|
||||||
|
{
|
||||||
|
social.providers.map(p =>
|
||||||
|
<li className={cx(properties.kcFormSocialAccountListLinkClass)}>
|
||||||
|
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
|
||||||
|
<span>{p.displayName}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
displayInfoNode={
|
||||||
|
(
|
||||||
|
realm.password &&
|
||||||
|
realm.resetPasswordAllowed &&
|
||||||
|
!registrationDisabled
|
||||||
|
) &&
|
||||||
|
<div id="kc-registration">
|
||||||
|
<span>
|
||||||
|
{t("noAccount")}
|
||||||
|
<a tabIndex={6} href={url.registrationUrl}>
|
||||||
|
{t("doRegister")}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
import { useState, useEffect, memo } from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useKeycloakThemeTranslation } from "./i18n/useKeycloakTranslation";
|
import { useKeycloakThemeTranslation } from "./i18n/useKeycloakTranslation";
|
||||||
import { keycloakPagesContext } from "./keycloakFtlValues";
|
import { keycloakPagesContext } from "./keycloakFtlValues";
|
||||||
import { assert } from "evt/tools/typeSafety/assert";
|
import { assert } from "evt/tools/typeSafety/assert";
|
||||||
@ -12,53 +12,84 @@ import { appendLinkInHead } from "./tools/appendLinkInHead";
|
|||||||
import { appendScriptInHead } from "./tools/appendScriptInHead";
|
import { appendScriptInHead } from "./tools/appendScriptInHead";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { useConstCallback } from "powerhooks";
|
import { useConstCallback } from "powerhooks";
|
||||||
|
import { allPropertiesValuesToUndefined } from "./tools/allPropertiesValuesToUndefined";
|
||||||
type KcClasses<T extends string> = { [key in T]?: string[] | string };
|
|
||||||
|
|
||||||
export type KcProperties = {
|
|
||||||
stylesCommon?: string[];
|
|
||||||
styles?: string[];
|
|
||||||
scripts?: string[];
|
|
||||||
} & KcClasses<
|
|
||||||
"kcLoginClass" |
|
|
||||||
"kcHeaderClass" |
|
|
||||||
"kcHeaderWrapperClass" |
|
|
||||||
"kcFormCardClass" |
|
|
||||||
"kcFormCardAccountClass" |
|
|
||||||
"kcFormHeaderClass" |
|
|
||||||
"kcLocaleWrapperClass" |
|
|
||||||
"kcContentWrapperClass" |
|
|
||||||
"kcLabelWrapperClass" |
|
|
||||||
"kcContentWrapperClass" |
|
|
||||||
"kcLabelWrapperClass" |
|
|
||||||
"kcFormGroupClass" |
|
|
||||||
"kcResetFlowIcon" |
|
|
||||||
"kcResetFlowIcon" |
|
|
||||||
"kcFeedbackSuccessIcon" |
|
|
||||||
"kcFeedbackWarningIcon" |
|
|
||||||
"kcFeedbackErrorIcon" |
|
|
||||||
"kcFeedbackInfoIcon" |
|
|
||||||
"kcContentWrapperClass" |
|
|
||||||
"kcFormSocialAccountContentClass" |
|
|
||||||
"kcFormSocialAccountClass" |
|
|
||||||
"kcSignUpClass" |
|
|
||||||
"kcInfoAreaWrapperClass"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
displayInfo?: boolean;
|
displayInfo?: boolean;
|
||||||
displayMessage: boolean;
|
displayMessage?: boolean;
|
||||||
displayRequiredFields: boolean;
|
displayRequiredFields?: boolean;
|
||||||
displayWide: boolean;
|
displayWide?: boolean;
|
||||||
showAnotherWayIfPresent: boolean;
|
showAnotherWayIfPresent?: boolean;
|
||||||
properties?: KcProperties;
|
properties: KcTemplateProperties;
|
||||||
headerNode: ReactNode;
|
headerNode: ReactNode;
|
||||||
showUsernameNode: ReactNode;
|
showUsernameNode: ReactNode;
|
||||||
formNode: ReactNode;
|
formNode: ReactNode;
|
||||||
displayInfoNode: ReactNode;
|
displayInfoNode: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Template(props: Props) {
|
/** Class names can be provided as an array or separated by whitespace */
|
||||||
|
export type KcClasses<T extends string> = { [key in T]?: string[] | string };
|
||||||
|
|
||||||
|
export type KcTemplateProperties = {
|
||||||
|
stylesCommon?: string[];
|
||||||
|
styles?: string[];
|
||||||
|
scripts?: string[];
|
||||||
|
} & KcClasses<
|
||||||
|
"kcLoginClass" |
|
||||||
|
"kcHeaderClass" |
|
||||||
|
"kcHeaderWrapperClass" |
|
||||||
|
"kcFormCardClass" |
|
||||||
|
"kcFormCardAccountClass" |
|
||||||
|
"kcFormHeaderClass" |
|
||||||
|
"kcLocaleWrapperClass" |
|
||||||
|
"kcContentWrapperClass" |
|
||||||
|
"kcLabelWrapperClass" |
|
||||||
|
"kcContentWrapperClass" |
|
||||||
|
"kcLabelWrapperClass" |
|
||||||
|
"kcFormGroupClass" |
|
||||||
|
"kcResetFlowIcon" |
|
||||||
|
"kcResetFlowIcon" |
|
||||||
|
"kcFeedbackSuccessIcon" |
|
||||||
|
"kcFeedbackWarningIcon" |
|
||||||
|
"kcFeedbackErrorIcon" |
|
||||||
|
"kcFeedbackInfoIcon" |
|
||||||
|
"kcContentWrapperClass" |
|
||||||
|
"kcFormSocialAccountContentClass" |
|
||||||
|
"kcFormSocialAccountClass" |
|
||||||
|
"kcSignUpClass" |
|
||||||
|
"kcInfoAreaWrapperClass"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const defaultKcTemplateProperties: KcTemplateProperties = {
|
||||||
|
"styles": ["css/login.css"],
|
||||||
|
"stylesCommon": [
|
||||||
|
...[".min.css", "-additions.min.css"]
|
||||||
|
.map(end => `node_modules/patternfly/dist/css/patternfly${end}`),
|
||||||
|
"lib/zocial/zocial.css"
|
||||||
|
],
|
||||||
|
"kcLoginClass": "login-pf-page",
|
||||||
|
"kcContentWrapperClass": "row",
|
||||||
|
"kcHeaderClass": "login-pf-page-header",
|
||||||
|
"kcFormCardClass": "card-pf",
|
||||||
|
"kcFormCardAccountClass": "login-pf-accounts",
|
||||||
|
"kcFormSocialAccountClass": "login-pf-social-section",
|
||||||
|
"kcFormSocialAccountContentClass": "col-xs-12 col-sm-6",
|
||||||
|
"kcFormHeaderClass": "login-pf-header",
|
||||||
|
"kcFeedbackErrorIcon": "pficon pficon-error-circle-o",
|
||||||
|
"kcFeedbackWarningIcon": "pficon pficon-warning-triangle-o",
|
||||||
|
"kcFeedbackSuccessIcon": "pficon pficon-ok",
|
||||||
|
"kcFeedbackInfoIcon": "pficon pficon-info",
|
||||||
|
"kcResetFlowIcon": "pficon pficon-arrow fa-2x",
|
||||||
|
"kcFormGroupClass": "form-group",
|
||||||
|
"kcLabelWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcSignUpClass": "login-pf-sighup"
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Tu use if you don't want any default */
|
||||||
|
export const allClearKcTemplateProperties =
|
||||||
|
allPropertiesValuesToUndefined(defaultKcTemplateProperties);
|
||||||
|
|
||||||
|
export const Template = memo((props: Props) =>{
|
||||||
|
|
||||||
const {
|
const {
|
||||||
displayInfo = false,
|
displayInfo = false,
|
||||||
@ -75,6 +106,8 @@ export function Template(props: Props) {
|
|||||||
|
|
||||||
const { t } = useKeycloakThemeTranslation();
|
const { t } = useKeycloakThemeTranslation();
|
||||||
|
|
||||||
|
Object.assign(properties, defaultKcTemplateProperties);
|
||||||
|
|
||||||
const { keycloakLanguage, setKeycloakLanguage } = useKeycloakLanguage();
|
const { keycloakLanguage, setKeycloakLanguage } = useKeycloakLanguage();
|
||||||
|
|
||||||
const onChangeLanguageClickFactory = useCallbackFactory(
|
const onChangeLanguageClickFactory = useCallbackFactory(
|
||||||
@ -286,4 +319,4 @@ export function Template(props: Props) {
|
|||||||
</div >
|
</div >
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -3,18 +3,18 @@ import { useKeycloakLanguage } from "./useKeycloakLanguage";
|
|||||||
import { messages } from "./generated_messages/login";
|
import { messages } from "./generated_messages/login";
|
||||||
import { useConstCallback } from "powerhooks";
|
import { useConstCallback } from "powerhooks";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { id } from "evt/tools/typeSafety/id";
|
||||||
|
|
||||||
export type MessageKey = keyof typeof messages["en"]
|
export type MessageKey = keyof typeof messages["en"];
|
||||||
|
|
||||||
|
|
||||||
export function useKeycloakThemeTranslation() {
|
export function useKeycloakThemeTranslation() {
|
||||||
|
|
||||||
const { keycloakLanguage } = useKeycloakLanguage();
|
const { keycloakLanguage } = useKeycloakLanguage();
|
||||||
|
|
||||||
const t = useConstCallback(
|
const tStr = useConstCallback(
|
||||||
(key: MessageKey, ...args: (string | undefined)[]): ReactNode => {
|
(key: MessageKey, ...args: (string | undefined)[]): string => {
|
||||||
|
|
||||||
let out: string = messages[keycloakLanguage as any as "en"][key] ?? messages["en"][key];
|
let str: string = messages[keycloakLanguage as any as "en"][key] ?? messages["en"][key];
|
||||||
|
|
||||||
args.forEach((arg, i) => {
|
args.forEach((arg, i) => {
|
||||||
|
|
||||||
@ -22,15 +22,22 @@ export function useKeycloakThemeTranslation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
out = out.replace(new RegExp(`\\{${i}\\}`, "g"), arg);
|
str = str.replace(new RegExp(`\\{${i}\\}`, "g"), arg);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return <span className={key} dangerouslySetInnerHTML={{ "__html": out }} />;
|
return str;
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return { t };
|
const t = useConstCallback(
|
||||||
|
id<(...args: Parameters<typeof tStr>) => ReactNode>(
|
||||||
|
(key, ...args) =>
|
||||||
|
<span className={key} dangerouslySetInnerHTML={{ "__html": tStr(key, ...args) }} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { t, tStr };
|
||||||
|
|
||||||
}
|
}
|
@ -13,12 +13,19 @@ export type KeycloakFtlValues = {
|
|||||||
resourcesPath: string;
|
resourcesPath: string;
|
||||||
resourcesCommonPath: string;
|
resourcesCommonPath: string;
|
||||||
loginRestartFlowUrl: string;
|
loginRestartFlowUrl: string;
|
||||||
},
|
loginResetCredentialsUrl: string;
|
||||||
|
registrationUrl: string;
|
||||||
|
};
|
||||||
realm: {
|
realm: {
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
displayNameHtml?: string;
|
displayNameHtml?: string;
|
||||||
internationalizationEnabled: boolean;
|
internationalizationEnabled: boolean;
|
||||||
},
|
password: boolean;
|
||||||
|
loginWithEmailAllowed: boolean;
|
||||||
|
registrationEmailAsUsername: boolean;
|
||||||
|
rememberMe: boolean;
|
||||||
|
resetPasswordAllowed: boolean;
|
||||||
|
};
|
||||||
/** Undefined if !realm.internationalizationEnabled */
|
/** Undefined if !realm.internationalizationEnabled */
|
||||||
locale?: {
|
locale?: {
|
||||||
supported: {
|
supported: {
|
||||||
@ -38,13 +45,29 @@ export type KeycloakFtlValues = {
|
|||||||
showResetCredentials: boolean;
|
showResetCredentials: boolean;
|
||||||
showTryAnotherWayLink: boolean;
|
showTryAnotherWayLink: boolean;
|
||||||
attemptedUsername?: boolean;
|
attemptedUsername?: boolean;
|
||||||
},
|
selectedCredential?: string;
|
||||||
|
};
|
||||||
scripts: string[];
|
scripts: string[];
|
||||||
message?: {
|
message?: {
|
||||||
type: "success" | "warning" | "error" | "info";
|
type: "success" | "warning" | "error" | "info";
|
||||||
summary: string;
|
summary: string;
|
||||||
},
|
};
|
||||||
isAppInitiatedAction: boolean;
|
isAppInitiatedAction: boolean;
|
||||||
|
social: {
|
||||||
|
displayInfo: boolean;
|
||||||
|
providers?: {
|
||||||
|
loginUrl: string;
|
||||||
|
alias: string;
|
||||||
|
providerId: string;
|
||||||
|
displayName: string;
|
||||||
|
}[]
|
||||||
|
};
|
||||||
|
usernameEditDisabled: boolean;
|
||||||
|
login: {
|
||||||
|
username?: string;
|
||||||
|
rememberMe: boolean;
|
||||||
|
};
|
||||||
|
registrationDisabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const { keycloakPagesContext } =
|
export const { keycloakPagesContext } =
|
||||||
|
10
src/lib/tools/allPropertiesValuesToUndefined.ts
Normal file
10
src/lib/tools/allPropertiesValuesToUndefined.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
|
|
||||||
|
export function allPropertiesValuesToUndefined<T extends Record<string, unknown>>(obj: T): Record<keyof T, undefined> {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(obj)
|
||||||
|
.map(([key]) => [key, undefined])
|
||||||
|
) as any;
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["es2015", "DOM"],
|
"lib": ["es2015", "DOM", "ES2019.Object"],
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
@ -816,7 +816,7 @@ memoizee@^0.4.15:
|
|||||||
next-tick "^1.1.0"
|
next-tick "^1.1.0"
|
||||||
timers-ext "^0.1.7"
|
timers-ext "^0.1.7"
|
||||||
|
|
||||||
minimal-polyfills@^2.1.5:
|
minimal-polyfills@^2.1.5, minimal-polyfills@^2.1.6:
|
||||||
version "2.1.6"
|
version "2.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.1.6.tgz#eab50832add31afd40a22b38fb76d1fdcd2a51e4"
|
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.1.6.tgz#eab50832add31afd40a22b38fb76d1fdcd2a51e4"
|
||||||
integrity sha512-vqoxj7eMzsqX0M6/dkgoNFPw6Mztgn5qjSl0bWGboQeU7Y4UPLeyoqQw6JI+0qmBcJYdkr3nK7dqY8u/fgRp5g==
|
integrity sha512-vqoxj7eMzsqX0M6/dkgoNFPw6Mztgn5qjSl0bWGboQeU7Y4UPLeyoqQw6JI+0qmBcJYdkr3nK7dqY8u/fgRp5g==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user