feat: Add Invitation Code to Generate Invitation Link (#2666)

Add auto-population of invitation fields in the registration page based on the invitation code in the link
This commit is contained in:
HGZ-20
2024-02-02 21:12:56 +08:00
committed by GitHub
parent bbbda1982f
commit b7be1943fa
56 changed files with 269 additions and 27 deletions

View File

@ -19,6 +19,7 @@ import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import copy from "copy-to-clipboard";
const {Option} = Select;
@ -99,6 +100,18 @@ class InvitationEditPage extends React.Component {
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}    
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
<Button style={{marginLeft: "20px"}} onClick={() => {
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")}
</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
@ -140,10 +153,24 @@ class InvitationEditPage extends React.Component {
</Col>
<Col span={22} >
<Input value={this.state.invitation.code} onChange={e => {
const regex = /[^a-zA-Z0-9]/;
if (!regex.test(e.target.value)) {
this.updateInvitationField("defaultCode", e.target.value);
}
this.updateInvitationField("code", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("invitation:Default code"), i18next.t("invitation:Default code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.invitation.defaultCode} onChange={e => {
this.updateInvitationField("defaultCode", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("invitation:Quota"), i18next.t("invitation:Quota - Tooltip"))} :
@ -274,6 +301,18 @@ class InvitationEditPage extends React.Component {
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
<Button style={{marginLeft: "20px"}} size="large" onClick={() => {
let defaultApplication;
if (this.state.invitation.owner === "built-in") {
defaultApplication = "app-built-in";
} else {
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
}
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")}
</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div>

View File

@ -22,19 +22,20 @@ import * as InvitationBackend from "./backend/InvitationBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./common/modal/PopconfirmModal";
import copy from "copy-to-clipboard";
class InvitationListPage extends BaseListPage {
newInvitation() {
const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
const code = Math.random().toString(36).slice(-10);
return {
owner: owner,
name: `invitation_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
displayName: `New Invitation - ${randomName}`,
code: Math.random().toString(36).slice(-10),
code: code,
defaultCode: code,
quota: 1,
usedCount: 0,
application: "All",
@ -225,17 +226,11 @@ class InvitationListPage extends BaseListPage {
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "350px",
width: "180px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => {
copy(`${window.location.origin}/login/${record.owner}?invitation_code=${record.code}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")}
</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/invitations/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}

View File

@ -29,6 +29,7 @@ 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: {
@ -93,6 +94,15 @@ class SignupPage extends React.Component {
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 {
@ -133,6 +143,17 @@ class SignupPage extends React.Component {
});
}
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
@ -235,7 +256,7 @@ class SignupPage extends React.Component {
},
]}
>
<Input placeholder={signupItem.placeholder} />
<Input placeholder={signupItem.placeholder} disabled={this.state.invitation !== undefined && this.state.invitation.username !== ""} />
</Form.Item>
);
} else if (signupItem.name === "Display name") {
@ -363,7 +384,7 @@ class SignupPage extends React.Component {
},
]}
>
<Input placeholder={signupItem.placeholder} onChange={e => this.setState({email: e.target.value})} />
<Input placeholder={signupItem.placeholder} disabled={this.state.invitation !== undefined && this.state.invitation.email !== ""} onChange={e => this.setState({email: e.target.value})} />
</Form.Item>
{
signupItem.rule !== "No verification" &&
@ -434,6 +455,7 @@ class SignupPage extends React.Component {
<Input
placeholder={signupItem.placeholder}
style={{width: "65%"}}
disabled={this.state.invitation !== undefined && this.state.invitation.phone !== ""}
onChange={e => this.setState({phone: e.target.value})}
/>
</Form.Item>
@ -524,7 +546,7 @@ class SignupPage extends React.Component {
},
]}
>
<Input placeholder={signupItem.placeholder} />
<Input placeholder={signupItem.placeholder} disabled={this.state.invitation !== undefined && this.state.invitation !== ""} />
</Form.Item>
);
} else if (signupItem.name === "Agreement") {
@ -554,6 +576,20 @@ class SignupPage extends React.Component {
</Result>
);
}
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 (
<Form
{...formItemLayout}

View File

@ -34,6 +34,16 @@ export function getInvitation(owner, name) {
}).then(res => res.json());
}
export function getInvitationCodeInfo(code, applicationName) {
return fetch(`${Setting.ServerUrl}/api/get-invitation-info?code=${code}&applicationId=${encodeURIComponent(applicationName)}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function updateInvitation(owner, name, invitation) {
const newInvitation = Setting.deepCopy(invitation);
return fetch(`${Setting.ServerUrl}/api/update-invitation?id=${owner}/${encodeURIComponent(name)}`, {

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -390,6 +390,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Can be a single string as an invitation code, or a regular expression. All strings matching the regular expression are valid invitation codes",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -388,6 +388,8 @@
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Default code": "Default code",
"Default code - Tooltip": "When the invitation code is a regular expression, please enter the invitation code that matches the regular expression rule as the default invitation code for the invitation link",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",

View File

@ -390,6 +390,8 @@
"invitation": {
"Code": "邀请码",
"Code - Tooltip": "可以是一个单独的字符串作为邀请码,也可以是正则表达式,所有符合正则表达式的字符串都是合法的邀请码",
"Default code": "默认邀请码",
"Default code - Tooltip": "当邀请码为正则表达式时,请输入符合正则表达式规则的邀请码作为邀请链接的默认邀请码",
"Edit Invitation": "编辑邀请码",
"New Invitation": "新建邀请码",
"Quota": "配额",