Apply the new way i18n is implemented to every pages

This commit is contained in:
Joseph Garrone 2024-06-09 04:43:18 +02:00
parent 77e32aad2a
commit 8f006f0009
45 changed files with 308 additions and 312 deletions

View File

@ -6,14 +6,14 @@ import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
import { useSetClassName } from "keycloakify/tools/useSetClassName";
import type { TemplateProps } from "keycloakify/account/TemplateProps";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
import { useI18n } from "./i18n";
export default function Template(props: TemplateProps<KcContext, I18n>) {
const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props;
export default function Template(props: TemplateProps<KcContext>) {
const { kcContext, doUseDefaultCss, active, classes, children } = props;
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = useI18n({ kcContext });
const { locale, url, features, realm, message, referrer } = kcContext;

View File

@ -1,13 +1,8 @@
import type { ReactNode } from "react";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
export type TemplateProps<
KcContext extends KcContext.Common,
I18nExtended extends I18n
> = {
export type TemplateProps<KcContext extends KcContext.Common> = {
kcContext: KcContext;
i18n: I18nExtended;
doUseDefaultCss: boolean;
active: string;
classes?: Partial<Record<ClassKey, string>>;

View File

@ -1,8 +1,7 @@
import "keycloakify/tools/Object.fromEntries";
import { useEffect, useState, useMemo } from "react";
import { useConst } from "keycloakify/tools/useConst";
import { useEffect, useState } from "react";
import { assert } from "tsafe/assert";
import fallbackMessages from "./baseMessages/en";
import messages_fallbackLanguage from "./baseMessages/en";
import { getMessages } from "./baseMessages";
import type { KcContext } from "../KcContext";
import { Reflect } from "tsafe/Reflect";
@ -18,7 +17,7 @@ export type KcContextLike = {
assert<KcContext extends KcContextLike ? true : false>();
export type MessageKey = keyof typeof fallbackMessages;
export type MessageKey = keyof typeof messages_fallbackLanguage;
export type GenericI18n<MessageKey extends string> = {
/**
@ -89,74 +88,109 @@ export type GenericI18n<MessageKey extends string> = {
isFetchingTranslations: boolean;
};
export type I18n = GenericI18n<MessageKey>;
function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } }) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
type Result = { i18n: I18n; prI18n_currentLanguage: Promise<I18n> | undefined };
const cachedResultByKcContext = new WeakMap<KcContextLike, Result>();
function getI18n(params: { kcContext: KcContextLike }): Result {
const { kcContext } = params;
use_cache: {
const cachedResult = cachedResultByKcContext.get(kcContext);
if (cachedResult === undefined) {
break use_cache;
}
return cachedResult;
}
const partialI18n: Pick<I18n, "currentLanguageTag" | "getChangeLocalUrl" | "labelBySupportedLanguageTag"> = {
currentLanguageTag: kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag,
getChangeLocalUrl: newLanguageTag => {
const { locale } = kcContext;
assert(locale !== undefined, "Internationalization not enabled");
const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === newLanguageTag);
assert(targetSupportedLocale !== undefined, `${newLanguageTag} need to be enabled in Keycloak admin`);
return targetSupportedLocale.url;
},
labelBySupportedLanguageTag: Object.fromEntries((kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label]))
};
const { createI18nTranslationFunctions } = createI18nTranslationFunctionsFactory<MessageKey, ExtraMessageKey>({
messages_fallbackLanguage,
extraMessages_fallbackLanguage: extraMessages[fallbackLanguageTag],
extraMessages: extraMessages[partialI18n.currentLanguageTag]
});
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag !== fallbackLanguageTag;
const result: Result = {
i18n: {
...partialI18n,
...createI18nTranslationFunctions({ messages: undefined }),
isFetchingTranslations: !isCurrentLanguageFallbackLanguage
},
prI18n_currentLanguage: isCurrentLanguageFallbackLanguage
? undefined
: (async () => {
const messages = await getMessages(partialI18n.currentLanguageTag);
const i18n_currentLanguage: I18n = {
...partialI18n,
...createI18nTranslationFunctions({ messages }),
isFetchingTranslations: false
};
// NOTE: This promise.resolve is just because without it we TypeScript
// gives a Variable 'result' is used before being assigned. error
await Promise.resolve().then(() => {
result.i18n = i18n_currentLanguage;
result.prI18n_currentLanguage = undefined;
});
return i18n_currentLanguage;
})()
};
cachedResultByKcContext.set(kcContext, result);
return result;
}
return { getI18n };
}
export function createUseI18n<ExtraMessageKey extends string = never>(extraMessages: {
[languageTag: string]: { [key in ExtraMessageKey]: string };
}) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
const { getI18n } = createGetI18n(extraMessages);
function useI18n(params: { kcContext: KcContextLike }): I18n {
const { kcContext } = params;
const partialI18n = useMemo(
(): Pick<I18n, "currentLanguageTag" | "getChangeLocalUrl" | "labelBySupportedLanguageTag"> => ({
currentLanguageTag: kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag,
getChangeLocalUrl: newLanguageTag => {
const { locale } = kcContext;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
assert(locale !== undefined, "Internationalization not enabled");
const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === newLanguageTag);
assert(targetSupportedLocale !== undefined, `${newLanguageTag} need to be enabled in Keycloak admin`);
return targetSupportedLocale.url;
},
labelBySupportedLanguageTag: Object.fromEntries(
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
)
}),
[]
);
const { createI18nTranslationFunctions } = useMemo(
() =>
createI18nTranslationFunctionsFactory<MessageKey, ExtraMessageKey>({
fallbackMessages,
extraFallbackMessages: extraMessages[fallbackLanguageTag],
extraMessages: extraMessages[partialI18n.currentLanguageTag]
}),
[]
);
const [i18n, setI18n] = useState<I18n | undefined>(undefined);
const refHasStartedFetching = useConst(() => ({ current: false }));
const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(i18n);
useEffect(() => {
if (partialI18n.currentLanguageTag === fallbackLanguageTag) {
return;
}
if (refHasStartedFetching.current) {
return;
}
let isActive = true;
refHasStartedFetching.current = true;
getMessages(partialI18n.currentLanguageTag).then(messages => {
prI18n_currentLanguage?.then(i18n => {
if (!isActive) {
return;
}
setI18n({
...partialI18n,
...createI18nTranslationFunctions({ messages }),
isFetchingTranslations: false
});
setI18n_toReturn(i18n);
});
return () => {
@ -164,35 +198,22 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
};
}, []);
const fallbackI18n = useMemo(
(): I18n => ({
...partialI18n,
...createI18nTranslationFunctions({ messages: undefined }),
isFetchingTranslations: partialI18n.currentLanguageTag !== fallbackLanguageTag
}),
[]
);
return i18n ?? fallbackI18n;
return i18n_toReturn;
}
return {
useI18n,
ofTypeI18n: Reflect<I18n>()
};
return { useI18n, ofTypeI18n: Reflect<I18n>() };
}
/** Note exported only for hypothetical usage in non react framework */
export function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraMessageKey extends string>(params: {
fallbackMessages: Record<MessageKey, string>;
extraFallbackMessages: Record<ExtraMessageKey, string> | undefined;
function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraMessageKey extends string>(params: {
messages_fallbackLanguage: Record<MessageKey, string>;
extraMessages_fallbackLanguage: Record<ExtraMessageKey, string> | undefined;
extraMessages: Partial<Record<ExtraMessageKey, string>> | undefined;
}) {
const { extraMessages } = params;
const fallbackMessages = {
...params.fallbackMessages,
...params.extraFallbackMessages
const messages_fallbackLanguage = {
...params.messages_fallbackLanguage,
...params.extraMessages_fallbackLanguage
};
function createI18nTranslationFunctions(params: {
@ -206,7 +227,7 @@ export function createI18nTranslationFunctionsFactory<MessageKey extends string,
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
const { key, args, doRenderAsHtml } = props;
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (fallbackMessages as any)[key];
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (messages_fallbackLanguage as any)[key];
if (messageOrUndefined === undefined) {
return undefined;

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Account(props: PageProps<Extract<KcContext, { pageId: "account.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Account(props: PageProps<Extract<KcContext, { pageId: "account.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -17,10 +17,10 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
const { url, realm, messagesPerField, stateChecker, account, referrer } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="account">
<Template {...{ kcContext, doUseDefaultCss, classes }} active="account">
<div className="row">
<div className="col-md-10">
<h2>{msg("editAccountHtmlTitle")}</h2>

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -18,10 +18,10 @@ export default function Applications(props: PageProps<Extract<KcContext, { pageI
stateChecker
} = kcContext;
const { msg, advancedMsg } = i18n;
const { msg, advancedMsg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="applications">
<Template {...{ kcContext, doUseDefaultCss, classes }} active="applications">
<div className="row">
<div className="col-md-10">
<h2>{msg("applicationsHtmlTitle")}</h2>

View File

@ -1,14 +1,14 @@
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function FederatedIdentity(props: PageProps<Extract<KcContext, { pageId: "federatedIdentity.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
export default function FederatedIdentity(props: PageProps<Extract<KcContext, { pageId: "federatedIdentity.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
const { url, federatedIdentity, stateChecker } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="federatedIdentity">
<Template {...{ kcContext, doUseDefaultCss, classes }} active="federatedIdentity">
<div className="main-layout social">
<div className="row">
<div className="col-md-10">

View File

@ -1,11 +1,11 @@
import type { Key } from "react";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { I18n } from "../i18n";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,10 +14,10 @@ export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.f
const { log } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="log">
<Template {...{ kcContext, doUseDefaultCss, classes }} active="log">
<div className={getClassName("kcContentWrapperClass")}>
<div className="col-md-10">
<h2>{msg("accountLogHtmlTitle")}</h2>

View File

@ -1,12 +1,10 @@
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { I18n } from "../i18n";
import type { KcContext } from "../KcContext";
export type PageProps<NarrowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
export type PageProps<NarrowedKcContext = KcContext> = {
Template: LazyOrNot<(props: TemplateProps<any>) => JSX.Element | null>;
kcContext: NarrowedKcContext;
i18n: I18nExtended;
doUseDefaultCss: boolean;
classes?: Partial<Record<ClassKey, string>>;
};

View File

@ -3,10 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Password(props: PageProps<Extract<KcContext, { pageId: "password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Password(props: PageProps<Extract<KcContext, { pageId: "password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -18,7 +18,7 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
const { url, password, account, stateChecker } = kcContext;
const { msgStr, msg } = i18n;
const { msgStr, msg } = useI18n({ kcContext });
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
@ -75,7 +75,6 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
return kcContext.message;
})()
},
i18n,
doUseDefaultCss,
classes
}}

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,9 +14,9 @@ export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "
const { url, stateChecker, sessions } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="sessions">
<Template {...{ kcContext, doUseDefaultCss, classes }} active="sessions">
<div className={getClassName("kcContentWrapperClass")}>
<div className="col-md-10">
<h2>{msg("sessionsHtmlTitle")}</h2>

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,7 +14,7 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
const { totp, mode, url, messagesPerField, stateChecker } = kcContext;
const { msg, msgStr, advancedMsg } = i18n;
const { msg, msgStr, advancedMsg } = useI18n({ kcContext });
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
HmacSHA1: "SHA1",
@ -23,7 +23,7 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
};
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="totp">
<Template {...{ kcContext, doUseDefaultCss, classes }} active="totp">
<>
<div className="row">
<div className="col-md-10">

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext, { pageId: "delete-account-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext, { pageId: "delete-account-confirm.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,10 +14,10 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
const { url, triggered_from_aia } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("deleteAccountConfirm")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("deleteAccountConfirm")}>
<form action={url.loginAction} className="form-vertical" method="post">
<div className="alert alert-warning" style={{ marginTop: "0", marginBottom: "30px" }}>
<span className="pficon pficon-warning-triangle-o"></span>

View File

@ -2,12 +2,12 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function DeleteCredential(props: PageProps<Extract<KcContext, { pageId: "delete-credential.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function DeleteCredential(props: PageProps<Extract<KcContext, { pageId: "delete-credential.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { msgStr, msg } = i18n;
const { msgStr, msg } = useI18n({ kcContext });
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -17,11 +17,7 @@ export default function DeleteCredential(props: PageProps<Extract<KcContext, { p
const { url, credentialLabel } = kcContext;
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={msg("deleteCredentialTitle", credentialLabel)}
>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("deleteCredentialTitle", credentialLabel)}>
<div id="kc-delete-text">{msg("deleteCredentialMessage", credentialLabel)}</div>
<form className="form-actions" action={url.loginAction} method="POST">
<input

View File

@ -1,16 +1,16 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { message, client, skipLink } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("errorTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("errorTitle")}>
<div id="kc-error-message">
<p className="instruction">{message.summary}</p>
{!skipLink && client !== undefined && client.baseUrl !== undefined && (

View File

@ -1,14 +1,14 @@
import { useEffect } from "react";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function FrontchannelLogout(props: PageProps<Extract<KcContext, { pageId: "frontchannel-logout.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function FrontchannelLogout(props: PageProps<Extract<KcContext, { pageId: "frontchannel-logout.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { logout } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
useEffect(() => {
if (logout.logoutRedirectUri) {
@ -18,7 +18,7 @@ export default function FrontchannelLogout(props: PageProps<Extract<KcContext, {
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
documentTitle={msgStr("frontchannel-logout.title")}
headerNode={msg("frontchannel-logout.title")}
>

View File

@ -5,21 +5,21 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
type IdpReviewUserProfileProps = PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>, I18n> & {
type IdpReviewUserProfileProps = PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
};
export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
const { url, messagesPerField } = kcContext;
@ -27,18 +27,13 @@ export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayMessage={messagesPerField.exists("global")}
displayRequiredFields
headerNode={msg("loginIdpReviewProfileTitle")}
>
<form id="kc-idp-review-profile-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<UserProfileFormFields
kcContext={kcContext}
onIsFormSubmittableValueChange={setIsFomSubmittable}
i18n={i18n}
getClassName={getClassName}
/>
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} getClassName={getClassName} />
<div className={getClassName("kcFormGroupClass")}>
<div id="kc-form-options" className={getClassName("kcFormOptionsClass")}>
<div className={getClassName("kcFormOptionsWrapperClass")} />

View File

@ -1,12 +1,12 @@
import { assert } from "keycloakify/tools/assert";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { msgStr, msg } = i18n;
const { msgStr, msg } = useI18n({ kcContext });
assert(
kcContext.message !== undefined,
@ -17,7 +17,7 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayMessage={false}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
>

View File

@ -4,10 +4,10 @@ import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -16,13 +16,14 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayMessage={!messagesPerField.existsError("username", "password")}
headerNode={msg("loginAccountTitle")}
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}

View File

@ -1,11 +1,11 @@
import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useGetClassName, type ClassKey } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,10 +14,12 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr, advancedMsg } = i18n;
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("loginTotpTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("loginTotpTitle")}>
<>
<ol id="kc-totp-settings">
<li>
@ -184,7 +186,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
);
}
function LogoutOtherSessions(props: { getClassName: ReturnType<typeof useGetClassName>["getClassName"]; i18n: I18n }) {
function LogoutOtherSessions(props: { getClassName: (key: ClassKey) => string; i18n: I18n }) {
const { getClassName, i18n } = props;
const { msg } = i18n;

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,10 +14,10 @@ export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext,
const { url, idpAlias } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("confirmLinkIdpTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("confirmLinkIdpTitle")}>
<form id="kc-register-form" action={url.loginAction} method="post">
<div className={getClassName("kcFormGroupClass")}>
<button

View File

@ -1,16 +1,16 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-email.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-email.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { url, realm, brokerContext, idpAlias } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("emailLinkIdpTitle", idpAlias)}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("emailLinkIdpTitle", idpAlias)}>
<p id="instruction1" className="instruction">
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
</p>

View File

@ -1,16 +1,16 @@
import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { PageProps } from "keycloakify/login/pages/PageProps";
import { I18n } from "../i18n";
import { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
export default function LoginOauth2DeviceVerifyUserCode(
props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>, I18n>
props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>>
) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { kcContext, doUseDefaultCss, classes, Template } = props;
const { url } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -18,7 +18,7 @@ export default function LoginOauth2DeviceVerifyUserCode(
});
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("oauth2DeviceVerificationTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("oauth2DeviceVerificationTitle")}>
<form
id="kc-user-verify-device-user-code-form"
className={getClassName("kcFormClass")}

View File

@ -2,13 +2,13 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { PageProps } from "keycloakify/login/pages/PageProps";
import { KcContext } from "../KcContext";
import { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth-grant.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth-grant.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
const { url, oauth, client } = kcContext;
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
const { msg, msgStr, advancedMsg, advancedMsgStr } = useI18n({ kcContext });
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -17,7 +17,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
bodyClassName="oauth"
headerNode={
<>

View File

@ -3,10 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "login-otp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "login-otp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -15,14 +15,10 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
const { otpLogin, url, messagesPerField } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={!messagesPerField.existsError("totp")}
headerNode={msg("doLogIn")}
>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayMessage={!messagesPerField.existsError("totp")} headerNode={msg("doLogIn")}>
<form id="kc-otp-login-form" className={clsx(getClassName("kcFormClass"))} action={url.loginAction} method="post">
{otpLogin.userOtpCredentials.length > 1 && (
<div className={getClassName("kcFormGroupClass")}>

View File

@ -1,16 +1,16 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { url } = kcContext;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("pageExpiredTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("pageExpiredTitle")}>
<p id="instruction1" className="instruction">
{msg("pageExpiredMsg1")}
<a id="loginRestartLink" href={url.loginRestartFlowUrl}>

View File

@ -4,10 +4,10 @@ import { assert } from "tsafe/assert";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -16,16 +16,13 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
const { realm, url, messagesPerField } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
headerNode={msg("doLogIn")}
displayMessage={!messagesPerField.existsError("password")}
>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("doLogIn")} displayMessage={!messagesPerField.existsError("password")}>
<div id="kc-form">
<div id="kc-form-wrapper">
<form

View File

@ -4,10 +4,10 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -16,6 +16,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
const { recoveryAuthnCodesConfigBean, isAppInitiatedAction } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { insertScriptTags } = useInsertScriptTags({
@ -144,7 +145,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
}, []);
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("recovery-code-config-header")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("recovery-code-config-header")}>
<div className={clsx("pf-c-alert", "pf-m-warning", "pf-m-inline", getClassName("kcRecoveryCodesWarning"))} aria-label="Warning alert">
<div className="pf-c-alert__icon">
<i className="pficon-warning-triangle-o" aria-hidden="true" />

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-input.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-input.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,11 +14,11 @@ export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcC
const { url, messagesPerField, recoveryAuthnCodesInputBean } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
headerNode={msg("auth-recovery-code-header")}
displayMessage={!messagesPerField.existsError("recoveryCodeInput")}
>

View File

@ -3,10 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginResetOtp(props: PageProps<Extract<KcContext, { pageId: "login-reset-otp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginResetOtp(props: PageProps<Extract<KcContext, { pageId: "login-reset-otp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -15,14 +15,10 @@ export default function LoginResetOtp(props: PageProps<Extract<KcContext, { page
const { url, messagesPerField, configuredOtpCredentials } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
displayMessage={!messagesPerField.existsError("totp")}
headerNode={msg("doLogIn")}
>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayMessage={!messagesPerField.existsError("totp")} headerNode={msg("doLogIn")}>
<form id="kc-otp-reset-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<div className={getClassName("kcInputWrapperClass")}>
<div className={getClassName("kcInfoAreaWrapperClass")}>

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,11 +14,11 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
const { url, realm, auth, messagesPerField } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayInfo
displayMessage={!messagesPerField.existsError("username")}
infoNode={realm.duplicateEmailsAllowed ? msg("emailInstructionUsername") : msg("emailInstruction")}

View File

@ -4,23 +4,24 @@ import { assert } from "tsafe/assert";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { url, messagesPerField, isAppInitiatedAction } = kcContext;
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayMessage={!messagesPerField.existsError("password", "password-confirm")}
headerNode={msg("updatePasswordTitle")}
>

View File

@ -5,14 +5,14 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
type LoginUpdateProfileProps = PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>, I18n> & {
type LoginUpdateProfileProps = PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
};
export default function LoginUpdateProfile(props: LoginUpdateProfileProps) {
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -21,17 +21,16 @@ export default function LoginUpdateProfile(props: LoginUpdateProfileProps) {
const { url, messagesPerField, isAppInitiatedAction } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayRequiredFields headerNode={msg("loginProfileTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayRequiredFields headerNode={msg("loginProfileTitle")}>
<form id="kc-update-profile-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<UserProfileFormFields
{...{
kcContext,
i18n,
getClassName,
messagesPerField
}}

View File

@ -3,10 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginUsername(props: PageProps<Extract<KcContext, { pageId: "login-username.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginUsername(props: PageProps<Extract<KcContext, { pageId: "login-username.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -15,13 +15,13 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
const { social, realm, url, usernameHidden, login, registrationDisabled, messagesPerField } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayMessage={!messagesPerField.existsError("username")}
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
infoNode={

View File

@ -1,17 +1,17 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
const { url, user } = kcContext;
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayInfo
headerNode={msg("emailVerifyTitle")}
infoNode={

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LoginX509Info(props: PageProps<Extract<KcContext, { pageId: "login-x509-info.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LoginX509Info(props: PageProps<Extract<KcContext, { pageId: "login-x509-info.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,10 +14,10 @@ export default function LoginX509Info(props: PageProps<Extract<KcContext, { page
const { url, x509 } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("doLogIn")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("doLogIn")}>
<form id="kc-x509-login-info" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<div className={getClassName("kcFormGroupClass")}>
<div className={getClassName("kcLabelWrapperClass")}>

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function LogoutConfirm(props: PageProps<Extract<KcContext, { pageId: "logout-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function LogoutConfirm(props: PageProps<Extract<KcContext, { pageId: "logout-confirm.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -14,10 +14,10 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
const { url, client, logoutConfirm } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("logoutConfirmTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("logoutConfirmTitle")}>
<div id="kc-logout-confirm" className="content-area">
<p className="instruction">{msg("logoutConfirmHeader")}</p>
<form className="form-actions" action={url.logoutConfirmAction} method="POST">

View File

@ -3,7 +3,7 @@ import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { KcContext } from "keycloakify/account/KcContext";
export type PageProps<NarowedKcContext = KcContext> = {
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
Template: LazyOrNot<(props: TemplateProps<any>) => JSX.Element | null>;
kcContext: NarowedKcContext;
doUseDefaultCss: boolean;
classes?: Partial<Record<ClassKey, string>>;

View File

@ -7,14 +7,14 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
type RegisterProps = PageProps<Extract<KcContext, { pageId: "register.ftl" }>, I18n> & {
type RegisterProps = PageProps<Extract<KcContext, { pageId: "register.ftl" }>> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
};
export default function Register(props: RegisterProps) {
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -23,17 +23,17 @@ export default function Register(props: RegisterProps) {
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey, termsAcceptanceRequired } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("registerTitle")} displayRequiredFields>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("registerTitle")} displayRequiredFields>
<form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post">
<UserProfileFormFields
{...{
kcContext,
i18n,
getClassName,
messagesPerField
}}

View File

@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageId: "saml-post-form.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageId: "saml-post-form.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { msgStr, msg } = i18n;
const { msgStr, msg } = useI18n({ kcContext });
const { samlPost } = kcContext;
@ -26,7 +26,7 @@ export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageI
htmlFormElement.submit();
}, [htmlFormElement]);
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("saml.post-form.title")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} headerNode={msg("saml.post-form.title")}>
<p>{msg("saml.post-form.message")}</p>
<form name="saml-post-binding" method="post" action={samlPost.url} ref={setHtmlFormElement}>
{samlPost.SAMLRequest && <input type="hidden" name="SAMLRequest" value={samlPost.SAMLRequest} />}

View File

@ -2,17 +2,17 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { url, auth } = kcContext;
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
const { msg } = i18n;
const { msg } = useI18n({ kcContext });
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayInfo={false} headerNode={msg("loginChooseAuthenticator")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayInfo={false} headerNode={msg("loginChooseAuthenticator")}>
<form id="kc-select-credential-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<div className={getClassName("kcSelectAuthListClass")}>
{auth.authenticationSelections.map((authenticationSelection, i) => (
@ -26,8 +26,6 @@ export default function SelectAuthenticator(props: PageProps<Extract<KcContext,
<div className={getClassName("kcSelectAuthListItemIconClass")}>
<i
className={clsx(
// @ts-expect-error: iconCssClass is a string and not a class key
// however getClassName gracefully handles this case at runtime
getClassName(authenticationSelection.iconCssClass),
getClassName("kcSelectAuthListItemIconPropertyClass")
)}

View File

@ -4,17 +4,17 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
const { locale, url } = kcContext;
@ -25,7 +25,7 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
}
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("termsTitle")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("termsTitle")}>
<div id="kc-terms-text" lang={termsLanguageTag !== locale?.currentLanguageTag ? termsLanguageTag : undefined}>
<Markdown>{termsMarkdown}</Markdown>
</div>

View File

@ -5,20 +5,21 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
type UpdateEmailProps = PageProps<Extract<KcContext, { pageId: "update-email.ftl" }>, I18n> & {
type UpdateEmailProps = PageProps<Extract<KcContext, { pageId: "update-email.ftl" }>> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
};
export default function UpdateEmail(props: UpdateEmailProps) {
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { getClassName } = useGetClassName({
doUseDefaultCss,
classes
});
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
@ -27,7 +28,7 @@ export default function UpdateEmail(props: UpdateEmailProps) {
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayMessage={messagesPerField.exists("global")}
displayRequiredFields
headerNode={msg("updateEmailTitle")}
@ -36,7 +37,6 @@ export default function UpdateEmail(props: UpdateEmailProps) {
<UserProfileFormFields
{...{
kcContext,
i18n,
getClassName,
messagesPerField
}}

View File

@ -5,10 +5,10 @@ import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
@ -26,7 +26,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
shouldDisplayAuthenticators
} = kcContext;
const { msg, msgStr, advancedMsg } = i18n;
const { msg, msgStr, advancedMsg } = useI18n({ kcContext });
const { insertScriptTags } = useInsertScriptTags({
componentOrHookName: "WebauthnAuthenticate",
@ -136,7 +136,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
displayMessage={!messagesPerField.existsError("username")}
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
infoNode={

View File

@ -2,14 +2,14 @@ import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n } from "../i18n";
export default function WebauthnError(props: PageProps<Extract<KcContext, { pageId: "webauthn-error.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function WebauthnError(props: PageProps<Extract<KcContext, { pageId: "webauthn-error.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { url, isAppInitiatedAction } = kcContext;
const { msg, msgStr } = i18n;
const { msg, msgStr } = useI18n({ kcContext });
const { getClassName } = useGetClassName({
doUseDefaultCss,
@ -17,7 +17,7 @@ export default function WebauthnError(props: PageProps<Extract<KcContext, { page
});
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage headerNode={msg("webauthn-error-title")}>
<Template {...{ kcContext, doUseDefaultCss, classes }} displayMessage headerNode={msg("webauthn-error-title")}>
<form id="kc-error-credential-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
<input type="hidden" id="executionValue" name="authenticationExecution" />
<input type="hidden" id="isSetRetry" name="isSetRetry" />

View File

@ -5,10 +5,10 @@ import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useI18n, type I18n } from "../i18n";
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
@ -30,6 +30,7 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
isAppInitiatedAction
} = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { insertScriptTags } = useInsertScriptTags({
@ -206,7 +207,7 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
return (
<Template
{...{ kcContext, i18n, doUseDefaultCss, classes }}
{...{ kcContext, doUseDefaultCss, classes }}
headerNode={
<>
<span className={getClassName("kcWebAuthnKeyIcon")} />