Implement LoginVerifyEmail

This commit is contained in:
Joseph Garrone 2021-03-07 15:37:37 +01:00
parent 438ca4595f
commit adc6d69201
12 changed files with 126 additions and 67 deletions

View File

@ -9,7 +9,7 @@ import fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { objectKeys } from "evt/tools/typeSafety/objectKeys"; import { objectKeys } from "evt/tools/typeSafety/objectKeys";
export const pageIds= [ "login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl"] as const; export const pageIds = ["login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl"] as const;
export type PageId = typeof pageIds[number]; export type PageId = typeof pageIds[number];
@ -19,8 +19,14 @@ function loadAdjacentFile(fileBasename: string){
}; };
function loadFtlFile(ftlFileBasename: PageId | "template.ftl") { function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
try {
return loadAdjacentFile(ftlFileBasename) return loadAdjacentFile(ftlFileBasename)
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1]; .match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1];
} catch {
return "{}";
}
} }
export function generateFtlFilesCodeFactory( export function generateFtlFilesCodeFactory(

View File

@ -4,11 +4,11 @@ import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert"; import { assert } from "../tools/assert";
import { kcContext } from "../kcContext"; import { kcContext } from "../kcContext";
import { useKcTranslation } from "../i18n/useKcTranslation"; import { useKcMessage } from "../i18n/useKcMessage";
export const Error = memo((props: KcProps) => { export const Error = memo((props: KcProps) => {
const { t } = useKcTranslation(); const { msg } = useKcMessage();
assert( assert(
kcContext !== undefined && kcContext !== undefined &&
@ -22,7 +22,7 @@ export const Error = memo((props: KcProps) => {
<Template <Template
{...props} {...props}
displayMessage={false} displayMessage={false}
headerNode={t("errorTitle")} headerNode={msg("errorTitle")}
formNode={ formNode={
<div id="kc-error-message"> <div id="kc-error-message">
<p className="instruction">{message.summary}</p> <p className="instruction">{message.summary}</p>
@ -30,7 +30,7 @@ export const Error = memo((props: KcProps) => {
client !== undefined && client.baseUrl !== undefined && client !== undefined && client.baseUrl !== undefined &&
<p> <p>
<a id="backToApplication" href={client.baseUrl}> <a id="backToApplication" href={client.baseUrl}>
{t("backToApplication")} {msg("backToApplication")}
</a> </a>
</p> </p>
} }

View File

@ -4,11 +4,11 @@ import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert"; import { assert } from "../tools/assert";
import { kcContext } from "../kcContext"; import { kcContext } from "../kcContext";
import { useKcTranslation } from "../i18n/useKcTranslation"; import { useKcMessage } from "../i18n/useKcMessage";
export const Info = memo((props: KcProps) => { export const Info = memo((props: KcProps) => {
const { t } = useKcTranslation(); const { msg } = useKcMessage();
assert( assert(
kcContext !== undefined && kcContext !== undefined &&
@ -45,7 +45,7 @@ export const Info = memo((props: KcProps) => {
<b> <b>
{ {
requiredActions requiredActions
.map(requiredAction => t(`requiredAction.${requiredAction}` as const)) .map(requiredAction => msg(`requiredAction.${requiredAction}` as const))
.join(",") .join(",")
} }
@ -57,13 +57,13 @@ export const Info = memo((props: KcProps) => {
{ {
!skipLink && !skipLink &&
pageRedirectUri !== undefined ? pageRedirectUri !== undefined ?
<p><a href="${pageRedirectUri}">${(t("backToApplication"))}</a></p> <p><a href="${pageRedirectUri}">${(msg("backToApplication"))}</a></p>
: :
actionUri !== undefined ? actionUri !== undefined ?
<p><a href="${actionUri}">${t("proceedWithAction")}</a></p> <p><a href="${actionUri}">${msg("proceedWithAction")}</a></p>
: :
client.baseUrl !== undefined && client.baseUrl !== undefined &&
<p><a href="${client.baseUrl}">${t("backToApplication")}</a></p> <p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
} }
</div> </div>

View File

@ -8,7 +8,7 @@ import { Register } from "./Register";
import { Info } from "./Info"; import { Info } from "./Info";
import { Error } from "./Error"; import { Error } from "./Error";
import { LoginResetPassword } from "./LoginResetPassword"; import { LoginResetPassword } from "./LoginResetPassword";
import { LoginVerifyEmail } from "./LoginVerifyEmail";
export const KcApp = memo((props: KcProps) => { export const KcApp = memo((props: KcProps) => {
@ -20,6 +20,7 @@ export const KcApp = memo((props: KcProps) => {
case "info.ftl": return <Info {...props} />; case "info.ftl": return <Info {...props} />;
case "error.ftl": return <Error {...props} />; case "error.ftl": return <Error {...props} />;
case "login-reset-password.ftl": return <LoginResetPassword {...props} />; case "login-reset-password.ftl": return <LoginResetPassword {...props} />;
case "login-verify-email.ftl": return <LoginVerifyEmail {...props} />;
} }
}); });

View File

@ -4,13 +4,13 @@ import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert"; import { assert } from "../tools/assert";
import { kcContext } from "../kcContext"; import { kcContext } from "../kcContext";
import { useKcTranslation } from "../i18n/useKcTranslation"; import { useKcMessage } from "../i18n/useKcMessage";
import { cx } from "tss-react"; import { cx } from "tss-react";
import { useConstCallback } from "powerhooks"; import { useConstCallback } from "powerhooks";
export const Login = memo((props: KcProps) => { export const Login = memo((props: KcProps) => {
const { t, tStr } = useKcTranslation(); const { msg, msgStr } = useKcMessage();
assert( assert(
kcContext !== undefined && kcContext !== undefined &&
@ -34,7 +34,7 @@ export const Login = memo((props: KcProps) => {
{...props} {...props}
displayInfo={social.displayInfo} displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined} displayWide={realm.password && social.providers !== undefined}
headerNode={t("doLogIn")} headerNode={msg("doLogIn")}
formNode={ formNode={
<div <div
id="kc-form" id="kc-form"
@ -52,12 +52,12 @@ export const Login = memo((props: KcProps) => {
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label htmlFor="username" className={cx(props.kcLabelClass)}>
{ {
!realm.loginWithEmailAllowed ? !realm.loginWithEmailAllowed ?
t("username") msg("username")
: :
( (
!realm.registrationEmailAsUsername ? !realm.registrationEmailAsUsername ?
t("usernameOrEmail") : msg("usernameOrEmail") :
t("email") msg("email")
) )
} }
</label> </label>
@ -73,7 +73,7 @@ export const Login = memo((props: KcProps) => {
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}> <label htmlFor="password" className={cx(props.kcLabelClass)}>
{t("password")} {msg("password")}
</label> </label>
<input tabIndex={2} id="password" className={cx(props.kcInputClass)} name="password" type="password" autoComplete="off" /> <input tabIndex={2} id="password" className={cx(props.kcInputClass)} name="password" type="password" autoComplete="off" />
</div> </div>
@ -87,7 +87,7 @@ export const Login = memo((props: KcProps) => {
<div className="checkbox"> <div className="checkbox">
<label> <label>
<input tabIndex={3} id="rememberMe" name="rememberMe" type="checkbox" {...(login.rememberMe ? { "checked": true } : {})} /> <input tabIndex={3} id="rememberMe" name="rememberMe" type="checkbox" {...(login.rememberMe ? { "checked": true } : {})} />
{t("rememberMe")} {msg("rememberMe")}
</label> </label>
</div> </div>
} }
@ -96,7 +96,7 @@ export const Login = memo((props: KcProps) => {
{ {
realm.resetPasswordAllowed && realm.resetPasswordAllowed &&
<span> <span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>{t("doForgotPassword")}</a> <a tabIndex={5} href={url.loginResetCredentialsUrl}>{msg("doForgotPassword")}</a>
</span> </span>
} }
</div> </div>
@ -112,7 +112,7 @@ export const Login = memo((props: KcProps) => {
<input <input
tabIndex={4} tabIndex={4}
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} name="login" id="kc-login" type="submit" className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} name="login" id="kc-login" type="submit"
value={tStr("doLogIn")} value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled} disabled={isLoginButtonDisabled}
/> />
</div> </div>
@ -138,7 +138,7 @@ export const Login = memo((props: KcProps) => {
} }
</div> </div>
} }
displayInfoNode={ infoNode={
( (
realm.password && realm.password &&
realm.registrationAllowed && realm.registrationAllowed &&
@ -146,9 +146,9 @@ export const Login = memo((props: KcProps) => {
) && ) &&
<div id="kc-registration"> <div id="kc-registration">
<span> <span>
{t("noAccount")} {msg("noAccount")}
<a tabIndex={6} href={url.registrationUrl}> <a tabIndex={6} href={url.registrationUrl}>
{t("doRegister")} {msg("doRegister")}
</a> </a>
</span> </span>
</div> </div>

View File

@ -4,12 +4,12 @@ import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert"; import { assert } from "../tools/assert";
import { kcContext } from "../kcContext"; import { kcContext } from "../kcContext";
import { useKcTranslation } from "../i18n/useKcTranslation"; import { useKcMessage } from "../i18n/useKcMessage";
import { cx } from "tss-react"; import { cx } from "tss-react";
export const LoginResetPassword = memo((props: KcProps) => { export const LoginResetPassword = memo((props: KcProps) => {
const { t, tStr } = useKcTranslation(); const { msg, msgStr } = useKcMessage();
assert( assert(
kcContext !== undefined && kcContext !== undefined &&
@ -26,7 +26,7 @@ export const LoginResetPassword = memo((props: KcProps) => {
<Template <Template
{...props} {...props}
displayMessage={false} displayMessage={false}
headerNode={t("emailForgotTitle")} headerNode={msg("emailForgotTitle")}
formNode={ formNode={
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> <form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
@ -34,11 +34,11 @@ export const LoginResetPassword = memo((props: KcProps) => {
<label htmlFor="username" className={cx(props.kcLabelClass)}> <label htmlFor="username" className={cx(props.kcLabelClass)}>
{ {
!realm.loginWithEmailAllowed ? !realm.loginWithEmailAllowed ?
t("username") msg("username")
: :
!realm.registrationEmailAsUsername ? !realm.registrationEmailAsUsername ?
t("usernameOrEmail") : msg("usernameOrEmail") :
t("email") msg("email")
} }
</label> </label>
</div> </div>
@ -60,7 +60,7 @@ export const LoginResetPassword = memo((props: KcProps) => {
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={cx(props.kcFormOptionsWrapperClass)}>
<span> <span>
<a href={url.loginUrl}>{t("backToLogin")}</a> <a href={url.loginUrl}>{msg("backToLogin")}</a>
</span> </span>
</div> </div>
</div> </div>
@ -72,13 +72,13 @@ export const LoginResetPassword = memo((props: KcProps) => {
props.kcButtonBlockClass, props.kcButtonLargeClass props.kcButtonBlockClass, props.kcButtonLargeClass
)} )}
type="submit" type="submit"
defaultValue={tStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
</div> </div>
</div> </div>
</form> </form>
} }
infoNode={t("emailInstruction")} infoNode={msg("emailInstruction")}
/> />
); );
}); });

View File

@ -0,0 +1,44 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert";
import { kcContext } from "../kcContext";
import { useKcMessage } from "../i18n/useKcMessage";
export const LoginVerifyEmail = memo((props: KcProps) => {
const { msg } = useKcMessage();
assert(
kcContext !== undefined &&
kcContext.pageId === "login-verify-email.ftl"
);
const {
url
} = kcContext;
return (
<Template
{...props}
displayMessage={false}
headerNode={msg("emailVerifyTitle")}
formNode={
<>
<p className="instruction">
{msg("emailVerifyInstruction1")}
</p>
<p className="instruction">
{msg("emailVerifyInstruction2")}
<a href={url.loginAction}>{msg("doClickHere")}</a>
{msg("emailVerifyInstruction3")}
</p>
</>
}
/>
);
});

View File

@ -3,12 +3,12 @@ import { Template } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert"; import { assert } from "../tools/assert";
import { kcContext } from "../kcContext"; import { kcContext } from "../kcContext";
import { useKcTranslation } from "../i18n/useKcTranslation"; import { useKcMessage } from "../i18n/useKcMessage";
import { cx } from "tss-react"; import { cx } from "tss-react";
export const Register = memo((props: KcProps) => { export const Register = memo((props: KcProps) => {
const { t, tStr } = useKcTranslation(); const { msg, msgStr } = useKcMessage();
assert( assert(
kcContext !== undefined && kcContext !== undefined &&
@ -28,13 +28,13 @@ export const Register = memo((props: KcProps) => {
return ( return (
<Template <Template
{...props} {...props}
headerNode={t("registerTitle")} headerNode={msg("registerTitle")}
formNode={ formNode={
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post"> <form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('firstName', props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('firstName', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>{t("firstName")}</label> <label htmlFor="firstName" className={cx(props.kcLabelClass)}>{msg("firstName")}</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="firstName" className={cx(props.kcInputClass)} name="firstName" <input type="text" id="firstName" className={cx(props.kcInputClass)} name="firstName"
@ -45,7 +45,7 @@ export const Register = memo((props: KcProps) => {
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>{t("lastName")}</label> <label htmlFor="lastName" className={cx(props.kcLabelClass)}>{msg("lastName")}</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="lastName" className={cx(props.kcInputClass)} name="lastName" <input type="text" id="lastName" className={cx(props.kcInputClass)} name="lastName"
@ -56,7 +56,7 @@ export const Register = memo((props: KcProps) => {
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('email', props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('email', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>{t("email")}</label> <label htmlFor="email" className={cx(props.kcLabelClass)}>{msg("email")}</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="email" className={cx(props.kcInputClass)} name="email" <input type="text" id="email" className={cx(props.kcInputClass)} name="email"
@ -69,7 +69,7 @@ export const Register = memo((props: KcProps) => {
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('username', props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('username', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>{t("username")}</label> <label htmlFor="username" className={cx(props.kcLabelClass)}>{msg("username")}</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="username" className={cx(props.kcInputClass)} name="username" <input type="text" id="username" className={cx(props.kcInputClass)} name="username"
@ -84,7 +84,7 @@ export const Register = memo((props: KcProps) => {
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>{t("password")}</label> <label htmlFor="password" className={cx(props.kcLabelClass)}>{msg("password")}</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="password" id="password" className={cx(props.kcInputClass)} name="password" autoComplete="new-password" /> <input type="password" id="password" className={cx(props.kcInputClass)} name="password" autoComplete="new-password" />
@ -93,7 +93,7 @@ export const Register = memo((props: KcProps) => {
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}> <div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>{t("passwordConfirm")}</label> <label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>{msg("passwordConfirm")}</label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(props.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" /> <input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" />
@ -113,13 +113,13 @@ export const Register = memo((props: KcProps) => {
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className={cx(props.kcFormOptionsWrapperClass)}>
<span><a href={url.loginUrl}>{t("backToLogin")}</a></span> <span><a href={url.loginUrl}>{msg("backToLogin")}</a></span>
</div> </div>
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit" <input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit"
defaultValue={tStr("doRegister")} /> defaultValue={msgStr("doRegister")} />
</div> </div>
</div> </div>
</form > </form >

View File

@ -1,7 +1,7 @@
import { useReducer, useEffect, memo } from "react"; import { useReducer, useEffect, memo } from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useKcTranslation } from "../i18n/useKcTranslation"; import { useKcMessage } from "../i18n/useKcMessage";
import { useKcLanguageTag } from "../i18n/useKcLanguageTag"; import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
import { kcContext } from "../kcContext"; import { kcContext } from "../kcContext";
import { assert } from "../tools/assert"; import { assert } from "../tools/assert";
@ -44,7 +44,7 @@ export const Template = memo((props: TemplateProps) => {
useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []); useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []);
const { t } = useKcTranslation(); const { msg } = useKcMessage();
const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag(); const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
@ -132,7 +132,7 @@ export const Template = memo((props: TemplateProps) => {
<div id="kc-header" className={cx(props.kcHeaderClass)}> <div id="kc-header" className={cx(props.kcHeaderClass)}>
<div id="kc-header-wrapper" className={cx(props.kcHeaderWrapperClass)}> <div id="kc-header-wrapper" className={cx(props.kcHeaderWrapperClass)}>
{t("loginTitleHtml", realm.displayNameHtml)} {msg("loginTitleHtml", realm.displayNameHtml)}
</div> </div>
</div> </div>
@ -182,7 +182,7 @@ export const Template = memo((props: TemplateProps) => {
<div className={cx(props.kcLabelWrapperClass, "subtitle")}> <div className={cx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle"> <span className="subtitle">
<span className="required">*</span> <span className="required">*</span>
{t("requiredFields")} {msg("requiredFields")}
</span> </span>
</div> </div>
<div className="col-md-10"> <div className="col-md-10">
@ -201,7 +201,7 @@ export const Template = memo((props: TemplateProps) => {
displayRequiredFields ? ( displayRequiredFields ? (
<div className={cx(props.kcContentWrapperClass)}> <div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}> <div className={cx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle"><span className="required">*</span> {t("requiredFields")}</span> <span className="subtitle"><span className="required">*</span> {msg("requiredFields")}</span>
</div> </div>
<div className="col-md-10"> <div className="col-md-10">
{showUsernameNode} {showUsernameNode}
@ -211,7 +211,7 @@ export const Template = memo((props: TemplateProps) => {
<a id="reset-login" href={url.loginRestartFlowUrl}> <a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip"> <div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i> <i className={cx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{t("restartLoginTooltip")}</span> <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div> </div>
</a> </a>
</div> </div>
@ -227,7 +227,7 @@ export const Template = memo((props: TemplateProps) => {
<a id="reset-login" href={url.loginRestartFlowUrl}> <a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip"> <div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i> <i className={cx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{t("restartLoginTooltip")}</span> <span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div> </div>
</a> </a>
</div> </div>
@ -269,7 +269,7 @@ export const Template = memo((props: TemplateProps) => {
<div className={cx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} > <div className={cx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} >
<div className={cx(props.kcFormGroupClass)}> <div className={cx(props.kcFormGroupClass)}>
<input type="hidden" name="tryAnotherWay" value="on" /> <input type="hidden" name="tryAnotherWay" value="on" />
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{t("doTryAnotherWay")}</a> <a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{msg("doTryAnotherWay")}</a>
</div> </div>
</div > </div >
</form> </form>

View File

@ -7,11 +7,11 @@ import { id } from "evt/tools/typeSafety/id";
export type MessageKey = keyof typeof messages["en"]; export type MessageKey = keyof typeof messages["en"];
export function useKcTranslation() { export function useKcMessage() {
const { kcLanguageTag } = useKcLanguageTag(); const { kcLanguageTag } = useKcLanguageTag();
const tStr = useConstCallback( const msgStr = useConstCallback(
(key: MessageKey, ...args: (string | undefined)[]): string => { (key: MessageKey, ...args: (string | undefined)[]): string => {
let str: string = messages[kcLanguageTag as any as "en"][key] ?? messages["en"][key]; let str: string = messages[kcLanguageTag as any as "en"][key] ?? messages["en"][key];
@ -31,13 +31,13 @@ export function useKcTranslation() {
} }
); );
const t = useConstCallback( const msg = useConstCallback(
id<(...args: Parameters<typeof tStr>) => ReactNode>( id<(...args: Parameters<typeof msgStr>) => ReactNode>(
(key, ...args) => (key, ...args) =>
<span className={key} dangerouslySetInnerHTML={{ "__html": tStr(key, ...args) }} /> <span className={key} dangerouslySetInnerHTML={{ "__html": msgStr(key, ...args) }} />
) )
); );
return { t, tStr }; return { msg, msgStr };
} }

View File

@ -2,7 +2,7 @@ export * from "./kcContext";
export * from "./i18n/KcLanguageTag"; export * from "./i18n/KcLanguageTag";
export * from "./i18n/useKcLanguageTag"; export * from "./i18n/useKcLanguageTag";
export * from "./i18n/useKcTranslation"; export * from "./i18n/useKcMessage";
export * from "./components/KcProps"; export * from "./components/KcProps";
export * from "./components/Login"; export * from "./components/Login";
@ -10,5 +10,7 @@ export * from "./components/Template";
export * from "./components/KcApp"; export * from "./components/KcApp";
export * from "./components/Info"; export * from "./components/Info";
export * from "./components/Error"; export * from "./components/Error";
export * from "./components/LoginResetPassword";
export * from "./components/LoginVerifyEmail";
export * from "./tools/assert"; export * from "./tools/assert";

View File

@ -4,13 +4,15 @@ import type { PageId } from "../bin/build-keycloak-theme/generateFtl";
import { id } from "evt/tools/typeSafety/id"; import { id } from "evt/tools/typeSafety/id";
import type { KcLanguageTag } from "./i18n/KcLanguageTag"; import type { KcLanguageTag } from "./i18n/KcLanguageTag";
import { doExtends } from "evt/tools/typeSafety/doExtends"; import { doExtends } from "evt/tools/typeSafety/doExtends";
import type { MessageKey } from "./i18n/useKcTranslation"; import type { MessageKey } from "./i18n/useKcMessage";
import type { LanguageLabel } from "./i18n/KcLanguageTag"; import type { LanguageLabel } from "./i18n/KcLanguageTag";
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
StrEnum extends `${Prefix}${infer U}` ? U : never; StrEnum extends `${Prefix}${infer U}` ? U : never;
export type KcContext = KcContext.Login | KcContext.Register | KcContext.Info | KcContext.Error | KcContext.LoginResetPassword; export type KcContext =
KcContext.Login | KcContext.Register | KcContext.Info |
KcContext.Error | KcContext.LoginResetPassword | KcContext.LoginVerifyEmail;
export declare namespace KcContext { export declare namespace KcContext {
export type Template = { export type Template = {
@ -122,7 +124,7 @@ export declare namespace KcContext {
export type Info = Template & { export type Info = Template & {
pageId: "info.ftl"; pageId: "info.ftl";
messageHeader?: string; messageHeader?: string;
requiredActions?: ExtractAfterStartingWith<"requiredAction.",MessageKey>[]; requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[];
skipLink: boolean; skipLink: boolean;
pageRedirectUri?: string; pageRedirectUri?: string;
actionUri?: string; actionUri?: string;
@ -145,6 +147,10 @@ export declare namespace KcContext {
} }
}; };
export type LoginVerifyEmail = Template & {
pageId: "login-verify-email.ftl";
};
} }
doExtends<KcContext["pageId"], PageId>(); doExtends<KcContext["pageId"], PageId>();