New i18n with dynamic imports
This commit is contained in:
@ -2,10 +2,10 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error } & KcProps) => {
|
||||
const { msg } = getMsg(kcContext);
|
||||
const { msg } = useI18n();
|
||||
|
||||
const { message, client } = kcContext;
|
||||
|
||||
|
@ -3,10 +3,10 @@ import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import { assert } from "../tools/assert";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info } & KcProps) => {
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
assert(kcContext.message !== undefined);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { lazy, memo, Suspense } from "react";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import { I18nProvider } from "../i18n";
|
||||
|
||||
const Login = lazy(() => import("./Login"));
|
||||
const Register = lazy(() => import("./Register"));
|
||||
@ -21,44 +22,46 @@ const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
|
||||
|
||||
const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase } & KcProps) => {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
{(() => {
|
||||
switch (kcContext.pageId) {
|
||||
case "login.ftl":
|
||||
return <Login {...{ kcContext, ...props }} />;
|
||||
case "register.ftl":
|
||||
return <Register {...{ kcContext, ...props }} />;
|
||||
case "register-user-profile.ftl":
|
||||
return <RegisterUserProfile {...{ kcContext, ...props }} />;
|
||||
case "info.ftl":
|
||||
return <Info {...{ kcContext, ...props }} />;
|
||||
case "error.ftl":
|
||||
return <Error {...{ kcContext, ...props }} />;
|
||||
case "login-reset-password.ftl":
|
||||
return <LoginResetPassword {...{ kcContext, ...props }} />;
|
||||
case "login-verify-email.ftl":
|
||||
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
|
||||
case "terms.ftl":
|
||||
return <Terms {...{ kcContext, ...props }} />;
|
||||
case "login-otp.ftl":
|
||||
return <LoginOtp {...{ kcContext, ...props }} />;
|
||||
case "login-update-password.ftl":
|
||||
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
|
||||
case "login-update-profile.ftl":
|
||||
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-confirm.ftl":
|
||||
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-email.ftl":
|
||||
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
|
||||
case "login-page-expired.ftl":
|
||||
return <LoginPageExpired {...{ kcContext, ...props }} />;
|
||||
case "login-config-totp.ftl":
|
||||
return <LoginConfigTotp {...{ kcContext, ...props }} />;
|
||||
case "logout-confirm.ftl":
|
||||
return <LogoutConfirm {...{ kcContext, ...props }} />;
|
||||
}
|
||||
})()}
|
||||
</Suspense>
|
||||
<I18nProvider kcContext={kcContext}>
|
||||
<Suspense>
|
||||
{(() => {
|
||||
switch (kcContext.pageId) {
|
||||
case "login.ftl":
|
||||
return <Login {...{ kcContext, ...props }} />;
|
||||
case "register.ftl":
|
||||
return <Register {...{ kcContext, ...props }} />;
|
||||
case "register-user-profile.ftl":
|
||||
return <RegisterUserProfile {...{ kcContext, ...props }} />;
|
||||
case "info.ftl":
|
||||
return <Info {...{ kcContext, ...props }} />;
|
||||
case "error.ftl":
|
||||
return <Error {...{ kcContext, ...props }} />;
|
||||
case "login-reset-password.ftl":
|
||||
return <LoginResetPassword {...{ kcContext, ...props }} />;
|
||||
case "login-verify-email.ftl":
|
||||
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
|
||||
case "terms.ftl":
|
||||
return <Terms {...{ kcContext, ...props }} />;
|
||||
case "login-otp.ftl":
|
||||
return <LoginOtp {...{ kcContext, ...props }} />;
|
||||
case "login-update-password.ftl":
|
||||
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
|
||||
case "login-update-profile.ftl":
|
||||
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-confirm.ftl":
|
||||
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-email.ftl":
|
||||
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
|
||||
case "login-page-expired.ftl":
|
||||
return <LoginPageExpired {...{ kcContext, ...props }} />;
|
||||
case "login-config-totp.ftl":
|
||||
return <LoginConfigTotp {...{ kcContext, ...props }} />;
|
||||
case "logout-confirm.ftl":
|
||||
return <LogoutConfirm {...{ kcContext, ...props }} />;
|
||||
}
|
||||
})()}
|
||||
</Suspense>
|
||||
</I18nProvider>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2,15 +2,15 @@ import React, { useState, memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import type { FormEventHandler } from "react";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login } & KcProps) => {
|
||||
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
const LoginConfigTotp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginConfigTotp } & KcProps) => {
|
||||
@ -10,7 +10,7 @@ const LoginConfigTotp = memo(({ kcContext, ...props }: { kcContext: KcContextBas
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
|
||||
HmacSHA1: "SHA1",
|
||||
HmacSHA256: "SHA256",
|
||||
|
@ -2,13 +2,13 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm } & KcProps) => {
|
||||
const { url, idpAlias } = kcContext;
|
||||
|
||||
const { msg } = getMsg(kcContext);
|
||||
const { msg } = useI18n();
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
@ -2,12 +2,12 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
const LoginIdpLinkEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail } & KcProps) => {
|
||||
const { url, realm, brokerContext, idpAlias } = kcContext;
|
||||
|
||||
const { msg } = getMsg(kcContext);
|
||||
const { msg } = useI18n();
|
||||
|
||||
return (
|
||||
<Template
|
||||
|
@ -2,7 +2,7 @@ import React, { useEffect, memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { headInsert } from "../tools/headInsert";
|
||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
@ -12,7 +12,7 @@ const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
useEffect(() => {
|
||||
let isCleanedUp = false;
|
||||
|
@ -2,12 +2,12 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
const LoginPageExpired = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginPageExpired } & KcProps) => {
|
||||
const { url } = kcContext;
|
||||
|
||||
const { msg } = getMsg(kcContext);
|
||||
const { msg } = useI18n();
|
||||
|
||||
return (
|
||||
<Template
|
||||
|
@ -2,13 +2,13 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginResetPassword } & KcProps) => {
|
||||
const { url, realm, auth } = kcContext;
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
@ -2,13 +2,13 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
const LoginUpdatePassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdatePassword } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
|
||||
|
||||
|
@ -2,13 +2,13 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdateProfile } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
|
||||
|
||||
|
@ -2,10 +2,10 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginVerifyEmail } & KcProps) => {
|
||||
const { msg } = getMsg(kcContext);
|
||||
const { msg } = useI18n();
|
||||
|
||||
const { url, user } = kcContext;
|
||||
|
||||
|
@ -4,14 +4,14 @@ import { useCssAndCx } from "tss-react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
const LogoutConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LogoutConfirm } & KcProps) => {
|
||||
const { url, client, logoutConfirm } = kcContext;
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
return (
|
||||
<Template
|
||||
|
@ -2,13 +2,13 @@ import React, { memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Register } & KcProps) => {
|
||||
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React, { useMemo, memo, useEffect, useState, Fragment } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import type { ReactComponent } from "../tools/ReactComponent";
|
||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||
@ -11,7 +11,7 @@ import { useFormValidationSlice } from "../useFormValidationSlice";
|
||||
const RegisterUserProfile = memo(({ kcContext, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile } & KcProps) => {
|
||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
const { cx, css } = useCssAndCx();
|
||||
|
||||
@ -74,7 +74,7 @@ type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile
|
||||
const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, ...props }: UserProfileFormFieldsProps) => {
|
||||
const { cx, css } = useCssAndCx();
|
||||
|
||||
const { advancedMsg } = getMsg(kcContext);
|
||||
const { advancedMsg } = useI18n();
|
||||
|
||||
const {
|
||||
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
||||
|
@ -1,7 +1,5 @@
|
||||
import React, { useReducer, useEffect, memo } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { getMsg, getCurrentKcLanguageTag, changeLocale, getTagLabel } from "../i18n";
|
||||
import type { KcLanguageTag } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { assert } from "../tools/assert";
|
||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||
@ -10,6 +8,7 @@ import { pathJoin } from "../../bin/tools/pathJoin";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import type { KcTemplateProps } from "./KcProps";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
export type TemplateProps = {
|
||||
displayInfo?: boolean;
|
||||
@ -48,14 +47,9 @@ const Template = memo((props: TemplateProps) => {
|
||||
console.log("Rendering this page with react using keycloakify");
|
||||
}, []);
|
||||
|
||||
const { msg } = getMsg(kcContext);
|
||||
const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = useI18n();
|
||||
|
||||
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [KcLanguageTag]) =>
|
||||
changeLocale({
|
||||
kcContext,
|
||||
kcLanguageTag,
|
||||
}),
|
||||
);
|
||||
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [string]) => changeLocale(kcLanguageTag));
|
||||
|
||||
const onTryAnotherWayClick = useConstCallback(() => (document.forms["kc-select-try-another-way-form" as never].submit(), false));
|
||||
|
||||
@ -138,13 +132,13 @@ const Template = memo((props: TemplateProps) => {
|
||||
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
|
||||
<div className="kc-dropdown" id="kc-locale-dropdown">
|
||||
<a href="#" id="kc-current-locale-link">
|
||||
{getTagLabel({ "kcLanguageTag": getCurrentKcLanguageTag(kcContext), kcContext })}
|
||||
{labelBySupportedLanguageTag[currentLanguageTag]}
|
||||
</a>
|
||||
<ul>
|
||||
{locale.supported.map(({ languageTag }) => (
|
||||
<li key={languageTag} className="kc-dropdown-item">
|
||||
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
|
||||
{getTagLabel({ "kcLanguageTag": languageTag, kcContext })}
|
||||
{labelBySupportedLanguageTag[languageTag]}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
|
@ -1,38 +1,50 @@
|
||||
import React, { useReducer, useEffect, memo } from "react";
|
||||
import React, { useEffect, memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { kcMessages, getCurrentKcLanguageTag } from "../i18n";
|
||||
import type { KcLanguageTag } from "../i18n";
|
||||
import { Evt } from "evt";
|
||||
import { useRerenderOnStateChange } from "evt/hooks";
|
||||
|
||||
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
||||
|
||||
/** Allow to avoid bundling the terms and download it on demand*/
|
||||
export function useDownloadTerms(params: {
|
||||
kcContext: KcContextBase;
|
||||
downloadTermMarkdown: (params: { currentKcLanguageTag: KcLanguageTag }) => Promise<string>;
|
||||
}) {
|
||||
const { kcContext, downloadTermMarkdown } = params;
|
||||
export function useDownloadTerms(params: { downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string> }) {
|
||||
const { downloadTermMarkdown } = params;
|
||||
|
||||
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
||||
const { currentLanguageTag } = useI18n();
|
||||
|
||||
useEffect(() => {
|
||||
const currentKcLanguageTag = getCurrentKcLanguageTag(kcContext);
|
||||
let isMounted = true;
|
||||
|
||||
downloadTermMarkdown({ currentKcLanguageTag }).then(thermMarkdown => {
|
||||
kcMessages[currentKcLanguageTag].termsText = thermMarkdown;
|
||||
forceUpdate();
|
||||
downloadTermMarkdown({ currentLanguageTag }).then(thermMarkdown => {
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
evtTermMarkdown.state = thermMarkdown;
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms } & KcProps) => {
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
const { msg, msgStr } = useI18n();
|
||||
|
||||
useRerenderOnStateChange(evtTermMarkdown);
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { url } = kcContext;
|
||||
|
||||
if (evtTermMarkdown.state === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
@ -41,7 +53,7 @@ const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms }
|
||||
headerNode={msg("termsTitle")}
|
||||
formNode={
|
||||
<>
|
||||
<div id="kc-terms-text">{msg("termsText")}</div>
|
||||
<div id="kc-terms-text">{evtTermMarkdown.state}</div>
|
||||
<form className="form-actions" action={url.loginAction} method="POST">
|
||||
<input
|
||||
className={cx(
|
||||
|
Reference in New Issue
Block a user