mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 23:23:50 +08:00
feat: support "label" field for signin item table (#2956)
This commit is contained in:
@ -46,6 +46,7 @@ type SigninItem struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Visible bool `json:"visible"`
|
Visible bool `json:"visible"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
|
CustomCss string `json:"customCss"`
|
||||||
Placeholder string `json:"placeholder"`
|
Placeholder string `json:"placeholder"`
|
||||||
Rule string `json:"rule"`
|
Rule string `json:"rule"`
|
||||||
IsCustom bool `json:"isCustom"`
|
IsCustom bool `json:"isCustom"`
|
||||||
@ -209,7 +210,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem := &SigninItem{
|
signinItem := &SigninItem{
|
||||||
Name: "Back button",
|
Name: "Back button",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".back-button {\n top: 65px;\n left: 15px;\n position: absolute;\n}\n.back-inner-button{}",
|
CustomCss: ".back-button {\n top: 65px;\n left: 15px;\n position: absolute;\n}\n.back-inner-button{}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -217,7 +218,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Languages",
|
Name: "Languages",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-languages {\n top: 55px;\n right: 5px;\n position: absolute;\n}",
|
CustomCss: ".login-languages {\n top: 55px;\n right: 5px;\n position: absolute;\n}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -225,7 +226,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Logo",
|
Name: "Logo",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-logo-box {}",
|
CustomCss: ".login-logo-box {}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -233,7 +234,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Signin methods",
|
Name: "Signin methods",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".signin-methods {}",
|
CustomCss: ".signin-methods {}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -241,7 +242,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Username",
|
Name: "Username",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-username {}\n.login-username-input{}",
|
CustomCss: ".login-username {}\n.login-username-input{}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -249,7 +250,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Password",
|
Name: "Password",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-password {}\n.login-password-input{}",
|
CustomCss: ".login-password {}\n.login-password-input{}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -257,7 +258,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Agreement",
|
Name: "Agreement",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-agreement {}",
|
CustomCss: ".login-agreement {}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -265,7 +266,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Forgot password?",
|
Name: "Forgot password?",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-forget-password {\n display: inline-flex;\n justify-content: space-between;\n width: 320px;\n margin-bottom: 25px;\n}",
|
CustomCss: ".login-forget-password {\n display: inline-flex;\n justify-content: space-between;\n width: 320px;\n margin-bottom: 25px;\n}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -273,7 +274,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Login button",
|
Name: "Login button",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-button-box {\n margin-bottom: 5px;\n}\n.login-button {\n width: 100%;\n}",
|
CustomCss: ".login-button-box {\n margin-bottom: 5px;\n}\n.login-button {\n width: 100%;\n}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -281,7 +282,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Signup link",
|
Name: "Signup link",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".login-signup-link {\n margin-bottom: 24px;\n display: flex;\n justify-content: end;\n}",
|
CustomCss: ".login-signup-link {\n margin-bottom: 24px;\n display: flex;\n justify-content: end;\n}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
@ -289,12 +290,18 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
|
|||||||
signinItem = &SigninItem{
|
signinItem = &SigninItem{
|
||||||
Name: "Providers",
|
Name: "Providers",
|
||||||
Visible: true,
|
Visible: true,
|
||||||
Label: ".provider-img {\n width: 30px;\n margin: 5px;\n}\n.provider-big-img {\n margin-bottom: 10px;\n}",
|
CustomCss: ".provider-img {\n width: 30px;\n margin: 5px;\n}\n.provider-big-img {\n margin-bottom: 10px;\n}",
|
||||||
Placeholder: "",
|
Placeholder: "",
|
||||||
Rule: "None",
|
Rule: "None",
|
||||||
}
|
}
|
||||||
application.SigninItems = append(application.SigninItems, signinItem)
|
application.SigninItems = append(application.SigninItems, signinItem)
|
||||||
}
|
}
|
||||||
|
for idx, item := range application.SigninItems {
|
||||||
|
if item.Label != "" && item.CustomCss == "" {
|
||||||
|
application.SigninItems[idx].CustomCss = item.Label
|
||||||
|
application.SigninItems[idx].Label = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,7 +532,7 @@ class LoginPage extends React.Component {
|
|||||||
if (signinItem.name === "Logo") {
|
if (signinItem.name === "Logo") {
|
||||||
return (
|
return (
|
||||||
<div className="login-logo-box">
|
<div className="login-logo-box">
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
{
|
{
|
||||||
Setting.renderHelmet(application)
|
Setting.renderHelmet(application)
|
||||||
}
|
}
|
||||||
@ -544,7 +544,7 @@ class LoginPage extends React.Component {
|
|||||||
} else if (signinItem.name === "Back button") {
|
} else if (signinItem.name === "Back button") {
|
||||||
return (
|
return (
|
||||||
<div className="back-button">
|
<div className="back-button">
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
{
|
{
|
||||||
this.renderBackButton()
|
this.renderBackButton()
|
||||||
}
|
}
|
||||||
@ -562,14 +562,14 @@ class LoginPage extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="login-languages">
|
<div className="login-languages">
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
<LanguageSelect languages={application.organizationObj.languages} />
|
<LanguageSelect languages={application.organizationObj.languages} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (signinItem.name === "Signin methods") {
|
} else if (signinItem.name === "Signin methods") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
{this.renderMethodChoiceBox()}
|
{this.renderMethodChoiceBox()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -577,10 +577,11 @@ class LoginPage extends React.Component {
|
|||||||
} else if (signinItem.name === "Username") {
|
} else if (signinItem.name === "Username") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="username"
|
name="username"
|
||||||
className="login-username"
|
className="login-username"
|
||||||
|
label={signinItem.label ? signinItem.label : null}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@ -653,14 +654,14 @@ class LoginPage extends React.Component {
|
|||||||
} else if (signinItem.name === "Password") {
|
} else if (signinItem.name === "Password") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
{this.renderPasswordOrCodeInput()}
|
{this.renderPasswordOrCodeInput(signinItem)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (signinItem.name === "Forgot password?") {
|
} else if (signinItem.name === "Forgot password?") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
<div className="login-forget-password">
|
<div className="login-forget-password">
|
||||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||||
<Checkbox style={{float: "left"}}>
|
<Checkbox style={{float: "left"}}>
|
||||||
@ -668,7 +669,7 @@ class LoginPage extends React.Component {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{
|
{
|
||||||
signinItem.visible ? Setting.renderForgetLink(application, i18next.t("login:Forgot password?")) : null
|
signinItem.visible ? Setting.renderForgetLink(application, signinItem.label ? signinItem.label : i18next.t("login:Forgot password?")) : null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -678,7 +679,7 @@ class LoginPage extends React.Component {
|
|||||||
} else if (signinItem.name === "Login button") {
|
} else if (signinItem.name === "Login button") {
|
||||||
return (
|
return (
|
||||||
<Form.Item className="login-button-box">
|
<Form.Item className="login-button-box">
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
@ -687,7 +688,7 @@ class LoginPage extends React.Component {
|
|||||||
{
|
{
|
||||||
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
|
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
|
||||||
this.state.loginMethod === "faceId" ? i18next.t("login:Sign in with Face ID") :
|
this.state.loginMethod === "faceId" ? i18next.t("login:Sign in with Face ID") :
|
||||||
i18next.t("login:Sign In")
|
signinItem.label ? signinItem.label : i18next.t("login:Sign In")
|
||||||
}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
{
|
{
|
||||||
@ -722,7 +723,7 @@ class LoginPage extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
{
|
{
|
||||||
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
||||||
@ -737,13 +738,13 @@ class LoginPage extends React.Component {
|
|||||||
);
|
);
|
||||||
} else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) {
|
} else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) {
|
||||||
return (
|
return (
|
||||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
<div dangerouslySetInnerHTML={{__html: signinItem.customCss}} />
|
||||||
);
|
);
|
||||||
} else if (signinItem.name === "Signup link") {
|
} else if (signinItem.name === "Signup link") {
|
||||||
return (
|
return (
|
||||||
<div style={{width: "100%"}} className="login-signup-link">
|
<div style={{width: "100%"}} className="login-signup-link">
|
||||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||||
{this.renderFooter(application)}
|
{this.renderFooter(application, signinItem)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -896,17 +897,20 @@ class LoginPage extends React.Component {
|
|||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFooter(application) {
|
renderFooter(application, signinItem) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
!application.enableSignUp ? null : (
|
!application.enableSignUp ? null : (
|
||||||
<React.Fragment>
|
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
|
||||||
{i18next.t("login:No account?")}
|
(
|
||||||
{
|
<React.Fragment>
|
||||||
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
{i18next.t("login:No account?")}
|
||||||
}
|
{
|
||||||
</React.Fragment>
|
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -1022,7 +1026,7 @@ class LoginPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPasswordOrCodeInput() {
|
renderPasswordOrCodeInput(signinItem) {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
|
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
|
||||||
return (
|
return (
|
||||||
@ -1031,6 +1035,7 @@ class LoginPage extends React.Component {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
className="login-password"
|
className="login-password"
|
||||||
|
label={signinItem.label ? signinItem.label : null}
|
||||||
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
|
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
|
||||||
>
|
>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
|
@ -134,7 +134,7 @@ class SigninTable extends React.Component {
|
|||||||
value={getItemDisplayName(text)}
|
value={getItemDisplayName(text)}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
this.updateField(table, index, "name", value);
|
this.updateField(table, index, "name", value);
|
||||||
this.updateField(table, index, "label", SigninTableDefaultCssMap[value]);
|
this.updateField(table, index, "customCss", SigninTableDefaultCssMap[value]);
|
||||||
}} >
|
}} >
|
||||||
{
|
{
|
||||||
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
||||||
@ -166,7 +166,7 @@ class SigninTable extends React.Component {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("signup:Label HTML"),
|
title: i18next.t("signup:Label"),
|
||||||
dataIndex: "label",
|
dataIndex: "label",
|
||||||
key: "label",
|
key: "label",
|
||||||
width: "200px",
|
width: "200px",
|
||||||
@ -188,14 +188,18 @@ class SigninTable extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
} else if (["Username", "Password", "Signup link", "Forgot password?", "Login button"].includes(record.name)) {
|
||||||
|
return <Input value={text} style={{marginBottom: "10px"}} onChange={e => {
|
||||||
|
this.updateField(table, index, "label", e.target.value);
|
||||||
|
}} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("application:Custom CSS"),
|
title: i18next.t("application:Custom CSS"),
|
||||||
dataIndex: "label",
|
dataIndex: "customCss",
|
||||||
key: "label",
|
key: "customCss",
|
||||||
width: "200px",
|
width: "200px",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (!record.name.startsWith("Text ") && !record?.isCustom) {
|
if (!record.name.startsWith("Text ") && !record?.isCustom) {
|
||||||
@ -205,13 +209,13 @@ class SigninTable extends React.Component {
|
|||||||
<CodeMirror value={text?.replaceAll("<style>", "").replaceAll("</style>", "")}
|
<CodeMirror value={text?.replaceAll("<style>", "").replaceAll("</style>", "")}
|
||||||
options={{mode: "css", theme: "material-darker"}}
|
options={{mode: "css", theme: "material-darker"}}
|
||||||
onBeforeChange={(editor, data, value) => {
|
onBeforeChange={(editor, data, value) => {
|
||||||
this.updateField(table, index, "label", value);
|
this.updateField(table, index, "customCss", value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
} title={i18next.t("application:CSS style")} trigger="click">
|
} title={i18next.t("application:CSS style")} trigger="click">
|
||||||
<Input value={text?.replaceAll("<style>", "").replaceAll("</style>", "")} onChange={e => {
|
<Input value={text?.replaceAll("<style>", "").replaceAll("</style>", "")} onChange={e => {
|
||||||
this.updateField(table, index, "label", e.target.value);
|
this.updateField(table, index, "customCss", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user