mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 03:33:49 +08:00
feat: add signin items table (#2695)
* feat: add signin items table * fix:unable to login * feat: improve code format * fix: fix display err on signup link * feat: improve display of sign up link
This commit is contained in:
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd";
|
||||
import {Button, Checkbox, Col, Form, Input, Result, Spin, Tabs} from "antd";
|
||||
import {ArrowLeftOutlined, LockOutlined, UserOutlined} from "@ant-design/icons";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
|
||||
@ -32,11 +32,11 @@ import i18next from "i18next";
|
||||
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||
import {SendCodeInput} from "../common/SendCodeInput";
|
||||
import LanguageSelect from "../common/select/LanguageSelect";
|
||||
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
||||
import {CaptchaRule} from "../common/modal/CaptchaModal";
|
||||
import {CaptchaModal, CaptchaRule} from "../common/modal/CaptchaModal";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
|
||||
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -494,6 +494,200 @@ class LoginPage extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
renderFormItem(application, signinItem) {
|
||||
if (!signinItem.visible && signinItem.name !== "ForgetPassword") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (signinItem.name === "Logo") {
|
||||
return (
|
||||
<div className="login-logo-box">
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
{
|
||||
Setting.renderHelmet(application)
|
||||
}
|
||||
{
|
||||
Setting.renderLogo(application)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Back button") {
|
||||
return (
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
{
|
||||
this.renderBackButton()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Languages") {
|
||||
return (
|
||||
<div className="login-languages">
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
<LanguageSelect languages={application.organizationObj.languages} />
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Signin methods") {
|
||||
return (
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
{this.renderMethodChoiceBox()}
|
||||
</div>
|
||||
)
|
||||
;
|
||||
} else if (signinItem.name === "Username") {
|
||||
return (
|
||||
<div className="login-username">
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: () => {
|
||||
switch (this.state.loginMethod) {
|
||||
case "verificationCodeEmail":
|
||||
return i18next.t("login:Please input your Email!");
|
||||
case "verificationCodePhone":
|
||||
return i18next.t("login:Please input your Phone!");
|
||||
case "ldap":
|
||||
return i18next.t("login:Please input your LDAP username!");
|
||||
default:
|
||||
return i18next.t("login:Please input your Email or Phone!");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (value === "") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.state.loginMethod === "verificationCode") {
|
||||
if (!Setting.isValidEmail(value) && !Setting.isValidPhone(value)) {
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email or phone number!"));
|
||||
}
|
||||
|
||||
if (Setting.isValidEmail(value)) {
|
||||
this.setState({validEmail: true});
|
||||
} else {
|
||||
this.setState({validEmail: false});
|
||||
}
|
||||
} else if (this.state.loginMethod === "verificationCodeEmail") {
|
||||
if (!Setting.isValidEmail(value)) {
|
||||
this.setState({validEmail: false});
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email!"));
|
||||
} else {
|
||||
this.setState({validEmail: true});
|
||||
}
|
||||
} else if (this.state.loginMethod === "verificationCodePhone") {
|
||||
if (!Setting.isValidPhone(value)) {
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid phone number!"));
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({validEmailOrPhone: true});
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
|
||||
<Input
|
||||
id="input"
|
||||
prefix={<UserOutlined className="site-form-item-icon" />}
|
||||
placeholder={this.getPlaceholder()}
|
||||
onChange={e => {
|
||||
this.setState({
|
||||
username: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Password") {
|
||||
return (
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
{this.renderPasswordOrCodeInput()}
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Forgot password?") {
|
||||
return (
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
<div className="login-forget-password">
|
||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||
<Checkbox style={{float: "left"}}>
|
||||
{i18next.t("login:Auto sign in")}
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
{
|
||||
signinItem.visible ? Setting.renderForgetLink(application, i18next.t("login:Forgot password?")) : null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name === "Agreement") {
|
||||
return AgreementModal.isAgreementRequired(application) ? AgreementModal.renderAgreementFormItem(application, true, {}, this) : null;
|
||||
} else if (signinItem.name === "Login button") {
|
||||
return (
|
||||
<Form.Item className="login-button-box">
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-button"
|
||||
>
|
||||
{
|
||||
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
</Button>
|
||||
{
|
||||
this.renderCaptchaModal(application)
|
||||
}
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signinItem.name === "Providers") {
|
||||
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application);
|
||||
if (signinItem.rule === "None") {
|
||||
signinItem.rule = showForm ? "small" : "big";
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
<Form.Item>
|
||||
{
|
||||
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
||||
return ProviderButton.renderProviderLogo(providerItem.provider, application, null, null, signinItem.rule, this.props.location);
|
||||
})
|
||||
}
|
||||
{
|
||||
this.renderOtherFormProvider(application)
|
||||
}
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
} else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) {
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
);
|
||||
} else if (signinItem.name === "Signup link") {
|
||||
return (
|
||||
<div style={{width: "100%"}} className="login-signup-link">
|
||||
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
|
||||
{this.renderFooter(application)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderForm(application) {
|
||||
if (this.state.msg !== null) {
|
||||
return Util.renderMessage(this.state.msg);
|
||||
@ -569,116 +763,10 @@ class LoginPage extends React.Component {
|
||||
]}
|
||||
>
|
||||
</Form.Item>
|
||||
{this.renderMethodChoiceBox()}
|
||||
<Row style={{minHeight: 130, alignItems: "center"}}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: () => {
|
||||
switch (this.state.loginMethod) {
|
||||
case "verificationCodeEmail": return i18next.t("login:Please input your Email!");
|
||||
case "verificationCodePhone": return i18next.t("login:Please input your Phone!");
|
||||
case "ldap": return i18next.t("login:Please input your LDAP username!");
|
||||
default: return i18next.t("login:Please input your Email or Phone!");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (value === "") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.state.loginMethod === "verificationCode") {
|
||||
if (!Setting.isValidEmail(value) && !Setting.isValidPhone(value)) {
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email or phone number!"));
|
||||
}
|
||||
|
||||
if (Setting.isValidEmail(value)) {
|
||||
this.setState({validEmail: true});
|
||||
} else {
|
||||
this.setState({validEmail: false});
|
||||
}
|
||||
} else if (this.state.loginMethod === "verificationCodeEmail") {
|
||||
if (!Setting.isValidEmail(value)) {
|
||||
this.setState({validEmail: false});
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email!"));
|
||||
} else {
|
||||
this.setState({validEmail: true});
|
||||
}
|
||||
} else if (this.state.loginMethod === "verificationCodePhone") {
|
||||
if (!Setting.isValidPhone(value)) {
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid phone number!"));
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({validEmailOrPhone: true});
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
id="input"
|
||||
prefix={<UserOutlined className="site-form-item-icon" />}
|
||||
placeholder={this.getPlaceholder()}
|
||||
onChange={e => {
|
||||
this.setState({
|
||||
username: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
{
|
||||
this.renderPasswordOrCodeInput()
|
||||
}
|
||||
</Row>
|
||||
<div style={{display: "inline-flex", justifyContent: "space-between", width: "320px", marginBottom: AgreementModal.isAgreementRequired(application) ? "5px" : "25px"}}>
|
||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||
<Checkbox style={{float: "left"}}>
|
||||
{i18next.t("login:Auto sign in")}
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
{
|
||||
Setting.renderForgetLink(application, i18next.t("login:Forgot password?"))
|
||||
}
|
||||
</div>
|
||||
{AgreementModal.isAgreementRequired(application) ? AgreementModal.renderAgreementFormItem(application, true, {}, this) : null}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{width: "100%", marginBottom: "5px"}}
|
||||
>
|
||||
{
|
||||
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
</Button>
|
||||
{
|
||||
this.renderCaptchaModal(application)
|
||||
}
|
||||
{
|
||||
this.renderFooter(application)
|
||||
}
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
{
|
||||
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
||||
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small", this.props.location);
|
||||
})
|
||||
}
|
||||
{
|
||||
this.renderOtherFormProvider(application)
|
||||
}
|
||||
</Form.Item>
|
||||
{
|
||||
application.signinItems?.map(signinItem => this.renderFormItem(application, signinItem))
|
||||
}
|
||||
</Form>
|
||||
);
|
||||
} else {
|
||||
@ -694,20 +782,7 @@ class LoginPage extends React.Component {
|
||||
:
|
||||
</div>
|
||||
<br />
|
||||
{
|
||||
application.providers?.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
||||
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location);
|
||||
})
|
||||
}
|
||||
{
|
||||
this.renderOtherFormProvider(application)
|
||||
}
|
||||
<div>
|
||||
<br />
|
||||
{
|
||||
this.renderFooter(application)
|
||||
}
|
||||
</div>
|
||||
{application.signinItems.map(signinItem => signinItem.name === "ThirdParty" || signinItem.name === "Footer" ? this.renderFormItem(application, signinItem) : null)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -760,7 +835,7 @@ class LoginPage extends React.Component {
|
||||
|
||||
renderFooter(application) {
|
||||
return (
|
||||
<span style={{float: "right"}}>
|
||||
<div>
|
||||
{
|
||||
!application.enableSignUp ? null : (
|
||||
<React.Fragment>
|
||||
@ -771,7 +846,7 @@ class LoginPage extends React.Component {
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -889,33 +964,37 @@ class LoginPage extends React.Component {
|
||||
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
|
||||
return (
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||
type="password"
|
||||
placeholder={i18next.t("general:Password")}
|
||||
disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="login-password">
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||
type="password"
|
||||
placeholder={i18next.t("general:Password")}
|
||||
disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
} else if (this.state.loginMethod?.includes("verificationCode")) {
|
||||
return (
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="code"
|
||||
rules={[{required: true, message: i18next.t("login:Please input your code!")}]}
|
||||
>
|
||||
<SendCodeInput
|
||||
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
|
||||
method={"login"}
|
||||
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="login-password">
|
||||
<Form.Item
|
||||
name="code"
|
||||
rules={[{required: true, message: i18next.t("login:Please input your code!")}]}
|
||||
>
|
||||
<SendCodeInput
|
||||
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
|
||||
method={"login"}
|
||||
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
} else {
|
||||
@ -952,7 +1031,7 @@ class LoginPage extends React.Component {
|
||||
if (items.length > 1) {
|
||||
return (
|
||||
<div>
|
||||
<Tabs items={items} size={"small"} defaultActiveKey={this.getDefaultLoginMethod(application)} onChange={(key) => {
|
||||
<Tabs className="signin-methods" items={items} size={"small"} defaultActiveKey={this.getDefaultLoginMethod(application)} onChange={(key) => {
|
||||
this.setState({loginMethod: key});
|
||||
}} centered>
|
||||
</Tabs>
|
||||
@ -1047,10 +1126,10 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
renderBackButton() {
|
||||
if (this.state.orgChoiceMode === "None") {
|
||||
if (this.state.orgChoiceMode === "None" || this.props.preview === "auto") {
|
||||
return (
|
||||
<Button type="text" size="large" icon={<ArrowLeftOutlined />}
|
||||
style={{top: "65px", left: "15px", position: "absolute"}}
|
||||
className="back-button"
|
||||
onClick={() => history.back()}>
|
||||
</Button>
|
||||
);
|
||||
@ -1099,16 +1178,6 @@ class LoginPage extends React.Component {
|
||||
<div className="login-form">
|
||||
<div>
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(application)
|
||||
}
|
||||
{
|
||||
Setting.renderLogo(application)
|
||||
}
|
||||
{
|
||||
this.renderBackButton()
|
||||
}
|
||||
<LanguageSelect languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||
{
|
||||
this.renderLoginPanel(application)
|
||||
}
|
||||
|
Reference in New Issue
Block a user