Refactor and get rid of unessesary dependencies
This commit is contained in:
@ -1,19 +1,10 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import React from "react";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type ErrorProps = KcProps & {
|
||||
kcContext: KcContextBase.Error;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const Error = memo((props: ErrorProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function Error(props: PageProps<KcContextBase.Error, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { message, client } = kcContext;
|
||||
|
||||
@ -38,6 +29,4 @@ const Error = memo((props: ErrorProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default Error;
|
||||
}
|
||||
|
@ -1,21 +1,12 @@
|
||||
import React, { useState, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useState } from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type IdpReviewUserProfileProps = KcProps & {
|
||||
kcContext: KcContextBase.IdpReviewUserProfile;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const IdpReviewUserProfile = memo((props: IdpReviewUserProfileProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function IdpReviewUserProfile(props: PageProps<KcContextBase.IdpReviewUserProfile, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
@ -53,6 +44,4 @@ const IdpReviewUserProfile = memo((props: IdpReviewUserProfileProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default IdpReviewUserProfile;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import React from "react";
|
||||
import { assert } from "../tools/assert";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type InfoProps = KcProps & {
|
||||
kcContext: KcContextBase.Info;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const Info = memo((props: InfoProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function Info(props: PageProps<KcContextBase.Info, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { msgStr, msg } = i18n;
|
||||
|
||||
@ -55,6 +46,4 @@ const Info = memo((props: InfoProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default Info;
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React, { lazy, memo, Suspense } from "react";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import React, { lazy, Suspense } from "react";
|
||||
import { __unsafe_useI18n as useI18n } from "../i18n";
|
||||
import type { I18n } from "../i18n";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
const Login = lazy(() => import("./Login"));
|
||||
const Register = lazy(() => import("./Register"));
|
||||
@ -28,14 +27,7 @@ const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
|
||||
const UpdateUserProfile = lazy(() => import("./UpdateUserProfile"));
|
||||
const IdpReviewUserProfile = lazy(() => import("./IdpReviewUserProfile"));
|
||||
|
||||
export type KcAppProps = KcProps & {
|
||||
kcContext: KcContextBase;
|
||||
i18n?: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const KcApp = memo((props_: KcAppProps) => {
|
||||
export default function KcApp(props_: PageProps<KcContextBase, I18nBase>) {
|
||||
const { kcContext, i18n: userProvidedI18n, Template = DefaultTemplate, ...kcProps } = props_;
|
||||
|
||||
const i18n = (function useClosure() {
|
||||
@ -104,6 +96,4 @@ const KcApp = memo((props_: KcAppProps) => {
|
||||
})()}
|
||||
</Suspense>
|
||||
);
|
||||
});
|
||||
|
||||
export default KcApp;
|
||||
}
|
||||
|
@ -1,22 +1,13 @@
|
||||
import React, { useState, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useState } from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { useConstCallback } from "../tools/useConstCallback";
|
||||
import type { FormEventHandler } from "react";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginProps = KcProps & {
|
||||
kcContext: KcContextBase.Login;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const Login = memo((props: LoginProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function Login(props: PageProps<KcContextBase.Login, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
||||
|
||||
@ -199,6 +190,4 @@ const Login = memo((props: LoginProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default Login;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginConfigTotpProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginConfigTotp;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginConfigTotp = memo((props: LoginConfigTotpProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginConfigTotp(props: PageProps<KcContextBase.LoginConfigTotp, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
|
||||
|
||||
@ -188,6 +179,4 @@ const LoginConfigTotp = memo((props: LoginConfigTotpProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginConfigTotp;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginIdpLinkConfirmProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginIdpLinkConfirm;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginIdpLinkConfirm = memo((props: LoginIdpLinkConfirmProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginIdpLinkConfirm(props: PageProps<KcContextBase.LoginIdpLinkConfirm, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url, idpAlias } = kcContext;
|
||||
|
||||
@ -60,6 +51,4 @@ const LoginIdpLinkConfirm = memo((props: LoginIdpLinkConfirmProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginIdpLinkConfirm;
|
||||
}
|
||||
|
@ -1,19 +1,10 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import React from "react";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginIdpLinkEmailProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginIdpLinkEmail;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginIdpLinkEmail = memo((props: LoginIdpLinkEmailProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginIdpLinkEmail(props: PageProps<KcContextBase.LoginIdpLinkEmail, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url, realm, brokerContext, idpAlias } = kcContext;
|
||||
|
||||
@ -38,6 +29,4 @@ const LoginIdpLinkEmail = memo((props: LoginIdpLinkEmailProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginIdpLinkEmail;
|
||||
}
|
||||
|
@ -1,22 +1,13 @@
|
||||
import React, { useEffect, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useEffect } from "react";
|
||||
import { headInsert } from "../tools/headInsert";
|
||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginOtpProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginOtp;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginOtp = memo((props: LoginOtpProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginOtp(props: PageProps<KcContextBase.LoginOtp, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { otpLogin, url } = kcContext;
|
||||
|
||||
@ -96,7 +87,7 @@ const LoginOtp = memo((props: LoginOtpProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
declare const $: any;
|
||||
|
||||
@ -121,5 +112,3 @@ function evaluateInlineScript() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default LoginOtp;
|
||||
|
@ -1,19 +1,10 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import React from "react";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginPageExpired = KcProps & {
|
||||
kcContext: KcContextBase.LoginPageExpired;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginPageExpired = memo((props: LoginPageExpired) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginPageExpired(props: PageProps<KcContextBase.LoginPageExpired, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url } = kcContext;
|
||||
|
||||
@ -42,6 +33,4 @@ const LoginPageExpired = memo((props: LoginPageExpired) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginPageExpired;
|
||||
}
|
||||
|
@ -1,22 +1,13 @@
|
||||
import React, { useState, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useState } from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { useConstCallback } from "../tools/useConstCallback";
|
||||
import type { FormEventHandler } from "react";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginPasswordProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginPassword;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginPassword = memo((props: LoginPasswordProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginPassword(props: PageProps<KcContextBase.LoginPassword, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { realm, url, login } = kcContext;
|
||||
|
||||
@ -92,6 +83,4 @@ const LoginPassword = memo((props: LoginPasswordProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginPassword;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginResetPasswordProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginResetPassword;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginResetPassword = memo((props: LoginResetPasswordProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginResetPassword(props: PageProps<KcContextBase.LoginResetPassword, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url, realm, auth } = kcContext;
|
||||
|
||||
@ -75,6 +66,4 @@ const LoginResetPassword = memo((props: LoginResetPasswordProps) => {
|
||||
infoNode={msg("emailInstruction")}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginResetPassword;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginUpdatePasswordProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginUpdatePassword;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginUpdatePassword = memo((props: LoginUpdatePasswordProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginUpdatePassword(props: PageProps<KcContextBase.LoginUpdatePassword, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
@ -123,6 +114,4 @@ const LoginUpdatePassword = memo((props: LoginUpdatePasswordProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginUpdatePassword;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginUpdateProfile = KcProps & {
|
||||
kcContext: KcContextBase.LoginUpdateProfile;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginUpdateProfile = memo((props: LoginUpdateProfile) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginUpdateProfile(props: PageProps<KcContextBase.LoginUpdateProfile, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
@ -130,6 +121,4 @@ const LoginUpdateProfile = memo((props: LoginUpdateProfile) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginUpdateProfile;
|
||||
}
|
||||
|
@ -1,22 +1,13 @@
|
||||
import React, { useState, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useState } from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { useConstCallback } from "../tools/useConstCallback";
|
||||
import type { FormEventHandler } from "react";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginUsernameProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginUsername;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginUsername = memo((props: LoginUsernameProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginUsername(props: PageProps<KcContextBase.LoginUsername, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { social, realm, url, usernameHidden, login, registrationDisabled } = kcContext;
|
||||
|
||||
@ -164,6 +155,4 @@ const LoginUsername = memo((props: LoginUsernameProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginUsername;
|
||||
}
|
||||
|
@ -1,19 +1,10 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import React from "react";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LoginVerifyEmailProps = KcProps & {
|
||||
kcContext: KcContextBase.LoginVerifyEmail;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LoginVerifyEmail = memo((props: LoginVerifyEmailProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LoginVerifyEmail(props: PageProps<KcContextBase.LoginVerifyEmail, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { msg } = i18n;
|
||||
|
||||
@ -38,6 +29,4 @@ const LoginVerifyEmail = memo((props: LoginVerifyEmailProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginVerifyEmail;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import React from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type LogoutConfirmProps = KcProps & {
|
||||
kcContext: KcContextBase.LogoutConfirm;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const LogoutConfirm = memo((props: LogoutConfirmProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function LogoutConfirm(props: PageProps<KcContextBase.LogoutConfirm, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url, client, logoutConfirm } = kcContext;
|
||||
|
||||
@ -64,6 +55,4 @@ const LogoutConfirm = memo((props: LogoutConfirmProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LogoutConfirm;
|
||||
}
|
||||
|
@ -1,20 +1,11 @@
|
||||
import React, { memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type RegisterProps = KcProps & {
|
||||
kcContext: KcContextBase.Register;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const Register = memo((props: RegisterProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function Register(props: PageProps<KcContextBase.Register, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||
|
||||
@ -167,6 +158,4 @@ const Register = memo((props: RegisterProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default Register;
|
||||
}
|
||||
|
@ -1,21 +1,12 @@
|
||||
import React, { memo, useState } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useState } from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type RegisterUserProfileProps = KcProps & {
|
||||
kcContext: KcContextBase.RegisterUserProfile;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const RegisterUserProfile = memo((props: RegisterUserProfileProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function RegisterUserProfile(props: PageProps<KcContextBase.RegisterUserProfile, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||
|
||||
@ -66,6 +57,4 @@ const RegisterUserProfile = memo((props: RegisterUserProfileProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default RegisterUserProfile;
|
||||
}
|
||||
|
@ -1,32 +1,15 @@
|
||||
import React, { useReducer, useEffect, memo } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import React, { useReducer, useEffect } from "react";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { assert } from "../tools/assert";
|
||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||
import { useCallbackFactory } from "../tools/useCallbackFactory";
|
||||
import { headInsert } from "../tools/headInsert";
|
||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import type { KcTemplateProps } from "./KcProps";
|
||||
import { useConstCallback } from "../tools/useConstCallback";
|
||||
import type { TemplateProps } from "./shared/KcProps";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type TemplateProps = {
|
||||
displayInfo?: boolean;
|
||||
displayMessage?: boolean;
|
||||
displayRequiredFields?: boolean;
|
||||
displayWide?: boolean;
|
||||
showAnotherWayIfPresent?: boolean;
|
||||
headerNode: ReactNode;
|
||||
showUsernameNode?: ReactNode;
|
||||
formNode: ReactNode;
|
||||
infoNode?: ReactNode;
|
||||
/** If you write your own page you probably want
|
||||
* to avoid pulling the default theme assets.
|
||||
*/
|
||||
doFetchDefaultThemeResources: boolean;
|
||||
} & { kcContext: KcContextBase; i18n: I18n } & KcTemplateProps;
|
||||
|
||||
const Template = memo((props: TemplateProps) => {
|
||||
export default function Template(props: TemplateProps<KcContextBase.Common, I18nBase>) {
|
||||
const {
|
||||
displayInfo = false,
|
||||
displayMessage = true,
|
||||
@ -244,6 +227,4 @@ const Template = memo((props: TemplateProps) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Template;
|
||||
}
|
||||
|
@ -1,70 +1,20 @@
|
||||
import React, { useEffect, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useEffect } from "react";
|
||||
import { memoize } from "../tools/memoize";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import { Evt } from "evt";
|
||||
import { useRerenderOnStateChange } from "evt/hooks";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { fallbackLanguageTag } from "../i18n";
|
||||
import type { I18n } from "../i18n";
|
||||
import memoize from "memoizee";
|
||||
import { useConst } from "powerhooks/useConst";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { useConst } from "../tools/useConst";
|
||||
import { useConstCallback } from "../tools/useConstCallback";
|
||||
import { Markdown } from "../tools/Markdown";
|
||||
import type { Extends } from "tsafe";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
||||
|
||||
export type KcContextLike = {
|
||||
pageId: KcContextBase["pageId"];
|
||||
locale?: {
|
||||
currentLanguageTag: string;
|
||||
};
|
||||
};
|
||||
|
||||
assert<Extends<KcContextBase, KcContextLike>>();
|
||||
|
||||
/** Allow to avoid bundling the terms and download it on demand*/
|
||||
export function useDownloadTerms(params: {
|
||||
kcContext: KcContextLike;
|
||||
downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string>;
|
||||
}) {
|
||||
const { kcContext } = params;
|
||||
|
||||
const { downloadTermMarkdownMemoized } = (function useClosure() {
|
||||
const { downloadTermMarkdown } = params;
|
||||
|
||||
const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown);
|
||||
|
||||
const downloadTermMarkdownMemoized = useConst(() =>
|
||||
memoize((currentLanguageTag: string) => downloadTermMarkdownConst({ currentLanguageTag }), { "promise": true })
|
||||
);
|
||||
|
||||
return { downloadTermMarkdownMemoized };
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (kcContext.pageId !== "terms.ftl") {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadTermMarkdownMemoized(kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag).then(
|
||||
thermMarkdown => (evtTermMarkdown.state = thermMarkdown)
|
||||
);
|
||||
}, []);
|
||||
}
|
||||
|
||||
export type TermsProps = KcProps & {
|
||||
kcContext: KcContextBase.Terms;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const Terms = memo((props: TermsProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function Terms(props: PageProps<KcContextBase.Terms, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
@ -111,6 +61,45 @@ const Terms = memo((props: TermsProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default Terms;
|
||||
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
||||
|
||||
export type KcContextLike = {
|
||||
pageId: KcContextBase["pageId"];
|
||||
locale?: {
|
||||
currentLanguageTag: string;
|
||||
};
|
||||
};
|
||||
|
||||
assert<Extends<KcContextBase, KcContextLike>>();
|
||||
|
||||
/** Allow to avoid bundling the terms and download it on demand*/
|
||||
export function useDownloadTerms(params: {
|
||||
kcContext: KcContextLike;
|
||||
downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string>;
|
||||
}) {
|
||||
const { kcContext } = params;
|
||||
|
||||
const { downloadTermMarkdownMemoized } = (function useClosure() {
|
||||
const { downloadTermMarkdown } = params;
|
||||
|
||||
const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown);
|
||||
|
||||
const downloadTermMarkdownMemoized = useConst(() =>
|
||||
memoize((currentLanguageTag: string) => downloadTermMarkdownConst({ currentLanguageTag }))
|
||||
);
|
||||
|
||||
return { downloadTermMarkdownMemoized };
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (kcContext.pageId !== "terms.ftl") {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadTermMarkdownMemoized(kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag).then(
|
||||
thermMarkdown => (evtTermMarkdown.state = thermMarkdown)
|
||||
);
|
||||
}, []);
|
||||
}
|
||||
|
@ -1,21 +1,12 @@
|
||||
import React, { useState, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useState } from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n } from "../i18n";
|
||||
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type UpdateUserProfileProps = KcProps & {
|
||||
kcContext: KcContextBase.UpdateUserProfile;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const UpdateUserProfile = memo((props: UpdateUserProfileProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function UpdateUserProfile(props: PageProps<KcContextBase.UpdateUserProfile, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
@ -73,6 +64,4 @@ const UpdateUserProfile = memo((props: UpdateUserProfileProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default UpdateUserProfile;
|
||||
}
|
||||
|
@ -1,22 +1,14 @@
|
||||
import React, { useRef, useState, memo } from "react";
|
||||
import DefaultTemplate from "./Template";
|
||||
import type { TemplateProps } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { clsx } from "../tools/clsx";
|
||||
import type { I18n, MessageKeyBase } from "../i18n";
|
||||
import type { MessageKeyBase } from "../i18n";
|
||||
import { base64url } from "rfc4648";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { useConstCallback } from "../tools/useConstCallback";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { PageProps } from "./shared/KcProps";
|
||||
import type { I18nBase } from "../i18n";
|
||||
|
||||
export type WebauthnAuthenticateProps = KcProps & {
|
||||
kcContext: KcContextBase.WebauthnAuthenticate;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template?: (props: TemplateProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
const WebauthnAuthenticate = memo((props: WebauthnAuthenticateProps) => {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
|
||||
export default function WebauthnAuthenticate(props: PageProps<KcContextBase.WebauthnAuthenticate, I18nBase>) {
|
||||
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
|
||||
|
||||
const { url } = kcContext;
|
||||
|
||||
@ -198,6 +190,4 @@ const WebauthnAuthenticate = memo((props: WebauthnAuthenticateProps) => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default WebauthnAuthenticate;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
|
||||
import { allPropertiesValuesToUndefined } from "../../tools/allPropertiesValuesToUndefined";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { KcContextBase } from "../../getKcContext";
|
||||
import type { ReactNode } from "react";
|
||||
import { I18nBase } from "../../i18n";
|
||||
|
||||
/** Class names can be provided as an array or separated by whitespace */
|
||||
export type KcPropsGeneric<CssClasses extends string> = {
|
||||
@ -205,6 +208,29 @@ export const defaultKcProps = {
|
||||
"kcFormOptionsWrapperClass": []
|
||||
} as const;
|
||||
|
||||
export type TemplateProps<KcContext extends KcContextBase.Common, I18n extends I18nBase> = {
|
||||
kcContext: KcContext;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources: boolean;
|
||||
} & {
|
||||
displayInfo?: boolean;
|
||||
displayMessage?: boolean;
|
||||
displayRequiredFields?: boolean;
|
||||
displayWide?: boolean;
|
||||
showAnotherWayIfPresent?: boolean;
|
||||
headerNode: ReactNode;
|
||||
showUsernameNode?: ReactNode;
|
||||
formNode: ReactNode;
|
||||
infoNode?: ReactNode;
|
||||
} & KcTemplateProps;
|
||||
|
||||
export type PageProps<KcContext extends KcContextBase, I18n extends I18nBase> = {
|
||||
kcContext: KcContext;
|
||||
i18n: I18n;
|
||||
doFetchDefaultThemeResources?: boolean;
|
||||
Template: (props: TemplateProps<KcContext, I18n>) => JSX.Element | null;
|
||||
} & KcProps;
|
||||
|
||||
assert<typeof defaultKcProps extends KcProps ? true : false>();
|
||||
|
||||
/** Tu use if you don't want any default */
|
233
src/lib/components/shared/Template.tsx
Normal file
233
src/lib/components/shared/Template.tsx
Normal file
@ -0,0 +1,233 @@
|
||||
import React, { useReducer, useEffect } from "react";
|
||||
import { useCallbackFactory } from "../../tools/useCallbackFactory";
|
||||
import { useConstCallback } from "../../tools/useConstCallback";
|
||||
import { assert } from "../../tools/assert";
|
||||
import { headInsert } from "../../tools/headInsert";
|
||||
import { pathJoin } from "../../../bin/tools/pathJoin";
|
||||
import { clsx } from "../../tools/clsx";
|
||||
import type { TemplateProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../../getKcContext/KcContextBase";
|
||||
import type { I18nBase } from "../../i18n";
|
||||
|
||||
export default function Template(props: TemplateProps<KcContextBase.Common, I18nBase>) {
|
||||
const {
|
||||
displayInfo = false,
|
||||
displayMessage = true,
|
||||
displayRequiredFields = false,
|
||||
displayWide = false,
|
||||
showAnotherWayIfPresent = true,
|
||||
headerNode,
|
||||
showUsernameNode = null,
|
||||
formNode,
|
||||
infoNode = null,
|
||||
kcContext,
|
||||
i18n,
|
||||
doFetchDefaultThemeResources
|
||||
} = props;
|
||||
|
||||
const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
||||
|
||||
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [string]) => changeLocale(kcLanguageTag));
|
||||
|
||||
const onTryAnotherWayClick = useConstCallback(() => (document.forms["kc-select-try-another-way-form" as never].submit(), false));
|
||||
|
||||
const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
|
||||
|
||||
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!doFetchDefaultThemeResources) {
|
||||
setExtraCssLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
let isUnmounted = false;
|
||||
const cleanups: (() => void)[] = [];
|
||||
|
||||
const toArr = (x: string | readonly string[] | undefined) => (typeof x === "string" ? x.split(" ") : x ?? []);
|
||||
|
||||
Promise.all(
|
||||
[
|
||||
...toArr(props.stylesCommon).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)),
|
||||
...toArr(props.styles).map(relativePath => pathJoin(url.resourcesPath, relativePath))
|
||||
]
|
||||
.reverse()
|
||||
.map(href =>
|
||||
headInsert({
|
||||
"type": "css",
|
||||
href,
|
||||
"position": "prepend"
|
||||
})
|
||||
)
|
||||
).then(() => {
|
||||
if (isUnmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setExtraCssLoaded();
|
||||
});
|
||||
|
||||
toArr(props.scripts).forEach(relativePath =>
|
||||
headInsert({
|
||||
"type": "javascript",
|
||||
"src": pathJoin(url.resourcesPath, relativePath)
|
||||
})
|
||||
);
|
||||
|
||||
if (props.kcHtmlClass !== undefined) {
|
||||
const htmlClassList = document.getElementsByTagName("html")[0].classList;
|
||||
|
||||
const tokens = clsx(props.kcHtmlClass).split(" ");
|
||||
|
||||
htmlClassList.add(...tokens);
|
||||
|
||||
cleanups.push(() => htmlClassList.remove(...tokens));
|
||||
}
|
||||
|
||||
return () => {
|
||||
isUnmounted = true;
|
||||
|
||||
cleanups.forEach(f => f());
|
||||
};
|
||||
}, [props.kcHtmlClass]);
|
||||
|
||||
if (!isExtraCssLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx(props.kcLoginClass)}>
|
||||
<div id="kc-header" className={clsx(props.kcHeaderClass)}>
|
||||
<div id="kc-header-wrapper" className={clsx(props.kcHeaderWrapperClass)}>
|
||||
{msg("loginTitleHtml", realm.displayNameHtml)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
|
||||
<header className={clsx(props.kcFormHeaderClass)}>
|
||||
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
|
||||
<div id="kc-locale">
|
||||
<div id="kc-locale-wrapper" className={clsx(props.kcLocaleWrapperClass)}>
|
||||
<div className="kc-dropdown" id="kc-locale-dropdown">
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a href="#" id="kc-current-locale-link">
|
||||
{labelBySupportedLanguageTag[currentLanguageTag]}
|
||||
</a>
|
||||
<ul>
|
||||
{locale.supported.map(({ languageTag }) => (
|
||||
<li key={languageTag} className="kc-dropdown-item">
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
|
||||
{labelBySupportedLanguageTag[languageTag]}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
|
||||
displayRequiredFields ? (
|
||||
<div className={clsx(props.kcContentWrapperClass)}>
|
||||
<div className={clsx(props.kcLabelWrapperClass, "subtitle")}>
|
||||
<span className="subtitle">
|
||||
<span className="required">*</span>
|
||||
{msg("requiredFields")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-md-10">
|
||||
<h1 id="kc-page-title">{headerNode}</h1>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<h1 id="kc-page-title">{headerNode}</h1>
|
||||
)
|
||||
) : displayRequiredFields ? (
|
||||
<div className={clsx(props.kcContentWrapperClass)}>
|
||||
<div className={clsx(props.kcLabelWrapperClass, "subtitle")}>
|
||||
<span className="subtitle">
|
||||
<span className="required">*</span> {msg("requiredFields")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-md-10">
|
||||
{showUsernameNode}
|
||||
<div className={clsx(props.kcFormGroupClass)}>
|
||||
<div id="kc-username">
|
||||
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
||||
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
||||
<div className="kc-login-tooltip">
|
||||
<i className={clsx(props.kcResetFlowIcon)}></i>
|
||||
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{showUsernameNode}
|
||||
<div className={clsx(props.kcFormGroupClass)}>
|
||||
<div id="kc-username">
|
||||
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
|
||||
<a id="reset-login" href={url.loginRestartFlowUrl}>
|
||||
<div className="kc-login-tooltip">
|
||||
<i className={clsx(props.kcResetFlowIcon)}></i>
|
||||
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</header>
|
||||
<div id="kc-content">
|
||||
<div id="kc-content-wrapper">
|
||||
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
||||
{displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
|
||||
<div className={clsx("alert", `alert-${message.type}`)}>
|
||||
{message.type === "success" && <span className={clsx(props.kcFeedbackSuccessIcon)}></span>}
|
||||
{message.type === "warning" && <span className={clsx(props.kcFeedbackWarningIcon)}></span>}
|
||||
{message.type === "error" && <span className={clsx(props.kcFeedbackErrorIcon)}></span>}
|
||||
{message.type === "info" && <span className={clsx(props.kcFeedbackInfoIcon)}></span>}
|
||||
<span
|
||||
className="kc-feedback-text"
|
||||
dangerouslySetInnerHTML={{
|
||||
"__html": message.summary
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{formNode}
|
||||
{auth !== undefined && auth.showTryAnotherWayLink && showAnotherWayIfPresent && (
|
||||
<form
|
||||
id="kc-select-try-another-way-form"
|
||||
action={url.loginAction}
|
||||
method="post"
|
||||
className={clsx(displayWide && props.kcContentWrapperClass)}
|
||||
>
|
||||
<div className={clsx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}>
|
||||
<div className={clsx(props.kcFormGroupClass)}>
|
||||
<input type="hidden" name="tryAnotherWay" value="on" />
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>
|
||||
{msg("doTryAnotherWay")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
{displayInfo && (
|
||||
<div id="kc-info" className={clsx(props.kcSignUpClass)}>
|
||||
<div id="kc-info-wrapper" className={clsx(props.kcInfoAreaWrapperClass)}>
|
||||
{infoNode}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,173 +1,178 @@
|
||||
import React, { memo, useEffect, Fragment } from "react";
|
||||
import type { KcProps } from "../KcProps";
|
||||
import React, { useEffect, Fragment } from "react";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { Attribute } from "../../getKcContext/KcContextBase";
|
||||
import { clsx } from "../../tools/clsx";
|
||||
import type { ReactComponent } from "../../tools/ReactComponent";
|
||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||
import { useCallbackFactory } from "../../tools/useCallbackFactory";
|
||||
import { useFormValidationSlice } from "../../useFormValidationSlice";
|
||||
import type { I18n } from "../../i18n";
|
||||
import type { I18nBase } from "../../i18n";
|
||||
import type { Param0 } from "tsafe/Param0";
|
||||
|
||||
export type UserProfileFormFieldsProps = {
|
||||
kcContext: Param0<typeof useFormValidationSlice>["kcContext"];
|
||||
i18n: I18n;
|
||||
i18n: I18nBase;
|
||||
} & KcProps &
|
||||
Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
|
||||
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
||||
};
|
||||
|
||||
export const UserProfileFormFields = memo(
|
||||
({ kcContext, onIsFormSubmittableValueChange, i18n, BeforeField, AfterField, ...props }: UserProfileFormFieldsProps) => {
|
||||
const { advancedMsg } = i18n;
|
||||
export function UserProfileFormFields({
|
||||
kcContext,
|
||||
onIsFormSubmittableValueChange,
|
||||
i18n,
|
||||
BeforeField,
|
||||
AfterField,
|
||||
...props
|
||||
}: UserProfileFormFieldsProps) {
|
||||
const { advancedMsg } = i18n;
|
||||
|
||||
const {
|
||||
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
||||
formValidationReducer,
|
||||
attributesWithPassword
|
||||
} = useFormValidationSlice({
|
||||
kcContext,
|
||||
i18n
|
||||
});
|
||||
const {
|
||||
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
||||
formValidationReducer,
|
||||
attributesWithPassword
|
||||
} = useFormValidationSlice({
|
||||
kcContext,
|
||||
i18n
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onIsFormSubmittableValueChange(isFormSubmittable);
|
||||
}, [isFormSubmittable]);
|
||||
useEffect(() => {
|
||||
onIsFormSubmittableValueChange(isFormSubmittable);
|
||||
}, [isFormSubmittable]);
|
||||
|
||||
const onChangeFactory = useCallbackFactory(
|
||||
(
|
||||
[name]: [string],
|
||||
[
|
||||
{
|
||||
target: { value }
|
||||
}
|
||||
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>]
|
||||
) =>
|
||||
formValidationReducer({
|
||||
"action": "update value",
|
||||
name,
|
||||
"newValue": value
|
||||
})
|
||||
);
|
||||
|
||||
const onBlurFactory = useCallbackFactory(([name]: [string]) =>
|
||||
const onChangeFactory = useCallbackFactory(
|
||||
(
|
||||
[name]: [string],
|
||||
[
|
||||
{
|
||||
target: { value }
|
||||
}
|
||||
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>]
|
||||
) =>
|
||||
formValidationReducer({
|
||||
"action": "focus lost",
|
||||
name
|
||||
"action": "update value",
|
||||
name,
|
||||
"newValue": value
|
||||
})
|
||||
);
|
||||
);
|
||||
|
||||
let currentGroup = "";
|
||||
const onBlurFactory = useCallbackFactory(([name]: [string]) =>
|
||||
formValidationReducer({
|
||||
"action": "focus lost",
|
||||
name
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{attributesWithPassword.map((attribute, i) => {
|
||||
const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
|
||||
let currentGroup = "";
|
||||
|
||||
const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
|
||||
return (
|
||||
<>
|
||||
{attributesWithPassword.map((attribute, i) => {
|
||||
const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
|
||||
|
||||
const formGroupClassName = clsx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
|
||||
const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
|
||||
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
{group !== currentGroup && (currentGroup = group) !== "" && (
|
||||
<div className={formGroupClassName}>
|
||||
<div className={clsx(props.kcContentWrapperClass)}>
|
||||
<label id={`header-${group}`} className={clsx(props.kcFormGroupHeader)}>
|
||||
{advancedMsg(groupDisplayHeader) || currentGroup}
|
||||
const formGroupClassName = clsx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
|
||||
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
{group !== currentGroup && (currentGroup = group) !== "" && (
|
||||
<div className={formGroupClassName}>
|
||||
<div className={clsx(props.kcContentWrapperClass)}>
|
||||
<label id={`header-${group}`} className={clsx(props.kcFormGroupHeader)}>
|
||||
{advancedMsg(groupDisplayHeader) || currentGroup}
|
||||
</label>
|
||||
</div>
|
||||
{groupDisplayDescription !== "" && (
|
||||
<div className={clsx(props.kcLabelWrapperClass)}>
|
||||
<label id={`description-${group}`} className={`${clsx(props.kcLabelClass)}`}>
|
||||
{advancedMsg(groupDisplayDescription)}
|
||||
</label>
|
||||
</div>
|
||||
{groupDisplayDescription !== "" && (
|
||||
<div className={clsx(props.kcLabelWrapperClass)}>
|
||||
<label id={`description-${group}`} className={`${clsx(props.kcLabelClass)}`}>
|
||||
{advancedMsg(groupDisplayDescription)}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{BeforeField && <BeforeField attribute={attribute} />}
|
||||
{BeforeField && <BeforeField attribute={attribute} />}
|
||||
|
||||
<div className={formGroupClassName}>
|
||||
<div className={clsx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor={attribute.name} className={clsx(props.kcLabelClass)}>
|
||||
{advancedMsg(attribute.displayName ?? "")}
|
||||
</label>
|
||||
{attribute.required && <>*</>}
|
||||
</div>
|
||||
<div className={clsx(props.kcInputWrapperClass)}>
|
||||
{(() => {
|
||||
const { options } = attribute.validators;
|
||||
|
||||
if (options !== undefined) {
|
||||
return (
|
||||
<select
|
||||
id={attribute.name}
|
||||
name={attribute.name}
|
||||
onChange={onChangeFactory(attribute.name)}
|
||||
onBlur={onBlurFactory(attribute.name)}
|
||||
value={value}
|
||||
>
|
||||
{options.options.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
<div className={formGroupClassName}>
|
||||
<div className={clsx(props.kcLabelWrapperClass)}>
|
||||
<label htmlFor={attribute.name} className={clsx(props.kcLabelClass)}>
|
||||
{advancedMsg(attribute.displayName ?? "")}
|
||||
</label>
|
||||
{attribute.required && <>*</>}
|
||||
</div>
|
||||
<div className={clsx(props.kcInputWrapperClass)}>
|
||||
{(() => {
|
||||
const { options } = attribute.validators;
|
||||
|
||||
if (options !== undefined) {
|
||||
return (
|
||||
<input
|
||||
type={(() => {
|
||||
switch (attribute.name) {
|
||||
case "password-confirm":
|
||||
case "password":
|
||||
return "password";
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
})()}
|
||||
<select
|
||||
id={attribute.name}
|
||||
name={attribute.name}
|
||||
value={value}
|
||||
onChange={onChangeFactory(attribute.name)}
|
||||
className={clsx(props.kcInputClass)}
|
||||
aria-invalid={displayableErrors.length !== 0}
|
||||
disabled={attribute.readOnly}
|
||||
autoComplete={attribute.autocomplete}
|
||||
onBlur={onBlurFactory(attribute.name)}
|
||||
/>
|
||||
value={value}
|
||||
>
|
||||
{options.options.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type={(() => {
|
||||
switch (attribute.name) {
|
||||
case "password-confirm":
|
||||
case "password":
|
||||
return "password";
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
})()}
|
||||
id={attribute.name}
|
||||
name={attribute.name}
|
||||
value={value}
|
||||
onChange={onChangeFactory(attribute.name)}
|
||||
className={clsx(props.kcInputClass)}
|
||||
aria-invalid={displayableErrors.length !== 0}
|
||||
disabled={attribute.readOnly}
|
||||
autoComplete={attribute.autocomplete}
|
||||
onBlur={onBlurFactory(attribute.name)}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
{displayableErrors.length !== 0 &&
|
||||
(() => {
|
||||
const divId = `input-error-${attribute.name}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`#${divId} > span: { display: block; }`}</style>
|
||||
<span
|
||||
id={divId}
|
||||
className={clsx(props.kcInputErrorMessageClass)}
|
||||
style={{
|
||||
"position": displayableErrors.length === 1 ? "absolute" : undefined
|
||||
}}
|
||||
aria-live="polite"
|
||||
>
|
||||
{displayableErrors.map(({ errorMessage }) => errorMessage)}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
{displayableErrors.length !== 0 &&
|
||||
(() => {
|
||||
const divId = `input-error-${attribute.name}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`#${divId} > span: { display: block; }`}</style>
|
||||
<span
|
||||
id={divId}
|
||||
className={clsx(props.kcInputErrorMessageClass)}
|
||||
style={{
|
||||
"position": displayableErrors.length === 1 ? "absolute" : undefined
|
||||
}}
|
||||
aria-live="polite"
|
||||
>
|
||||
{displayableErrors.map(({ errorMessage }) => errorMessage)}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{AfterField && <AfterField attribute={attribute} />}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
{AfterField && <AfterField attribute={attribute} />}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import type { PageId } from "../../bin/keycloakify/generateFtl";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
import type { MessageKeyBase } from "../i18n";
|
||||
import type { KcTemplateClassKey } from "../components/KcProps";
|
||||
import type { KcTemplateClassKey } from "../components/shared/KcProps";
|
||||
|
||||
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||
|
||||
|
292
src/lib/i18n/i18n.tsx
Normal file
292
src/lib/i18n/i18n.tsx
Normal file
@ -0,0 +1,292 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import type baseMessages from "./generated_messages/18.0.1/login/en";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { Markdown } from "../tools/Markdown";
|
||||
|
||||
export const fallbackLanguageTag = "en";
|
||||
|
||||
export type KcContextLike = {
|
||||
locale?: {
|
||||
currentLanguageTag: string;
|
||||
supported: { languageTag: string; url: string; label: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
assert<KcContextBase extends KcContextLike ? true : false>();
|
||||
|
||||
export type MessageKeyBase = keyof typeof baseMessages | keyof typeof keycloakifyExtraMessages[typeof fallbackLanguageTag];
|
||||
|
||||
export type I18n<MessageKey extends string> = {
|
||||
/**
|
||||
* e.g: "en", "fr", "zh-CN"
|
||||
*
|
||||
* The current language
|
||||
*/
|
||||
currentLanguageTag: string;
|
||||
/**
|
||||
* To call when the user switch language.
|
||||
* This will cause the page to be reloaded,
|
||||
* on next load currentLanguageTag === newLanguageTag
|
||||
*/
|
||||
changeLocale: (newLanguageTag: string) => never;
|
||||
/**
|
||||
* e.g. "en" => "English", "fr" => "Français", ...
|
||||
*
|
||||
* Used to render a select that enable user to switch language.
|
||||
* ex: https://user-images.githubusercontent.com/6702424/186044799-38801eec-4e89-483b-81dd-8e9233e8c0eb.png
|
||||
* */
|
||||
labelBySupportedLanguageTag: Record<string, string>;
|
||||
/**
|
||||
* Examples assuming currentLanguageTag === "en"
|
||||
*
|
||||
* msg("access-denied") === <span>Access denied</span>
|
||||
* msg("impersonateTitleHtml", "Foo") === <span><strong>Foo</strong> Impersonate User</span>
|
||||
*/
|
||||
msg: (key: MessageKey, ...args: (string | undefined)[]) => JSX.Element;
|
||||
/**
|
||||
* It's the same thing as msg() but instead of returning a JSX.Element it returns a string.
|
||||
* It can be more convenient to manipulate strings but if there are HTML tags it wont render.
|
||||
* msgStr("impersonateTitleHtml", "Foo") === "<strong>Foo</strong> Impersonate User"
|
||||
*/
|
||||
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
||||
/**
|
||||
* Examples assuming currentLanguageTag === "en"
|
||||
* advancedMsg("${access-denied} foo bar") === <span>${msgStr("access-denied")} foo bar<span> === <span>Access denied foo bar</span>
|
||||
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied") === <span>Access denied</span>
|
||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === <span>not-a-message-key</span>
|
||||
*/
|
||||
advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element;
|
||||
/**
|
||||
* Examples assuming currentLanguageTag === "en"
|
||||
* advancedMsg("${access-denied} foo bar") === msg("access-denied") + " foo bar" === "Access denied foo bar"
|
||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
||||
*/
|
||||
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
||||
};
|
||||
|
||||
export type I18nBase = I18n<MessageKeyBase>;
|
||||
|
||||
export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params: {
|
||||
kcContext: KcContextLike;
|
||||
extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } };
|
||||
doSkip: boolean;
|
||||
}): I18n<MessageKeyBase | ExtraMessageKey> | null {
|
||||
const { kcContext, extraMessages, doSkip } = params;
|
||||
|
||||
const [i18n, setI18n] = useState<I18n<ExtraMessageKey | MessageKeyBase> | undefined>(undefined);
|
||||
|
||||
const refHasStartedFetching = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (doSkip || refHasStartedFetching.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
refHasStartedFetching.current = true;
|
||||
|
||||
(async () => {
|
||||
const { currentLanguageTag = fallbackLanguageTag } = kcContext.locale ?? {};
|
||||
|
||||
const [fallbackMessages, messages] = await Promise.all([
|
||||
import("./generated_messages/18.0.1/login/en"),
|
||||
(() => {
|
||||
switch (currentLanguageTag) {
|
||||
case "ca":
|
||||
return import("./generated_messages/18.0.1/login/ca");
|
||||
case "cs":
|
||||
return import("./generated_messages/18.0.1/login/cs");
|
||||
case "da":
|
||||
return import("./generated_messages/18.0.1/login/da");
|
||||
case "de":
|
||||
return import("./generated_messages/18.0.1/login/de");
|
||||
case "en":
|
||||
return import("./generated_messages/18.0.1/login/en");
|
||||
case "es":
|
||||
return import("./generated_messages/18.0.1/login/es");
|
||||
case "fi":
|
||||
return import("./generated_messages/18.0.1/login/fi");
|
||||
case "fr":
|
||||
return import("./generated_messages/18.0.1/login/fr");
|
||||
case "hu":
|
||||
return import("./generated_messages/18.0.1/login/hu");
|
||||
case "it":
|
||||
return import("./generated_messages/18.0.1/login/it");
|
||||
case "ja":
|
||||
return import("./generated_messages/18.0.1/login/ja");
|
||||
case "lt":
|
||||
return import("./generated_messages/18.0.1/login/lt");
|
||||
case "lv":
|
||||
return import("./generated_messages/18.0.1/login/lv");
|
||||
case "nl":
|
||||
return import("./generated_messages/18.0.1/login/nl");
|
||||
case "no":
|
||||
return import("./generated_messages/18.0.1/login/no");
|
||||
case "pl":
|
||||
return import("./generated_messages/18.0.1/login/pl");
|
||||
case "pt-BR":
|
||||
return import("./generated_messages/18.0.1/login/pt-BR");
|
||||
case "ru":
|
||||
return import("./generated_messages/18.0.1/login/ru");
|
||||
case "sk":
|
||||
return import("./generated_messages/18.0.1/login/sk");
|
||||
case "sv":
|
||||
return import("./generated_messages/18.0.1/login/sv");
|
||||
case "tr":
|
||||
return import("./generated_messages/18.0.1/login/tr");
|
||||
case "zh-CN":
|
||||
return import("./generated_messages/18.0.1/login/zh-CN");
|
||||
default:
|
||||
return { "default": {} };
|
||||
}
|
||||
})()
|
||||
]).then(modules => modules.map(module => module.default));
|
||||
|
||||
setI18n({
|
||||
...createI18nTranslationFunctions({
|
||||
"fallbackMessages": {
|
||||
...fallbackMessages,
|
||||
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
|
||||
...(extraMessages[fallbackLanguageTag] ?? {})
|
||||
} as any,
|
||||
"messages": {
|
||||
...messages,
|
||||
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
|
||||
...(extraMessages[currentLanguageTag] ?? {})
|
||||
} as any
|
||||
}),
|
||||
currentLanguageTag,
|
||||
"changeLocale": 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`);
|
||||
|
||||
window.location.href = targetSupportedLocale.url;
|
||||
|
||||
assert(false, "never");
|
||||
},
|
||||
"labelBySupportedLanguageTag": Object.fromEntries(
|
||||
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
|
||||
)
|
||||
});
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return i18n ?? null;
|
||||
}
|
||||
|
||||
const useI18n_private = __unsafe_useI18n;
|
||||
|
||||
export function useI18n<ExtraMessageKey extends string = never>(params: {
|
||||
kcContext: KcContextLike;
|
||||
extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } };
|
||||
}): I18n<MessageKeyBase | ExtraMessageKey> | null {
|
||||
return useI18n_private({
|
||||
...params,
|
||||
"doSkip": false
|
||||
});
|
||||
}
|
||||
|
||||
function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
fallbackMessages: Record<MessageKey, string>;
|
||||
messages: Record<MessageKey, string>;
|
||||
}): Pick<I18n<MessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
|
||||
const { fallbackMessages, messages } = params;
|
||||
|
||||
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderMarkdown: boolean }): string | JSX.Element | undefined {
|
||||
const { key, args, doRenderMarkdown } = props;
|
||||
|
||||
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (fallbackMessages as any)[key];
|
||||
|
||||
if (messageOrUndefined === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const message = messageOrUndefined;
|
||||
|
||||
const messageWithArgsInjectedIfAny = (() => {
|
||||
const startIndex = message
|
||||
.match(/{[0-9]+}/g)
|
||||
?.map(g => g.match(/{([0-9]+)}/)![1])
|
||||
.map(indexStr => parseInt(indexStr))
|
||||
.sort((a, b) => a - b)[0];
|
||||
|
||||
if (startIndex === undefined) {
|
||||
// No {0} in message (no arguments expected)
|
||||
return message;
|
||||
}
|
||||
|
||||
let messageWithArgsInjected = message;
|
||||
|
||||
args.forEach((arg, i) => {
|
||||
if (arg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageWithArgsInjected = messageWithArgsInjected.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
|
||||
});
|
||||
|
||||
return messageWithArgsInjected;
|
||||
})();
|
||||
|
||||
return doRenderMarkdown ? (
|
||||
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}>
|
||||
{messageWithArgsInjectedIfAny}
|
||||
</Markdown>
|
||||
) : (
|
||||
messageWithArgsInjectedIfAny
|
||||
);
|
||||
}
|
||||
|
||||
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderMarkdown: boolean }): JSX.Element | string {
|
||||
const { key, args, doRenderMarkdown } = props;
|
||||
|
||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
||||
|
||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||
|
||||
const out = resolveMsg({
|
||||
"key": keyUnwrappedFromCurlyBraces,
|
||||
args,
|
||||
doRenderMarkdown
|
||||
});
|
||||
|
||||
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
||||
}
|
||||
|
||||
return {
|
||||
"msgStr": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": false }) as string,
|
||||
"msg": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsg": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsgStr": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": false }) as string
|
||||
};
|
||||
}
|
||||
|
||||
const keycloakifyExtraMessages = {
|
||||
"en": {
|
||||
"shouldBeEqual": "{0} should be equal to {1}",
|
||||
"shouldBeDifferent": "{0} should be different to {1}",
|
||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||
"mustBeAnInteger": "Must be an integer",
|
||||
"notAValidOption": "Not a valid option"
|
||||
},
|
||||
"fr": {
|
||||
/* spell-checker: disable */
|
||||
"shouldBeEqual": "{0} doit être égal à {1}",
|
||||
"shouldBeDifferent": "{0} doit être différent de {1}",
|
||||
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
||||
"mustBeAnInteger": "Doit être un nombre entier",
|
||||
"notAValidOption": "N'est pas une option valide",
|
||||
|
||||
"logoutConfirmTitle": "Déconnexion",
|
||||
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
||||
"doLogout": "Se déconnecter"
|
||||
/* spell-checker: enable */
|
||||
}
|
||||
};
|
@ -1,290 +1 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import type baseMessages from "./generated_messages/18.0.1/login/en";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { Markdown } from "../tools/Markdown";
|
||||
|
||||
export const fallbackLanguageTag = "en";
|
||||
|
||||
export type KcContextLike = {
|
||||
locale?: {
|
||||
currentLanguageTag: string;
|
||||
supported: { languageTag: string; url: string; label: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
assert<KcContextBase extends KcContextLike ? true : false>();
|
||||
|
||||
export type MessageKeyBase = keyof typeof baseMessages | keyof typeof keycloakifyExtraMessages[typeof fallbackLanguageTag];
|
||||
|
||||
export type I18n<MessageKey extends string = MessageKeyBase> = {
|
||||
/**
|
||||
* e.g: "en", "fr", "zh-CN"
|
||||
*
|
||||
* The current language
|
||||
*/
|
||||
currentLanguageTag: string;
|
||||
/**
|
||||
* To call when the user switch language.
|
||||
* This will cause the page to be reloaded,
|
||||
* on next load currentLanguageTag === newLanguageTag
|
||||
*/
|
||||
changeLocale: (newLanguageTag: string) => never;
|
||||
/**
|
||||
* e.g. "en" => "English", "fr" => "Français", ...
|
||||
*
|
||||
* Used to render a select that enable user to switch language.
|
||||
* ex: https://user-images.githubusercontent.com/6702424/186044799-38801eec-4e89-483b-81dd-8e9233e8c0eb.png
|
||||
* */
|
||||
labelBySupportedLanguageTag: Record<string, string>;
|
||||
/**
|
||||
* Examples assuming currentLanguageTag === "en"
|
||||
*
|
||||
* msg("access-denied") === <span>Access denied</span>
|
||||
* msg("impersonateTitleHtml", "Foo") === <span><strong>Foo</strong> Impersonate User</span>
|
||||
*/
|
||||
msg: (key: MessageKey, ...args: (string | undefined)[]) => JSX.Element;
|
||||
/**
|
||||
* It's the same thing as msg() but instead of returning a JSX.Element it returns a string.
|
||||
* It can be more convenient to manipulate strings but if there are HTML tags it wont render.
|
||||
* msgStr("impersonateTitleHtml", "Foo") === "<strong>Foo</strong> Impersonate User"
|
||||
*/
|
||||
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
||||
/**
|
||||
* Examples assuming currentLanguageTag === "en"
|
||||
* advancedMsg("${access-denied} foo bar") === <span>${msgStr("access-denied")} foo bar<span> === <span>Access denied foo bar</span>
|
||||
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied") === <span>Access denied</span>
|
||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === <span>not-a-message-key</span>
|
||||
*/
|
||||
advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element;
|
||||
/**
|
||||
* Examples assuming currentLanguageTag === "en"
|
||||
* advancedMsg("${access-denied} foo bar") === msg("access-denied") + " foo bar" === "Access denied foo bar"
|
||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
||||
*/
|
||||
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
||||
};
|
||||
|
||||
export function __unsafe_useI18n<ExtraMessageKey extends string = never>(params: {
|
||||
kcContext: KcContextLike;
|
||||
extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } };
|
||||
doSkip: boolean;
|
||||
}): I18n<MessageKeyBase | ExtraMessageKey> | null {
|
||||
const { kcContext, extraMessages, doSkip } = params;
|
||||
|
||||
const [i18n, setI18n] = useState<I18n<ExtraMessageKey | MessageKeyBase> | undefined>(undefined);
|
||||
|
||||
const refHasStartedFetching = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (doSkip || refHasStartedFetching.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
refHasStartedFetching.current = true;
|
||||
|
||||
(async () => {
|
||||
const { currentLanguageTag = fallbackLanguageTag } = kcContext.locale ?? {};
|
||||
|
||||
const [fallbackMessages, messages] = await Promise.all([
|
||||
import("./generated_messages/18.0.1/login/en"),
|
||||
(() => {
|
||||
switch (currentLanguageTag) {
|
||||
case "ca":
|
||||
return import("./generated_messages/18.0.1/login/ca");
|
||||
case "cs":
|
||||
return import("./generated_messages/18.0.1/login/cs");
|
||||
case "da":
|
||||
return import("./generated_messages/18.0.1/login/da");
|
||||
case "de":
|
||||
return import("./generated_messages/18.0.1/login/de");
|
||||
case "en":
|
||||
return import("./generated_messages/18.0.1/login/en");
|
||||
case "es":
|
||||
return import("./generated_messages/18.0.1/login/es");
|
||||
case "fi":
|
||||
return import("./generated_messages/18.0.1/login/fi");
|
||||
case "fr":
|
||||
return import("./generated_messages/18.0.1/login/fr");
|
||||
case "hu":
|
||||
return import("./generated_messages/18.0.1/login/hu");
|
||||
case "it":
|
||||
return import("./generated_messages/18.0.1/login/it");
|
||||
case "ja":
|
||||
return import("./generated_messages/18.0.1/login/ja");
|
||||
case "lt":
|
||||
return import("./generated_messages/18.0.1/login/lt");
|
||||
case "lv":
|
||||
return import("./generated_messages/18.0.1/login/lv");
|
||||
case "nl":
|
||||
return import("./generated_messages/18.0.1/login/nl");
|
||||
case "no":
|
||||
return import("./generated_messages/18.0.1/login/no");
|
||||
case "pl":
|
||||
return import("./generated_messages/18.0.1/login/pl");
|
||||
case "pt-BR":
|
||||
return import("./generated_messages/18.0.1/login/pt-BR");
|
||||
case "ru":
|
||||
return import("./generated_messages/18.0.1/login/ru");
|
||||
case "sk":
|
||||
return import("./generated_messages/18.0.1/login/sk");
|
||||
case "sv":
|
||||
return import("./generated_messages/18.0.1/login/sv");
|
||||
case "tr":
|
||||
return import("./generated_messages/18.0.1/login/tr");
|
||||
case "zh-CN":
|
||||
return import("./generated_messages/18.0.1/login/zh-CN");
|
||||
default:
|
||||
return { "default": {} };
|
||||
}
|
||||
})()
|
||||
]).then(modules => modules.map(module => module.default));
|
||||
|
||||
setI18n({
|
||||
...createI18nTranslationFunctions({
|
||||
"fallbackMessages": {
|
||||
...fallbackMessages,
|
||||
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
|
||||
...(extraMessages[fallbackLanguageTag] ?? {})
|
||||
} as any,
|
||||
"messages": {
|
||||
...messages,
|
||||
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
|
||||
...(extraMessages[currentLanguageTag] ?? {})
|
||||
} as any
|
||||
}),
|
||||
currentLanguageTag,
|
||||
"changeLocale": 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`);
|
||||
|
||||
window.location.href = targetSupportedLocale.url;
|
||||
|
||||
assert(false, "never");
|
||||
},
|
||||
"labelBySupportedLanguageTag": Object.fromEntries(
|
||||
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
|
||||
)
|
||||
});
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return i18n ?? null;
|
||||
}
|
||||
|
||||
const useI18n_private = __unsafe_useI18n;
|
||||
|
||||
export function useI18n<ExtraMessageKey extends string = never>(params: {
|
||||
kcContext: KcContextLike;
|
||||
extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } };
|
||||
}): I18n<MessageKeyBase | ExtraMessageKey> | null {
|
||||
return useI18n_private({
|
||||
...params,
|
||||
"doSkip": false
|
||||
});
|
||||
}
|
||||
|
||||
function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
fallbackMessages: Record<MessageKey, string>;
|
||||
messages: Record<MessageKey, string>;
|
||||
}): Pick<I18n<MessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
|
||||
const { fallbackMessages, messages } = params;
|
||||
|
||||
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderMarkdown: boolean }): string | JSX.Element | undefined {
|
||||
const { key, args, doRenderMarkdown } = props;
|
||||
|
||||
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (fallbackMessages as any)[key];
|
||||
|
||||
if (messageOrUndefined === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const message = messageOrUndefined;
|
||||
|
||||
const messageWithArgsInjectedIfAny = (() => {
|
||||
const startIndex = message
|
||||
.match(/{[0-9]+}/g)
|
||||
?.map(g => g.match(/{([0-9]+)}/)![1])
|
||||
.map(indexStr => parseInt(indexStr))
|
||||
.sort((a, b) => a - b)[0];
|
||||
|
||||
if (startIndex === undefined) {
|
||||
// No {0} in message (no arguments expected)
|
||||
return message;
|
||||
}
|
||||
|
||||
let messageWithArgsInjected = message;
|
||||
|
||||
args.forEach((arg, i) => {
|
||||
if (arg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageWithArgsInjected = messageWithArgsInjected.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
|
||||
});
|
||||
|
||||
return messageWithArgsInjected;
|
||||
})();
|
||||
|
||||
return doRenderMarkdown ? (
|
||||
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}>
|
||||
{messageWithArgsInjectedIfAny}
|
||||
</Markdown>
|
||||
) : (
|
||||
messageWithArgsInjectedIfAny
|
||||
);
|
||||
}
|
||||
|
||||
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderMarkdown: boolean }): JSX.Element | string {
|
||||
const { key, args, doRenderMarkdown } = props;
|
||||
|
||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
||||
|
||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||
|
||||
const out = resolveMsg({
|
||||
"key": keyUnwrappedFromCurlyBraces,
|
||||
args,
|
||||
doRenderMarkdown
|
||||
});
|
||||
|
||||
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
||||
}
|
||||
|
||||
return {
|
||||
"msgStr": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": false }) as string,
|
||||
"msg": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsg": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsgStr": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": false }) as string
|
||||
};
|
||||
}
|
||||
|
||||
const keycloakifyExtraMessages = {
|
||||
"en": {
|
||||
"shouldBeEqual": "{0} should be equal to {1}",
|
||||
"shouldBeDifferent": "{0} should be different to {1}",
|
||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||
"mustBeAnInteger": "Must be an integer",
|
||||
"notAValidOption": "Not a valid option"
|
||||
},
|
||||
"fr": {
|
||||
/* spell-checker: disable */
|
||||
"shouldBeEqual": "{0} doit être égal à {1}",
|
||||
"shouldBeDifferent": "{0} doit être différent de {1}",
|
||||
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
||||
"mustBeAnInteger": "Doit être un nombre entier",
|
||||
"notAValidOption": "N'est pas une option valide",
|
||||
|
||||
"logoutConfirmTitle": "Déconnexion",
|
||||
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
||||
"doLogout": "Se déconnecter"
|
||||
/* spell-checker: enable */
|
||||
}
|
||||
};
|
||||
export * from "./i18n";
|
||||
|
@ -4,7 +4,7 @@ export * from "./i18n";
|
||||
|
||||
export { useDownloadTerms } from "./components/Terms";
|
||||
|
||||
export * from "./components/KcProps";
|
||||
export * from "./components/shared/KcProps";
|
||||
export * from "./keycloakJsAdapter";
|
||||
export * from "./useFormValidationSlice";
|
||||
|
||||
|
@ -1,7 +1,44 @@
|
||||
import { classnames } from "tss-react/tools/classnames";
|
||||
import type { Cx } from "tss-react";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { typeGuard } from "tsafe/typeGuard";
|
||||
|
||||
/** Drop in replacement for https://www.npmjs.com/package/clsx */
|
||||
export const clsx: Cx = (...args) => {
|
||||
return classnames(args);
|
||||
export type CxArg = undefined | null | string | boolean | Partial<Record<string, boolean | null | undefined>> | readonly CxArg[];
|
||||
|
||||
export const clsx = (...args: CxArg[]): string => {
|
||||
const len = args.length;
|
||||
let i = 0;
|
||||
let cls = "";
|
||||
for (; i < len; i++) {
|
||||
const arg = args[i];
|
||||
if (arg == null) continue;
|
||||
|
||||
let toAdd;
|
||||
switch (typeof arg) {
|
||||
case "boolean":
|
||||
break;
|
||||
case "object": {
|
||||
if (Array.isArray(arg)) {
|
||||
toAdd = clsx(arg);
|
||||
} else {
|
||||
assert(!typeGuard<{ length: number }>(arg, false));
|
||||
|
||||
toAdd = "";
|
||||
for (const k in arg) {
|
||||
if (arg[k as string] && k) {
|
||||
toAdd && (toAdd += " ");
|
||||
toAdd += k;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
toAdd = arg;
|
||||
}
|
||||
}
|
||||
if (toAdd) {
|
||||
cls && (cls += " ");
|
||||
cls += toAdd;
|
||||
}
|
||||
}
|
||||
return cls;
|
||||
};
|
||||
|
55
src/lib/tools/memoize.ts
Normal file
55
src/lib/tools/memoize.ts
Normal file
@ -0,0 +1,55 @@
|
||||
type SimpleType = number | string | boolean | null | undefined;
|
||||
type FuncWithSimpleParams<T extends SimpleType[], R> = (...args: T) => R;
|
||||
|
||||
export function memoize<T extends SimpleType[], R>(
|
||||
fn: FuncWithSimpleParams<T, R>,
|
||||
options?: {
|
||||
argsLength?: number;
|
||||
max?: number;
|
||||
}
|
||||
): FuncWithSimpleParams<T, R> {
|
||||
const cache = new Map<string, ReturnType<FuncWithSimpleParams<T, R>>>();
|
||||
|
||||
const { argsLength = fn.length, max = Infinity } = options ?? {};
|
||||
|
||||
return ((...args: Parameters<FuncWithSimpleParams<T, R>>) => {
|
||||
const key = JSON.stringify(
|
||||
args
|
||||
.slice(0, argsLength)
|
||||
.map(v => {
|
||||
if (v === null) {
|
||||
return "null";
|
||||
}
|
||||
if (v === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
switch (typeof v) {
|
||||
case "number":
|
||||
return `number-${v}`;
|
||||
case "string":
|
||||
return `string-${v}`;
|
||||
case "boolean":
|
||||
return `boolean-${v ? "true" : "false"}`;
|
||||
}
|
||||
})
|
||||
.join("-sIs9sAslOdeWlEdIos3-")
|
||||
);
|
||||
|
||||
if (cache.has(key)) {
|
||||
return cache.get(key);
|
||||
}
|
||||
|
||||
if (max === cache.size) {
|
||||
for (const key of cache.keys()) {
|
||||
cache.delete(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const value = fn(...args);
|
||||
|
||||
cache.set(key, value);
|
||||
|
||||
return value;
|
||||
}) as any;
|
||||
}
|
45
src/lib/tools/useCallbackFactory.ts
Normal file
45
src/lib/tools/useCallbackFactory.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { id } from "tsafe/id";
|
||||
import { memoize } from "./memoize";
|
||||
|
||||
export type CallbackFactory<FactoryArgs extends unknown[], Args extends unknown[], R> = (...factoryArgs: FactoryArgs) => (...args: Args) => R;
|
||||
|
||||
/**
|
||||
* https://docs.powerhooks.dev/api-reference/usecallbackfactory
|
||||
*
|
||||
* const callbackFactory= useCallbackFactory(
|
||||
* ([key]: [string], [params]: [{ foo: number; }]) => {
|
||||
* ...
|
||||
* },
|
||||
* []
|
||||
* );
|
||||
*
|
||||
* WARNING: Factory args should not be of variable length.
|
||||
*
|
||||
*/
|
||||
export function useCallbackFactory<FactoryArgs extends (string | number | boolean)[], Args extends unknown[], R = void>(
|
||||
callback: (...callbackArgs: [FactoryArgs, Args]) => R
|
||||
): CallbackFactory<FactoryArgs, Args, R> {
|
||||
type Out = CallbackFactory<FactoryArgs, Args, R>;
|
||||
|
||||
const callbackRef = useRef<typeof callback>(callback);
|
||||
|
||||
callbackRef.current = callback;
|
||||
|
||||
const memoizedRef = useRef<Out | undefined>(undefined);
|
||||
|
||||
return useState(() =>
|
||||
id<Out>((...factoryArgs) => {
|
||||
if (memoizedRef.current === undefined) {
|
||||
memoizedRef.current = memoize(
|
||||
(...factoryArgs: FactoryArgs) =>
|
||||
(...args: Args) =>
|
||||
callbackRef.current(factoryArgs, args),
|
||||
{ "argsLength": factoryArgs.length }
|
||||
);
|
||||
}
|
||||
|
||||
return memoizedRef.current(...factoryArgs);
|
||||
})
|
||||
)[0];
|
||||
}
|
10
src/lib/tools/useConst.ts
Normal file
10
src/lib/tools/useConst.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useState } from "react";
|
||||
|
||||
/**
|
||||
* Compute a value on first render and never again,
|
||||
* Equivalent of const [x] = useState(()=> ...)
|
||||
*/
|
||||
export function useConst<T>(getValue: () => T): T {
|
||||
const [value] = useState(getValue);
|
||||
return value;
|
||||
}
|
15
src/lib/tools/useConstCallback.ts
Normal file
15
src/lib/tools/useConstCallback.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { Parameters } from "tsafe/Parameters";
|
||||
|
||||
/** https://stackoverflow.com/questions/65890278/why-cant-usecallback-always-return-the-same-ref */
|
||||
export function useConstCallback<T extends ((...args: any[]) => unknown) | undefined | null>(callback: NonNullable<T>): T {
|
||||
const callbackRef = useRef<typeof callback>(null as any);
|
||||
|
||||
callbackRef.current = callback;
|
||||
|
||||
return useState(
|
||||
() =>
|
||||
(...args: Parameters<T>) =>
|
||||
callbackRef.current(...args)
|
||||
)[0] as T;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import "./tools/Array.prototype.every";
|
||||
import React, { useMemo, useReducer, Fragment } from "react";
|
||||
import type { KcContextBase, Validators, Attribute } from "./getKcContext/KcContextBase";
|
||||
import type { I18n, MessageKeyBase } from "./i18n";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import type { I18nBase, MessageKeyBase } from "./i18n";
|
||||
import { useConstCallback } from "./tools/useConstCallback";
|
||||
import { id } from "tsafe/id";
|
||||
import { emailRegexp } from "./tools/emailRegExp";
|
||||
|
||||
@ -14,7 +14,7 @@ export function useGetErrors(params: {
|
||||
attributes: { name: string; value?: string; validators: Validators }[];
|
||||
};
|
||||
};
|
||||
i18n: I18n;
|
||||
i18n: I18nBase;
|
||||
}) {
|
||||
const { kcContext, i18n } = params;
|
||||
|
||||
@ -319,7 +319,7 @@ export function useFormValidationSlice(params: {
|
||||
};
|
||||
/** NOTE: Try to avoid passing a new ref every render for better performances. */
|
||||
passwordValidators?: Validators;
|
||||
i18n: I18n;
|
||||
i18n: I18nBase;
|
||||
}) {
|
||||
const {
|
||||
kcContext,
|
||||
|
Reference in New Issue
Block a user