diff --git a/src/Routes.tsx b/src/Routes.tsx index d357a20..de61b57 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -16,11 +16,12 @@ import BoardPage from "./layouts/entity/board/page/BoardPage"; import SubscriptionLayout from "./layouts/account/subscription/SubscriptionLayout"; import LibraryLayout from "./layouts/account/library/LibraryLayout"; import EditProjectLayout from "./layouts/entity/project/edit/EditProjectLayout"; -import ProjectPricingLayout from "./layouts/entity/project/pricing/ProjectPricingLayout"; +import ProjectPricingLayout from "./layouts/entity/project/pricing/view/ProjectPricingLayout"; import RegisterLayout from "./layouts/auth/register/RegisterLayout"; import ConfirmEmailLayout from "./layouts/auth/confirmEmail/ConfirmEmailLayout"; import NotFoundLayout from "./layouts/404/NotFoundLayout"; import HelpLayout from "./layouts/help/HelpLayout"; +import EditProjectPricingLayout from "./layouts/entity/project/pricing/edit/EditProjectPricingLayout"; class Routes extends React.Component { render() { @@ -64,6 +65,7 @@ class Routes extends React.Component { + diff --git a/src/client/bindings.ts b/src/client/bindings.ts index 5d18744..8f40534 100644 --- a/src/client/bindings.ts +++ b/src/client/bindings.ts @@ -143,6 +143,7 @@ export class ProjectProduct { project_guid?: string; usd_price?: number; duration_hours?: number; + users_count?: number; created_at?: string; updated_at?: string; } diff --git a/src/client/models/index.ts b/src/client/models/index.ts index c966f64..33474e7 100644 --- a/src/client/models/index.ts +++ b/src/client/models/index.ts @@ -913,6 +913,34 @@ export interface GetProjectProductsOKResponse { data?: GetProjectProductsOKResponseData; } +/** + * An interface representing PostProjectProductCreatedResponseData. + */ +export interface PostProjectProductCreatedResponseData { + product?: ProjectProduct; +} + +/** + * An interface representing PostProjectProductCreatedResponse. + */ +export interface PostProjectProductCreatedResponse { + data?: PostProjectProductCreatedResponseData; +} + +/** + * An interface representing DeleteProjectProductOKResponseData. + */ +export interface DeleteProjectProductOKResponseData { + product?: ProjectProduct; +} + +/** + * An interface representing DeleteProjectProductOKResponse. + */ +export interface DeleteProjectProductOKResponse { + data?: DeleteProjectProductOKResponseData; +} + /** * An interface representing SupportHubApiOptions. */ @@ -983,6 +1011,13 @@ export interface SupportHubApiEditCardOptionalParams extends msRest.RequestOptio columnGuid?: string; } +/** + * Optional Parameters. + */ +export interface SupportHubApiPostProjectProductOptionalParams extends msRest.RequestOptionsBase { + durationHours?: number; +} + /** * Defines values for ServiceType. * Possible values include: 'GitHub', 'GitLab' @@ -1988,3 +2023,43 @@ export type GetProjectProductsResponse = GetProjectProductsOKResponse & { parsedBody: GetProjectProductsOKResponse; }; }; + +/** + * Contains response data for the postProjectProduct operation. + */ +export type PostProjectProductResponse = PostProjectProductCreatedResponse & { + /** + * The underlying HTTP response. + */ + _response: msRest.HttpResponse & { + /** + * The response body as text (string format) + */ + bodyAsText: string; + + /** + * The response body as parsed JSON or XML + */ + parsedBody: PostProjectProductCreatedResponse; + }; +}; + +/** + * Contains response data for the deleteProjectProduct operation. + */ +export type DeleteProjectProductResponse = DeleteProjectProductOKResponse & { + /** + * The underlying HTTP response. + */ + _response: msRest.HttpResponse & { + /** + * The response body as text (string format) + */ + bodyAsText: string; + + /** + * The response body as parsed JSON or XML + */ + parsedBody: DeleteProjectProductOKResponse; + }; +}; diff --git a/src/client/models/mappers.ts b/src/client/models/mappers.ts index 028436b..13899a9 100644 --- a/src/client/models/mappers.ts +++ b/src/client/models/mappers.ts @@ -2498,3 +2498,71 @@ export const GetProjectProductsOKResponse: msRest.CompositeMapper = { } } }; + +export const PostProjectProductCreatedResponseData: msRest.CompositeMapper = { + serializedName: "PostProjectProductCreatedResponse_data", + type: { + name: "Composite", + className: "PostProjectProductCreatedResponseData", + modelProperties: { + product: { + serializedName: "product", + type: { + name: "Composite", + className: "ProjectProduct" + } + } + } + } +}; + +export const PostProjectProductCreatedResponse: msRest.CompositeMapper = { + serializedName: "PostProjectProductCreatedResponse", + type: { + name: "Composite", + className: "PostProjectProductCreatedResponse", + modelProperties: { + data: { + serializedName: "data", + type: { + name: "Composite", + className: "PostProjectProductCreatedResponseData" + } + } + } + } +}; + +export const DeleteProjectProductOKResponseData: msRest.CompositeMapper = { + serializedName: "DeleteProjectProductOKResponse_data", + type: { + name: "Composite", + className: "DeleteProjectProductOKResponseData", + modelProperties: { + product: { + serializedName: "product", + type: { + name: "Composite", + className: "ProjectProduct" + } + } + } + } +}; + +export const DeleteProjectProductOKResponse: msRest.CompositeMapper = { + serializedName: "DeleteProjectProductOKResponse", + type: { + name: "Composite", + className: "DeleteProjectProductOKResponse", + modelProperties: { + data: { + serializedName: "data", + type: { + name: "Composite", + className: "DeleteProjectProductOKResponseData" + } + } + } + } +}; diff --git a/src/client/models/parameters.ts b/src/client/models/parameters.ts index 3284a3a..5fb806d 100644 --- a/src/client/models/parameters.ts +++ b/src/client/models/parameters.ts @@ -134,7 +134,7 @@ export const currencyType: msRest.OperationQueryParameter = { } } }; -export const description: msRest.OperationQueryParameter = { +export const description0: msRest.OperationQueryParameter = { parameterPath: [ "options", "description" @@ -146,6 +146,28 @@ export const description: msRest.OperationQueryParameter = { } } }; +export const description1: msRest.OperationQueryParameter = { + parameterPath: "description", + mapper: { + required: true, + serializedName: "description", + type: { + name: "String" + } + } +}; +export const durationHours: msRest.OperationQueryParameter = { + parameterPath: [ + "options", + "durationHours" + ], + mapper: { + serializedName: "duration_hours", + type: { + name: "Number" + } + } +}; export const email0: msRest.OperationQueryParameter = { parameterPath: [ "options", @@ -302,6 +324,16 @@ export const password1: msRest.OperationQueryParameter = { } } }; +export const productGuid: msRest.OperationQueryParameter = { + parameterPath: "productGuid", + mapper: { + required: true, + serializedName: "product_guid", + type: { + name: "String" + } + } +}; export const projectGuid: msRest.OperationQueryParameter = { parameterPath: "projectGuid", mapper: { @@ -364,6 +396,36 @@ export const status: msRest.OperationQueryParameter = { } } }; +export const url: msRest.OperationQueryParameter = { + parameterPath: "url", + mapper: { + required: true, + serializedName: "url", + type: { + name: "String" + } + } +}; +export const usdPrice: msRest.OperationQueryParameter = { + parameterPath: "usdPrice", + mapper: { + required: true, + serializedName: "usd_price", + type: { + name: "Number" + } + } +}; +export const useUrl: msRest.OperationQueryParameter = { + parameterPath: "useUrl", + mapper: { + required: true, + serializedName: "use_url", + type: { + name: "String" + } + } +}; export const value: msRest.OperationQueryParameter = { parameterPath: "value", mapper: { diff --git a/src/client/supportHubApi.ts b/src/client/supportHubApi.ts index 9c89a4b..c862a30 100644 --- a/src/client/supportHubApi.ts +++ b/src/client/supportHubApi.ts @@ -1310,6 +1310,88 @@ class SupportHubApi extends SupportHubApiContext { getProjectProductsOperationSpec, callback) as Promise; } + + /** + * @param apiToken JWT token + * @param projectGuid + * @param name + * @param description + * @param usdPrice + * @param url + * @param useUrl + * @param [options] The optional parameters + * @returns Promise + */ + postProjectProduct(apiToken: string, projectGuid: string, name: string, description: string, usdPrice: number, url: string, useUrl: string, options?: Models.SupportHubApiPostProjectProductOptionalParams): Promise; + /** + * @param apiToken JWT token + * @param projectGuid + * @param name + * @param description + * @param usdPrice + * @param url + * @param useUrl + * @param callback The callback + */ + postProjectProduct(apiToken: string, projectGuid: string, name: string, description: string, usdPrice: number, url: string, useUrl: string, callback: msRest.ServiceCallback): void; + /** + * @param apiToken JWT token + * @param projectGuid + * @param name + * @param description + * @param usdPrice + * @param url + * @param useUrl + * @param options The optional parameters + * @param callback The callback + */ + postProjectProduct(apiToken: string, projectGuid: string, name: string, description: string, usdPrice: number, url: string, useUrl: string, options: Models.SupportHubApiPostProjectProductOptionalParams, callback: msRest.ServiceCallback): void; + postProjectProduct(apiToken: string, projectGuid: string, name: string, description: string, usdPrice: number, url: string, useUrl: string, options?: Models.SupportHubApiPostProjectProductOptionalParams | msRest.ServiceCallback, callback?: msRest.ServiceCallback): Promise { + return this.sendOperationRequest( + { + apiToken, + projectGuid, + name, + description, + usdPrice, + url, + useUrl, + options + }, + postProjectProductOperationSpec, + callback) as Promise; + } + + /** + * @param apiToken JWT token + * @param productGuid + * @param [options] The optional parameters + * @returns Promise + */ + deleteProjectProduct(apiToken: string, productGuid: string, options?: msRest.RequestOptionsBase): Promise; + /** + * @param apiToken JWT token + * @param productGuid + * @param callback The callback + */ + deleteProjectProduct(apiToken: string, productGuid: string, callback: msRest.ServiceCallback): void; + /** + * @param apiToken JWT token + * @param productGuid + * @param options The optional parameters + * @param callback The callback + */ + deleteProjectProduct(apiToken: string, productGuid: string, options: msRest.RequestOptionsBase, callback: msRest.ServiceCallback): void; + deleteProjectProduct(apiToken: string, productGuid: string, options?: msRest.RequestOptionsBase | msRest.ServiceCallback, callback?: msRest.ServiceCallback): Promise { + return this.sendOperationRequest( + { + apiToken, + productGuid, + options + }, + deleteProjectProductOperationSpec, + callback) as Promise; + } } // Operation Specifications @@ -1500,7 +1582,7 @@ const editProjectOperationSpec: msRest.OperationSpec = { queryParameters: [ Parameters.apiToken, Parameters.projectGuid, - Parameters.description + Parameters.description0 ], responses: { 200: { @@ -1594,7 +1676,7 @@ const createCardOperationSpec: msRest.OperationSpec = { Parameters.apiToken, Parameters.columnGuid0, Parameters.name0, - Parameters.description, + Parameters.description0, Parameters.columnOrder ], responses: { @@ -1613,7 +1695,7 @@ const editCardOperationSpec: msRest.OperationSpec = { Parameters.apiToken, Parameters.cardGuid, Parameters.name1, - Parameters.description, + Parameters.description0, Parameters.columnOrder, Parameters.columnGuid1 ], @@ -2011,6 +2093,44 @@ const getProjectProductsOperationSpec: msRest.OperationSpec = { serializer }; +const postProjectProductOperationSpec: msRest.OperationSpec = { + httpMethod: "POST", + path: "api/v1/project/product/new", + queryParameters: [ + Parameters.apiToken, + Parameters.projectGuid, + Parameters.name0, + Parameters.description1, + Parameters.usdPrice, + Parameters.url, + Parameters.useUrl, + Parameters.durationHours + ], + responses: { + 201: { + bodyMapper: Mappers.PostProjectProductCreatedResponse + }, + default: {} + }, + serializer +}; + +const deleteProjectProductOperationSpec: msRest.OperationSpec = { + httpMethod: "DELETE", + path: "api/v1/project/product/delete", + queryParameters: [ + Parameters.apiToken, + Parameters.productGuid + ], + responses: { + 200: { + bodyMapper: Mappers.DeleteProjectProductOKResponse + }, + default: {} + }, + serializer +}; + export { SupportHubApi, SupportHubApiContext, diff --git a/src/components/entity/invoice/single/create/NewInvoice.tsx b/src/components/entity/invoice/single/create/NewInvoice.tsx index b915176..4f38cf9 100644 --- a/src/components/entity/invoice/single/create/NewInvoice.tsx +++ b/src/components/entity/invoice/single/create/NewInvoice.tsx @@ -172,8 +172,6 @@ class NewInvoice extends React.Component { } render() { - const currencyTypes = this.getCurrencyTypes(); - return
@@ -211,7 +209,7 @@ class NewInvoice extends React.Component { }} onSelect={(value) => this.onCurrencySelected(value)} > - {currencyTypes.map((option: any, i: number) => { + {this.getCurrencyTypes().map((option: any, i: number) => { return ; })} diff --git a/src/components/entity/product/action/delete/DeleteProductButton.tsx b/src/components/entity/product/action/delete/DeleteProductButton.tsx new file mode 100644 index 0000000..699809a --- /dev/null +++ b/src/components/entity/product/action/delete/DeleteProductButton.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import {ProjectProduct} from "../../../../../client/bindings"; +import {Button, notification, Row} from "antd"; +import {handleApiError} from "../../../../../classes/notification/errorHandler/errorHandler"; + +interface IProps { + product: ProjectProduct +} + +interface IState { + isLoading: boolean +} + +class DeleteProductButton extends React.Component { + constructor(props: IProps) { + super(props); + this.state = { + isLoading: false, + } + } + + sendDeleteRequest() { + window.App.apiClient.deleteProjectProduct(window.App.apiToken, this.props.product.guid!) + .then(() => { + notification['warning']({ + message: 'Product was deleted' + }); + setTimeout(() => { window.location.reload(); }, 1500); + }) + .catch((error) => handleApiError(error.response)); + } + + render() { + return
+ + + +
+ } +} + +export default DeleteProductButton; diff --git a/src/components/entity/product/action/new/NewProductButton.tsx b/src/components/entity/product/action/new/NewProductButton.tsx new file mode 100644 index 0000000..24e15be --- /dev/null +++ b/src/components/entity/product/action/new/NewProductButton.tsx @@ -0,0 +1,196 @@ +import React, {SyntheticEvent} from "react"; +import {Button, Col, Input, Modal, notification, Row, Switch} from "antd"; +import {handleApiError} from "../../../../../classes/notification/errorHandler/errorHandler"; + +const { TextArea } = Input; + +interface IProps { + projectGuid: string +} + +interface IState { + showModal: boolean, + isLoading: boolean, + form: { + name: string, + description: string, + amount: number, + url: string, + use_url: string, + duration_hours: number + } +} + +class NewProductButton extends React.Component { + constructor(props: IProps) { + super(props); + this.state = { + showModal: false, + isLoading: false, + form: { + name: 'Pro edition', + description: 'product description', + amount: 0, + url: '', + use_url: '', + duration_hours: 0 + } + } + } + + updateForm(field: string, val: string) { + let form: any = this.state.form; + form[field] = val; + this.setState({form}); + } + + updatedAmount(e: SyntheticEvent) { + const target: any = e.target; + + const floatVal = parseFloat(target.value); + + if (floatVal <= 0) return; + + let form = this.state.form; + form.amount = floatVal; + this.setState({form}); + } + + createProduct() { + let form = this.state.form; + window.App.apiClient.postProjectProduct( + window.App.apiToken!, this.props.projectGuid, form.name, form.description, form.amount, form.url, form.use_url, { + durationHours: this.state.form.duration_hours + } + ) + .then(() => { + notification['success']({ + message: 'Product was created successfully!' + }); + this.setState({showModal: false}); + setTimeout(() => { + window.location.reload(); + }, 1500); + }) + .catch((error) => { + try { + notification['error']({ + message: JSON.parse(error.response.body).errors[0].message + }); + } catch (e) { + handleApiError(error.response); + } + }); + } + + render() { + return
+ + Create new product + } + visible={this.state.showModal} + width={window.innerWidth < 1000 ? "90%" : "40%"} + onCancel={() => { + this.setState({showModal: false}) + }} + footer={null} + > + + + Product name + + + {this.updateForm('name', e.target.value)}} + defaultValue={'Professional edition'} + /> + + + + + Description + + +