// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// 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";
import {Button, Form, Input, Radio, Result, Row, Select, message} from "antd";
import * as Setting from "../Setting";
import * as AuthBackend from "./AuthBackend";
import * as ProviderButton from "./ProviderButton";
import i18next from "i18next";
import * as Util from "./Util";
import {authConfig} from "./Auth";
import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as AgreementModal from "../common/modal/AgreementModal";
import {SendCodeInput} from "../common/SendCodeInput";
import RegionSelect from "../common/select/RegionSelect";
import CustomGithubCorner from "../common/CustomGithubCorner";
import LanguageSelect from "../common/select/LanguageSelect";
import {withRouter} from "react-router-dom";
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
import * as PasswordChecker from "../common/PasswordChecker";
import * as InvitationBackend from "../backend/InvitationBackend";
const formItemLayout = {
labelCol: {
xs: {
span: 24,
},
sm: {
span: 8,
},
},
wrapperCol: {
xs: {
span: 24,
},
sm: {
span: 16,
},
},
};
export const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
class SignupPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
applicationName: (props.applicationName ?? props.match?.params?.applicationName) ?? null,
email: "",
phone: "",
emailOrPhoneMode: "",
countryCode: "",
emailCode: "",
phoneCode: "",
validEmail: false,
validPhone: false,
region: "",
isTermsOfUseVisible: false,
termsOfUseContent: "",
};
this.form = React.createRef();
}
componentDidMount() {
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null) {
const signinUrl = window.location.pathname.replace("/signup/oauth/authorize", "/login/oauth/authorize");
sessionStorage.setItem("signinUrl", signinUrl + window.location.search);
}
if (this.getApplicationObj() === undefined) {
if (this.state.applicationName !== null) {
this.getApplication(this.state.applicationName);
const sp = new URLSearchParams(window.location.search);
if (sp.has("invitationCode")) {
const invitationCode = sp.get("invitationCode");
this.setState({invitationCode: invitationCode});
if (invitationCode !== "") {
this.getInvitationCodeInfo(invitationCode, "admin/" + this.state.applicationName);
}
}
} else if (oAuthParams !== null) {
this.getApplicationLogin(oAuthParams);
} else {
Setting.showMessage("error", `Unknown application name: ${this.state.applicationName}`);
this.onUpdateApplication(null);
}
}
}
getApplication(applicationName) {
if (applicationName === undefined) {
return;
}
ApplicationBackend.getApplication("admin", applicationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res.data);
});
}
getApplicationLogin(oAuthParams) {
AuthBackend.getApplicationLogin(oAuthParams)
.then((res) => {
if (res.status === "ok") {
const application = res.data;
this.onUpdateApplication(application);
} else {
this.onUpdateApplication(null);
this.setState({
msg: res.msg,
});
}
});
}
getInvitationCodeInfo(invitationCode, application) {
InvitationBackend.getInvitationCodeInfo(invitationCode, application)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({invitation: res.data});
});
}
getResultPath(application, signupParams) {
if (signupParams?.plan && signupParams?.pricing) {
// the prompt page needs the user to be signed in, so for paid-user sign up, just go to buy-plan page
return `/buy-plan/${application.organization}/${signupParams?.pricing}?user=${signupParams.username}&plan=${signupParams.plan}`;
}
if (authConfig.appName === application.name) {
return "/result";
} else {
if (Setting.hasPromptPage(application)) {
return `/prompt/${application.name}`;
} else {
return `/result/${application.name}`;
}
}
}
getApplicationObj() {
return this.props.application;
}
onUpdateAccount(account) {
this.props.onUpdateAccount(account);
}
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
}
parseOffset(offset) {
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
return "0 auto";
}
if (offset === 1) {
return "0 10%";
}
if (offset === 3) {
return "0 60%";
}
}
onFinish(values) {
const application = this.getApplicationObj();
if (Array.isArray(values.gender)) {
values.gender = values.gender.join(", ");
}
const params = new URLSearchParams(window.location.search);
values.plan = params.get("plan");
values.pricing = params.get("pricing");
AuthBackend.signup(values)
.then((res) => {
if (res.status === "ok") {
// the user's id will be returned by `signup()`, if user signup by phone, the `username` in `values` is undefined.
values.username = res.data.split("/")[1];
if (Setting.hasPromptPage(application) && (!values.plan || !values.pricing)) {
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, values));
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
}
});
} else {
Setting.goToLinkSoft(this, this.getResultPath(application, values));
}
} else {
Setting.showMessage("error", res.msg);
}
});
}
onFinishFailed(values, errorFields, outOfDate) {
this.form.current.scrollToField(errorFields[0].name);
}
isProviderVisible(providerItem) {
return Setting.isProviderVisibleForSignUp(providerItem);
}
renderFormItem(application, signupItem) {
if (!signupItem.visible) {
return null;
}
const required = signupItem.required;
if (signupItem.name === "Username") {
return (
);
} else if (signupItem.name === "Display name") {
if (signupItem.rule === "First, last" && Setting.getLanguage() !== "zh") {
return (
);
}
return (
);
} else if (signupItem.name === "Affiliation") {
return (
);
} else if (signupItem.name === "ID card") {
return (
);
} else if (signupItem.name === "Country/Region") {
return (
{
this.setState({region: value});
}} />
);
} else if (signupItem.name === "Email" || signupItem.name === "Phone" || signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
const renderEmailItem = () => {
return (
{
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!"));
}
if (signupItem.regex) {
const reg = new RegExp(signupItem.regex);
if (!reg.test(this.state.email)) {
this.setState({validEmail: false});
return Promise.reject(i18next.t("signup:The input Email doesn't match the signup item regex!"));
}
}
this.setState({validEmail: true});
return Promise.resolve();
},
},
]}
>
this.setState({email: e.target.value})} />
{
signupItem.rule !== "No verification" &&
}
);
};
const renderPhoneItem = () => {
return (
({
validator: (_, value) => {
if (!required && !value) {
return Promise.resolve();
}
if (value && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
this.setState({validPhone: true});
return Promise.resolve();
},
}),
]}
>
this.setState({phone: e.target.value})}
/>
{
signupItem.rule !== "No verification" &&
}
);
};
if (signupItem.name === "Email") {
return renderEmailItem();
} else if (signupItem.name === "Phone") {
return renderPhoneItem();
} else if (signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
let emailOrPhoneMode = this.state.emailOrPhoneMode;
if (emailOrPhoneMode === "") {
emailOrPhoneMode = signupItem.name === "Email or Phone" ? "Email" : "Phone";
}
return (
{
this.setState({
emailOrPhoneMode: e.target.value,
});
}} value={emailOrPhoneMode}>
{
signupItem.name === "Email or Phone" ? (
{i18next.t("general:Email")}
{i18next.t("general:Phone")}
) : (
{i18next.t("general:Phone")}
{i18next.t("general:Email")}
)
}
{
emailOrPhoneMode === "Email" ? renderEmailItem() : renderPhoneItem()
}
);
} else {
return null;
}
} else if (signupItem.name === "Password") {
return (
{
const errorMsg = PasswordChecker.checkPasswordComplexity(value, application.organizationObj.passwordOptions);
if (errorMsg === "") {
return Promise.resolve();
} else {
return Promise.reject(errorMsg);
}
},
},
]}
hasFeedback
>
);
} else if (signupItem.name === "Confirm password") {
return (
({
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!"));
},
}),
]}
>
);
} else if (signupItem.name === "Invitation code") {
return (
);
} else if (signupItem.name === "Agreement") {
return AgreementModal.renderAgreementFormItem(application, required, tailFormItemLayout, this);
} else if (signupItem.name.startsWith("Text ")) {
if (signupItem.type) {
if (!signupItem.type || signupItem.type === "Input") {
return (
);
} else if (signupItem.type === "Single Choice" || signupItem.type === "Multiple Choices") {
return (
);
}
}
} else if (signupItem.name === "Signup button") {
return (
{i18next.t("signup:Have account?")}
{
const linkInStorage = sessionStorage.getItem("signinUrl");
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLinkSoft(this, linkInStorage);
} else {
Setting.redirectToLoginPage(application, this.props.history);
}
}}>
{i18next.t("signup:sign in now")}
);
} else if (signupItem.name === "Providers") {
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application);
if (signupItem.rule === "None" || signupItem.rule === "") {
signupItem.rule = showForm ? "small" : "big";
}
return (
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map((providerItem, id) => {
return (
{
const agreementChecked = this.form.current.getFieldValue("agreement");
if (agreementChecked !== undefined && typeof agreementChecked === "boolean" && !agreementChecked) {
e.preventDefault();
message.error(i18next.t("signup:Please accept the agreement!"));
}
}}>
{
ProviderButton.renderProviderLogo(providerItem.provider, application, null, null, signupItem.rule, this.props.location)
}
);
})
);
} else if (signupItem.name === "Gender") {
if (!signupItem.type) {
return (
);
}
if (!signupItem.type || signupItem.type === "Input") {
return (
);
} else if (signupItem.type === "Single Choice" || signupItem.type === "Multiple Choices") {
return (
);
}
}
}
renderForm(application) {
if (!application.enableSignUp) {
return (
Setting.redirectToLoginPage(application, this.props.history)}>
{
i18next.t("login:Sign In")
}
,
]}
>
);
}
if (this.state.invitation !== undefined) {
if (this.state.invitation.username !== "") {
this.form.current?.setFieldValue("username", this.state.invitation.username);
}
if (this.state.invitation.email !== "") {
this.form.current?.setFieldValue("email", this.state.invitation.email);
}
if (this.state.invitation.phone !== "") {
this.form.current?.setFieldValue("phone", this.state.invitation.phone);
}
if (this.state.invitationCode !== "") {
this.form.current?.setFieldValue("invitationCode", this.state.invitationCode);
}
}
return (
{
application.signupItems?.map((signupItem, idx) => {
return (
" + signupItem.customCss + "")}} />
{this.renderFormItem(application, signupItem)}
);
})
}
);
}
render() {
const application = this.getApplicationObj();
if (application === undefined || application === null) {
return null;
}
let existSignupButton = false;
application.signupItems?.map(item => {
item.name === "Signup button" ? existSignupButton = true : null;
});
if (!existSignupButton) {
application.signupItems?.push({
customCss: "",
label: "",
name: "Signup button",
placeholder: "",
visible: true,
});
}
if (application.signupHtml !== "") {
return (
);
}
return (
{Setting.inIframe() || Setting.isMobile() ? null :
}
{Setting.inIframe() || !Setting.isMobile() ? null :
}
{
Setting.renderHelmet(application)
}
{
Setting.renderLogo(application)
}
{
this.renderForm(application)
}
);
}
}
export default withRouter(SignupPage);