forked from casdoor/casdoor
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7e7d18ee7 | ||
|
|
66d1e28300 | ||
|
|
53782a6706 | ||
|
|
30bb0ce92f | ||
|
|
29f7dda858 | ||
|
|
68b82ed524 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")}
|
||||
<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"))}
|
||||
{!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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
<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")}
|
||||
<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}
|
||||
/>
|
||||
|
||||
@@ -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")}
|
||||
<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"))}
|
||||
{!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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
<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")}
|
||||
<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}
|
||||
/>
|
||||
|
||||
@@ -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")}
|
||||
<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"))}
|
||||
{!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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
<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")}
|
||||
<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}
|
||||
/>
|
||||
|
||||
@@ -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")}
|
||||
<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"))}
|
||||
{!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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
<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")}
|
||||
<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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")}
|
||||
<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"))}
|
||||
{!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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
<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")}
|
||||
<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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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...")}
|
||||
|
||||
@@ -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")}
|
||||
<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"))}
|
||||
{!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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")}
|
||||
<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")}
|
||||
<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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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!": "المدخل ليس بريدًا إلكترونيًا صالحًا!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!": "ورودی ایمیل معتبر نیست!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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 !",
|
||||
|
||||
@@ -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!": "הקלט אינו דוא\"ל תקף!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!": "入力されたものは有効なメールではありません",
|
||||
|
||||
@@ -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!": "Енгізілген электрондық пошта жарамсыз!",
|
||||
|
||||
@@ -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!": "입력 값은 유효한 이메일이 아닙니다!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!": "Ввод не является действительным адресом электронной почты!",
|
||||
|
||||
@@ -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ý!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!": "Введена недійсна адреса електронної пошти!",
|
||||
|
||||
@@ -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ệ!",
|
||||
|
||||
@@ -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!": "您输入的电子邮箱格式有误!",
|
||||
|
||||
Reference in New Issue
Block a user