Merge branch 'master' into fix/new-variant-attributes
This commit is contained in:
commit
542ff0c237
53 changed files with 6771 additions and 216 deletions
|
@ -32,6 +32,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Allow sorting products by attribute - #180 by @dominik-zeglen
|
||||
- Hide variants and attributes if product has none - #179 by @dominik-zeglen
|
||||
- Add service account section - #188 by @dominik-zeglen
|
||||
- Add webhook section - #206 by @benekex2
|
||||
- Add variant creator - #177 by @dominik-zeglen
|
||||
- Add git hooks - #209 by @dominik-zeglen
|
||||
- Do not send customer invitation email - #211 by @dominik-zeglen
|
||||
|
|
|
@ -604,7 +604,7 @@ type Checkout implements Node {
|
|||
privateMeta: [MetaStore]!
|
||||
meta: [MetaStore]!
|
||||
availableShippingMethods: [ShippingMethod]!
|
||||
availablePaymentGateways: [String]!
|
||||
availablePaymentGateways: [PaymentGateway]!
|
||||
email: String!
|
||||
isShippingRequired: Boolean!
|
||||
lines: [CheckoutLine]
|
||||
|
@ -625,13 +625,13 @@ type CheckoutBillingAddressUpdate {
|
|||
checkoutErrors: [CheckoutError!]
|
||||
}
|
||||
|
||||
type CheckoutClearStoredMeta {
|
||||
type CheckoutClearMeta {
|
||||
errors: [Error!]
|
||||
checkoutErrors: [CheckoutError!]
|
||||
checkout: Checkout
|
||||
}
|
||||
|
||||
type CheckoutClearStoredPrivateMeta {
|
||||
type CheckoutClearPrivateMeta {
|
||||
errors: [Error!]
|
||||
checkoutErrors: [CheckoutError!]
|
||||
checkout: Checkout
|
||||
|
@ -1610,6 +1610,11 @@ input FulfillmentUpdateTrackingInput {
|
|||
notifyCustomer: Boolean
|
||||
}
|
||||
|
||||
type GatewayConfigLine {
|
||||
field: String!
|
||||
value: String
|
||||
}
|
||||
|
||||
scalar GenericScalar
|
||||
|
||||
type Geolocalization {
|
||||
|
@ -2107,12 +2112,16 @@ type Mutations {
|
|||
orderAddNote(order: ID!, input: OrderAddNoteInput!): OrderAddNote
|
||||
orderCancel(id: ID!, restock: Boolean!): OrderCancel
|
||||
orderCapture(amount: Decimal!, id: ID!): OrderCapture
|
||||
orderClearPrivateMeta(id: ID!, input: MetaPath!): OrderClearPrivateMeta
|
||||
orderClearMeta(id: ID!, input: MetaPath!): OrderClearMeta
|
||||
orderFulfillmentCancel(id: ID!, input: FulfillmentCancelInput!): FulfillmentCancel
|
||||
orderFulfillmentCreate(input: FulfillmentCreateInput!, order: ID): FulfillmentCreate
|
||||
orderFulfillmentUpdateTracking(id: ID!, input: FulfillmentUpdateTrackingInput!): FulfillmentUpdateTracking
|
||||
orderMarkAsPaid(id: ID!): OrderMarkAsPaid
|
||||
orderRefund(amount: Decimal!, id: ID!): OrderRefund
|
||||
orderUpdate(id: ID!, input: OrderUpdateInput!): OrderUpdate
|
||||
orderUpdateMeta(id: ID!, input: MetaInput!): OrderUpdateMeta
|
||||
orderUpdatePrivateMeta(id: ID!, input: MetaInput!): OrderUpdatePrivateMeta
|
||||
orderUpdateShipping(order: ID!, input: OrderUpdateShippingInput): OrderUpdateShipping
|
||||
orderVoid(id: ID!): OrderVoid
|
||||
orderBulkCancel(ids: [ID]!, restock: Boolean!): OrderBulkCancel
|
||||
|
@ -2165,14 +2174,14 @@ type Mutations {
|
|||
checkoutShippingMethodUpdate(checkoutId: ID, shippingMethodId: ID!): CheckoutShippingMethodUpdate
|
||||
checkoutUpdateVoucher(checkoutId: ID!, voucherCode: String): CheckoutUpdateVoucher
|
||||
checkoutUpdateMetadata(id: ID!, input: MetaInput!): CheckoutUpdateMeta
|
||||
checkoutClearMetadata(id: ID!, input: MetaPath!): CheckoutClearStoredMeta
|
||||
checkoutClearMetadata(id: ID!, input: MetaPath!): CheckoutClearMeta
|
||||
checkoutUpdatePrivateMetadata(id: ID!, input: MetaInput!): CheckoutUpdatePrivateMeta
|
||||
checkoutClearPrivateMetadata(id: ID!, input: MetaPath!): CheckoutClearStoredPrivateMeta
|
||||
checkoutClearPrivateMetadata(id: ID!, input: MetaPath!): CheckoutClearPrivateMeta
|
||||
requestPasswordReset(email: String!, redirectUrl: String!): RequestPasswordReset
|
||||
setPassword(token: String!, email: String!, password: String!): SetPassword
|
||||
passwordChange(newPassword: String!, oldPassword: String!): PasswordChange
|
||||
userUpdateMetadata(id: ID!, input: MetaInput!): UserUpdateMeta
|
||||
userClearStoredMetadata(id: ID!, input: MetaPath!): UserClearStoredMeta
|
||||
userClearMetadata(id: ID!, input: MetaPath!): UserClearMeta
|
||||
accountAddressCreate(input: AddressInput!, type: AddressTypeEnum): AccountAddressCreate
|
||||
accountAddressUpdate(id: ID!, input: AddressInput!): AccountAddressUpdate
|
||||
accountAddressDelete(id: ID!): AccountAddressDelete
|
||||
|
@ -2202,12 +2211,12 @@ type Mutations {
|
|||
userAvatarDelete: UserAvatarDelete
|
||||
userBulkSetActive(ids: [ID]!, isActive: Boolean!): UserBulkSetActive
|
||||
userUpdatePrivateMetadata(id: ID!, input: MetaInput!): UserUpdatePrivateMeta
|
||||
userClearStoredPrivateMetadata(id: ID!, input: MetaPath!): UserClearStoredPrivateMeta
|
||||
userClearPrivateMetadata(id: ID!, input: MetaPath!): UserClearPrivateMeta
|
||||
serviceAccountCreate(input: ServiceAccountInput!): ServiceAccountCreate
|
||||
serviceAccountUpdate(id: ID!, input: ServiceAccountInput!): ServiceAccountUpdate
|
||||
serviceAccountDelete(id: ID!): ServiceAccountDelete
|
||||
serviceAccountUpdatePrivateMetadata(id: ID!, input: MetaInput!): ServiceAccountUpdatePrivateMeta
|
||||
serviceAccountClearStoredPrivateMetadata(id: ID!, input: MetaPath!): ServiceAccountClearStoredPrivateMeta
|
||||
serviceAccountClearPrivateMetadata(id: ID!, input: MetaPath!): ServiceAccountClearPrivateMeta
|
||||
serviceAccountTokenCreate(input: ServiceAccountTokenInput!): ServiceAccountTokenCreate
|
||||
serviceAccountTokenDelete(id: ID!): ServiceAccountTokenDelete
|
||||
passwordReset(email: String!): PasswordReset
|
||||
|
@ -2252,6 +2261,8 @@ type Order implements Node {
|
|||
displayGrossPrices: Boolean!
|
||||
customerNote: String!
|
||||
weight: Weight
|
||||
privateMeta: [MetaStore]!
|
||||
meta: [MetaStore]!
|
||||
fulfillments: [Fulfillment]!
|
||||
lines: [OrderLine]!
|
||||
actions: [OrderAction]!
|
||||
|
@ -2310,6 +2321,16 @@ type OrderCapture {
|
|||
orderErrors: [OrderError!]
|
||||
}
|
||||
|
||||
type OrderClearMeta {
|
||||
errors: [Error!]
|
||||
order: Order
|
||||
}
|
||||
|
||||
type OrderClearPrivateMeta {
|
||||
errors: [Error!]
|
||||
order: Order
|
||||
}
|
||||
|
||||
type OrderCountableConnection {
|
||||
pageInfo: PageInfo!
|
||||
edges: [OrderCountableEdge!]!
|
||||
|
@ -2504,6 +2525,16 @@ input OrderUpdateInput {
|
|||
shippingAddress: AddressInput
|
||||
}
|
||||
|
||||
type OrderUpdateMeta {
|
||||
errors: [Error!]
|
||||
order: Order
|
||||
}
|
||||
|
||||
type OrderUpdatePrivateMeta {
|
||||
errors: [Error!]
|
||||
order: Order
|
||||
}
|
||||
|
||||
type OrderUpdateShipping {
|
||||
errors: [Error!]
|
||||
order: Order
|
||||
|
@ -2689,6 +2720,11 @@ enum PaymentErrorCode {
|
|||
UNIQUE
|
||||
}
|
||||
|
||||
type PaymentGateway {
|
||||
name: String!
|
||||
config: [GatewayConfigLine!]!
|
||||
}
|
||||
|
||||
input PaymentInput {
|
||||
gateway: String!
|
||||
token: String!
|
||||
|
@ -2800,11 +2836,11 @@ type Product implements Node {
|
|||
meta: [MetaStore]!
|
||||
url: String!
|
||||
thumbnail(size: Int): Image
|
||||
availability: ProductPricingInfo @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, Has been renamed to 'pricing'.")
|
||||
availability: ProductPricingInfo @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, Has been renamed to `pricing`.")
|
||||
pricing: ProductPricingInfo
|
||||
isAvailable: Boolean
|
||||
basePrice: Money
|
||||
price: Money @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, has been replaced by 'basePrice'")
|
||||
price: Money @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, has been replaced by `basePrice`")
|
||||
minimalVariantPrice: Money
|
||||
taxType: TaxType
|
||||
attributes: [SelectedAttribute!]!
|
||||
|
@ -3160,15 +3196,15 @@ type ProductVariant implements Node {
|
|||
name: String!
|
||||
product: Product!
|
||||
trackInventory: Boolean!
|
||||
quantity: Int!
|
||||
quantityAllocated: Int!
|
||||
weight: Weight
|
||||
privateMeta: [MetaStore]!
|
||||
meta: [MetaStore]!
|
||||
quantity: Int!
|
||||
stockQuantity: Int!
|
||||
priceOverride: Money
|
||||
price: Money @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, has been replaced by 'pricing.priceUndiscounted'")
|
||||
availability: VariantPricingInfo @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, has been renamed to 'pricing'.")
|
||||
availability: VariantPricingInfo @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, has been renamed to `pricing`.")
|
||||
pricing: VariantPricingInfo
|
||||
isAvailable: Boolean
|
||||
attributes: [SelectedAttribute!]!
|
||||
|
@ -3291,7 +3327,7 @@ type ProductVariantUpdatePrivateMeta {
|
|||
|
||||
type Query {
|
||||
webhook(id: ID!): Webhook
|
||||
webhooks(before: String, after: String, first: Int, last: Int): WebhookCountableConnection
|
||||
webhooks(filter: WebhookFilterInput, before: String, after: String, first: Int, last: Int): WebhookCountableConnection
|
||||
translations(kind: TranslatableKinds!, before: String, after: String, first: Int, last: Int): TranslatableItemConnection
|
||||
shop: Shop
|
||||
shippingZone(id: ID!): ShippingZone
|
||||
|
@ -3313,7 +3349,7 @@ type Query {
|
|||
reportProductSales(period: ReportingPeriod!, before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection
|
||||
payment(id: ID!): Payment
|
||||
payments(before: String, after: String, first: Int, last: Int): PaymentCountableConnection
|
||||
paymentClientToken(gateway: String!): String
|
||||
paymentClientToken(gateway: String!): String @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, use payment gateway config instead in availablePaymentGateways.")
|
||||
page(id: ID, slug: String): Page
|
||||
pages(query: String, filter: PageFilterInput, before: String, after: String, first: Int, last: Int): PageCountableConnection
|
||||
homepageEvents(before: String, after: String, first: Int, last: Int): OrderEventCountableConnection
|
||||
|
@ -3484,7 +3520,7 @@ type ServiceAccount implements Node {
|
|||
name: String
|
||||
}
|
||||
|
||||
type ServiceAccountClearStoredPrivateMeta {
|
||||
type ServiceAccountClearPrivateMeta {
|
||||
errors: [Error!]
|
||||
accountErrors: [AccountError!]
|
||||
serviceAccount: ServiceAccount
|
||||
|
@ -4026,13 +4062,13 @@ type UserBulkSetActive {
|
|||
accountErrors: [AccountError!]
|
||||
}
|
||||
|
||||
type UserClearStoredMeta {
|
||||
type UserClearMeta {
|
||||
errors: [Error!]
|
||||
accountErrors: [AccountError!]
|
||||
user: User
|
||||
}
|
||||
|
||||
type UserClearStoredPrivateMeta {
|
||||
type UserClearPrivateMeta {
|
||||
errors: [Error!]
|
||||
accountErrors: [AccountError!]
|
||||
user: User
|
||||
|
@ -4223,6 +4259,7 @@ type VoucherUpdate {
|
|||
}
|
||||
|
||||
type Webhook implements Node {
|
||||
name: String
|
||||
serviceAccount: ServiceAccount!
|
||||
targetUrl: String!
|
||||
isActive: Boolean!
|
||||
|
@ -4249,6 +4286,7 @@ type WebhookCreate {
|
|||
}
|
||||
|
||||
input WebhookCreateInput {
|
||||
name: String
|
||||
targetUrl: String
|
||||
events: [WebhookEventTypeEnum]
|
||||
serviceAccount: ID
|
||||
|
@ -4277,11 +4315,11 @@ enum WebhookErrorCode {
|
|||
}
|
||||
|
||||
type WebhookEvent {
|
||||
eventType: String
|
||||
eventType: WebhookEventTypeEnum
|
||||
}
|
||||
|
||||
enum WebhookEventTypeEnum {
|
||||
ALL_EVENTS
|
||||
ANY_EVENTS
|
||||
ORDER_CREATED
|
||||
ORDER_FULLY_PAID
|
||||
ORDER_UPDATED
|
||||
|
@ -4290,6 +4328,11 @@ enum WebhookEventTypeEnum {
|
|||
PRODUCT_CREATED
|
||||
}
|
||||
|
||||
input WebhookFilterInput {
|
||||
search: String
|
||||
isActive: Boolean
|
||||
}
|
||||
|
||||
type WebhookUpdate {
|
||||
errors: [Error!]
|
||||
webhook: Webhook
|
||||
|
@ -4297,6 +4340,7 @@ type WebhookUpdate {
|
|||
}
|
||||
|
||||
input WebhookUpdateInput {
|
||||
name: String
|
||||
targetUrl: String
|
||||
events: [WebhookEventTypeEnum]
|
||||
serviceAccount: ID
|
||||
|
|
|
@ -27,6 +27,7 @@ export interface AppListViewSettings {
|
|||
[ListViews.SHIPPING_METHODS_LIST]: ListSettings;
|
||||
[ListViews.STAFF_MEMBERS_LIST]: ListSettings;
|
||||
[ListViews.VOUCHER_LIST]: ListSettings;
|
||||
[ListViews.WEBHOOK_LIST]: ListSettings;
|
||||
}
|
||||
export const defaultListSettings: AppListViewSettings = {
|
||||
[ListViews.CATEGORY_LIST]: {
|
||||
|
@ -68,5 +69,8 @@ export const defaultListSettings: AppListViewSettings = {
|
|||
},
|
||||
[ListViews.VOUCHER_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.WEBHOOK_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import ShippingMethods from "@saleor/icons/ShippingMethods";
|
|||
import SiteSettings from "@saleor/icons/SiteSettings";
|
||||
import StaffMembers from "@saleor/icons/StaffMembers";
|
||||
import Taxes from "@saleor/icons/Taxes";
|
||||
import Webhooks from "@saleor/icons/Webhooks";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { menuListUrl } from "@saleor/navigation/urls";
|
||||
|
@ -27,6 +28,7 @@ import { siteSettingsUrl } from "@saleor/siteSettings/urls";
|
|||
import { staffListUrl } from "@saleor/staff/urls";
|
||||
import { taxSection } from "@saleor/taxes/urls";
|
||||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||
import { webhooksListUrl } from "@saleor/webhooks/urls";
|
||||
import ConfigurationPage, { MenuSection } from "./ConfigurationPage";
|
||||
|
||||
export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
||||
|
@ -161,6 +163,15 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
permission: PermissionEnum.MANAGE_SERVICE_ACCOUNTS,
|
||||
title: intl.formatMessage(sectionNames.serviceAccounts),
|
||||
url: serviceListUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "View and update your webhook and their settings"
|
||||
}),
|
||||
icon: <Webhooks fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_WEBHOOKS,
|
||||
title: intl.formatMessage(sectionNames.webhooks),
|
||||
url: webhooksListUrl()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
34
src/containers/SearchServiceAccount/index.tsx
Normal file
34
src/containers/SearchServiceAccount/index.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { pageInfoFragment } from "@saleor/queries";
|
||||
import TopLevelSearch from "../TopLevelSearch";
|
||||
import {
|
||||
SearchServiceAccount,
|
||||
SearchServiceAccountVariables
|
||||
} from "./types/SearchServiceAccount";
|
||||
|
||||
export const searchServiceAccount = gql`
|
||||
${pageInfoFragment}
|
||||
query SearchServiceAccount($after: String, $first: Int!, $query: String!) {
|
||||
search: serviceAccounts(
|
||||
after: $after
|
||||
first: $first
|
||||
filter: { search: $query }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfoFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default TopLevelSearch<
|
||||
SearchServiceAccount,
|
||||
SearchServiceAccountVariables
|
||||
>(searchServiceAccount);
|
|
@ -0,0 +1,42 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: SearchServiceAccount
|
||||
// ====================================================
|
||||
|
||||
export interface SearchServiceAccount_search_edges_node {
|
||||
__typename: "ServiceAccount";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface SearchServiceAccount_search_edges {
|
||||
__typename: "ServiceAccountCountableEdge";
|
||||
node: SearchServiceAccount_search_edges_node;
|
||||
}
|
||||
|
||||
export interface SearchServiceAccount_search_pageInfo {
|
||||
__typename: "PageInfo";
|
||||
endCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
}
|
||||
|
||||
export interface SearchServiceAccount_search {
|
||||
__typename: "ServiceAccountCountableConnection";
|
||||
edges: SearchServiceAccount_search_edges[];
|
||||
pageInfo: SearchServiceAccount_search_pageInfo;
|
||||
}
|
||||
|
||||
export interface SearchServiceAccount {
|
||||
search: SearchServiceAccount_search | null;
|
||||
}
|
||||
|
||||
export interface SearchServiceAccountVariables {
|
||||
after?: string | null;
|
||||
first: number;
|
||||
query: string;
|
||||
}
|
15
src/icons/Webhooks.tsx
Normal file
15
src/icons/Webhooks.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
|
||||
import React from "react";
|
||||
|
||||
export const Webhooks = createSvgIcon(
|
||||
<>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.188 25.403L14.8603 17.9921C11.5958 15.3023 10.5525 10.9218 12.3952 7.03423C14.4554 2.68805 19.4727 0.911694 23.8632 2.67405C28.3581 4.47829 30.6541 10.5097 28.3722 15.6112L26.9311 14.9665C28.8961 10.5736 26.8313 5.56662 23.2751 4.13916C19.6146 2.66981 15.5043 4.16105 13.8218 7.71045C12.2078 11.1154 13.2502 14.9597 16.3677 17.1575L16.9804 17.5895L11.5587 26.1892C11.9146 26.496 12.2246 26.8684 12.469 27.301C13.6059 29.3129 12.8965 31.8654 10.8847 33.0023C8.87279 34.1392 6.32024 33.4298 5.18339 31.4179C4.04653 29.4061 4.75587 26.8535 6.76775 25.7167C7.85287 25.1035 9.09527 25.0274 10.188 25.403ZM23.2649 10.8173C23.2649 12.256 22.0986 13.4222 20.6599 13.4222C19.2213 13.4222 18.055 12.256 18.055 10.8173C18.055 9.37865 19.2213 8.21239 20.6599 8.21239C22.0986 8.21239 23.2649 9.37865 23.2649 10.8173ZM22.1884 14.713C21.7149 14.8989 21.1994 15.001 20.6599 15.001C18.3494 15.001 16.4763 13.1279 16.4763 10.8173C16.4763 8.50674 18.3494 6.63365 20.6599 6.63365C22.9705 6.63365 24.8436 8.50674 24.8436 10.8173C24.8436 12.0191 24.3368 13.1026 23.5253 13.8657L27.6887 21.6091C31.6202 20.096 35.9283 21.3402 38.3955 24.8467C41.154 28.7675 40.2364 33.995 36.5573 36.9426C32.7897 39.9611 26.4344 38.9945 23.1295 34.5103L24.4003 33.5736C27.2448 37.4331 32.5933 38.0955 35.5702 35.7105C38.6356 33.2546 39.3563 28.956 37.1043 25.7552C34.9442 22.6851 31.1037 21.7052 27.6738 23.3323L26.9957 23.6539L22.1884 14.713ZM28.4266 28.5411C27.7395 29.8054 28.2075 31.3873 29.4718 32.0744C30.7362 32.7614 32.3181 32.2935 33.0051 31.0291C33.6922 29.7648 33.2242 28.1829 31.9599 27.4958C30.6955 26.8088 29.1136 27.2768 28.4266 28.5411ZM26.6391 30.7276C26.8998 31.8533 27.6229 32.8664 28.718 33.4615C30.7485 34.5649 33.2889 33.8134 34.3923 31.7829C35.4956 29.7525 34.7441 27.2121 32.7137 26.1087C30.6832 25.0053 28.1428 25.7569 27.0394 27.7873C26.8022 28.2239 26.6507 28.6841 26.5792 29.1486L16.4131 29.1096L16.3716 29.8582C16.1604 33.6666 13.4532 36.5883 9.71044 37.0246C5.80888 37.4794 2.36564 34.7845 1.67123 30.9016C0.99662 27.1295 4.1478 22.7246 8.91444 22.0623L8.69716 20.4986C3.16177 21.2677 -0.735523 26.4118 0.117155 31.1796C0.950033 35.8367 5.11583 39.1496 9.89322 38.5927C14.1665 38.0946 17.3279 34.8879 17.8783 30.694L26.6391 30.7276ZM11.0946 28.0777C10.3866 26.8249 8.7972 26.3832 7.54443 27.0911C6.29165 27.799 5.84995 29.3885 6.55786 30.6413C7.26577 31.894 8.85522 32.3357 10.108 31.6278C11.3608 30.9199 11.8025 29.3305 11.0946 28.0777Z"
|
||||
fill="#06847B"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
Webhooks.displayName = "Webhooks";
|
||||
export default Webhooks;
|
|
@ -49,6 +49,7 @@ import StaffSection from "./staff";
|
|||
import TaxesSection from "./taxes";
|
||||
import TranslationsSection from "./translations";
|
||||
import { PermissionEnum } from "./types/globalTypes";
|
||||
import WebhooksSection from "./webhooks";
|
||||
|
||||
interface ResponseError extends ErrorResponse {
|
||||
networkError?: Error & {
|
||||
|
@ -219,6 +220,11 @@ const Routes: React.FC = () => {
|
|||
path="/translations"
|
||||
component={TranslationsSection}
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_WEBHOOKS]}
|
||||
path="/webhooks"
|
||||
component={WebhooksSection}
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_MENUS]}
|
||||
path={navigationSection}
|
||||
|
|
|
@ -47,6 +47,9 @@ export const commonMessages = defineMessages({
|
|||
properties: {
|
||||
defaultMessage: "Properties"
|
||||
},
|
||||
requiredField: {
|
||||
defaultMessage: "This field is required"
|
||||
},
|
||||
savedChanges: {
|
||||
defaultMessage: "Saved changes"
|
||||
},
|
||||
|
@ -202,6 +205,10 @@ export const sectionNames = defineMessages({
|
|||
vouchers: {
|
||||
defaultMessage: "Vouchers",
|
||||
description: "vouchers section name"
|
||||
},
|
||||
webhooks: {
|
||||
defaultMessage: "Webhooks",
|
||||
description: "webhooks section name"
|
||||
}
|
||||
});
|
||||
|
||||
|
|
157
src/misc.ts
157
src/misc.ts
|
@ -1,8 +1,8 @@
|
|||
import moment from "moment-timezone";
|
||||
import { MutationFunction, MutationResult } from "react-apollo";
|
||||
import { defineMessages, IntlShape } from "react-intl";
|
||||
import urlJoin from "url-join";
|
||||
|
||||
import { defineMessages, IntlShape } from "react-intl";
|
||||
import { ConfirmButtonTransitionState } from "./components/ConfirmButton/ConfirmButton";
|
||||
import { APP_MOUNT_URI } from "./config";
|
||||
import { AddressType } from "./customers/types";
|
||||
|
@ -10,8 +10,7 @@ import { PartialMutationProviderOutput, UserError } from "./types";
|
|||
import {
|
||||
AuthorizationKeyType,
|
||||
OrderStatus,
|
||||
PaymentChargeStatusEnum,
|
||||
TaxRateType
|
||||
PaymentChargeStatusEnum
|
||||
} from "./types/globalTypes";
|
||||
|
||||
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
|
||||
|
@ -181,158 +180,6 @@ export const transformAddressToForm = (data: AddressType) => ({
|
|||
streetAddress2: maybe(() => data.streetAddress2, "")
|
||||
});
|
||||
|
||||
const taxRatesMessages = defineMessages({
|
||||
accommodation: {
|
||||
defaultMessage: "Accommodation",
|
||||
description: "tax rate"
|
||||
},
|
||||
admissionToCulturalEvents: {
|
||||
defaultMessage: "Admission to cultural events",
|
||||
description: "tax rate"
|
||||
},
|
||||
admissionToEntertainmentEvents: {
|
||||
defaultMessage: "Admission to entertainment events",
|
||||
description: "tax rate"
|
||||
},
|
||||
admissionToSportingEvents: {
|
||||
defaultMessage: "Admission to sporting events",
|
||||
description: "tax rate"
|
||||
},
|
||||
advertising: {
|
||||
defaultMessage: "Advertising",
|
||||
description: "tax rate"
|
||||
},
|
||||
agriculturalSupplies: {
|
||||
defaultMessage: "Agricultural supplies",
|
||||
description: "tax rate"
|
||||
},
|
||||
babyFoodstuffs: {
|
||||
defaultMessage: "Baby foodstuffs",
|
||||
description: "tax rate"
|
||||
},
|
||||
bikes: {
|
||||
defaultMessage: "Bikes",
|
||||
description: "tax rate"
|
||||
},
|
||||
books: {
|
||||
defaultMessage: "Books",
|
||||
description: "tax rate"
|
||||
},
|
||||
childrensClothing: {
|
||||
defaultMessage: "Children's clothing",
|
||||
description: "tax rate"
|
||||
},
|
||||
domesticFuel: {
|
||||
defaultMessage: "Domestic fuel",
|
||||
description: "tax rate"
|
||||
},
|
||||
domesticServices: {
|
||||
defaultMessage: "Domestic services",
|
||||
description: "tax rate"
|
||||
},
|
||||
ebooks: {
|
||||
defaultMessage: "E-books",
|
||||
description: "tax rate"
|
||||
},
|
||||
foodstuffs: {
|
||||
defaultMessage: "Foodstuffs",
|
||||
description: "tax rate"
|
||||
},
|
||||
hotels: {
|
||||
defaultMessage: "Hotels",
|
||||
description: "tax rate"
|
||||
},
|
||||
medical: {
|
||||
defaultMessage: "Medical",
|
||||
description: "tax rate"
|
||||
},
|
||||
newspapers: {
|
||||
defaultMessage: "Newspapers",
|
||||
description: "tax rate"
|
||||
},
|
||||
passengerTransport: {
|
||||
defaultMessage: "Passenger transport",
|
||||
description: "tax rate"
|
||||
},
|
||||
pharmaceuticals: {
|
||||
defaultMessage: "Pharmaceuticals",
|
||||
description: "tax rate"
|
||||
},
|
||||
propertyRenovations: {
|
||||
defaultMessage: "Property renovations",
|
||||
description: "tax rate"
|
||||
},
|
||||
restaurants: {
|
||||
defaultMessage: "Restaurants",
|
||||
description: "tax rate"
|
||||
},
|
||||
socialHousing: {
|
||||
defaultMessage: "Social housing",
|
||||
description: "tax rate"
|
||||
},
|
||||
standard: {
|
||||
defaultMessage: "Standard",
|
||||
description: "tax rate"
|
||||
},
|
||||
water: {
|
||||
defaultMessage: "Water",
|
||||
description: "tax rate"
|
||||
}
|
||||
});
|
||||
|
||||
export const translatedTaxRates = (intl: IntlShape) => ({
|
||||
[TaxRateType.ACCOMMODATION]: intl.formatMessage(
|
||||
taxRatesMessages.accommodation
|
||||
),
|
||||
[TaxRateType.ADMISSION_TO_CULTURAL_EVENTS]: intl.formatMessage(
|
||||
taxRatesMessages.admissionToCulturalEvents
|
||||
),
|
||||
[TaxRateType.ADMISSION_TO_ENTERTAINMENT_EVENTS]: intl.formatMessage(
|
||||
taxRatesMessages.admissionToEntertainmentEvents
|
||||
),
|
||||
[TaxRateType.ADMISSION_TO_SPORTING_EVENTS]: intl.formatMessage(
|
||||
taxRatesMessages.admissionToSportingEvents
|
||||
),
|
||||
[TaxRateType.ADVERTISING]: intl.formatMessage(taxRatesMessages.advertising),
|
||||
[TaxRateType.AGRICULTURAL_SUPPLIES]: intl.formatMessage(
|
||||
taxRatesMessages.agriculturalSupplies
|
||||
),
|
||||
[TaxRateType.BABY_FOODSTUFFS]: intl.formatMessage(
|
||||
taxRatesMessages.babyFoodstuffs
|
||||
),
|
||||
[TaxRateType.BIKES]: intl.formatMessage(taxRatesMessages.bikes),
|
||||
[TaxRateType.BOOKS]: intl.formatMessage(taxRatesMessages.books),
|
||||
[TaxRateType.CHILDRENS_CLOTHING]: intl.formatMessage(
|
||||
taxRatesMessages.childrensClothing
|
||||
),
|
||||
[TaxRateType.DOMESTIC_FUEL]: intl.formatMessage(
|
||||
taxRatesMessages.domesticFuel
|
||||
),
|
||||
[TaxRateType.DOMESTIC_SERVICES]: intl.formatMessage(
|
||||
taxRatesMessages.domesticServices
|
||||
),
|
||||
[TaxRateType.E_BOOKS]: intl.formatMessage(taxRatesMessages.ebooks),
|
||||
[TaxRateType.FOODSTUFFS]: intl.formatMessage(taxRatesMessages.foodstuffs),
|
||||
[TaxRateType.HOTELS]: intl.formatMessage(taxRatesMessages.hotels),
|
||||
[TaxRateType.MEDICAL]: intl.formatMessage(taxRatesMessages.medical),
|
||||
[TaxRateType.NEWSPAPERS]: intl.formatMessage(taxRatesMessages.newspapers),
|
||||
[TaxRateType.PASSENGER_TRANSPORT]: intl.formatMessage(
|
||||
taxRatesMessages.passengerTransport
|
||||
),
|
||||
[TaxRateType.PHARMACEUTICALS]: intl.formatMessage(
|
||||
taxRatesMessages.pharmaceuticals
|
||||
),
|
||||
[TaxRateType.PROPERTY_RENOVATIONS]: intl.formatMessage(
|
||||
taxRatesMessages.propertyRenovations
|
||||
),
|
||||
[TaxRateType.RESTAURANTS]: intl.formatMessage(taxRatesMessages.restaurants),
|
||||
[TaxRateType.SOCIAL_HOUSING]: intl.formatMessage(
|
||||
taxRatesMessages.socialHousing
|
||||
),
|
||||
[TaxRateType.STANDARD]: intl.formatMessage(taxRatesMessages.standard),
|
||||
[TaxRateType.WATER]: intl.formatMessage(taxRatesMessages.water)
|
||||
});
|
||||
|
||||
export const authorizationKeyTypes = {
|
||||
[AuthorizationKeyType.FACEBOOK]: "Facebook",
|
||||
[AuthorizationKeyType.GOOGLE_OAUTH2]: "Google OAuth2"
|
||||
|
|
|
@ -5,12 +5,13 @@ import { FormattedMessage, IntlShape, useIntl } from "react-intl";
|
|||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
|
||||
export type OrderDraftFinalizeWarning =
|
||||
| "no-shipping"
|
||||
| "no-billing"
|
||||
| "no-user"
|
||||
| "no-shipping-method"
|
||||
| "unnecessary-shipping-method";
|
||||
export enum OrderDraftFinalizeWarning {
|
||||
NO_SHIPPING,
|
||||
NO_BILLING,
|
||||
NO_USER,
|
||||
NO_SHIPPING_METHOD,
|
||||
UNNECESSARY_SHIPPING_METHOD
|
||||
}
|
||||
|
||||
export interface OrderDraftFinalizeDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
|
@ -21,30 +22,29 @@ export interface OrderDraftFinalizeDialogProps {
|
|||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
const warningToText = (warning: OrderDraftFinalizeWarning, intl: IntlShape) => {
|
||||
switch (warning) {
|
||||
case "no-shipping":
|
||||
return intl.formatMessage({
|
||||
defaultMessage: "No shipping address"
|
||||
});
|
||||
case "no-billing":
|
||||
return intl.formatMessage({
|
||||
defaultMessage: "No billing address"
|
||||
});
|
||||
case "no-user":
|
||||
return intl.formatMessage({
|
||||
defaultMessage: "No user information"
|
||||
});
|
||||
case "no-shipping-method":
|
||||
return intl.formatMessage({
|
||||
defaultMessage: "Some products require shipping, but no method provided"
|
||||
});
|
||||
case "unnecessary-shipping-method":
|
||||
return intl.formatMessage({
|
||||
function translateWarnings(
|
||||
intl: IntlShape
|
||||
): Record<OrderDraftFinalizeWarning, string> {
|
||||
return {
|
||||
[OrderDraftFinalizeWarning.NO_BILLING]: intl.formatMessage({
|
||||
defaultMessage: "No billing address"
|
||||
}),
|
||||
[OrderDraftFinalizeWarning.NO_SHIPPING]: intl.formatMessage({
|
||||
defaultMessage: "No shipping address"
|
||||
}),
|
||||
[OrderDraftFinalizeWarning.NO_SHIPPING_METHOD]: intl.formatMessage({
|
||||
defaultMessage: "Some products require shipping, but no method provided"
|
||||
}),
|
||||
[OrderDraftFinalizeWarning.NO_USER]: intl.formatMessage({
|
||||
defaultMessage: "No user information"
|
||||
}),
|
||||
[OrderDraftFinalizeWarning.UNNECESSARY_SHIPPING_METHOD]: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Shipping method provided, but no product requires it"
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const OrderDraftFinalizeDialog: React.StatelessComponent<
|
||||
OrderDraftFinalizeDialogProps
|
||||
|
@ -57,6 +57,7 @@ const OrderDraftFinalizeDialog: React.StatelessComponent<
|
|||
orderNumber
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const translatedWarnings = translateWarnings(intl);
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
|
@ -89,7 +90,7 @@ const OrderDraftFinalizeDialog: React.StatelessComponent<
|
|||
</p>
|
||||
<ul>
|
||||
{warnings.map(warning => (
|
||||
<li key={warning}>{warningToText(warning, intl)}</li>
|
||||
<li key={warning}>{translatedWarnings[warning]}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
|
|
|
@ -38,13 +38,13 @@ import { OrderDetailsMessages } from "./OrderDetailsMessages";
|
|||
const orderDraftFinalizeWarnings = (order: OrderDetails_order) => {
|
||||
const warnings = [] as OrderDraftFinalizeWarning[];
|
||||
if (!(order && order.shippingAddress)) {
|
||||
warnings.push("no-shipping");
|
||||
warnings.push(OrderDraftFinalizeWarning.NO_SHIPPING);
|
||||
}
|
||||
if (!(order && order.billingAddress)) {
|
||||
warnings.push("no-billing");
|
||||
warnings.push(OrderDraftFinalizeWarning.NO_BILLING);
|
||||
}
|
||||
if (!(order && (order.user || order.userEmail))) {
|
||||
warnings.push("no-user");
|
||||
warnings.push(OrderDraftFinalizeWarning.NO_USER);
|
||||
}
|
||||
if (
|
||||
order &&
|
||||
|
@ -52,7 +52,7 @@ const orderDraftFinalizeWarnings = (order: OrderDetails_order) => {
|
|||
order.lines.filter(line => line.isShippingRequired).length > 0 &&
|
||||
order.shippingMethod === null
|
||||
) {
|
||||
warnings.push("no-shipping-method");
|
||||
warnings.push(OrderDraftFinalizeWarning.NO_SHIPPING_METHOD);
|
||||
}
|
||||
if (
|
||||
order &&
|
||||
|
@ -60,7 +60,7 @@ const orderDraftFinalizeWarnings = (order: OrderDetails_order) => {
|
|||
order.lines.filter(line => line.isShippingRequired).length === 0 &&
|
||||
order.shippingMethod !== null
|
||||
) {
|
||||
warnings.push("unnecessary-shipping-method");
|
||||
warnings.push(OrderDraftFinalizeWarning.UNNECESSARY_SHIPPING_METHOD);
|
||||
}
|
||||
return warnings;
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,8 @@ import { storiesOf } from "@storybook/react";
|
|||
import React from "react";
|
||||
|
||||
import OrderDraftFinalize, {
|
||||
OrderDraftFinalizeDialogProps
|
||||
OrderDraftFinalizeDialogProps,
|
||||
OrderDraftFinalizeWarning
|
||||
} from "../../../orders/components/OrderDraftFinalizeDialog";
|
||||
import Decorator from "../../Decorator";
|
||||
|
||||
|
@ -21,6 +22,11 @@ storiesOf("Orders / OrderDraftFinalizeDialog", module)
|
|||
.add("with warnings", () => (
|
||||
<OrderDraftFinalize
|
||||
{...props}
|
||||
warnings={["no-shipping-method", "no-shipping", "no-billing", "no-user"]}
|
||||
warnings={[
|
||||
OrderDraftFinalizeWarning.NO_SHIPPING_METHOD,
|
||||
OrderDraftFinalizeWarning.NO_SHIPPING,
|
||||
OrderDraftFinalizeWarning.NO_BILLING,
|
||||
OrderDraftFinalizeWarning.NO_USER
|
||||
]}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -6,7 +6,7 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { FormattedMessage, IntlShape, useIntl } from "react-intl";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { Container } from "@saleor/components/Container";
|
||||
|
@ -14,7 +14,8 @@ import Grid from "@saleor/components/Grid";
|
|||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe, renderCollection, translatedTaxRates } from "../../../misc";
|
||||
import { TaxRateType } from "@saleor/types/globalTypes";
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { CountryList_shop_countries_vat_reducedRates } from "../../types/CountryList";
|
||||
|
||||
const styles = createStyles({
|
||||
|
@ -23,6 +24,111 @@ const styles = createStyles({
|
|||
}
|
||||
});
|
||||
|
||||
function translateTaxRates(intl: IntlShape): Record<TaxRateType, string> {
|
||||
return {
|
||||
[TaxRateType.ACCOMMODATION]: intl.formatMessage({
|
||||
defaultMessage: "Accommodation",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.ADMISSION_TO_CULTURAL_EVENTS]: intl.formatMessage({
|
||||
defaultMessage: "Admission to cultural events",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.ADMISSION_TO_ENTERTAINMENT_EVENTS]: intl.formatMessage({
|
||||
defaultMessage: "Admission to entertainment events",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.ADMISSION_TO_SPORTING_EVENTS]: intl.formatMessage({
|
||||
defaultMessage: "Admission to sporting events",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.ADVERTISING]: intl.formatMessage({
|
||||
defaultMessage: "Advertising",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.AGRICULTURAL_SUPPLIES]: intl.formatMessage({
|
||||
defaultMessage: "Agricultural supplies",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.BABY_FOODSTUFFS]: intl.formatMessage({
|
||||
defaultMessage: "Baby foodstuffs",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.BIKES]: intl.formatMessage({
|
||||
defaultMessage: "Bikes",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.BOOKS]: intl.formatMessage({
|
||||
defaultMessage: "Books",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.CHILDRENS_CLOTHING]: intl.formatMessage({
|
||||
defaultMessage: "Children's clothing",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.DOMESTIC_FUEL]: intl.formatMessage({
|
||||
defaultMessage: "Domestic fuel",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.DOMESTIC_SERVICES]: intl.formatMessage({
|
||||
defaultMessage: "Domestic services",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.E_BOOKS]: intl.formatMessage({
|
||||
defaultMessage: "E-books",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.FOODSTUFFS]: intl.formatMessage({
|
||||
defaultMessage: "Foodstuffs",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.HOTELS]: intl.formatMessage({
|
||||
defaultMessage: "Hotels",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.MEDICAL]: intl.formatMessage({
|
||||
defaultMessage: "Medical",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.NEWSPAPERS]: intl.formatMessage({
|
||||
defaultMessage: "Newspapers",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.PASSENGER_TRANSPORT]: intl.formatMessage({
|
||||
defaultMessage: "Passenger transport",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.PHARMACEUTICALS]: intl.formatMessage({
|
||||
defaultMessage: "Pharmaceuticals",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.PROPERTY_RENOVATIONS]: intl.formatMessage({
|
||||
defaultMessage: "Property renovations",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.RESTAURANTS]: intl.formatMessage({
|
||||
defaultMessage: "Restaurants",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.SOCIAL_HOUSING]: intl.formatMessage({
|
||||
defaultMessage: "Social housing",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.STANDARD]: intl.formatMessage({
|
||||
defaultMessage: "Standard",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.WATER]: intl.formatMessage({
|
||||
defaultMessage: "Water",
|
||||
description: "tax rate"
|
||||
}),
|
||||
[TaxRateType.WINE]: intl.formatMessage({
|
||||
defaultMessage: "Wine",
|
||||
description: "tax rate"
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export interface CountryTaxesPageProps {
|
||||
countryName: string;
|
||||
taxCategories: CountryList_shop_countries_vat_reducedRates[];
|
||||
|
@ -37,8 +143,8 @@ const CountryTaxesPage = withStyles(styles, { name: "CountryTaxesPage" })(
|
|||
onBack
|
||||
}: CountryTaxesPageProps & WithStyles<typeof styles>) => {
|
||||
const intl = useIntl();
|
||||
const translatedTaxRates = translateTaxRates(intl);
|
||||
|
||||
const taxRates = translatedTaxRates(intl);
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
|
@ -82,7 +188,7 @@ const CountryTaxesPage = withStyles(styles, { name: "CountryTaxesPage" })(
|
|||
>
|
||||
<TableCell>
|
||||
{maybe<React.ReactNode>(
|
||||
() => taxRates[taxCategory.rateType],
|
||||
() => translatedTaxRates[taxCategory.rateType],
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
|
|
|
@ -28,7 +28,8 @@ export enum ListViews {
|
|||
SALES_LIST = "SALES_LIST",
|
||||
SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST",
|
||||
STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST",
|
||||
VOUCHER_LIST = "VOUCHER_LIST"
|
||||
VOUCHER_LIST = "VOUCHER_LIST",
|
||||
WEBHOOK_LIST = "WEBHOOK_LIST"
|
||||
}
|
||||
|
||||
export interface ListProps<TColumns extends string = string> {
|
||||
|
|
|
@ -286,6 +286,24 @@ export enum VoucherTypeEnum {
|
|||
SPECIFIC_PRODUCT = "SPECIFIC_PRODUCT",
|
||||
}
|
||||
|
||||
export enum WebhookErrorCode {
|
||||
GRAPHQL_ERROR = "GRAPHQL_ERROR",
|
||||
INVALID = "INVALID",
|
||||
NOT_FOUND = "NOT_FOUND",
|
||||
REQUIRED = "REQUIRED",
|
||||
UNIQUE = "UNIQUE",
|
||||
}
|
||||
|
||||
export enum WebhookEventTypeEnum {
|
||||
ANY_EVENTS = "ANY_EVENTS",
|
||||
CUSTOMER_CREATED = "CUSTOMER_CREATED",
|
||||
ORDER_CANCELLED = "ORDER_CANCELLED",
|
||||
ORDER_CREATED = "ORDER_CREATED",
|
||||
ORDER_FULLY_PAID = "ORDER_FULLY_PAID",
|
||||
ORDER_UPDATED = "ORDER_UPDATED",
|
||||
PRODUCT_CREATED = "PRODUCT_CREATED",
|
||||
}
|
||||
|
||||
export enum WeightUnitsEnum {
|
||||
G = "G",
|
||||
KG = "KG",
|
||||
|
@ -810,6 +828,29 @@ export interface VoucherInput {
|
|||
usageLimit?: number | null;
|
||||
}
|
||||
|
||||
export interface WebhookCreateInput {
|
||||
name?: string | null;
|
||||
targetUrl?: string | null;
|
||||
events?: (WebhookEventTypeEnum | null)[] | null;
|
||||
serviceAccount?: string | null;
|
||||
isActive?: boolean | null;
|
||||
secretKey?: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookFilterInput {
|
||||
search?: string | null;
|
||||
isActive?: boolean | null;
|
||||
}
|
||||
|
||||
export interface WebhookUpdateInput {
|
||||
name?: string | null;
|
||||
targetUrl?: string | null;
|
||||
events?: (WebhookEventTypeEnum | null)[] | null;
|
||||
serviceAccount?: string | null;
|
||||
isActive?: boolean | null;
|
||||
secretKey?: string | null;
|
||||
}
|
||||
|
||||
//==============================================================
|
||||
// END Enums and Input Objects
|
||||
//==============================================================
|
||||
|
|
134
src/webhooks/components/WebhookCreatePage/WebhookCreatePage.tsx
Normal file
134
src/webhooks/components/WebhookCreatePage/WebhookCreatePage.tsx
Normal file
|
@ -0,0 +1,134 @@
|
|||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { SearchServiceAccount_search_edges_node } from "@saleor/containers/SearchServiceAccount/types/SearchServiceAccount";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import WebhookEvents from "@saleor/webhooks/components/WebhookEvents";
|
||||
import WebhookInfo from "@saleor/webhooks/components/WebhookInfo";
|
||||
import WebhookStatus from "@saleor/webhooks/components/WebhookStatus";
|
||||
import { WebhookCreate_webhookCreate_webhookErrors } from "@saleor/webhooks/types/WebhookCreate";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
export interface FormData {
|
||||
id: string;
|
||||
events: WebhookEventTypeEnum[];
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
secretKey: string | null;
|
||||
targetUrl: string;
|
||||
serviceAccount: string;
|
||||
allEvents: boolean;
|
||||
}
|
||||
|
||||
export interface WebhookCreatePageProps {
|
||||
disabled: boolean;
|
||||
errors: WebhookCreate_webhookCreate_webhookErrors[];
|
||||
services?: SearchServiceAccount_search_edges_node[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
fetchServiceAccounts: (data: string) => void;
|
||||
onBack: () => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
const WebhookCreatePage: React.FC<WebhookCreatePageProps> = ({
|
||||
disabled,
|
||||
errors: apiErrors,
|
||||
saveButtonBarState,
|
||||
services,
|
||||
fetchServiceAccounts,
|
||||
onBack,
|
||||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const initialForm: FormData = {
|
||||
allEvents: false,
|
||||
events: [],
|
||||
id: null,
|
||||
isActive: false,
|
||||
name: null,
|
||||
secretKey: "",
|
||||
serviceAccount: "",
|
||||
targetUrl: ""
|
||||
};
|
||||
const [selectedServiceAcccount, setSelectedServiceAcccount] = React.useState(
|
||||
""
|
||||
);
|
||||
const servicesChoiceList = maybe(
|
||||
() =>
|
||||
services.map(node => ({
|
||||
label: node.name,
|
||||
value: node.id
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Form errors={apiErrors} initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ data, errors, hasChanged, submit, change }) => {
|
||||
const handleServiceSelect = createSingleAutocompleteSelectHandler(
|
||||
change,
|
||||
setSelectedServiceAcccount,
|
||||
servicesChoiceList
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.webhooks)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create Webhook",
|
||||
description: "header"
|
||||
})}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
<WebhookInfo
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
serviceDisplayValue={selectedServiceAcccount}
|
||||
services={servicesChoiceList}
|
||||
fetchServiceAccounts={fetchServiceAccounts}
|
||||
apiErrors={apiErrors}
|
||||
errors={errors}
|
||||
serviceOnChange={handleServiceSelect}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<WebhookEvents
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<WebhookStatus
|
||||
data={data.isActive}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
disabled={disabled || !hasChanged}
|
||||
state={saveButtonBarState}
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
WebhookCreatePage.displayName = "WebhookCreatePage";
|
||||
export default WebhookCreatePage;
|
|
@ -0,0 +1,35 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { WebhookErrorCode } from "@saleor/types/globalTypes";
|
||||
import WebhookCreatePage, { WebhookCreatePageProps } from "./WebhookCreatePage";
|
||||
|
||||
const props: WebhookCreatePageProps = {
|
||||
disabled: false,
|
||||
errors: [],
|
||||
fetchServiceAccounts: () => undefined,
|
||||
onBack: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
saveButtonBarState: "default",
|
||||
services: []
|
||||
};
|
||||
storiesOf("Views / Webhooks / Create webhook", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <WebhookCreatePage {...props} />)
|
||||
.add("loading", () => <WebhookCreatePage {...props} disabled={true} />)
|
||||
.add("form errors", () => (
|
||||
<WebhookCreatePage
|
||||
{...props}
|
||||
errors={[
|
||||
{
|
||||
code: WebhookErrorCode.INVALID,
|
||||
field: null
|
||||
}
|
||||
].map(error => ({
|
||||
__typename: "WebhookError",
|
||||
message: "Generic form error",
|
||||
...error
|
||||
}))}
|
||||
/>
|
||||
));
|
2
src/webhooks/components/WebhookCreatePage/index.ts
Normal file
2
src/webhooks/components/WebhookCreatePage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhookCreatePage";
|
||||
export * from "./WebhookCreatePage";
|
|
@ -0,0 +1,19 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import WebhookDeleteDialog, {
|
||||
WebhookDeleteDialogProps
|
||||
} from "./WebhookDeleteDialog";
|
||||
|
||||
const props: WebhookDeleteDialogProps = {
|
||||
confirmButtonState: "default",
|
||||
name: "Magento Importer",
|
||||
onClose: () => undefined,
|
||||
onConfirm: () => undefined,
|
||||
open: true
|
||||
};
|
||||
|
||||
storiesOf("Views / Webhooks / Delete webhook", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <WebhookDeleteDialog {...props} />);
|
|
@ -0,0 +1,50 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
|
||||
export interface WebhookDeleteDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
open: boolean;
|
||||
name: string;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
const WebhookDeleteDialog: React.FC<WebhookDeleteDialogProps> = ({
|
||||
confirmButtonState,
|
||||
open,
|
||||
name,
|
||||
onClose,
|
||||
onConfirm
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Webhook",
|
||||
description: "dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {name}?"
|
||||
description="delete webhook"
|
||||
values={{
|
||||
name: <strong>{name}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
WebhookDeleteDialog.displayName = "WebhookDeleteDialog";
|
||||
export default WebhookDeleteDialog;
|
2
src/webhooks/components/WebhookDeleteDialog/index.ts
Normal file
2
src/webhooks/components/WebhookDeleteDialog/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhookDeleteDialog";
|
||||
export * from "./WebhookDeleteDialog";
|
113
src/webhooks/components/WebhookEvents/WebhookEvents.tsx
Normal file
113
src/webhooks/components/WebhookEvents/WebhookEvents.tsx
Normal file
|
@ -0,0 +1,113 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
|
||||
import { toggle } from "@saleor/utils/lists";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
interface WebhookEventsProps {
|
||||
data: {
|
||||
allEvents: boolean;
|
||||
events: string[];
|
||||
};
|
||||
disabled: boolean;
|
||||
onChange: (event: ChangeEvent, cb?: () => void) => void;
|
||||
}
|
||||
|
||||
const WebhookEvents: React.StatelessComponent<WebhookEventsProps> = ({
|
||||
data,
|
||||
disabled,
|
||||
onChange
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const eventsEnum = Object.values(WebhookEventTypeEnum);
|
||||
|
||||
const translatedEvents = {
|
||||
[WebhookEventTypeEnum.ANY_EVENTS]: intl.formatMessage({
|
||||
defaultMessage: "Any events",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.CUSTOMER_CREATED]: intl.formatMessage({
|
||||
defaultMessage: "Customer created",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.ORDER_CANCELLED]: intl.formatMessage({
|
||||
defaultMessage: "Order cancelled",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.ORDER_CREATED]: intl.formatMessage({
|
||||
defaultMessage: "Order created",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.ORDER_FULLY_PAID]: intl.formatMessage({
|
||||
defaultMessage: "Order fully paid",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.ORDER_UPDATED]: intl.formatMessage({
|
||||
defaultMessage: "Order updated",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.PRODUCT_CREATED]: intl.formatMessage({
|
||||
defaultMessage: "Product created",
|
||||
description: "event"
|
||||
})
|
||||
};
|
||||
|
||||
const handleEventsChange = (event: ChangeEvent) =>
|
||||
onChange({
|
||||
target: {
|
||||
name: "events",
|
||||
value: toggle(event.target.name, data.events, (a, b) => a === b)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Events",
|
||||
description: "section header"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
{intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Expand or restrict webhooks permissions to register certain events in Saleor system.",
|
||||
description: "webhook events"
|
||||
})}
|
||||
</Typography>
|
||||
<ControlledCheckbox
|
||||
checked={data.allEvents}
|
||||
disabled={disabled}
|
||||
label={translatedEvents.ANY_EVENTS}
|
||||
name="allEvents"
|
||||
onChange={onChange}
|
||||
/>
|
||||
{!data.allEvents && (
|
||||
<>
|
||||
<Hr />
|
||||
{eventsEnum.slice(1).map(event => (
|
||||
<div key={event}>
|
||||
<ControlledCheckbox
|
||||
checked={data.events.includes(event)}
|
||||
disabled={disabled}
|
||||
label={translatedEvents[event]}
|
||||
name={event}
|
||||
onChange={handleEventsChange}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
WebhookEvents.displayName = "WebhookEvents";
|
||||
export default WebhookEvents;
|
2
src/webhooks/components/WebhookEvents/index.ts
Normal file
2
src/webhooks/components/WebhookEvents/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhookEvents";
|
||||
export * from "./WebhookEvents";
|
154
src/webhooks/components/WebhookInfo/WebhookInfo.tsx
Normal file
154
src/webhooks/components/WebhookInfo/WebhookInfo.tsx
Normal file
|
@ -0,0 +1,154 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import SingleAutocompleteSelectField, {
|
||||
SingleAutocompleteChoiceType
|
||||
} from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { FormErrors } from "@saleor/types";
|
||||
import { WebhookCreate_webhookCreate_webhookErrors } from "@saleor/webhooks/types/WebhookCreate";
|
||||
import { FormData } from "../WebhooksDetailsPage";
|
||||
|
||||
interface WebhookInfoProps {
|
||||
apiErrors: WebhookCreate_webhookCreate_webhookErrors[];
|
||||
data: FormData;
|
||||
disabled: boolean;
|
||||
serviceDisplayValue: string;
|
||||
services: SingleAutocompleteChoiceType[];
|
||||
errors: FormErrors<"name" | "targetUrl" | "secretKey">;
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
serviceOnChange: (event: ChangeEvent) => void;
|
||||
fetchServiceAccounts: (data: string) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
status: {
|
||||
paddingTop: 20
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
lineHeight: 1.9,
|
||||
paddingBottom: 10
|
||||
}
|
||||
}));
|
||||
|
||||
const WebhookInfo: React.StatelessComponent<WebhookInfoProps> = ({
|
||||
apiErrors,
|
||||
data,
|
||||
disabled,
|
||||
services,
|
||||
serviceDisplayValue,
|
||||
fetchServiceAccounts,
|
||||
errors,
|
||||
onChange,
|
||||
serviceOnChange
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const serviceAccountsError =
|
||||
apiErrors.filter(error => error.field === null).length > 0;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Webhook Information",
|
||||
description: "section header"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography className={classes.title}>
|
||||
{intl.formatMessage(commonMessages.generalInformations)}
|
||||
</Typography>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Webhook Name",
|
||||
description: "webhook"
|
||||
})}
|
||||
fullWidth
|
||||
name="name"
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<Hr />
|
||||
<FormSpacer />
|
||||
<Typography className={classes.title}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Webhook specific information",
|
||||
description: "webhook specific information"
|
||||
})}
|
||||
</Typography>
|
||||
<SingleAutocompleteSelectField
|
||||
disabled={disabled}
|
||||
displayValue={serviceDisplayValue}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Assign to Service Account"
|
||||
})}
|
||||
error={serviceAccountsError}
|
||||
helperText={
|
||||
serviceAccountsError &&
|
||||
intl.formatMessage(commonMessages.requiredField)
|
||||
}
|
||||
name="serviceAccount"
|
||||
onChange={serviceOnChange}
|
||||
value={data.serviceAccount}
|
||||
choices={services}
|
||||
fetchChoices={fetchServiceAccounts}
|
||||
InputProps={{
|
||||
autoComplete: "off"
|
||||
}}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.targetUrl}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage: "This URL will receive webhook POST requests",
|
||||
description: "webhook target url help text"
|
||||
})}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Target URL",
|
||||
description: "webhook"
|
||||
})}
|
||||
fullWidth
|
||||
name="targetUrl"
|
||||
value={data.targetUrl}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.secretKey}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"secret key is used to create a hash signature with each payload. *optional field",
|
||||
description: "webhook secret key help text"
|
||||
})}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Secrect Key",
|
||||
description: "webhook"
|
||||
})}
|
||||
fullWidth
|
||||
name="secretKey"
|
||||
value={data.secretKey}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
WebhookInfo.displayName = "WebhookInfo";
|
||||
export default WebhookInfo;
|
2
src/webhooks/components/WebhookInfo/index.ts
Normal file
2
src/webhooks/components/WebhookInfo/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhookInfo";
|
||||
export * from "./WebhookInfo";
|
55
src/webhooks/components/WebhookStatus/WebhookStatus.tsx
Normal file
55
src/webhooks/components/WebhookStatus/WebhookStatus.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { FormData } from "../WebhooksDetailsPage";
|
||||
|
||||
interface WebhookStatusProps {
|
||||
data: boolean;
|
||||
disabled: boolean;
|
||||
onChange: (event: ChangeEvent, cb?: () => void) => void;
|
||||
}
|
||||
|
||||
const WebhookStatus: React.FC<WebhookStatusProps> = ({
|
||||
data,
|
||||
disabled,
|
||||
onChange
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Webhook Status",
|
||||
description: "section header"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
{intl.formatMessage({
|
||||
defaultMessage:
|
||||
"If you want to disable this webhook please uncheck the box below.",
|
||||
description: "webhook active"
|
||||
})}
|
||||
</Typography>
|
||||
<ControlledCheckbox
|
||||
name={"isActive" as keyof FormData}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Webhook is active",
|
||||
description: "webhooks active"
|
||||
})}
|
||||
checked={data}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
WebhookStatus.displayName = "WebhookStatus";
|
||||
export default WebhookStatus;
|
2
src/webhooks/components/WebhookStatus/index.ts
Normal file
2
src/webhooks/components/WebhookStatus/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhookStatus";
|
||||
export * from "./WebhookStatus";
|
|
@ -0,0 +1,46 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { WebhookErrorCode } from "@saleor/types/globalTypes";
|
||||
import WebhooksDetailsPage, {
|
||||
WebhooksDetailsPageProps
|
||||
} from "./WebhooksDetailsPage";
|
||||
|
||||
const props: WebhooksDetailsPageProps = {
|
||||
disabled: false,
|
||||
errors: [],
|
||||
fetchServiceAccounts: () => undefined,
|
||||
onBack: () => undefined,
|
||||
onDelete: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
saveButtonBarState: "default",
|
||||
services: [],
|
||||
webhook: null
|
||||
};
|
||||
storiesOf("Views / Webhooks / Webhook details", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <WebhooksDetailsPage {...props} />)
|
||||
.add("loading", () => (
|
||||
<WebhooksDetailsPage
|
||||
{...props}
|
||||
webhook={undefined}
|
||||
services={undefined}
|
||||
disabled={true}
|
||||
/>
|
||||
))
|
||||
.add("form errors", () => (
|
||||
<WebhooksDetailsPage
|
||||
{...props}
|
||||
errors={[
|
||||
{
|
||||
code: WebhookErrorCode.INVALID,
|
||||
field: null
|
||||
}
|
||||
].map(error => ({
|
||||
__typename: "WebhookError",
|
||||
message: "Generic form error",
|
||||
...error
|
||||
}))}
|
||||
/>
|
||||
));
|
|
@ -0,0 +1,151 @@
|
|||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { SearchServiceAccount_search_edges_node } from "@saleor/containers/SearchServiceAccount/types/SearchServiceAccount";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import WebhookEvents from "@saleor/webhooks/components/WebhookEvents";
|
||||
import WebhookInfo from "@saleor/webhooks/components/WebhookInfo";
|
||||
import WebhookStatus from "@saleor/webhooks/components/WebhookStatus";
|
||||
import { WebhookCreate_webhookCreate_webhookErrors } from "@saleor/webhooks/types/WebhookCreate";
|
||||
import { WebhookDetails_webhook } from "@saleor/webhooks/types/WebhookDetails";
|
||||
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
export interface FormData {
|
||||
id: string;
|
||||
events: WebhookEventTypeEnum[];
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
secretKey: string | null;
|
||||
targetUrl: string;
|
||||
serviceAccount: string;
|
||||
allEvents: boolean;
|
||||
}
|
||||
|
||||
export interface WebhooksDetailsPageProps {
|
||||
disabled: boolean;
|
||||
errors: WebhookCreate_webhookCreate_webhookErrors[];
|
||||
webhook: WebhookDetails_webhook;
|
||||
services?: SearchServiceAccount_search_edges_node[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
onBack: () => void;
|
||||
onDelete: () => void;
|
||||
fetchServiceAccounts: (data: string) => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
}
|
||||
|
||||
const WebhooksDetailsPage: React.FC<WebhooksDetailsPageProps> = ({
|
||||
disabled,
|
||||
errors: apiErrors,
|
||||
webhook,
|
||||
saveButtonBarState,
|
||||
services,
|
||||
fetchServiceAccounts,
|
||||
onBack,
|
||||
onDelete,
|
||||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const initialForm: FormData = {
|
||||
allEvents: !!maybe(() => webhook.events, []).find(
|
||||
event => event.eventType === WebhookEventTypeEnum.ANY_EVENTS
|
||||
),
|
||||
events: maybe(() => webhook.events, [])
|
||||
.map(event => event.eventType)
|
||||
.filter(event => event !== WebhookEventTypeEnum.ANY_EVENTS),
|
||||
id: maybe(() => webhook.id, null),
|
||||
isActive: maybe(() => webhook.isActive, false),
|
||||
name: maybe(() => webhook.name, ""),
|
||||
secretKey: maybe(() => webhook.secretKey, ""),
|
||||
serviceAccount: maybe(() => webhook.serviceAccount.id, ""),
|
||||
targetUrl: maybe(() => webhook.targetUrl, "")
|
||||
};
|
||||
const [
|
||||
selectedServiceAcccounts,
|
||||
setSelectedServiceAcccounts
|
||||
] = useStateFromProps(maybe(() => webhook.serviceAccount.name, ""));
|
||||
const servicesChoiceList = maybe(
|
||||
() =>
|
||||
services.map(node => ({
|
||||
label: node.name,
|
||||
value: node.id
|
||||
})),
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<Form errors={apiErrors} initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ data, errors, hasChanged, submit, change }) => {
|
||||
const handleServiceSelect = createSingleAutocompleteSelectHandler(
|
||||
change,
|
||||
setSelectedServiceAcccounts,
|
||||
servicesChoiceList
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.webhooks)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{webhookName} Details",
|
||||
description: "header"
|
||||
},
|
||||
{
|
||||
webhookName: maybe(() => webhook.name, "...")
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
<WebhookInfo
|
||||
apiErrors={apiErrors}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
serviceDisplayValue={selectedServiceAcccounts}
|
||||
services={servicesChoiceList}
|
||||
fetchServiceAccounts={fetchServiceAccounts}
|
||||
errors={errors}
|
||||
serviceOnChange={handleServiceSelect}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<WebhookEvents
|
||||
data={data}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<WebhookStatus
|
||||
data={data.isActive}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
disabled={disabled || !hasChanged}
|
||||
state={saveButtonBarState}
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
WebhooksDetailsPage.displayName = "WebhooksDetailsPage";
|
||||
export default WebhooksDetailsPage;
|
2
src/webhooks/components/WebhooksDetailsPage/index.ts
Normal file
2
src/webhooks/components/WebhooksDetailsPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhooksDetailsPage";
|
||||
export * from "./WebhooksDetailsPage";
|
166
src/webhooks/components/WebhooksList/WebhooksList.tsx
Normal file
166
src/webhooks/components/WebhooksList/WebhooksList.tsx
Normal file
|
@ -0,0 +1,166 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Theme } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import EditIcon from "@material-ui/icons/Edit";
|
||||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection, stopPropagation } from "@saleor/misc";
|
||||
import { ListProps } from "@saleor/types";
|
||||
import { Webhooks_webhooks_edges_node } from "../../types/Webhooks";
|
||||
|
||||
export interface WebhooksListProps extends ListProps {
|
||||
webhooks: Webhooks_webhooks_edges_node[];
|
||||
onRemove: (id: string) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
colAction: {
|
||||
"& svg": {
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
textAlign: "right"
|
||||
},
|
||||
colActive: {},
|
||||
colName: {
|
||||
"&&": {
|
||||
width: "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
colAction: {},
|
||||
colActive: {},
|
||||
colName: {
|
||||
paddingLeft: 0,
|
||||
width: 250
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
tableRow: {
|
||||
cursor: "pointer"
|
||||
}
|
||||
}));
|
||||
|
||||
const numberOfColumns = 3;
|
||||
|
||||
const WebhooksList: React.FC<WebhooksListProps> = ({
|
||||
settings,
|
||||
webhooks,
|
||||
disabled,
|
||||
onNextPage,
|
||||
pageInfo,
|
||||
onRowClick,
|
||||
onRemove,
|
||||
onUpdateListSettings,
|
||||
onPreviousPage
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
return (
|
||||
<Card>
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName} padding="dense">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Name",
|
||||
description: "webhook name"
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActive} padding="dense">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Service Account",
|
||||
description: "webhook service account"
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction} padding="dense">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Action",
|
||||
description: "user action bar"
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
webhooks,
|
||||
webhook => (
|
||||
<TableRow
|
||||
hover={!!webhook}
|
||||
className={!!webhook ? classes.tableRow : undefined}
|
||||
onClick={webhook ? onRowClick(webhook.id) : undefined}
|
||||
key={webhook ? webhook.id : "skeleton"}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
{maybe<React.ReactNode>(() => webhook.name, <Skeleton />)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActive}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => webhook.serviceAccount.name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={webhook ? onRowClick(webhook.id) : undefined}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={
|
||||
webhook
|
||||
? stopPropagation(() => onRemove(webhook.id))
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "No webhooks found"
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
WebhooksList.displayName = "WebhooksList";
|
||||
export default WebhooksList;
|
2
src/webhooks/components/WebhooksList/index.ts
Normal file
2
src/webhooks/components/WebhooksList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhooksList";
|
||||
export * from "./WebhooksList";
|
|
@ -0,0 +1,30 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
listActionsProps,
|
||||
pageListProps,
|
||||
searchPageProps,
|
||||
tabPageProps
|
||||
} from "@saleor/fixtures";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { webhookList } from "../../fixtures";
|
||||
import WebhooksListPage, { WebhooksListPageProps } from "./WebhooksListPage";
|
||||
|
||||
const props: WebhooksListPageProps = {
|
||||
...listActionsProps,
|
||||
...pageListProps.default,
|
||||
...searchPageProps,
|
||||
...tabPageProps,
|
||||
onBack: () => undefined,
|
||||
onRemove: () => undefined,
|
||||
webhooks: webhookList
|
||||
};
|
||||
|
||||
storiesOf("Views / Webhooks / Webhook list", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <WebhooksListPage {...props} />)
|
||||
.add("loading", () => (
|
||||
<WebhooksListPage {...props} disabled={true} webhooks={undefined} />
|
||||
))
|
||||
.add("no data", () => <WebhooksListPage {...props} webhooks={[]} />);
|
|
@ -0,0 +1,76 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SearchBar from "@saleor/components/SearchBar";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { PageListProps, SearchPageProps, TabPageProps } from "@saleor/types";
|
||||
import { Webhooks_webhooks_edges_node } from "../../types/Webhooks";
|
||||
import WebhooksList from "../WebhooksList/WebhooksList";
|
||||
|
||||
export interface WebhooksListPageProps
|
||||
extends PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps {
|
||||
webhooks: Webhooks_webhooks_edges_node[];
|
||||
onBack: () => void;
|
||||
onRemove: (id: string) => void;
|
||||
}
|
||||
|
||||
const WebhooksListPage: React.StatelessComponent<WebhooksListPageProps> = ({
|
||||
currentTab,
|
||||
initialSearch,
|
||||
onAdd,
|
||||
onAll,
|
||||
onBack,
|
||||
onSearchChange,
|
||||
onTabChange,
|
||||
onTabDelete,
|
||||
onTabSave,
|
||||
tabs,
|
||||
webhooks,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.configuration)}
|
||||
</AppHeader>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.webhooks)}>
|
||||
<Button onClick={onAdd} variant="contained" color="primary">
|
||||
<FormattedMessage
|
||||
defaultMessage="Create webhook"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Card>
|
||||
<SearchBar
|
||||
allTabLabel={intl.formatMessage({
|
||||
defaultMessage: "All Webhooks",
|
||||
description: "tab name"
|
||||
})}
|
||||
currentTab={currentTab}
|
||||
initialSearch={initialSearch}
|
||||
searchPlaceholder={intl.formatMessage({
|
||||
defaultMessage: "Search Webhooks"
|
||||
})}
|
||||
tabs={tabs}
|
||||
onAll={onAll}
|
||||
onSearchChange={onSearchChange}
|
||||
onTabChange={onTabChange}
|
||||
onTabDelete={onTabDelete}
|
||||
onTabSave={onTabSave}
|
||||
/>
|
||||
<WebhooksList webhooks={webhooks} {...listProps} />
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
WebhooksListPage.displayName = "WebhooksListPage";
|
||||
export default WebhooksListPage;
|
2
src/webhooks/components/WebhooksListPage/index.ts
Normal file
2
src/webhooks/components/WebhooksListPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./WebhooksListPage";
|
||||
export * from "./WebhooksListPage";
|
41
src/webhooks/fixtures.ts
Normal file
41
src/webhooks/fixtures.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { WebhookDetails_webhook } from "./types/WebhookDetails";
|
||||
import { Webhooks_webhooks_edges_node } from "./types/Webhooks";
|
||||
|
||||
export const webhookList: Webhooks_webhooks_edges_node[] = [
|
||||
{
|
||||
__typename: "Webhook",
|
||||
id: "Jzx123sEt==",
|
||||
isActive: true,
|
||||
name: "Webhook Test",
|
||||
serviceAccount: {
|
||||
__typename: "ServiceAccount",
|
||||
id: "Jzx123sEt==",
|
||||
name: "Test Account"
|
||||
}
|
||||
},
|
||||
{
|
||||
__typename: "Webhook",
|
||||
id: "Jzx123sEt==",
|
||||
isActive: true,
|
||||
name: "Webhook Test 2",
|
||||
serviceAccount: {
|
||||
__typename: "ServiceAccount",
|
||||
id: "Jzx1ss23sEt==",
|
||||
name: "Test Account 2"
|
||||
}
|
||||
}
|
||||
];
|
||||
export const webhook: WebhookDetails_webhook = {
|
||||
__typename: "Webhook",
|
||||
events: [],
|
||||
id: "Jzx123sEt==",
|
||||
isActive: true,
|
||||
name: "Webhook Test 2",
|
||||
secretKey: "zxczx_asdas",
|
||||
serviceAccount: {
|
||||
__typename: "ServiceAccount",
|
||||
id: "Jzx1ss23sEt==",
|
||||
name: "Test Account 2"
|
||||
},
|
||||
targetUrl: "http://www.getsaleor.com"
|
||||
};
|
51
src/webhooks/index.tsx
Normal file
51
src/webhooks/index.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { parse as parseQs } from "qs";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
webhooksAddUrl,
|
||||
webhooksListPath,
|
||||
WebhooksListUrlQueryParams,
|
||||
webhooksPath
|
||||
} from "./urls";
|
||||
import WebhookCreate from "./views/WebhooksCreate";
|
||||
import WebhooksDetails from "./views/WebhooksDetails";
|
||||
import WebhooksList from "./views/WebhooksList";
|
||||
|
||||
const WebhookList: React.StatelessComponent<RouteComponentProps<any>> = ({
|
||||
location
|
||||
}) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: WebhooksListUrlQueryParams = qs;
|
||||
return <WebhooksList params={params} />;
|
||||
};
|
||||
|
||||
const WebhookDetails: React.StatelessComponent<RouteComponentProps<any>> = ({
|
||||
match
|
||||
}) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: WebhooksListUrlQueryParams = qs;
|
||||
|
||||
return (
|
||||
<WebhooksDetails id={decodeURIComponent(match.params.id)} params={params} />
|
||||
);
|
||||
};
|
||||
|
||||
const Component = () => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.webhooks)} />
|
||||
<Switch>
|
||||
<Route exact path={webhooksListPath} component={WebhookList} />
|
||||
<Route exact path={webhooksAddUrl} component={WebhookCreate} />
|
||||
<Route path={webhooksPath(":id")} component={WebhookDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
70
src/webhooks/mutations.ts
Normal file
70
src/webhooks/mutations.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedMutation } from "../mutations";
|
||||
import { webhooksDetailsFragment } from "./queries";
|
||||
import { WebhookCreate, WebhookCreateVariables } from "./types/WebhookCreate";
|
||||
import { WebhookDelete, WebhookDeleteVariables } from "./types/WebhookDelete";
|
||||
import { WebhookUpdate, WebhookUpdateVariables } from "./types/WebhookUpdate";
|
||||
|
||||
const webhookCreate = gql`
|
||||
${webhooksDetailsFragment}
|
||||
mutation WebhookCreate($input: WebhookCreateInput!) {
|
||||
webhookCreate(input: $input) {
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
webhookErrors {
|
||||
code
|
||||
message
|
||||
field
|
||||
}
|
||||
webhook {
|
||||
...WebhooksDetailsFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TypedWebhookCreate = TypedMutation<
|
||||
WebhookCreate,
|
||||
WebhookCreateVariables
|
||||
>(webhookCreate);
|
||||
|
||||
const webhookUpdate = gql`
|
||||
${webhooksDetailsFragment}
|
||||
mutation WebhookUpdate($id: ID!, $input: WebhookUpdateInput!) {
|
||||
webhookUpdate(id: $id, input: $input) {
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
webhookErrors {
|
||||
code
|
||||
message
|
||||
field
|
||||
}
|
||||
webhook {
|
||||
...WebhooksDetailsFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TypedWebhookUpdate = TypedMutation<
|
||||
WebhookUpdate,
|
||||
WebhookUpdateVariables
|
||||
>(webhookUpdate);
|
||||
|
||||
const webhookDelete = gql`
|
||||
mutation WebhookDelete($id: ID!) {
|
||||
webhookDelete(id: $id) {
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TypedWebhookDelete = TypedMutation<
|
||||
WebhookDelete,
|
||||
WebhookDeleteVariables
|
||||
>(webhookDelete);
|
75
src/webhooks/queries.ts
Normal file
75
src/webhooks/queries.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedQuery } from "../queries";
|
||||
import { WebhookDetails, WebhookDetailsVariables } from "./types/WebhookDetails";
|
||||
import { Webhooks, WebhooksVariables } from "./types/Webhooks";
|
||||
|
||||
export const webhooksFragment = gql`
|
||||
fragment WebhookFragment on Webhook {
|
||||
id
|
||||
name
|
||||
isActive
|
||||
serviceAccount {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const webhooksDetailsFragment = gql`
|
||||
${webhooksFragment}
|
||||
fragment WebhooksDetailsFragment on Webhook {
|
||||
...WebhookFragment
|
||||
}
|
||||
`;
|
||||
|
||||
const webhooksList = gql`
|
||||
${webhooksFragment}
|
||||
query Webhooks(
|
||||
$first: Int
|
||||
$after: String
|
||||
$last: Int
|
||||
$before: String
|
||||
$filter: WebhookFilterInput
|
||||
) {
|
||||
webhooks(
|
||||
first: $first
|
||||
after: $after
|
||||
before: $before
|
||||
last: $last
|
||||
filter: $filter
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasPreviousPage
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TypedWebhooksListQuery = TypedQuery<Webhooks, WebhooksVariables>(
|
||||
webhooksList
|
||||
);
|
||||
|
||||
const webhooksDetails = gql`
|
||||
${webhooksFragment}
|
||||
query WebhookDetails($id: ID!) {
|
||||
webhook(id: $id) {
|
||||
...WebhookFragment
|
||||
events {
|
||||
eventType
|
||||
}
|
||||
secretKey
|
||||
targetUrl
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TypedWebhooksDetailsQuery = TypedQuery<WebhookDetails, WebhookDetailsVariables>(
|
||||
webhooksDetails
|
||||
);
|
51
src/webhooks/types/WebhookCreate.ts
Normal file
51
src/webhooks/types/WebhookCreate.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { WebhookCreateInput, WebhookErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: WebhookCreate
|
||||
// ====================================================
|
||||
|
||||
export interface WebhookCreate_webhookCreate_errors {
|
||||
__typename: "Error";
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookCreate_webhookCreate_webhookErrors {
|
||||
__typename: "WebhookError";
|
||||
code: WebhookErrorCode | null;
|
||||
message: string | null;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookCreate_webhookCreate_webhook_serviceAccount {
|
||||
__typename: "ServiceAccount";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookCreate_webhookCreate_webhook {
|
||||
__typename: "Webhook";
|
||||
id: string;
|
||||
name: string | null;
|
||||
isActive: boolean;
|
||||
serviceAccount: WebhookCreate_webhookCreate_webhook_serviceAccount;
|
||||
}
|
||||
|
||||
export interface WebhookCreate_webhookCreate {
|
||||
__typename: "WebhookCreate";
|
||||
errors: WebhookCreate_webhookCreate_errors[] | null;
|
||||
webhookErrors: WebhookCreate_webhookCreate_webhookErrors[] | null;
|
||||
webhook: WebhookCreate_webhookCreate_webhook | null;
|
||||
}
|
||||
|
||||
export interface WebhookCreate {
|
||||
webhookCreate: WebhookCreate_webhookCreate | null;
|
||||
}
|
||||
|
||||
export interface WebhookCreateVariables {
|
||||
input: WebhookCreateInput;
|
||||
}
|
26
src/webhooks/types/WebhookDelete.ts
Normal file
26
src/webhooks/types/WebhookDelete.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: WebhookDelete
|
||||
// ====================================================
|
||||
|
||||
export interface WebhookDelete_webhookDelete_errors {
|
||||
__typename: "Error";
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookDelete_webhookDelete {
|
||||
__typename: "WebhookDelete";
|
||||
errors: WebhookDelete_webhookDelete_errors[] | null;
|
||||
}
|
||||
|
||||
export interface WebhookDelete {
|
||||
webhookDelete: WebhookDelete_webhookDelete | null;
|
||||
}
|
||||
|
||||
export interface WebhookDeleteVariables {
|
||||
id: string;
|
||||
}
|
39
src/webhooks/types/WebhookDetails.ts
Normal file
39
src/webhooks/types/WebhookDetails.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { WebhookEventTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: WebhookDetails
|
||||
// ====================================================
|
||||
|
||||
export interface WebhookDetails_webhook_serviceAccount {
|
||||
__typename: "ServiceAccount";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookDetails_webhook_events {
|
||||
__typename: "WebhookEvent";
|
||||
eventType: WebhookEventTypeEnum | null;
|
||||
}
|
||||
|
||||
export interface WebhookDetails_webhook {
|
||||
__typename: "Webhook";
|
||||
id: string;
|
||||
name: string | null;
|
||||
isActive: boolean;
|
||||
serviceAccount: WebhookDetails_webhook_serviceAccount;
|
||||
events: (WebhookDetails_webhook_events | null)[] | null;
|
||||
secretKey: string | null;
|
||||
targetUrl: string;
|
||||
}
|
||||
|
||||
export interface WebhookDetails {
|
||||
webhook: WebhookDetails_webhook | null;
|
||||
}
|
||||
|
||||
export interface WebhookDetailsVariables {
|
||||
id: string;
|
||||
}
|
21
src/webhooks/types/WebhookFragment.ts
Normal file
21
src/webhooks/types/WebhookFragment.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: WebhookFragment
|
||||
// ====================================================
|
||||
|
||||
export interface WebhookFragment_serviceAccount {
|
||||
__typename: "ServiceAccount";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookFragment {
|
||||
__typename: "Webhook";
|
||||
id: string;
|
||||
name: string | null;
|
||||
isActive: boolean;
|
||||
serviceAccount: WebhookFragment_serviceAccount;
|
||||
}
|
52
src/webhooks/types/WebhookUpdate.ts
Normal file
52
src/webhooks/types/WebhookUpdate.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { WebhookUpdateInput, WebhookErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: WebhookUpdate
|
||||
// ====================================================
|
||||
|
||||
export interface WebhookUpdate_webhookUpdate_errors {
|
||||
__typename: "Error";
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookUpdate_webhookUpdate_webhookErrors {
|
||||
__typename: "WebhookError";
|
||||
code: WebhookErrorCode | null;
|
||||
message: string | null;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookUpdate_webhookUpdate_webhook_serviceAccount {
|
||||
__typename: "ServiceAccount";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface WebhookUpdate_webhookUpdate_webhook {
|
||||
__typename: "Webhook";
|
||||
id: string;
|
||||
name: string | null;
|
||||
isActive: boolean;
|
||||
serviceAccount: WebhookUpdate_webhookUpdate_webhook_serviceAccount;
|
||||
}
|
||||
|
||||
export interface WebhookUpdate_webhookUpdate {
|
||||
__typename: "WebhookUpdate";
|
||||
errors: WebhookUpdate_webhookUpdate_errors[] | null;
|
||||
webhookErrors: WebhookUpdate_webhookUpdate_webhookErrors[] | null;
|
||||
webhook: WebhookUpdate_webhookUpdate_webhook | null;
|
||||
}
|
||||
|
||||
export interface WebhookUpdate {
|
||||
webhookUpdate: WebhookUpdate_webhookUpdate | null;
|
||||
}
|
||||
|
||||
export interface WebhookUpdateVariables {
|
||||
id: string;
|
||||
input: WebhookUpdateInput;
|
||||
}
|
54
src/webhooks/types/Webhooks.ts
Normal file
54
src/webhooks/types/Webhooks.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { WebhookFilterInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Webhooks
|
||||
// ====================================================
|
||||
|
||||
export interface Webhooks_webhooks_edges_node_serviceAccount {
|
||||
__typename: "ServiceAccount";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface Webhooks_webhooks_edges_node {
|
||||
__typename: "Webhook";
|
||||
id: string;
|
||||
name: string | null;
|
||||
isActive: boolean;
|
||||
serviceAccount: Webhooks_webhooks_edges_node_serviceAccount;
|
||||
}
|
||||
|
||||
export interface Webhooks_webhooks_edges {
|
||||
__typename: "WebhookCountableEdge";
|
||||
node: Webhooks_webhooks_edges_node;
|
||||
}
|
||||
|
||||
export interface Webhooks_webhooks_pageInfo {
|
||||
__typename: "PageInfo";
|
||||
hasPreviousPage: boolean;
|
||||
hasNextPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
}
|
||||
|
||||
export interface Webhooks_webhooks {
|
||||
__typename: "WebhookCountableConnection";
|
||||
edges: Webhooks_webhooks_edges[];
|
||||
pageInfo: Webhooks_webhooks_pageInfo;
|
||||
}
|
||||
|
||||
export interface Webhooks {
|
||||
webhooks: Webhooks_webhooks | null;
|
||||
}
|
||||
|
||||
export interface WebhooksVariables {
|
||||
first?: number | null;
|
||||
after?: string | null;
|
||||
last?: number | null;
|
||||
before?: string | null;
|
||||
filter?: WebhookFilterInput | null;
|
||||
}
|
21
src/webhooks/types/WebhooksDetailsFragment.ts
Normal file
21
src/webhooks/types/WebhooksDetailsFragment.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: WebhooksDetailsFragment
|
||||
// ====================================================
|
||||
|
||||
export interface WebhooksDetailsFragment_serviceAccount {
|
||||
__typename: "ServiceAccount";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface WebhooksDetailsFragment {
|
||||
__typename: "Webhook";
|
||||
id: string;
|
||||
name: string | null;
|
||||
isActive: boolean;
|
||||
serviceAccount: WebhooksDetailsFragment_serviceAccount;
|
||||
}
|
36
src/webhooks/urls.ts
Normal file
36
src/webhooks/urls.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { stringify as stringifyQs } from "qs";
|
||||
import urlJoin from "url-join";
|
||||
|
||||
import {
|
||||
ActiveTab,
|
||||
Dialog,
|
||||
Filters,
|
||||
Pagination,
|
||||
SingleAction,
|
||||
TabActionDialog
|
||||
} from "../types";
|
||||
|
||||
export const webhooksSection = "/webhooks/";
|
||||
|
||||
export const webhooksListPath = webhooksSection;
|
||||
export enum WebhookListUrlFiltersEnum {
|
||||
query = "query"
|
||||
}
|
||||
export type WebhookListUrlFilters = Filters<WebhookListUrlFiltersEnum>;
|
||||
export type WebhookListUrlDialog = "remove" | TabActionDialog;
|
||||
export type WebhooksListUrlQueryParams = ActiveTab &
|
||||
WebhookListUrlFilters &
|
||||
Dialog<WebhookListUrlDialog> &
|
||||
Pagination &
|
||||
SingleAction;
|
||||
export const webhooksListUrl = (params?: WebhooksListUrlQueryParams) =>
|
||||
webhooksListPath + "?" + stringifyQs(params);
|
||||
|
||||
export const webhooksPath = (id: string) => urlJoin(webhooksSection, id);
|
||||
export type WebhookUrlDialog = "remove";
|
||||
export type WebhooksUrlQueryParams = Dialog<WebhookUrlDialog> & SingleAction;
|
||||
export const webhooksUrl = (id: string, params?: WebhooksUrlQueryParams) =>
|
||||
webhooksPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
||||
export const webhooksAddPath = urlJoin(webhooksSection, "add");
|
||||
export const webhooksAddUrl = webhooksAddPath;
|
103
src/webhooks/views/WebhooksCreate.tsx
Normal file
103
src/webhooks/views/WebhooksCreate.tsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import SearchServiceAccount from "@saleor/containers/SearchServiceAccount";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
|
||||
import { WebhookCreate as WebhookCreateData } from "@saleor/webhooks/types/WebhookCreate";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import WebhookCreatePage, { FormData } from "../components/WebhookCreatePage";
|
||||
import { TypedWebhookCreate } from "../mutations";
|
||||
import {
|
||||
webhooksListUrl,
|
||||
WebhooksListUrlQueryParams,
|
||||
webhooksUrl
|
||||
} from "../urls";
|
||||
|
||||
export interface WebhooksCreateProps {
|
||||
id: string;
|
||||
params: WebhooksListUrlQueryParams;
|
||||
}
|
||||
|
||||
export const WebhooksCreate: React.StatelessComponent<
|
||||
WebhooksCreateProps
|
||||
> = () => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const onSubmit = (data: WebhookCreateData) => {
|
||||
if (data.webhookCreate.webhookErrors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(webhooksUrl(data.webhookCreate.webhook.id));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => navigate(webhooksListUrl());
|
||||
|
||||
return (
|
||||
<SearchServiceAccount variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||
{({ search: searchServiceAccount, result: searchServiceAccountOpt }) => (
|
||||
<TypedWebhookCreate onCompleted={onSubmit}>
|
||||
{(webhookCreate, webhookCreateOpts) => {
|
||||
const handleSubmit = (data: FormData) =>
|
||||
webhookCreate({
|
||||
variables: {
|
||||
input: {
|
||||
events: data.allEvents
|
||||
? [WebhookEventTypeEnum.ANY_EVENTS]
|
||||
: data.events,
|
||||
isActive: data.isActive,
|
||||
name: data.name,
|
||||
secretKey: data.secretKey,
|
||||
serviceAccount: data.serviceAccount,
|
||||
targetUrl: data.targetUrl
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const formTransitionState = getMutationState(
|
||||
webhookCreateOpts.called,
|
||||
webhookCreateOpts.loading,
|
||||
maybe(() => webhookCreateOpts.data.webhookCreate.webhookErrors)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create Webhook",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<WebhookCreatePage
|
||||
disabled={false}
|
||||
errors={maybe(
|
||||
() => webhookCreateOpts.data.webhookCreate.webhookErrors,
|
||||
[]
|
||||
)}
|
||||
fetchServiceAccounts={searchServiceAccount}
|
||||
services={maybe(() =>
|
||||
searchServiceAccountOpt.data.search.edges.map(
|
||||
edge => edge.node
|
||||
)
|
||||
)}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmit}
|
||||
saveButtonBarState={formTransitionState}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedWebhookCreate>
|
||||
)}
|
||||
</SearchServiceAccount>
|
||||
);
|
||||
};
|
||||
WebhooksCreate.displayName = "WebhooksCreate";
|
||||
export default WebhooksCreate;
|
168
src/webhooks/views/WebhooksDetails.tsx
Normal file
168
src/webhooks/views/WebhooksDetails.tsx
Normal file
|
@ -0,0 +1,168 @@
|
|||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import SearchServiceAccount from "@saleor/containers/SearchServiceAccount";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
|
||||
import WebhookDeleteDialog from "@saleor/webhooks/components/WebhookDeleteDialog";
|
||||
import { WebhookDelete } from "@saleor/webhooks/types/WebhookDelete";
|
||||
import { WebhookUpdate } from "@saleor/webhooks/types/WebhookUpdate";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import WebhooksDetailsPage from "../components/WebhooksDetailsPage";
|
||||
import { TypedWebhookDelete, TypedWebhookUpdate } from "../mutations";
|
||||
import { TypedWebhooksDetailsQuery } from "../queries";
|
||||
import {
|
||||
webhooksListUrl,
|
||||
WebhooksListUrlQueryParams,
|
||||
webhooksUrl,
|
||||
WebhookUrlDialog
|
||||
} from "../urls";
|
||||
|
||||
export interface WebhooksDetailsProps {
|
||||
id: string;
|
||||
params: WebhooksListUrlQueryParams;
|
||||
}
|
||||
|
||||
export const WebhooksDetails: React.FC<WebhooksDetailsProps> = ({
|
||||
id,
|
||||
params
|
||||
}) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
webhooksUrl(id, {
|
||||
...params,
|
||||
action: undefined,
|
||||
id: undefined
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
const openModal = (action: WebhookUrlDialog, tokenId?: string) =>
|
||||
navigate(
|
||||
webhooksUrl(id, {
|
||||
...params,
|
||||
action,
|
||||
id: tokenId
|
||||
})
|
||||
);
|
||||
|
||||
const onWebhookDelete = (data: WebhookDelete) => {
|
||||
if (data.webhookDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(webhooksListUrl());
|
||||
}
|
||||
};
|
||||
|
||||
const onWebhookUpdate = (data: WebhookUpdate) => {
|
||||
if (data.webhookUpdate.webhookErrors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(webhooksUrl(data.webhookUpdate.webhook.id));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchServiceAccount variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
||||
{({ search: searchServiceAccount, result: searchServiceAccountOpt }) => (
|
||||
<TypedWebhookUpdate onCompleted={onWebhookUpdate}>
|
||||
{(webhookUpdate, webhookUpdateOpts) => (
|
||||
<TypedWebhookDelete onCompleted={onWebhookDelete}>
|
||||
{(webhookDelete, webhookDeleteOpts) => (
|
||||
<TypedWebhooksDetailsQuery variables={{ id }}>
|
||||
{webhookDetails => {
|
||||
const formTransitionState = getMutationState(
|
||||
webhookUpdateOpts.called,
|
||||
webhookUpdateOpts.loading,
|
||||
maybe(
|
||||
() => webhookUpdateOpts.data.webhookUpdate.webhookErrors
|
||||
)
|
||||
);
|
||||
|
||||
const handleRemoveConfirm = () =>
|
||||
webhookDelete({
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
});
|
||||
|
||||
const formErrors = maybe(
|
||||
() => webhookUpdateOpts.data.webhookUpdate.webhookErrors,
|
||||
[]
|
||||
);
|
||||
|
||||
const deleteTransitionState = getMutationState(
|
||||
webhookDeleteOpts.called,
|
||||
webhookDeleteOpts.loading,
|
||||
maybe(() => webhookDeleteOpts.data.webhookDelete.errors)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={maybe(() => webhookDetails.data.webhook.name)}
|
||||
/>
|
||||
<WebhooksDetailsPage
|
||||
disabled={webhookDetails.loading}
|
||||
errors={formErrors}
|
||||
saveButtonBarState={formTransitionState}
|
||||
webhook={maybe(() => webhookDetails.data.webhook)}
|
||||
fetchServiceAccounts={searchServiceAccount}
|
||||
services={maybe(() =>
|
||||
searchServiceAccountOpt.data.search.edges.map(
|
||||
edge => edge.node
|
||||
)
|
||||
)}
|
||||
onBack={() => navigate(webhooksListUrl())}
|
||||
onDelete={() => openModal("remove")}
|
||||
onSubmit={data => {
|
||||
webhookUpdate({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
events: data.allEvents
|
||||
? [WebhookEventTypeEnum.ANY_EVENTS]
|
||||
: data.events,
|
||||
isActive: data.isActive,
|
||||
name: data.name,
|
||||
secretKey: data.secretKey,
|
||||
serviceAccount: data.serviceAccount,
|
||||
targetUrl: data.targetUrl
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<WebhookDeleteDialog
|
||||
confirmButtonState={deleteTransitionState}
|
||||
name={maybe(
|
||||
() => webhookDetails.data.webhook.name,
|
||||
"..."
|
||||
)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleRemoveConfirm}
|
||||
open={params.action === "remove"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedWebhooksDetailsQuery>
|
||||
)}
|
||||
</TypedWebhookDelete>
|
||||
)}
|
||||
</TypedWebhookUpdate>
|
||||
)}
|
||||
</SearchServiceAccount>
|
||||
);
|
||||
};
|
||||
WebhooksDetails.displayName = "WebhooksDetails";
|
||||
export default WebhooksDetails;
|
220
src/webhooks/views/WebhooksList.tsx
Normal file
220
src/webhooks/views/WebhooksList.tsx
Normal file
|
@ -0,0 +1,220 @@
|
|||
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||
import SaveFilterTabDialog, {
|
||||
SaveFilterTabDialogFormData
|
||||
} from "@saleor/components/SaveFilterTabDialog";
|
||||
import { configurationMenuUrl } from "@saleor/configuration";
|
||||
import useListSettings from "@saleor/hooks/useListSettings";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import WebhookDeleteDialog from "@saleor/webhooks/components/WebhookDeleteDialog";
|
||||
import { WebhookDelete } from "@saleor/webhooks/types/WebhookDelete";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import WebhooksListPage from "../components/WebhooksListPage/WebhooksListPage";
|
||||
import { TypedWebhookDelete } from "../mutations";
|
||||
import { TypedWebhooksListQuery } from "../queries";
|
||||
import {
|
||||
WebhookListUrlDialog,
|
||||
WebhookListUrlFilters,
|
||||
webhooksAddUrl,
|
||||
webhooksListUrl,
|
||||
WebhooksListUrlQueryParams,
|
||||
webhooksUrl
|
||||
} from "../urls";
|
||||
import {
|
||||
areFiltersApplied,
|
||||
deleteFilterTab,
|
||||
getActiveFilters,
|
||||
getFilterTabs,
|
||||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filter";
|
||||
|
||||
interface WebhooksListProps {
|
||||
params: WebhooksListUrlQueryParams;
|
||||
}
|
||||
|
||||
export const WebhooksList: React.FC<WebhooksListProps> = ({ params }) => {
|
||||
const navigate = useNavigator();
|
||||
const paginate = usePaginator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
const { updateListSettings, settings } = useListSettings(
|
||||
ListViews.WEBHOOK_LIST
|
||||
);
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
params.activeTab === undefined
|
||||
? areFiltersApplied(params)
|
||||
? tabs.length + 1
|
||||
: 0
|
||||
: parseInt(params.activeTab, 0);
|
||||
|
||||
const changeFilterField = (filter: WebhookListUrlFilters) =>
|
||||
navigate(
|
||||
webhooksListUrl({
|
||||
...getActiveFilters(params),
|
||||
...filter,
|
||||
activeTab: undefined
|
||||
})
|
||||
);
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
webhooksListUrl({
|
||||
...params,
|
||||
action: undefined,
|
||||
id: undefined
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
const openModal = (action: WebhookListUrlDialog, id?: string) =>
|
||||
navigate(
|
||||
webhooksListUrl({
|
||||
...params,
|
||||
action,
|
||||
id
|
||||
})
|
||||
);
|
||||
|
||||
const handleTabChange = (tab: number) => {
|
||||
navigate(
|
||||
webhooksListUrl({
|
||||
activeTab: tab.toString(),
|
||||
...getFilterTabs()[tab - 1].data
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleTabDelete = () => {
|
||||
deleteFilterTab(currentTab);
|
||||
navigate(webhooksListUrl());
|
||||
};
|
||||
|
||||
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
|
||||
saveFilterTab(data.name, getActiveFilters(params));
|
||||
handleTabChange(tabs.length + 1);
|
||||
};
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
|
||||
return (
|
||||
<TypedWebhooksListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const onWebhookDelete = (data: WebhookDelete) => {
|
||||
if (data.webhookDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(webhooksListUrl());
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<TypedWebhookDelete onCompleted={onWebhookDelete}>
|
||||
{(webhookDelete, webhookDeleteOpts) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.webhooks.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
const handleRemove = (id: string) => {
|
||||
navigate(
|
||||
webhooksListUrl({
|
||||
...params,
|
||||
action: "remove",
|
||||
id
|
||||
})
|
||||
);
|
||||
};
|
||||
const handleRemoveConfirm = () => {
|
||||
webhookDelete({
|
||||
variables: {
|
||||
id: params.id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteTransitionState = getMutationState(
|
||||
webhookDeleteOpts.called,
|
||||
webhookDeleteOpts.loading,
|
||||
maybe(() => webhookDeleteOpts.data.webhookDelete.errors)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebhooksListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(webhooksListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
disabled={loading}
|
||||
settings={settings}
|
||||
webhooks={maybe(() =>
|
||||
data.webhooks.edges.map(edge => edge.node)
|
||||
)}
|
||||
pageInfo={pageInfo}
|
||||
onAdd={() => navigate(webhooksAddUrl)}
|
||||
onBack={() => navigate(configurationMenuUrl)}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onRemove={handleRemove}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(webhooksUrl(id))}
|
||||
/>
|
||||
<WebhookDeleteDialog
|
||||
confirmButtonState={deleteTransitionState}
|
||||
name={maybe(
|
||||
() =>
|
||||
data.webhooks.edges.find(
|
||||
edge => edge.node.id === params.id
|
||||
).node.name,
|
||||
"..."
|
||||
)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleRemoveConfirm}
|
||||
open={params.action === "remove"}
|
||||
/>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleTabSave}
|
||||
/>
|
||||
<DeleteFilterTabDialog
|
||||
open={params.action === "delete-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleTabDelete}
|
||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedWebhookDelete>
|
||||
);
|
||||
}}
|
||||
</TypedWebhooksListQuery>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebhooksList;
|
28
src/webhooks/views/filter.ts
Normal file
28
src/webhooks/views/filter.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { WebhookFilterInput } from "@saleor/types/globalTypes";
|
||||
import { createFilterTabUtils, createFilterUtils } from "../../utils/filters";
|
||||
import {
|
||||
WebhookListUrlFilters,
|
||||
WebhookListUrlFiltersEnum,
|
||||
WebhooksListUrlQueryParams
|
||||
} from "../urls";
|
||||
|
||||
export const WEBHOOK_FILTERS_KEY = "webhookFilters";
|
||||
|
||||
export function getFilterVariables(
|
||||
params: WebhookListUrlFilters
|
||||
): WebhookFilterInput {
|
||||
return {
|
||||
search: params.query
|
||||
};
|
||||
}
|
||||
|
||||
export const {
|
||||
deleteFilterTab,
|
||||
getFilterTabs,
|
||||
saveFilterTab
|
||||
} = createFilterTabUtils<WebhookListUrlFilters>(WEBHOOK_FILTERS_KEY);
|
||||
|
||||
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
|
||||
WebhooksListUrlQueryParams,
|
||||
WebhookListUrlFilters
|
||||
>(WebhookListUrlFiltersEnum);
|
Loading…
Reference in a new issue