casdoor/web/src/auth/SignupPage.js

643 lines
18 KiB
JavaScript
Raw Normal View History

2022-02-13 23:39:27 +08:00
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
2021-03-26 21:57:41 +08:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
2021-03-26 21:58:10 +08:00
import {Link} from "react-router-dom";
2022-07-18 20:57:38 +08:00
import {Button, Checkbox, Col, Form, Input, Modal, Result, Row} from "antd";
2021-03-26 21:57:41 +08:00
import * as Setting from "../Setting";
import * as AuthBackend from "./AuthBackend";
import * as ProviderButton from "./ProviderButton";
2021-04-28 00:47:12 +08:00
import i18next from "i18next";
2021-04-28 15:54:50 +08:00
import * as Util from "./Util";
import {authConfig} from "./Auth";
import * as ApplicationBackend from "../backend/ApplicationBackend";
2022-01-07 20:34:27 +08:00
import {CountDownInput} from "../common/CountDownInput";
import SelectRegionBox from "../SelectRegionBox";
import CustomGithubCorner from "../CustomGithubCorner";
2021-03-26 21:57:41 +08:00
const formItemLayout = {
labelCol: {
xs: {
span: 24,
},
sm: {
span: 8,
2021-03-26 21:57:41 +08:00
},
},
wrapperCol: {
xs: {
span: 24,
},
sm: {
2021-04-28 15:54:50 +08:00
span: 18,
2021-03-26 21:57:41 +08:00
},
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
2021-04-27 22:47:44 +08:00
class SignupPage extends React.Component {
2021-03-26 21:57:41 +08:00
constructor(props) {
super(props);
this.state = {
classes: props,
2021-06-14 13:13:39 +08:00
applicationName: props.match?.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName,
2021-04-28 15:54:50 +08:00
application: null,
email: "",
phone: "",
emailCode: "",
phoneCode: "",
validEmail: false,
validPhone: false,
region: "",
2021-08-03 21:00:07 +08:00
isTermsOfUseVisible: false,
termsOfUseContent: "",
2021-03-26 21:57:41 +08:00
};
this.form = React.createRef();
}
2021-04-28 15:54:50 +08:00
UNSAFE_componentWillMount() {
let applicationName = this.state.applicationName;
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null) {
applicationName = oAuthParams.state;
this.setState({applicationName: oAuthParams.state});
const signinUrl = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize");
sessionStorage.setItem("signinUrl", signinUrl);
}
if (applicationName !== undefined) {
this.getApplication(applicationName);
2021-04-28 15:54:50 +08:00
} else {
Util.showMessage("error", `Unknown application name: ${applicationName}`);
2021-04-28 15:54:50 +08:00
}
}
getApplication(applicationName) {
if (applicationName === undefined) {
2021-04-28 15:54:50 +08:00
return;
}
ApplicationBackend.getApplication("admin", applicationName)
2021-04-28 15:54:50 +08:00
.then((application) => {
this.setState({
application: application,
});
if (application !== null && application !== undefined) {
this.getTermsofuseContent(application.termsOfUse);
}
2021-04-28 15:54:50 +08:00
});
}
2021-04-28 22:40:21 +08:00
getResultPath(application) {
if (authConfig.appName === application.name) {
return "/result";
} else {
2021-06-20 09:46:06 +08:00
if (Setting.hasPromptPage(application)) {
return `/prompt/${application.name}`;
} else {
return `/result/${application.name}`;
}
2021-04-28 22:40:21 +08:00
}
}
2021-06-14 13:13:39 +08:00
getApplicationObj() {
if (this.props.application !== undefined) {
return this.props.application;
} else {
return this.state.application;
}
}
getTermsofuseContent(url) {
fetch(url, {
method: "GET",
}).then(r => {
r.text().then(res => {
this.setState({termsOfUseContent: res});
});
});
}
onUpdateAccount(account) {
this.props.onUpdateAccount(account);
}
2021-03-26 21:57:41 +08:00
onFinish(values) {
2021-06-14 13:13:39 +08:00
const application = this.getApplicationObj();
values.phonePrefix = application.organizationObj.phonePrefix;
2021-04-27 22:47:44 +08:00
AuthBackend.signup(values)
2021-03-26 21:57:41 +08:00
.then((res) => {
if (res.status === "ok") {
if (Setting.hasPromptPage(application)) {
AuthBackend.getAccount("")
.then((res) => {
let account = null;
if (res.status === "ok") {
account = res.data;
account.organization = res.data2;
this.onUpdateAccount(account);
Setting.goToLinkSoft(this, this.getResultPath(application));
} else {
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
}
});
} else {
Setting.goToLinkSoft(this, this.getResultPath(application));
}
2021-03-26 21:57:41 +08:00
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
2021-03-26 21:57:41 +08:00
}
});
}
onFinishFailed(values, errorFields, outOfDate) {
this.form.current.scrollToField(errorFields[0].name);
}
isProviderVisible(providerItem) {
if (this.state.mode === "signup") {
return Setting.isProviderVisibleForSignUp(providerItem);
} else {
return Setting.isProviderVisibleForSignIn(providerItem);
}
}
2021-06-16 14:06:41 +08:00
renderFormItem(application, signupItem) {
if (!signupItem.visible) {
return null;
}
2021-06-16 15:25:54 +08:00
const required = signupItem.required;
2021-06-16 14:06:41 +08:00
if (signupItem.name === "Username") {
return (
2021-03-26 21:57:41 +08:00
<Form.Item
2021-04-28 00:47:12 +08:00
name="username"
key="username"
2021-04-28 00:47:12 +08:00
label={i18next.t("signup:Username")}
2021-03-26 21:57:41 +08:00
rules={[
{
2021-06-16 15:25:54 +08:00
required: required,
message: i18next.t("forget:Please input your username!"),
2021-03-26 21:57:41 +08:00
whitespace: true,
},
]}
>
<Input />
</Form.Item>
);
2021-06-16 14:06:41 +08:00
} else if (signupItem.name === "Display name") {
if (signupItem.rule === "First, last" && Setting.getLanguage() !== "zh") {
return (
<React.Fragment>
<Form.Item
name="firstName"
key="firstName"
label={i18next.t("general:First name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your first name!"),
whitespace: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="lastName"
key="lastName"
label={i18next.t("general:Last name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your last name!"),
whitespace: true,
},
]}
>
<Input />
</Form.Item>
</React.Fragment>
);
}
2021-06-16 14:06:41 +08:00
return (
2021-03-26 21:57:41 +08:00
<Form.Item
2021-04-28 21:25:58 +08:00
name="name"
key="name"
label={(signupItem.rule === "Real name" || signupItem.rule === "First, last") ? i18next.t("general:Real name") : i18next.t("general:Display name")}
2021-03-26 21:57:41 +08:00
rules={[
{
2021-06-16 15:25:54 +08:00
required: required,
message: (signupItem.rule === "Real name" || signupItem.rule === "First, last") ? i18next.t("signup:Please input your real name!") : i18next.t("signup:Please input your display name!"),
2021-03-26 21:57:41 +08:00
whitespace: true,
},
]}
>
<Input />
</Form.Item>
);
2021-06-16 14:06:41 +08:00
} else if (signupItem.name === "Affiliation") {
return (
2021-03-26 21:57:41 +08:00
<Form.Item
2021-04-27 22:35:14 +08:00
name="affiliation"
key="affiliation"
2021-04-28 00:47:12 +08:00
label={i18next.t("user:Affiliation")}
2021-03-26 21:57:41 +08:00
rules={[
{
2021-06-16 15:25:54 +08:00
required: required,
2021-04-28 00:47:12 +08:00
message: i18next.t("signup:Please input your affiliation!"),
2021-03-26 21:57:41 +08:00
whitespace: true,
},
]}
>
<Input />
</Form.Item>
);
2021-12-28 17:48:24 +08:00
} else if (signupItem.name === "ID card") {
return (
<Form.Item
name="idCard"
key="idCard"
label={i18next.t("user:ID card")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your ID card number!"),
whitespace: true,
},
{
required: required,
pattern: new RegExp(/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9X]$/, "g"),
message: i18next.t("signup:Please input the correct ID card number!"),
},
]}
>
<Input />
</Form.Item>
);
} else if (signupItem.name === "Country/Region") {
return (
<Form.Item
name="country_region"
key="region"
label={i18next.t("user:Country/Region")}
rules={[
{
required: required,
message: i18next.t("signup:Please select your country/region!"),
},
]}
>
<SelectRegionBox onChange={(value) => {this.setState({region: value});}} />
</Form.Item>
);
2021-06-16 14:06:41 +08:00
} else if (signupItem.name === "Email") {
return (
<React.Fragment>
<Form.Item
name="email"
key="email"
2021-06-16 14:06:41 +08:00
label={i18next.t("general:Email")}
rules={[
{
2021-06-16 15:25:54 +08:00
required: required,
2021-06-16 14:06:41 +08:00
message: i18next.t("signup:Please input your Email!"),
},
{
validator: (_, value) => {
if (this.state.email !== "" && !Setting.isValidEmail(this.state.email)) {
this.setState({validEmail: false});
return Promise.reject(i18next.t("signup:The input is not valid Email!"));
}
this.setState({validEmail: true});
return Promise.resolve();
2022-08-06 23:54:56 +08:00
},
},
2021-06-16 14:06:41 +08:00
]}
>
<Input onChange={e => this.setState({email: e.target.value})} />
</Form.Item>
{
signupItem.rule !== "No verification" &&
<Form.Item
name="emailCode"
key="emailCode"
label={i18next.t("code:Email code")}
rules={[{
required: required,
message: i18next.t("code:Please input your verification code!"),
}]}
>
<CountDownInput
disabled={!this.state.validEmail}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]}
/>
</Form.Item>
}
2021-06-16 14:06:41 +08:00
</React.Fragment>
);
2021-06-16 14:06:41 +08:00
} else if (signupItem.name === "Phone") {
return (
<React.Fragment>
<Form.Item
name="phone"
key="phone"
2021-06-16 14:06:41 +08:00
label={i18next.t("general:Phone")}
rules={[
{
2021-06-16 15:25:54 +08:00
required: required,
2021-06-16 14:06:41 +08:00
message: i18next.t("signup:Please input your phone number!"),
},
{
validator: (_, value) => {
if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone)) {
this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
this.setState({validPhone: true});
return Promise.resolve();
2022-08-06 23:54:56 +08:00
},
},
2021-06-16 14:06:41 +08:00
]}
>
<Input
style={{
width: "100%",
2021-06-16 14:06:41 +08:00
}}
addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
onChange={e => this.setState({phone: e.target.value})}
/>
</Form.Item>
<Form.Item
name="phoneCode"
key="phoneCode"
label={i18next.t("code:Phone code")}
2021-06-16 14:06:41 +08:00
rules={[
{
2021-06-16 15:25:54 +08:00
required: required,
message: i18next.t("code:Please input your phone verification code!"),
2021-06-16 14:06:41 +08:00
},
]}
>
<CountDownInput
disabled={!this.state.validPhone}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
2021-06-16 14:06:41 +08:00
/>
</Form.Item>
</React.Fragment>
);
} else if (signupItem.name === "Password") {
return (
<Form.Item
name="password"
key="password"
label={i18next.t("general:Password")}
rules={[
{
required: required,
min: 6,
message: i18next.t("login:Please input your password, at least 6 characters!"),
},
]}
hasFeedback
>
<Input.Password />
</Form.Item>
);
} else if (signupItem.name === "Confirm password") {
return (
<Form.Item
name="confirm"
key="confirm"
label={i18next.t("signup:Confirm")}
dependencies={["password"]}
hasFeedback
rules={[
{
required: required,
message: i18next.t("signup:Please confirm your password!"),
},
({getFieldValue}) => ({
validator(rule, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(i18next.t("signup:Your confirmed password is inconsistent with the password!"));
},
}),
]}
>
<Input.Password />
</Form.Item>
);
2021-06-16 14:06:41 +08:00
} else if (signupItem.name === "Agreement") {
return (
2021-03-26 21:57:41 +08:00
<Form.Item
2021-06-16 14:06:41 +08:00
name="agreement"
key="agreement"
2021-06-16 14:06:41 +08:00
valuePropName="checked"
2021-06-16 15:25:54 +08:00
rules={[
{
required: required,
message: i18next.t("signup:Please accept the agreement!"),
},
]}
2021-06-16 14:06:41 +08:00
{...tailFormItemLayout}
2021-03-26 21:57:41 +08:00
>
2021-06-16 14:06:41 +08:00
<Checkbox>
{i18next.t("signup:Accept")}&nbsp;
2021-08-03 21:00:07 +08:00
<Link onClick={() => {
this.setState({
isTermsOfUseVisible: true,
});
}}>
2021-06-16 14:06:41 +08:00
{i18next.t("signup:Terms of Use")}
</Link>
</Checkbox>
2021-03-26 21:57:41 +08:00
</Form.Item>
);
2021-06-16 14:06:41 +08:00
}
}
2021-03-26 21:57:41 +08:00
2021-08-03 21:00:07 +08:00
renderModal() {
return (
<Modal
title={i18next.t("signup:Terms of Use")}
visible={this.state.isTermsOfUseVisible}
width={"55vw"}
2021-08-03 21:00:07 +08:00
closable={false}
okText={i18next.t("signup:Accept")}
cancelText={i18next.t("signup:Decline")}
onOk={() => {
this.form.current.setFieldsValue({agreement: true});
this.setState({
isTermsOfUseVisible: false,
});
}}
onCancel={() => {
this.form.current.setFieldsValue({agreement: false});
this.setState({
isTermsOfUseVisible: false,
});
this.props.history.goBack();
}}
2021-08-03 21:00:07 +08:00
>
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={this.state.termsOfUseContent} />
2021-08-03 21:00:07 +08:00
</Modal>
);
2021-08-03 21:00:07 +08:00
}
2021-06-16 14:06:41 +08:00
renderForm(application) {
if (!application.enableSignUp) {
return (
<Result
status="error"
title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"}
extra={[
<Button type="primary" key="signin" onClick={() => {
2021-06-16 14:06:41 +08:00
Setting.goToLogin(this, application);
}}>
Sign In
2022-08-06 23:54:56 +08:00
</Button>,
2021-03-26 21:57:41 +08:00
]}
>
2021-06-16 14:06:41 +08:00
</Result>
);
2021-06-16 14:06:41 +08:00
}
return (
<Form
{...formItemLayout}
ref={this.form}
name="signup"
onFinish={(values) => this.onFinish(values)}
onFinishFailed={(errorInfo) => this.onFinishFailed(errorInfo.values, errorInfo.errorFields, errorInfo.outOfDate)}
initialValues={{
application: application.name,
organization: application.organization,
}}
style={{width: !Setting.isMobile() ? "400px" : "250px"}}
size="large"
>
2021-03-26 21:57:41 +08:00
<Form.Item
2021-06-16 14:06:41 +08:00
style={{height: 0, visibility: "hidden"}}
name="application"
2021-03-26 21:57:41 +08:00
rules={[
{
required: true,
message: "Please input your application!",
2021-03-26 21:57:41 +08:00
},
]}
>
</Form.Item>
<Form.Item
2021-06-16 14:06:41 +08:00
style={{height: 0, visibility: "hidden"}}
name="organization"
rules={[
{
required: true,
message: "Please input your organization!",
},
]}
>
2021-03-26 21:57:41 +08:00
</Form.Item>
2021-06-16 14:06:41 +08:00
{
application.signupItems?.map(signupItem => this.renderFormItem(application, signupItem))
2021-06-16 14:06:41 +08:00
}
2021-03-26 21:57:41 +08:00
<Form.Item {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
2021-04-28 00:47:12 +08:00
{i18next.t("account:Sign Up")}
2021-03-26 21:57:41 +08:00
</Button>
2021-04-28 00:47:12 +08:00
&nbsp;&nbsp;{i18next.t("signup:Have account?")}&nbsp;
<a onClick={() => {
const linkInStorage = sessionStorage.getItem("signinUrl");
2022-08-13 11:23:16 +08:00
if(linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
}else{
Setting.goToLogin(this, application);
}
2021-04-28 22:40:21 +08:00
}}>
2021-04-28 00:47:12 +08:00
{i18next.t("signup:sign in now")}
</a>
2021-03-26 21:57:41 +08:00
</Form.Item>
{
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small");
})
}
2021-03-26 21:57:41 +08:00
</Form>
);
2021-03-26 21:57:41 +08:00
}
render() {
2021-06-14 13:13:39 +08:00
const application = this.getApplicationObj();
2021-04-28 15:54:50 +08:00
if (application === null) {
return null;
}
if (application.signupHtml !== "") {
return (
<div dangerouslySetInnerHTML={{__html: application.signupHtml}} />
);
}
2021-03-26 21:57:41 +08:00
return (
<div>
<CustomGithubCorner />
2021-03-26 21:57:41 +08:00
&nbsp;
<Row>
2022-08-06 23:47:28 +08:00
<Col span={24} style={{display: "flex", justifyContent: "center"}} >
2021-04-28 15:54:50 +08:00
<div style={{marginTop: "10px", textAlign: "center"}}>
2021-04-29 21:28:24 +08:00
{
Setting.renderHelmet(application)
}
2021-04-28 15:54:50 +08:00
{
Setting.renderLogo(application)
}
{
this.renderForm(application)
}
</div>
2021-03-26 21:57:41 +08:00
</Col>
</Row>
2021-08-03 21:00:07 +08:00
{
this.renderModal()
}
2021-03-26 21:57:41 +08:00
</div>
);
2021-03-26 21:57:41 +08:00
}
}
2021-04-27 22:47:44 +08:00
export default SignupPage;