Compare commits

...

6 Commits

44 changed files with 524 additions and 244 deletions

View File

@@ -27,18 +27,20 @@ type Product struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Image string `xorm:"varchar(100)" json:"image"`
Detail string `xorm:"varchar(1000)" json:"detail"`
Description string `xorm:"varchar(200)" json:"description"`
Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"`
Price float64 `json:"price"`
Quantity int `json:"quantity"`
Sold int `json:"sold"`
IsRecharge bool `json:"isRecharge"`
Providers []string `xorm:"varchar(255)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
SuccessUrl string `xorm:"varchar(1000)" json:"successUrl"`
Image string `xorm:"varchar(100)" json:"image"`
Detail string `xorm:"varchar(1000)" json:"detail"`
Description string `xorm:"varchar(200)" json:"description"`
Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"`
Price float64 `json:"price"`
Quantity int `json:"quantity"`
Sold int `json:"sold"`
IsRecharge bool `json:"isRecharge"`
RechargeOptions []float64 `xorm:"varchar(500)" json:"rechargeOptions"`
DisableCustomRecharge bool `json:"disableCustomRecharge"`
Providers []string `xorm:"varchar(255)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
SuccessUrl string `xorm:"varchar(1000)" json:"successUrl"`
State string `xorm:"varchar(100)" json:"state"`
@@ -107,7 +109,6 @@ func UpdateProduct(id string, product *Product) (bool, error) {
} else if p == nil {
return false, nil
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(product)
if err != nil {
return false, err

View File

@@ -73,6 +73,11 @@ func CorsFilter(ctx *context.Context) {
return
}
if ctx.Request.Method == "POST" && ctx.Request.RequestURI == "/api/acs" {
setCorsHeaders(ctx, origin)
return
}
if ctx.Request.RequestURI == "/api/userinfo" {
setCorsHeaders(ctx, origin)
return

View File

@@ -116,13 +116,16 @@ class OrderEditPage extends React.Component {
}
renderOrder() {
const isViewMode = this.state.mode === "view";
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("order:New Order") : i18next.t("order:Edit Order")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitOrderEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitOrderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteOrder()}>{i18next.t("general:Cancel")}</Button> : null}
{this.state.mode === "add" ? i18next.t("order:New Order") : (isViewMode ? i18next.t("order:View Order") : i18next.t("order:Edit Order"))}&nbsp;&nbsp;&nbsp;&nbsp;
{!isViewMode && (<>
<Button onClick={() => this.submitOrderEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitOrderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteOrder()}>{i18next.t("general:Cancel")}</Button> : null}
</>)}
</div>
} style={{marginLeft: "5px"}} type="inner">
<Row style={{marginTop: "10px"}} >
@@ -138,7 +141,7 @@ class OrderEditPage extends React.Component {
{i18next.t("general:Name")}:
</Col>
<Col span={22} >
<Input value={this.state.order.name} onChange={e => {
<Input value={this.state.order.name} disabled={isViewMode} onChange={e => {
this.updateOrderField("name", e.target.value);
}} />
</Col>
@@ -148,7 +151,7 @@ class OrderEditPage extends React.Component {
{i18next.t("general:Display name")}:
</Col>
<Col span={22} >
<Input value={this.state.order.displayName} onChange={e => {
<Input value={this.state.order.displayName} disabled={isViewMode} onChange={e => {
this.updateOrderField("displayName", e.target.value);
}} />
</Col>
@@ -158,7 +161,7 @@ class OrderEditPage extends React.Component {
{i18next.t("order:Product")}:
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.order.productName} onChange={(value) => {
<Select virtual={false} style={{width: "100%"}} value={this.state.order.productName} disabled={isViewMode} onChange={(value) => {
this.updateOrderField("productName", value);
}}>
{
@@ -172,7 +175,7 @@ class OrderEditPage extends React.Component {
{i18next.t("general:User")}:
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.order.user} onChange={(value) => {
<Select virtual={false} style={{width: "100%"}} value={this.state.order.user} disabled={isViewMode} onChange={(value) => {
this.updateOrderField("user", value);
}}>
{
@@ -186,7 +189,7 @@ class OrderEditPage extends React.Component {
{i18next.t("order:Payment")}:
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.order.payment} onChange={(value) => {
<Select virtual={false} style={{width: "100%"}} value={this.state.order.payment} disabled={isViewMode} onChange={(value) => {
this.updateOrderField("payment", value);
}}>
<Option value="">{"(empty)"}</Option>
@@ -201,7 +204,7 @@ class OrderEditPage extends React.Component {
{i18next.t("general:State")}:
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.order.state} onChange={(value) => {
<Select virtual={false} style={{width: "100%"}} value={this.state.order.state} disabled={isViewMode} onChange={(value) => {
this.updateOrderField("state", value);
}}>
{
@@ -294,11 +297,13 @@ class OrderEditPage extends React.Component {
{
this.state.order !== null ? this.renderOrder() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitOrderEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitOrderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteOrder()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
{this.state.mode !== "view" && (
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitOrderEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitOrderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteOrder()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
)}
</div>
);
}

View File

@@ -163,6 +163,24 @@ class OrderListPage extends BaseListPage {
);
},
},
{
title: i18next.t("order:Payment"),
dataIndex: "payment",
key: "payment",
width: "140px",
sorter: true,
...this.getColumnSearchProps("payment"),
render: (text, record, index) => {
if (text === "") {
return "(empty)";
}
return (
<Link to={`/payments/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:User"),
dataIndex: "user",
@@ -219,16 +237,18 @@ class OrderListPage extends BaseListPage {
width: "240px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div style={{display: "flex", flexWrap: "wrap", gap: "8px"}}>
<Button onClick={() => this.props.history.push(`/orders/${record.owner}/${record.name}/pay`)} disabled={record.state !== "Created"}>
{i18next.t("order:Pay")}
</Button>
<Button danger onClick={() => this.cancelOrder(record)} disabled={record.state !== "Created"}>
<Button danger onClick={() => this.cancelOrder(record)} disabled={record.state !== "Created" || !isAdmin}>
{i18next.t("general:Cancel")}
</Button>
<Button type="primary" onClick={() => this.props.history.push(`/orders/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button type="primary" onClick={() => this.props.history.push({pathname: `/orders/${record.owner}/${record.name}`, mode: isAdmin ? "edit" : "view"})}>{isAdmin ? i18next.t("general:Edit") : i18next.t("general:View")}</Button>
<PopconfirmModal
disabled={!isAdmin}
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteOrder(index)}
>
@@ -249,12 +269,15 @@ class OrderListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={orders} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Orders")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addOrder.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
title={() => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
{i18next.t("general:Orders")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" disabled={!isAdmin} onClick={this.addOrder.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
);
}}
loading={this.state.loading}
onChange={this.handleTableChange}
/>

View File

@@ -149,13 +149,16 @@ class PaymentEditPage extends React.Component {
}
renderPayment() {
const isViewMode = this.state.mode === "view";
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("payment:New Payment") : i18next.t("payment:Edit Payment")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
{this.state.mode === "add" ? i18next.t("payment:New Payment") : (isViewMode ? i18next.t("payment:View Payment") : i18next.t("payment:Edit Payment"))}&nbsp;&nbsp;&nbsp;&nbsp;
{!isViewMode && (<>
<Button onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
</>)}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
@@ -267,7 +270,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Person name"), i18next.t("payment:Person name - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personName} onChange={e => {
<Input disabled={isViewMode || this.state.payment.invoiceUrl !== ""} value={this.state.payment.personName} onChange={e => {
this.updatePaymentField("personName", e.target.value);
if (this.state.payment.invoiceType === "Individual") {
this.updatePaymentField("invoiceTitle", e.target.value);
@@ -281,7 +284,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Person ID card"), i18next.t("payment:Person ID card - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personIdCard} onChange={e => {
<Input disabled={isViewMode || this.state.payment.invoiceUrl !== ""} value={this.state.payment.personIdCard} onChange={e => {
this.updatePaymentField("personIdCard", e.target.value);
}} />
</Col>
@@ -291,7 +294,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Person Email"), i18next.t("payment:Person Email - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personEmail} onChange={e => {
<Input disabled={isViewMode || this.state.payment.invoiceUrl !== ""} value={this.state.payment.personEmail} onChange={e => {
this.updatePaymentField("personEmail", e.target.value);
}} />
</Col>
@@ -301,7 +304,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Person phone"), i18next.t("payment:Person phone - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personPhone} onChange={e => {
<Input disabled={isViewMode || this.state.payment.invoiceUrl !== ""} value={this.state.payment.personPhone} onChange={e => {
this.updatePaymentField("personPhone", e.target.value);
}} />
</Col>
@@ -311,7 +314,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Invoice type"), i18next.t("payment:Invoice type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} disabled={this.state.payment.invoiceUrl !== ""} style={{width: "100%"}} value={this.state.payment.invoiceType} onChange={(value => {
<Select virtual={false} disabled={isViewMode || this.state.payment.invoiceUrl !== ""} style={{width: "100%"}} value={this.state.payment.invoiceType} onChange={(value => {
this.updatePaymentField("invoiceType", value);
if (value === "Individual") {
this.updatePaymentField("invoiceTitle", this.state.payment.personName);
@@ -332,7 +335,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Invoice title"), i18next.t("payment:Invoice title - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTitle} onChange={e => {
<Input disabled={isViewMode || this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTitle} onChange={e => {
this.updatePaymentField("invoiceTitle", e.target.value);
}} />
</Col>
@@ -342,7 +345,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Invoice tax ID"), i18next.t("payment:Invoice tax ID - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTaxId} onChange={e => {
<Input disabled={isViewMode || this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTaxId} onChange={e => {
this.updatePaymentField("invoiceTaxId", e.target.value);
}} />
</Col>
@@ -352,7 +355,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Invoice remark"), i18next.t("payment:Invoice remark - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.invoiceRemark} onChange={e => {
<Input disabled={isViewMode || this.state.payment.invoiceUrl !== ""} value={this.state.payment.invoiceRemark} onChange={e => {
this.updatePaymentField("invoiceRemark", e.target.value);
}} />
</Col>
@@ -496,11 +499,13 @@ class PaymentEditPage extends React.Component {
{
this.renderModal()
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
{this.state.mode !== "view" && (
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
)}
</div>
);
}

View File

@@ -223,11 +223,13 @@ class PaymentListPage extends BaseListPage {
width: "240px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/payments/${record.owner}/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/payments/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push({pathname: `/payments/${record.owner}/${record.name}`, mode: isAdmin ? "edit" : "view"})}>{isAdmin ? i18next.t("general:Edit") : i18next.t("general:View")}</Button>
<PopconfirmModal
disabled={!isAdmin}
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deletePayment(index)}
>
@@ -248,12 +250,15 @@ class PaymentListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={payments} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Payments")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addPayment.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
title={() => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
{i18next.t("general:Payments")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" disabled={!isAdmin} onClick={this.addPayment.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
);
}}
loading={this.state.loading}
onChange={this.handleTableChange}
/>

View File

@@ -132,13 +132,16 @@ class PlanEditPage extends React.Component {
}
renderPlan() {
const isViewMode = this.state.mode === "view";
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("plan:New Plan") : i18next.t("plan:Edit Plan")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitPlanEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPlanEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePlan()}>{i18next.t("general:Cancel")}</Button> : null}
{this.state.mode === "add" ? i18next.t("plan:New Plan") : (isViewMode ? i18next.t("plan:View Plan") : i18next.t("plan:Edit Plan"))}&nbsp;&nbsp;&nbsp;&nbsp;
{!isViewMode && (<>
<Button onClick={() => this.submitPlanEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPlanEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePlan()}>{i18next.t("general:Cancel")}</Button> : null}
</>)}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
@@ -146,7 +149,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.owner} onChange={(owner => {
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.owner} disabled={isViewMode} onChange={(owner => {
this.updatePlanField("owner", owner);
this.getUsers(owner);
this.getRoles(owner);
@@ -161,7 +164,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.plan.name} onChange={e => {
<Input value={this.state.plan.name} disabled={isViewMode} onChange={e => {
this.updatePlanField("name", e.target.value);
}} />
</Col>
@@ -171,7 +174,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.plan.displayName} onChange={e => {
<Input value={this.state.plan.displayName} disabled={isViewMode} onChange={e => {
this.updatePlanField("displayName", e.target.value);
}} />
</Col>
@@ -181,7 +184,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Role"), i18next.t("general:Role - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.role} onChange={(value => {this.updatePlanField("role", value);})}
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.role} disabled={isViewMode} onChange={(value => {this.updatePlanField("role", value);})}
options={this.state.roles.map((role) => Setting.getOption(role.name, role.name))
} />
</Col>
@@ -191,7 +194,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.plan.description} onChange={e => {
<Input value={this.state.plan.description} disabled={isViewMode} onChange={e => {
this.updatePlanField("description", e.target.value);
}} />
</Col>
@@ -201,7 +204,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("plan:Price"), i18next.t("plan:Price - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.plan.price} onChange={value => {
<InputNumber value={this.state.plan.price} disabled={isViewMode} onChange={value => {
this.updatePlanField("price", value);
}} />
</Col>
@@ -211,7 +214,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("plan:Period"), i18next.t("plan:Period - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.period} onChange={value => {
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.period} disabled={isViewMode} onChange={value => {
this.updatePlanField("period", value);
}}
options={[
@@ -226,7 +229,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.currency} onChange={(value => {
<Select virtual={false} style={{width: "100%"}} value={this.state.plan.currency} disabled={isViewMode} onChange={(value => {
this.updatePlanField("currency", value);
})}>
{
@@ -240,7 +243,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.plan.paymentProviders ?? []} onChange={(value => {this.updatePlanField("paymentProviders", value);})}>
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.plan.paymentProviders ?? []} disabled={isViewMode} onChange={(value => {this.updatePlanField("paymentProviders", value);})}>
{
this.state.paymentProviders.map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
}
@@ -252,7 +255,7 @@ class PlanEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.plan.isEnabled} onChange={checked => {
<Switch checked={this.state.plan.isEnabled} disabled={isViewMode} onChange={checked => {
this.updatePlanField("isEnabled", checked);
}} />
</Col>
@@ -306,11 +309,13 @@ class PlanEditPage extends React.Component {
{
this.state.plan !== null ? this.renderPlan() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPlanEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPlanEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePlan()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
{this.state.mode !== "view" && (
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPlanEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPlanEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePlan()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
)}
</div>
);
}

View File

@@ -201,10 +201,12 @@ class PlanListPage extends BaseListPage {
width: "200px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/plans/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push({pathname: `/plans/${record.owner}/${record.name}`, mode: isAdmin ? "edit" : "view"})}>{isAdmin ? i18next.t("general:Edit") : i18next.t("general:View")}</Button>
<PopconfirmModal
disabled={!isAdmin}
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deletePlan(index)}
>
@@ -225,12 +227,15 @@ class PlanListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={plans} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Plans")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addPlan.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
title={() => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
{i18next.t("general:Plans")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" disabled={!isAdmin} onClick={this.addPlan.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
);
}}
loading={this.state.loading}
onChange={this.handleTableChange}
/>

View File

@@ -116,13 +116,16 @@ class PricingEditPage extends React.Component {
}
renderPricing() {
const isViewMode = this.state.mode === "view";
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("pricing:New Pricing") : i18next.t("pricing:Edit Pricing")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitPricingEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPricingEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePricing()}>{i18next.t("general:Cancel")}</Button> : null}
{this.state.mode === "add" ? i18next.t("pricing:New Pricing") : (isViewMode ? i18next.t("pricing:View Pricing") : i18next.t("pricing:Edit Pricing"))}&nbsp;&nbsp;&nbsp;&nbsp;
{!isViewMode && (<>
<Button onClick={() => this.submitPricingEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPricingEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePricing()}>{i18next.t("general:Cancel")}</Button> : null}
</>)}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
@@ -130,7 +133,7 @@ class PricingEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.pricing.owner} onChange={(owner => {
<Select virtual={false} style={{width: "100%"}} value={this.state.pricing.owner} disabled={isViewMode} onChange={(owner => {
this.updatePricingField("owner", owner);
this.getApplicationsByOrganization(owner);
this.getPlans(owner);
@@ -144,7 +147,7 @@ class PricingEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.pricing.name} onChange={e => {
<Input value={this.state.pricing.name} disabled={isViewMode} onChange={e => {
this.updatePricingField("name", e.target.value);
}} />
</Col>
@@ -154,7 +157,7 @@ class PricingEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.pricing.displayName} onChange={e => {
<Input value={this.state.pricing.displayName} disabled={isViewMode} onChange={e => {
this.updatePricingField("displayName", e.target.value);
}} />
</Col>
@@ -164,7 +167,7 @@ class PricingEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.pricing.description} onChange={e => {
<Input value={this.state.pricing.description} disabled={isViewMode} onChange={e => {
this.updatePricingField("description", e.target.value);
}} />
</Col>
@@ -175,6 +178,7 @@ class PricingEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.pricing.application}
disabled={isViewMode}
onChange={(value => {this.updatePricingField("application", value);})}
options={this.state.applications.map((application) => Setting.getOption(application.name, application.name))
} />
@@ -186,6 +190,7 @@ class PricingEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.pricing.plans}
disabled={isViewMode}
onChange={(value => {
this.updatePricingField("plans", value);
})}
@@ -198,7 +203,7 @@ class PricingEditPage extends React.Component {
{Setting.getLabel(i18next.t("pricing:Trial duration"), i18next.t("pricing:Trial duration - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber min={0} value={this.state.pricing.trialDuration} onChange={value => {
<InputNumber min={0} value={this.state.pricing.trialDuration} disabled={isViewMode} onChange={value => {
this.updatePricingField("trialDuration", value);
}} />
</Col>
@@ -208,7 +213,7 @@ class PricingEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.pricing.isEnabled} onChange={checked => {
<Switch checked={this.state.pricing.isEnabled} disabled={isViewMode} onChange={checked => {
this.updatePricingField("isEnabled", checked);
}} />
</Col>
@@ -270,11 +275,13 @@ class PricingEditPage extends React.Component {
{
this.state.pricing !== null ? this.renderPricing() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPricingEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPricingEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePricing()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
{this.state.mode !== "view" && (
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPricingEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPricingEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePricing()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
)}
</div>
);
}

View File

@@ -192,10 +192,12 @@ class PricingListPage extends BaseListPage {
width: "230px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/pricings/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push({pathname: `/pricings/${record.owner}/${record.name}`, mode: isAdmin ? "edit" : "view"})}>{isAdmin ? i18next.t("general:Edit") : i18next.t("general:View")}</Button>
<PopconfirmModal
disabled={!isAdmin}
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deletePricing(index)}
>
@@ -216,12 +218,15 @@ class PricingListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={pricings} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Pricings")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addPricing.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
title={() => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
{i18next.t("general:Pricings")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" disabled={!isAdmin} onClick={this.addPricing.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
);
}}
loading={this.state.loading}
onChange={this.handleTableChange}
/>

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Descriptions, InputNumber, Space, Spin} from "antd";
import {Button, Descriptions, Divider, InputNumber, Radio, Space, Spin, Typography} from "antd";
import i18next from "i18next";
import * as ProductBackend from "./backend/ProductBackend";
import * as PlanBackend from "./backend/PlanBackend";
@@ -105,6 +105,12 @@ class ProductBuyPage extends React.Component {
this.setState({
product: res.data,
});
if (res.data.isRecharge && res.data.rechargeOptions?.length > 0) {
this.setState({
customPrice: res.data.rechargeOptions[0],
});
}
} catch (err) {
Setting.showMessage("error", err.message);
return;
@@ -153,6 +159,58 @@ class ProductBuyPage extends React.Component {
});
}
renderRechargeInput(product) {
const hasOptions = product.rechargeOptions && product.rechargeOptions.length > 0;
const disableCustom = product.disableCustomRecharge;
if (!hasOptions && disableCustom) {
return (
<Typography.Text type="danger">
{i18next.t("product:This product is currently not purchasable (No options available)")}
</Typography.Text>
);
}
return (
<Space direction="vertical" style={{width: "100%"}}>
{hasOptions && (
<>
<div>
<span style={{marginRight: "10px", fontSize: 16}}>
{i18next.t("product:Select amount")}:
</span>
<Radio.Group
value={this.state.customPrice}
onChange={(e) => {this.setState({customPrice: e.target.value});}}
>
<Space wrap>
{product.rechargeOptions.map((amount, index) => (
<Radio.Button key={index} value={amount}>
{Setting.getCurrencySymbol(product.currency)}{amount}
</Radio.Button>
))}
</Space>
</Radio.Group>
</div>
{!disableCustom && <Divider style={{margin: "10px 0"}}>{i18next.t("general:Or")}</Divider>}
</>
)}
<Space>
<span style={{fontSize: 16}}>
{i18next.t("product:Amount")}:
</span>
<InputNumber
min={0}
value={this.state.customPrice}
onChange={(e) => {this.setState({customPrice: e});}}
disabled={disableCustom}
/>
<span style={{fontSize: 16}}>{Setting.getCurrencyText(product)}</span>
</Space>
</Space>
);
}
renderPlaceOrderButton(product) {
if (product === undefined || product === null) {
return null;
@@ -162,6 +220,10 @@ class ProductBuyPage extends React.Component {
return i18next.t("product:This product is currently not in sale.");
}
const hasOptions = product.rechargeOptions && product.rechargeOptions.length > 0;
const disableCustom = product.disableCustomRecharge;
const isRechargeUnpurchasable = product.isRecharge && !hasOptions && disableCustom;
return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Button
@@ -175,7 +237,7 @@ class ProductBuyPage extends React.Component {
paddingRight: "60px",
}}
onClick={() => this.placeOrder(product)}
disabled={this.state.isPlacingOrder}
disabled={this.state.isPlacingOrder || isRechargeUnpurchasable}
loading={this.state.isPlacingOrder}
>
{i18next.t("order:Place Order")}
@@ -210,9 +272,7 @@ class ProductBuyPage extends React.Component {
{
product.isRecharge ? (
<Descriptions.Item span={3} label={i18next.t("product:Price")}>
<Space>
<InputNumber min={0} value={this.state.customPrice} onChange={(e) => {this.setState({customPrice: e});}} /> {Setting.getCurrencyText(product)}
</Space>
{this.renderRechargeInput(product)}
</Descriptions.Item>
) : (
<React.Fragment>

View File

@@ -99,13 +99,16 @@ class ProductEditPage extends React.Component {
renderProduct() {
const isCreatedByPlan = this.state.product.tag === "auto_created_product_for_plan";
const isViewMode = this.state.mode === "view";
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("product:New Product") : i18next.t("product:Edit Product")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
{this.state.mode === "add" ? i18next.t("product:New Product") : (isViewMode ? i18next.t("product:View Product") : i18next.t("product:Edit Product"))}&nbsp;&nbsp;&nbsp;&nbsp;
{!isViewMode && (<>
<Button onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
</>)}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
@@ -113,7 +116,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.product.owner} onChange={(value => {this.updateProductField("owner", value);})}>
<Select virtual={false} style={{width: "100%"}} disabled={isViewMode || !Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.product.owner} onChange={(value => {this.updateProductField("owner", value);})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
@@ -125,7 +128,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.name} disabled={isCreatedByPlan} onChange={e => {
<Input value={this.state.product.name} disabled={isViewMode || isCreatedByPlan} onChange={e => {
this.updateProductField("name", e.target.value);
}} />
</Col>
@@ -135,7 +138,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.displayName} onChange={e => {
<Input value={this.state.product.displayName} disabled={isViewMode} onChange={e => {
this.updateProductField("displayName", e.target.value);
}} />
</Col>
@@ -150,7 +153,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined />} value={this.state.product.image} onChange={e => {
<Input prefix={<LinkOutlined />} value={this.state.product.image} disabled={isViewMode} onChange={e => {
this.updateProductField("image", e.target.value);
}} />
</Col>
@@ -172,7 +175,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("product:Tag - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.tag} disabled={isCreatedByPlan} onChange={e => {
<Input value={this.state.product.tag} disabled={isViewMode || isCreatedByPlan} onChange={e => {
this.updateProductField("tag", e.target.value);
}} />
</Col>
@@ -182,7 +185,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Detail"), i18next.t("product:Detail - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.detail} onChange={e => {
<Input value={this.state.product.detail} disabled={isViewMode} onChange={e => {
this.updateProductField("detail", e.target.value);
}} />
</Col>
@@ -192,7 +195,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.description} onChange={e => {
<Input value={this.state.product.description} disabled={isViewMode} onChange={e => {
this.updateProductField("description", e.target.value);
}} />
</Col>
@@ -202,7 +205,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.product.currency} disabled={isCreatedByPlan} onChange={(value => {
<Select virtual={false} style={{width: "100%"}} value={this.state.product.currency} disabled={isViewMode || isCreatedByPlan} onChange={(value => {
this.updateProductField("currency", value);
})}>
{
@@ -216,19 +219,57 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Is recharge"), i18next.t("product:Is recharge - Tooltip"))} :
</Col>
<Col span={22} >
<Switch checked={this.state.product.isRecharge} onChange={value => {
<Switch checked={this.state.product.isRecharge} disabled={isViewMode} onChange={value => {
this.updateProductField("isRecharge", value);
if (value) {
this.updateProductField("price", 0);
this.updateProductField("disableCustomRecharge", false);
this.updateProductField("rechargeOptions", []);
}
}} />
</Col>
</Row>
{
this.state.product.isRecharge ? null : (
this.state.product.isRecharge ? (
<>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Disable custom amount"), i18next.t("product:Disable custom amount - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.product.disableCustomRecharge} disabled={isViewMode} onChange={value => {
this.updateProductField("disableCustomRecharge", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Recharge options"), i18next.t("product:Recharge options - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}}
disabled={isViewMode}
placeholder={i18next.t("product:Enter preset amounts")}
value={(this.state.product.rechargeOptions || []).map(v => String(v))}
onChange={(values => {
const numbers = values
.map(v => parseFloat(v))
.filter(v => !isNaN(v) && v > 0)
.filter((v, i, arr) => arr.indexOf(v) === i)
.sort((a, b) => a - b);
this.updateProductField("rechargeOptions", numbers);
})}>
</Select>
</Col>
</Row>
</>
) : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Price"), i18next.t("product:Price - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.product.price} disabled={isCreatedByPlan} onChange={value => {
<InputNumber value={this.state.product.price} disabled={isViewMode || isCreatedByPlan} onChange={value => {
this.updateProductField("price", value);
}} />
</Col>
@@ -239,7 +280,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Quantity"), i18next.t("product:Quantity - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.product.quantity} disabled={isCreatedByPlan} onChange={value => {
<InputNumber value={this.state.product.quantity} disabled={isViewMode || isCreatedByPlan} onChange={value => {
this.updateProductField("quantity", value);
}} />
</Col>
@@ -249,7 +290,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Sold"), i18next.t("product:Sold - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.product.sold} disabled={isCreatedByPlan} onChange={value => {
<InputNumber value={this.state.product.sold} disabled={isViewMode || isCreatedByPlan} onChange={value => {
this.updateProductField("sold", value);
}} />
</Col>
@@ -259,7 +300,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} disabled={isCreatedByPlan} value={this.state.product.providers} onChange={(value => {this.updateProductField("providers", value);})}>
<Select virtual={false} mode="multiple" style={{width: "100%"}} disabled={isViewMode || isCreatedByPlan} value={this.state.product.providers} onChange={(value => {this.updateProductField("providers", value);})}>
{
this.state.providers.map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
}
@@ -271,7 +312,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Return URL"), i18next.t("product:Return URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={this.state.product.returnUrl} onChange={e => {
<Input prefix={<LinkOutlined />} value={this.state.product.returnUrl} disabled={isViewMode} onChange={e => {
this.updateProductField("returnUrl", e.target.value);
}} />
</Col>
@@ -281,7 +322,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("product:Success URL"), i18next.t("product:Success URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={this.state.product.successUrl} onChange={e => {
<Input prefix={<LinkOutlined />} value={this.state.product.successUrl} disabled={isViewMode} onChange={e => {
this.updateProductField("successUrl", e.target.value);
}} />
</Col>
@@ -291,7 +332,7 @@ class ProductEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.product.state} onChange={(value => {
<Select virtual={false} style={{width: "100%"}} value={this.state.product.state} disabled={isViewMode} onChange={(value => {
this.updateProductField("state", value);
})}>
{
@@ -333,6 +374,11 @@ class ProductEditPage extends React.Component {
submitProductEdit(exitAfterSave) {
const product = Setting.deepCopy(this.state.product);
if (product.isRecharge && product.disableCustomRecharge && (!product.rechargeOptions || product.rechargeOptions.length === 0)) {
Setting.showMessage("error", i18next.t("product:Please add at least one recharge option when custom amount is disabled"));
return;
}
ProductBackend.updateProduct(this.state.organizationName, this.state.productName, product)
.then((res) => {
if (res.status === "ok") {
@@ -376,11 +422,13 @@ class ProductEditPage extends React.Component {
{
this.state.product !== null ? this.renderProduct() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
{this.state.mode !== "view" && (
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
)}
</div>
);
}

View File

@@ -260,12 +260,13 @@ class ProductListPage extends BaseListPage {
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
const isCreatedByPlan = record.tag === "auto_created_product_for_plan";
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.owner}/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push({pathname: `/products/${record.owner}/${record.name}`, mode: isAdmin ? "edit" : "view"})}>{isAdmin ? i18next.t("general:Edit") : i18next.t("general:View")}</Button>
<PopconfirmModal
disabled={isCreatedByPlan}
disabled={isCreatedByPlan || !isAdmin}
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteProduct(index)}
>
@@ -286,12 +287,15 @@ class ProductListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={products} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Products")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addProduct.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
title={() => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
{i18next.t("general:Products")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" disabled={!isAdmin} onClick={this.addProduct.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
);
}}
loading={this.state.loading}
onChange={this.handleTableChange}
/>

View File

@@ -90,26 +90,30 @@ class ProductStorePage extends React.Component {
bodyStyle={{flex: 1, display: "flex", flexDirection: "column"}}
>
<div style={{flex: 1, display: "flex", flexDirection: "column"}}>
<Title level={5} ellipsis={{rows: 2}} style={{margin: "0 0 4px 0", minHeight: "44px"}}>
<Title level={5} ellipsis={{rows: 2}} style={{margin: "0 0 12px 0", minHeight: "44px", fontWeight: 600}}>
{Setting.getLanguageText(product.displayName)}
</Title>
<Text style={{display: "block", marginBottom: 4, minHeight: "40px"}} ellipsis={{rows: 2}}>
{Setting.getLanguageText(product.detail)}
</Text>
{product.tag && (
<Tag color="blue" style={{marginBottom: 4, display: "inline-block"}}>{product.tag}</Tag>
{product.detail && (
<Text type="secondary" style={{display: "block", marginBottom: 12, fontSize: "13px", lineHeight: "1.5"}} ellipsis={{rows: 2}}>
{Setting.getLanguageText(product.detail)}
</Text>
)}
<div style={{marginTop: "auto"}}>
<div style={{marginBottom: 4}}>
<Text strong style={{fontSize: "24px", color: "#ff4d4f"}}>
{product.tag && (
<div style={{marginBottom: 12}}>
<Tag color="blue" style={{display: "inline-block", width: "fit-content", fontSize: "12px"}}>{product.tag}</Tag>
</div>
)}
<div style={{marginTop: "auto", paddingTop: 8}}>
<div style={{marginBottom: 8}}>
<Text strong style={{fontSize: "28px", color: "#ff4d4f", fontWeight: 600}}>
{Setting.getCurrencySymbol(product.currency)}{product.price}
</Text>
<Text type="secondary" style={{fontSize: "12px", marginLeft: 8}}>
<Text type="secondary" style={{fontSize: "13px", marginLeft: 8}}>
{Setting.getCurrencyWithFlag(product.currency)}
</Text>
</div>
<div>
<Text type="secondary" style={{fontSize: "12px"}}>
<Text type="secondary" style={{fontSize: "13px"}}>
{i18next.t("product:Sold")}: {product.sold}
</Text>
</div>

View File

@@ -76,7 +76,7 @@ class ResourceListPage extends BaseListPage {
renderUpload() {
return (
<Upload maxCount={1} accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.csv,.xls,.xlsx" showUploadList={false}
<Upload maxCount={1} showUploadList={false}
beforeUpload={file => {return false;}} onChange={info => {this.handleUpload(info);}}>
<Button id="upload-button" icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small">
{i18next.t("resource:Upload a file...")}

View File

@@ -128,13 +128,16 @@ class SubscriptionEditPage extends React.Component {
}
renderSubscription() {
const isViewMode = this.state.mode === "view";
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("subscription:New Subscription") : i18next.t("subscription:Edit Subscription")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitSubscriptionEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitSubscriptionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteSubscription()}>{i18next.t("general:Cancel")}</Button> : null}
{this.state.mode === "add" ? i18next.t("subscription:New Subscription") : (isViewMode ? i18next.t("subscription:View Subscription") : i18next.t("subscription:Edit Subscription"))}&nbsp;&nbsp;&nbsp;&nbsp;
{!isViewMode && (<>
<Button onClick={() => this.submitSubscriptionEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitSubscriptionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteSubscription()}>{i18next.t("general:Cancel")}</Button> : null}
</>)}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
@@ -142,7 +145,7 @@ class SubscriptionEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.subscription.owner} onChange={(owner => {
<Select virtual={false} style={{width: "100%"}} value={this.state.subscription.owner} disabled={isViewMode} onChange={(owner => {
this.updateSubscriptionField("owner", owner);
this.getUsers(owner);
this.getPlans(owner);
@@ -156,7 +159,7 @@ class SubscriptionEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.subscription.name} onChange={e => {
<Input value={this.state.subscription.name} disabled={isViewMode} onChange={e => {
this.updateSubscriptionField("name", e.target.value);
}} />
</Col>
@@ -166,7 +169,7 @@ class SubscriptionEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.subscription.displayName} onChange={e => {
<Input value={this.state.subscription.displayName} disabled={isViewMode} onChange={e => {
this.updateSubscriptionField("displayName", e.target.value);
}} />
</Col>
@@ -176,7 +179,7 @@ class SubscriptionEditPage extends React.Component {
{Setting.getLabel(i18next.t("subscription:Start time"), i18next.t("subscription:Start time - Tooltip"))}
</Col>
<Col span={22} >
<DatePicker value={dayjs(this.state.subscription.startTime)} onChange={value => {
<DatePicker value={dayjs(this.state.subscription.startTime)} disabled={isViewMode} onChange={value => {
this.updateSubscriptionField("startTime", value);
}} />
</Col>
@@ -186,7 +189,7 @@ class SubscriptionEditPage extends React.Component {
{Setting.getLabel(i18next.t("subscription:End time"), i18next.t("subscription:End time - Tooltip"))}
</Col>
<Col span={22} >
<DatePicker value={dayjs(this.state.subscription.endTime)} onChange={value => {
<DatePicker value={dayjs(this.state.subscription.endTime)} disabled={isViewMode} onChange={value => {
this.updateSubscriptionField("endTime", value);
}} />
</Col>
@@ -198,6 +201,7 @@ class SubscriptionEditPage extends React.Component {
<Col span={22} >
<Select
defaultValue={this.state.subscription.period === "" ? "Monthly" : this.state.subscription.period}
disabled={isViewMode}
onChange={value => {
this.updateSubscriptionField("period", value);
}}
@@ -214,6 +218,7 @@ class SubscriptionEditPage extends React.Component {
</Col>
<Col span={22} >
<Select style={{width: "100%"}} value={this.state.subscription.user}
disabled={isViewMode}
onChange={(value => {this.updateSubscriptionField("user", value);})}
options={this.state.users.map((user) => Setting.getOption(user.name, user.name))}
/>
@@ -225,6 +230,7 @@ class SubscriptionEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.subscription.pricing}
disabled={isViewMode}
onChange={(value => {this.updateSubscriptionField("pricing", value);})}
options={this.state.pricings.map((pricing) => Setting.getOption(pricing.name, pricing.name))
} />
@@ -236,6 +242,7 @@ class SubscriptionEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.subscription.plan}
disabled={isViewMode}
onChange={(value => {this.updateSubscriptionField("plan", value);})}
options={this.state.plans.map((plan) => Setting.getOption(plan.name, plan.name))
} />
@@ -256,7 +263,7 @@ class SubscriptionEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.subscription.description} onChange={e => {
<Input value={this.state.subscription.description} disabled={isViewMode} onChange={e => {
this.updateSubscriptionField("description", e.target.value);
}} />
</Col>
@@ -266,7 +273,7 @@ class SubscriptionEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} disabled={!Setting.isLocalAdminUser(this.props.account)} style={{width: "100%"}} value={this.state.subscription.state} onChange={(value => {
<Select virtual={false} disabled={isViewMode || !Setting.isLocalAdminUser(this.props.account)} style={{width: "100%"}} value={this.state.subscription.state} onChange={(value => {
if (this.state.subscription.state !== value) {
if (value === "Approved") {
this.updateSubscriptionField("approver", this.props.account.name);
@@ -339,11 +346,13 @@ class SubscriptionEditPage extends React.Component {
{
this.state.subscription !== null ? this.renderSubscription() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitSubscriptionEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitSubscriptionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteSubscription()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
{this.state.mode !== "view" && (
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitSubscriptionEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitSubscriptionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteSubscription()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
)}
</div>
);
}

View File

@@ -232,10 +232,12 @@ class SubscriptionListPage extends BaseListPage {
width: "230px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/subscriptions/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push({pathname: `/subscriptions/${record.owner}/${record.name}`, mode: isAdmin ? "edit" : "view"})}>{isAdmin ? i18next.t("general:Edit") : i18next.t("general:View")}</Button>
<PopconfirmModal
disabled={!isAdmin}
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteSubscription(index)}
>
@@ -256,12 +258,15 @@ class SubscriptionListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={subscriptions} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Subscriptions")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addSubscription.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
title={() => {
const isAdmin = Setting.isLocalAdminUser(this.props.account);
return (
<div>
{i18next.t("general:Subscriptions")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" disabled={!isAdmin} onClick={this.addSubscription.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
);
}}
loading={this.state.loading}
onChange={this.handleTableChange}
/>

View File

@@ -51,26 +51,38 @@ const formItemLayout = {
};
const renderFormItem = (signupItem) => {
const commonProps = {
name: signupItem.name.toLowerCase(),
label: signupItem.label || signupItem.name,
rules: [
{
required: signupItem.required,
message: i18next.t("signup:Please input your {label}!").replace("{label}", signupItem.label || signupItem.name),
},
],
};
const commonRules = [
{
required: signupItem.required,
message: i18next.t("signup:Please input your {label}!").replace("{label}", signupItem.label || signupItem.name),
},
];
if (!signupItem.type || signupItem.type === "Input") {
const inputRules = [...commonRules];
if (signupItem.regex) {
inputRules.push({
pattern: new RegExp(signupItem.regex),
message: i18next.t("signup:The input doesn't match the signup item regex!"),
});
}
return (
<Form.Item {...commonProps}>
<Form.Item
name={signupItem.name.toLowerCase()}
label={signupItem.label || signupItem.name}
rules={inputRules}
>
<Input placeholder={signupItem.placeholder} />
</Form.Item>
);
} else if (signupItem.type === "Single Choice" || signupItem.type === "Multiple Choices") {
return (
<Form.Item {...commonProps}>
<Form.Item
name={signupItem.name.toLowerCase()}
label={signupItem.label || signupItem.name}
rules={commonRules}
>
<Select
mode={signupItem.type === "Multiple Choices" ? "multiple" : "single"}
placeholder={signupItem.placeholder}
@@ -309,18 +321,25 @@ class SignupPage extends React.Component {
const required = signupItem.required;
if (signupItem.name === "Username") {
const usernameRules = [
{
required: required,
message: i18next.t("forget:Please input your username!"),
whitespace: true,
},
];
if (signupItem.regex) {
usernameRules.push({
pattern: new RegExp(signupItem.regex),
message: i18next.t("signup:The input doesn't match the signup item regex!"),
});
}
return (
<Form.Item
name="username"
className="signup-username"
label={signupItem.label ? signupItem.label : i18next.t("signup:Username")}
rules={[
{
required: required,
message: i18next.t("forget:Please input your username!"),
whitespace: true,
},
]}
rules={usernameRules}
>
<Input className="signup-username-input" placeholder={signupItem.placeholder}
disabled={this.state.invitation !== undefined && this.state.invitation.username !== ""} />
@@ -328,19 +347,35 @@ class SignupPage extends React.Component {
);
} else if (signupItem.name === "Display name") {
if (signupItem.rule === "First, last" && Setting.getLanguage() !== "zh") {
const firstNameRules = [
{
required: required,
message: i18next.t("signup:Please input your first name!"),
whitespace: true,
},
];
const lastNameRules = [
{
required: required,
message: i18next.t("signup:Please input your last name!"),
whitespace: true,
},
];
if (signupItem.regex) {
const regexRule = {
pattern: new RegExp(signupItem.regex),
message: i18next.t("signup:The input doesn't match the signup item regex!"),
};
firstNameRules.push(regexRule);
lastNameRules.push(regexRule);
}
return (
<React.Fragment>
<Form.Item
name="firstName"
className="signup-first-name"
label={signupItem.label ? signupItem.label : i18next.t("general:First name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your first name!"),
whitespace: true,
},
]}
rules={firstNameRules}
>
<Input className="signup-first-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
@@ -348,13 +383,7 @@ class SignupPage extends React.Component {
name="lastName"
className="signup-last-name"
label={signupItem.label ? signupItem.label : i18next.t("general:Last name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your last name!"),
whitespace: true,
},
]}
rules={lastNameRules}
>
<Input className="signup-last-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
@@ -362,69 +391,98 @@ class SignupPage extends React.Component {
);
}
const displayNameRules = [
{
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!"),
whitespace: true,
},
];
if (signupItem.regex) {
displayNameRules.push({
pattern: new RegExp(signupItem.regex),
message: i18next.t("signup:The input doesn't match the signup item regex!"),
});
}
return (
<Form.Item
name="name"
className="signup-name"
label={(signupItem.label ? signupItem.label : (signupItem.rule === "Real name" || signupItem.rule === "First, last") ? i18next.t("general:Real name") : i18next.t("general:Display name"))}
rules={[
{
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!"),
whitespace: true,
},
]}
rules={displayNameRules}
>
<Input className="signup-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
);
} else if (signupItem.name === "First name" && this.state?.displayNameRule !== "First, last") {
const firstNameRules = [
{
required: required,
message: i18next.t("signup:Please input your first name!"),
whitespace: true,
},
];
if (signupItem.regex) {
firstNameRules.push({
pattern: new RegExp(signupItem.regex),
message: i18next.t("signup:The input doesn't match the signup item regex!"),
});
}
return (
<Form.Item
name="firstName"
className="signup-first-name"
label={signupItem.label ? signupItem.label : i18next.t("general:First name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your first name!"),
whitespace: true,
},
]}
rules={firstNameRules}
>
<Input className="signup-first-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
);
} else if (signupItem.name === "Last name" && this.state?.displayNameRule !== "First, last") {
const lastNameRules = [
{
required: required,
message: i18next.t("signup:Please input your last name!"),
whitespace: true,
},
];
if (signupItem.regex) {
lastNameRules.push({
pattern: new RegExp(signupItem.regex),
message: i18next.t("signup:The input doesn't match the signup item regex!"),
});
}
return (
<Form.Item
name="lastName"
className="signup-last-name"
label={signupItem.label ? signupItem.label : i18next.t("general:Last name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your last name!"),
whitespace: true,
},
]}
rules={lastNameRules}
>
<Input className="signup-last-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
);
} else if (signupItem.name === "Affiliation") {
const affiliationRules = [
{
required: required,
message: i18next.t("signup:Please input your affiliation!"),
whitespace: true,
},
];
if (signupItem.regex) {
affiliationRules.push({
pattern: new RegExp(signupItem.regex),
message: i18next.t("signup:The input doesn't match the signup item regex!"),
});
}
return (
<Form.Item
name="affiliation"
className="signup-affiliation"
label={signupItem.label ? signupItem.label : i18next.t("user:Affiliation")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your affiliation!"),
whitespace: true,
},
]}
rules={affiliationRules}
>
<Input className="signup-affiliation-input" placeholder={signupItem.placeholder} />
</Form.Item>

View File

@@ -1148,6 +1148,7 @@
"Text 4": "نص 4",
"Text 5": "نص 5",
"The input Email doesn't match the signup item regex!": "البريد الإلكتروني المدخل لا يطابق التعبير العادي لعنصر التسجيل!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "المدخل ليس معرف ضريبة الفاتورة!",
"The input is not invoice title!": "المدخل ليس عنوان الفاتورة!",
"The input is not valid Email!": "المدخل ليس بريدًا إلكترونيًا صالحًا!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Mətn 4",
"Text 5": "Mətn 5",
"The input Email doesn't match the signup item regex!": "Daxil edilən Email qeydiyyat elementi regex-inə uyğun gəlmir!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Daxil edilən faktura Vergi ID-si deyil!",
"The input is not invoice title!": "Daxil edilən faktura başlığı deyil!",
"The input is not valid Email!": "Daxil edilən Email etibarlı deyil!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "Zadaný e-mail neodpovídá regexu registrační položky!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Zadaný vstup není daňové ID faktury!",
"The input is not invoice title!": "Zadaný vstup není název faktury!",
"The input is not valid Email!": "Zadaný vstup není platný email!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "Die eingegebene E-Mail entspricht nicht dem Regex des Registrierungselements!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Die Eingabe ist keine Rechnungssteuer-ID!",
"The input is not invoice title!": "Der Eingabewert ist nicht die Rechnungsbezeichnung!",
"The input is not valid Email!": "Die Eingabe ist keine gültige E-Mail-Adresse!",

View File

@@ -537,7 +537,7 @@
"Show all": "Show all",
"Upload (.xlsx)": "Upload (.xlsx)",
"Virtual": "Virtual",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page"
},
"home": {
"New users past 30 days": "New users past 30 days",
@@ -1164,6 +1164,7 @@
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Texto 4",
"Text 5": "Texto 5",
"The input Email doesn't match the signup item regex!": "¡El correo electrónico ingresado no coincide con la expresión regular del elemento de registro!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "¡La entrada no es el ID fiscal de la factura!",
"The input is not invoice title!": "¡El entrada no es el título de la factura!",
"The input is not valid Email!": "¡La entrada no es un correo electrónico válido!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "متن ۴",
"Text 5": "متن ۵",
"The input Email doesn't match the signup item regex!": "ایمیل ورودی با عبارت منظم مورد ثبت‌نام مطابقت ندارد!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "ورودی شناسه مالیاتی فاکتور نیست!",
"The input is not invoice title!": "ورودی عنوان فاکتور نیست!",
"The input is not valid Email!": "ورودی ایمیل معتبر نیست!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Teksti 4",
"Text 5": "Teksti 5",
"The input Email doesn't match the signup item regex!": "Syötetty sähköposti ei vastaa rekisteröitymiskohdan regexiä!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Syöte ei ole laskun verotunnus!",
"The input is not invoice title!": "Syöte ei ole laskun otsikko!",
"The input is not valid Email!": "Syöte ei ole kelvollinen sähköpostiosoite!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Texte 4",
"Text 5": "Texte 5",
"The input Email doesn't match the signup item regex!": "L'e-mail saisi ne correspond pas au regex de l'élément d'inscription !",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "L'entrée n'est pas l'identifiant fiscal de la facture !",
"The input is not invoice title!": "L'entrée n'est pas un nom ou une dénomination sociale !",
"The input is not valid Email!": "L'entrée n'est pas une adresse e-mail valide !",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "טקסט 4",
"Text 5": "טקסט 5",
"The input Email doesn't match the signup item regex!": "הדוא\"ל שהוזן לא תואם לביטוי הרגולרי של פריט ההרשמה!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "הקלט אינו מזהה מס מסמך!",
"The input is not invoice title!": "הקלט אינו כותרת החשבונית!",
"The input is not valid Email!": "הקלט אינו דוא\"ל תקף!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Teks 4",
"Text 5": "Teks 5",
"The input Email doesn't match the signup item regex!": "Email yang dimasukkan tidak cocok dengan regex item pendaftaran!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Input ini bukan Tax ID faktur!",
"The input is not invoice title!": "Masukan bukan judul faktur!",
"The input is not valid Email!": "Input yang dimasukkan bukan sesuai dengan format Email yang valid!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Testo 4",
"Text 5": "Testo 5",
"The input Email doesn't match the signup item regex!": "L'email inserita non rispetta la regex dell'elemento di registrazione!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "L'input non è un ID fiscale di fattura!",
"The input is not invoice title!": "L'input non è un titolo di fattura!",
"The input is not valid Email!": "L'input non è un Email valido!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "テキスト4",
"Text 5": "テキスト5",
"The input Email doesn't match the signup item regex!": "入力されたメールアドレスがサインアップ項目の正規表現と一致しません!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "入力されたものは請求書の税番号ではありません!",
"The input is not invoice title!": "インプットは請求書タイトルではありません!",
"The input is not valid Email!": "入力されたものは有効なメールではありません",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Мәтін 4",
"Text 5": "Мәтін 5",
"The input Email doesn't match the signup item regex!": "Енгізілген электрондық пошта тіркелу элементінің тұрақты тіркесіміне сәйкес келмейді!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Енгізілген деректер есеп фактураның салық ID емес!",
"The input is not invoice title!": "Енгізілген деректер есеп фактураның атауы емес!",
"The input is not valid Email!": "Енгізілген электрондық пошта жарамсыз!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "텍스트 4",
"Text 5": "텍스트 5",
"The input Email doesn't match the signup item regex!": "입력한 이메일이 가입 항목 정규식과 일치하지 않습니다!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "입력한 것은 송장 세금 ID가 아닙니다!",
"The input is not invoice title!": "입력값은 송장 제목이 아닙니다!",
"The input is not valid Email!": "입력 값은 유효한 이메일이 아닙니다!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Teks 4",
"Text 5": "Teks 5",
"The input Email doesn't match the signup item regex!": "Emel yang dimasukkan tidak sepadan dengan regex item pendaftaran!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Input bukan ID Cukai invois!",
"The input is not invoice title!": "Input bukan tajuk invois!",
"The input is not valid Email!": "Input bukan emel yang sah!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Tekst 4",
"Text 5": "Tekst 5",
"The input Email doesn't match the signup item regex!": "Het ingevoerde e-mailadres voldoet niet aan het regex-patroon!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "De invoer is geen btw-nummer!",
"The input is not invoice title!": "De invoer is geen factuurtitel!",
"The input is not valid Email!": "Ongeldig e-mailadres!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Tekst 4",
"Text 5": "Tekst 5",
"The input Email doesn't match the signup item regex!": "Wprowadzony e-mail nie pasuje do wzoru rejestracji!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Wprowadzone dane nie są numerem identyfikacyjnym podatku od faktury!",
"The input is not invoice title!": "Wprowadzone dane nie są tytułem faktury!",
"The input is not valid Email!": "Wprowadzony e-mail jest nieprawidłowy!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Texto 4",
"Text 5": "Texto 5",
"The input Email doesn't match the signup item regex!": "O e-mail inserido não corresponde ao regex do item de cadastro!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "A entrada não é um ID fiscal de fatura válido!",
"The input is not invoice title!": "A entrada não é um título de fatura válido!",
"The input is not valid Email!": "A entrada não é um Email válido!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Текст 4",
"Text 5": "Текст 5",
"The input Email doesn't match the signup item regex!": "Введенный Email не соответствует регулярному выражению!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Входные данные не являются идентификатором налога по счету-фактуре!",
"The input is not invoice title!": "Входные данные не являются названием счета-фактуры!",
"The input is not valid Email!": "Ввод не является действительным адресом электронной почты!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "Zadaný e-mail nezodpovedá regexu registračného prvku!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Zadané nie je DIČ faktúry!",
"The input is not invoice title!": "Zadané nie je názov faktúry!",
"The input is not valid Email!": "Zadaný e-mail nie je platný!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "Den angivna e-posten matchar inte registreringsobjektets regex!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Inmatningen är inte fakturaskatte-ID!",
"The input is not invoice title!": "Inmatningen är inte fakturatitel!",
"The input is not valid Email!": "Inmatningen är inte en giltig e-post!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Metin 4",
"Text 5": "Metin 5",
"The input Email doesn't match the signup item regex!": "Girilen e-posta kayıt öğesi düzenli ifadesiyle eşleşmiyor!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Girdi fatura Vergi ID'si değil!",
"The input is not invoice title!": "Girdi fatura başlığı değil!",
"The input is not valid Email!": "Girdi geçerli bir E-posta değil!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Текст 4",
"Text 5": "Текст 5",
"The input Email doesn't match the signup item regex!": "Введений Email не відповідає регулярному виразу елемента реєстрації!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Введені дані не є ідентифікаційним номером платника податків!",
"The input is not invoice title!": "Введені дані не є назвою рахунку!",
"The input is not valid Email!": "Введена недійсна адреса електронної пошти!",

View File

@@ -1148,6 +1148,7 @@
"Text 4": "Văn bản 4",
"Text 5": "Văn bản 5",
"The input Email doesn't match the signup item regex!": "Email nhập vào không khớp biểu thức chính quy của mục đăng ký!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Đầu vào không phải là Mã số thuế của hóa đơn!",
"The input is not invoice title!": "Đầu vào không phải là tiêu đề hóa đơn!",
"The input is not valid Email!": "Đầu vào không phải là địa chỉ Email hợp lệ!",

View File

@@ -1151,6 +1151,7 @@
"Text 4": "文本4",
"Text 5": "文本5",
"The input Email doesn't match the signup item regex!": "您输入的邮箱地址与注册项的regex表达式不匹配!",
"The input doesn't match the signup item regex!": "The input doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "您输入的纳税人识别号有误!",
"The input is not invoice title!": "您输入的发票抬头有误!",
"The input is not valid Email!": "您输入的电子邮箱格式有误!",