diff --git a/.github/workflows/test-env-cleanup.yml b/.github/workflows/test-env-cleanup.yml index 602b83229..eb0bb5104 100644 --- a/.github/workflows/test-env-cleanup.yml +++ b/.github/workflows/test-env-cleanup.yml @@ -4,7 +4,6 @@ name: TEST-ENV-CLEANUP on: pull_request: types: [closed] - branches: ["*"] jobs: cleanup: diff --git a/.github/workflows/test-env-deploy.yml b/.github/workflows/test-env-deploy.yml index 45a364c84..744b55b66 100644 --- a/.github/workflows/test-env-deploy.yml +++ b/.github/workflows/test-env-deploy.yml @@ -1,11 +1,7 @@ name: TEST-ENV-DEPLOYMENT # Build and deploy test instance for every pull request -on: - pull_request: - # trigger on "edited" to update instance when configuration changes in PR description - types: [opened, reopened, synchronize] - branches: ["*"] +on: [pull_request] jobs: deploy: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e01508e62..5bbd49dc6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,6 @@ name: QA -on: - pull_request: - branches: ["*"] +on: [pull_request] jobs: check-lock: diff --git a/CHANGELOG.md b/CHANGELOG.md index a95ce0ccc..409e21ca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable, unreleased changes to this project will be documented in this file. - Add Page Types - #807 by @orzechdev - Add shipping methods to translation section - #864 by @marekchoinski - New Miscellaneous and Product refunds - #870 by @orzechdev +- Add zip code exclusion - #877 by @dominik-zeglen # 2.11.1 diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 23da84d8f..f6bfa6f61 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -5292,9 +5292,9 @@ "context": "channels discount info", "string": "Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency" }, - "src_dot_shipping_dot_components_dot_OrderValue_dot_2946165345": { + "src_dot_shipping_dot_components_dot_OrderValue_dot_3989471564": { "context": "card title", - "string": "Order value" + "string": "Order Value" }, "src_dot_shipping_dot_components_dot_OrderValue_dot_4226393146": { "context": "price rates info", @@ -5319,13 +5319,13 @@ "context": "info text", "string": "This rate will apply to all orders" }, + "src_dot_shipping_dot_components_dot_OrderWeight_dot_2211490913": { + "context": "card title", + "string": "Order Weight" + }, "src_dot_shipping_dot_components_dot_OrderWeight_dot_2935375344": { "string": "Min. Order Weight" }, - "src_dot_shipping_dot_components_dot_OrderWeight_dot_3721863048": { - "context": "card title", - "string": "Order weight" - }, "src_dot_shipping_dot_components_dot_OrderWeight_dot_882649212": { "context": "checkbox label", "string": "There are no value limits" @@ -5349,6 +5349,13 @@ "context": "column title", "string": "Channel name" }, + "src_dot_shipping_dot_components_dot_ShippingRateZipCodeRangeRemoveDialog_dot_1083561409": { + "string": "Are you sure you want to remove this ZIP-code rule?" + }, + "src_dot_shipping_dot_components_dot_ShippingRateZipCodeRangeRemoveDialog_dot_2944856644": { + "context": "header", + "string": "Remove ZIP-codes from Shipping Rate" + }, "src_dot_shipping_dot_components_dot_ShippingWeightUnitForm_dot_2863708228": { "string": "This unit will be used as default shipping weight" }, @@ -5477,6 +5484,54 @@ "context": "input placeholder", "string": "Select Warehouse" }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodeRangeDialog_dot_1938537617": { + "string": "Please provide range of ZIP codes you want to add to the include/exclude list." + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodeRangeDialog_dot_3099331554": { + "context": "add zip code range, button", + "string": "Add" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodeRangeDialog_dot_3529644799": { + "context": "range input label", + "string": "Zip Codes (Start)" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodeRangeDialog_dot_4196919717": { + "context": "dialog header", + "string": "Add ZIP-Codes" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodeRangeDialog_dot_551327109": { + "context": "range input label", + "string": "Zip Codes (End)" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_1462092303": { + "string": "Added ZIP-codes will be excluded from using this delivery methods. If none are added all ZIP-Codes will be able to use that shipping rate" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_1605967697": { + "context": "action", + "string": "Exclude ZIP-codes" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_2728850129": { + "string": "Only added ZIP-codes will be able to use this shipping rate" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_2850315665": { + "context": "number of zip code ranges", + "string": "{number} ZIP-Code ranges" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_3795380518": { + "context": "button", + "string": "Add ZIP-Code range" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_4288715411": { + "string": "This shipping rate has no ZIP-codes assigned" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_529495587": { + "context": "postal codes, header", + "string": "ZIP-Codes" + }, + "src_dot_shipping_dot_components_dot_ShippingZoneZipCodes_dot_671838332": { + "context": "action", + "string": "Include ZIP-codes" + }, "src_dot_shipping_dot_components_dot_ShippingZonesListPage_dot_1325966144": { "context": "header", "string": "Shipping" @@ -5525,6 +5580,10 @@ "src_dot_shipping_dot_views_dot_PriceRatesCreate_dot_3823295269": { "string": "Manage Channel Availability" }, + "src_dot_shipping_dot_views_dot_PriceRatesUpdate_dot_2710215007": { + "context": "zip code range add error text", + "string": "Cannot add specified zip codes range." + }, "src_dot_shipping_dot_views_dot_PriceRatesUpdate_dot_3823295269": { "string": "Manage Channel Availability" }, @@ -5543,6 +5602,10 @@ "src_dot_shipping_dot_views_dot_WeightRatesCreate_dot_3014453080": { "string": "Manage Channels Availability" }, + "src_dot_shipping_dot_views_dot_WeightRatesUpdate_dot_2710215007": { + "context": "zip code range add error text", + "string": "Cannot add specified zip codes range." + }, "src_dot_shipping_dot_views_dot_WeightRatesUpdate_dot_3014453080": { "string": "Manage Channels Availability" }, diff --git a/schema.graphql b/schema.graphql index 6d393f495..8dc7eeaaf 100644 --- a/schema.graphql +++ b/schema.graphql @@ -2565,11 +2565,15 @@ type Mutation { shopAddressUpdate(input: AddressInput): ShopAddressUpdate orderSettingsUpdate(input: OrderSettingsUpdateInput!): OrderSettingsUpdate shippingMethodChannelListingUpdate(id: ID!, input: ShippingMethodChannelListingInput!): ShippingMethodChannelListingUpdate + shippingMethodZipCodeRulesCreate(input: ShippingZipCodeRulesCreateInput!, shippingMethodId: ID!): ShippingZipCodeRulesCreate + shippingMethodZipCodeRulesDelete(id: ID!): ShippingZipCodeRulesDelete shippingPriceCreate(input: ShippingPriceInput!): ShippingPriceCreate shippingPriceDelete(id: ID!): ShippingPriceDelete shippingPriceBulkDelete(ids: [ID]!): ShippingPriceBulkDelete shippingPriceUpdate(id: ID!, input: ShippingPriceInput!): ShippingPriceUpdate shippingPriceTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ShippingPriceTranslate + shippingPriceExcludeProducts(id: ID!, input: ShippingPriceExcludeProductsInput!): ShippingPriceExcludeProducts + shippingPriceRemoveProductFromExclude(id: ID!, products: [ID]!): ShippingPriceRemoveProductFromExclude shippingZoneCreate(input: ShippingZoneCreateInput!): ShippingZoneCreate shippingZoneDelete(id: ID!): ShippingZoneDelete shippingZoneBulkDelete(ids: [ID]!): ShippingZoneBulkDelete @@ -2856,7 +2860,6 @@ type Order implements Node & ObjectWithMetadata { totalBalance: Money! userEmail: String isShippingRequired: Boolean! - linesAvailableForRefund: [OrderLineAvailableForRefund]! } enum OrderAction { @@ -3097,11 +3100,6 @@ type OrderLine implements Node { allocations: [Allocation!] } -type OrderLineAvailableForRefund { - quantity: Int - orderLine: OrderLine -} - input OrderLineCreateInput { quantity: Int! variantId: ID! @@ -3124,19 +3122,18 @@ type OrderRefund { } input OrderRefundFulfillmentLineInput { - fulfillmentLineId: ID + fulfillmentLineId: ID! quantity: Int! } input OrderRefundLineInput { - orderLineId: ID + orderLineId: ID! quantity: Int! } input OrderRefundProductsInput { orderLines: [OrderRefundLineInput!] fulfillmentLines: [OrderRefundFulfillmentLineInput!] - notifyCustomer: Boolean amountToRefund: PositiveDecimal includeShippingCosts: Boolean = false } @@ -4656,6 +4653,8 @@ type ShippingMethod implements Node & ObjectWithMetadata { price: Money maximumOrderPrice: Money minimumOrderPrice: Money + zipCodeRules: [ShippingMethodZipCodeRule] + excludedProducts(before: String, after: String, first: Int, last: Int): ProductCountableConnection } type ShippingMethodChannelListing implements Node { @@ -4702,6 +4701,12 @@ enum ShippingMethodTypeEnum { WEIGHT } +type ShippingMethodZipCodeRule implements Node { + start: String + end: String + id: ID! +} + type ShippingPriceBulkDelete { errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") count: Int! @@ -4722,6 +4727,16 @@ type ShippingPriceDelete { shippingErrors: [ShippingError!]! } +type ShippingPriceExcludeProducts { + errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") + shippingMethod: ShippingMethod + shippingErrors: [ShippingError!]! +} + +input ShippingPriceExcludeProductsInput { + products: [ID]! +} + input ShippingPriceInput { name: String minimumOrderWeight: WeightScalar @@ -4730,6 +4745,12 @@ input ShippingPriceInput { shippingZone: ID } +type ShippingPriceRemoveProductFromExclude { + errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") + shippingMethod: ShippingMethod + shippingErrors: [ShippingError!]! +} + type ShippingPriceTranslate { errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") translationErrors: [TranslationError!]! @@ -4743,6 +4764,29 @@ type ShippingPriceUpdate { shippingErrors: [ShippingError!]! } +type ShippingZipCodeRulesCreate { + errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") + zipCodeRules: [ShippingMethodZipCodeRule] + shippingMethod: ShippingMethod + shippingErrors: [ShippingError!]! +} + +input ShippingZipCodeRulesCreateInput { + zipCodeRules: [ShippingZipCodeRulesCreateInputRange]! +} + +input ShippingZipCodeRulesCreateInputRange { + start: String! + end: String +} + +type ShippingZipCodeRulesDelete { + errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") + shippingMethod: ShippingMethod + shippingErrors: [ShippingError!]! + shippingMethodZipCodeRule: ShippingMethodZipCodeRule +} + type ShippingZone implements Node & ObjectWithMetadata { id: ID! name: String! @@ -4807,6 +4851,7 @@ input ShippingZoneUpdateInput { type Shop { availablePaymentGateways(currency: String): [PaymentGateway!]! + availableShippingMethods(channel: String!, address: AddressInput): [ShippingMethod] geolocalization: Geolocalization authorizationKeys: [AuthorizationKey]! countries(languageCode: LanguageCodeEnum): [CountryDisplay!]! diff --git a/src/components/RadioGroupField/RadioGroupField.tsx b/src/components/RadioGroupField/RadioGroupField.tsx index bc30a5a8f..c170a5771 100644 --- a/src/components/RadioGroupField/RadioGroupField.tsx +++ b/src/components/RadioGroupField/RadioGroupField.tsx @@ -12,6 +12,11 @@ import { FormattedMessage } from "react-intl"; const useStyles = makeStyles( theme => ({ + alignTop: { + alignSelf: "baseline", + position: "relative", + top: -6 + }, formLabel: { marginBottom: theme.spacing(1) }, @@ -51,6 +56,7 @@ export interface RadioGroupFieldChoice< } interface RadioGroupFieldProps { + alignTop?: boolean; choices: RadioGroupFieldChoice[]; className?: string; disabled?: boolean; @@ -65,6 +71,7 @@ interface RadioGroupFieldProps { export const RadioGroupField: React.FC = props => { const { + alignTop, className, disabled, error, @@ -107,7 +114,14 @@ export const RadioGroupField: React.FC = props => { [classes.radioLabel]: variant !== "inline", [classes.radioLabelInline]: variant === "inline" })} - control={} + control={ + + } label={choice.label} key={choice.value} /> diff --git a/src/fragments/shipping.ts b/src/fragments/shipping.ts index 33d6bab2b..87aae776f 100644 --- a/src/fragments/shipping.ts +++ b/src/fragments/shipping.ts @@ -11,10 +11,22 @@ export const shippingZoneFragment = gql` name } `; + +export const shippingMethodWithZipCodesFragment = gql` + fragment ShippingMethodWithZipCodesFragment on ShippingMethod { + id + zipCodeRules { + id + start + end + } + } +`; export const shippingMethodFragment = gql` ${fragmentMoney} + ${shippingMethodWithZipCodesFragment} fragment ShippingMethodFragment on ShippingMethod { - id + ...ShippingMethodWithZipCodesFragment minimumOrderWeight { unit value diff --git a/src/fragments/types/ShippingMethodFragment.ts b/src/fragments/types/ShippingMethodFragment.ts index de7a4692e..b8e968b2b 100644 --- a/src/fragments/types/ShippingMethodFragment.ts +++ b/src/fragments/types/ShippingMethodFragment.ts @@ -8,6 +8,13 @@ import { WeightUnitsEnum, ShippingMethodTypeEnum } from "./../../types/globalTyp // GraphQL fragment: ShippingMethodFragment // ==================================================== +export interface ShippingMethodFragment_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface ShippingMethodFragment_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -57,6 +64,7 @@ export interface ShippingMethodFragment_channelListings { export interface ShippingMethodFragment { __typename: "ShippingMethod"; id: string; + zipCodeRules: (ShippingMethodFragment_zipCodeRules | null)[] | null; minimumOrderWeight: ShippingMethodFragment_minimumOrderWeight | null; maximumOrderWeight: ShippingMethodFragment_maximumOrderWeight | null; name: string; diff --git a/src/fragments/types/ShippingMethodWithZipCodesFragment.ts b/src/fragments/types/ShippingMethodWithZipCodesFragment.ts new file mode 100644 index 000000000..52a7d6567 --- /dev/null +++ b/src/fragments/types/ShippingMethodWithZipCodesFragment.ts @@ -0,0 +1,20 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL fragment: ShippingMethodWithZipCodesFragment +// ==================================================== + +export interface ShippingMethodWithZipCodesFragment_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + +export interface ShippingMethodWithZipCodesFragment { + __typename: "ShippingMethod"; + id: string; + zipCodeRules: (ShippingMethodWithZipCodesFragment_zipCodeRules | null)[] | null; +} diff --git a/src/fragments/types/ShippingZoneDetailsFragment.ts b/src/fragments/types/ShippingZoneDetailsFragment.ts index 5f941c4a6..bfaf23fb1 100644 --- a/src/fragments/types/ShippingZoneDetailsFragment.ts +++ b/src/fragments/types/ShippingZoneDetailsFragment.ts @@ -14,6 +14,13 @@ export interface ShippingZoneDetailsFragment_countries { country: string; } +export interface ShippingZoneDetailsFragment_shippingMethods_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface ShippingZoneDetailsFragment_shippingMethods_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -63,6 +70,7 @@ export interface ShippingZoneDetailsFragment_shippingMethods_channelListings { export interface ShippingZoneDetailsFragment_shippingMethods { __typename: "ShippingMethod"; id: string; + zipCodeRules: (ShippingZoneDetailsFragment_shippingMethods_zipCodeRules | null)[] | null; minimumOrderWeight: ShippingZoneDetailsFragment_shippingMethods_minimumOrderWeight | null; maximumOrderWeight: ShippingZoneDetailsFragment_shippingMethods_maximumOrderWeight | null; name: string; diff --git a/src/misc.ts b/src/misc.ts index 1abbd8bce..a1641a58f 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -264,15 +264,18 @@ export function getMutationState( interface SaleorMutationResult { errors?: UserError[]; } +export function getMutationErrors< + TData extends Record +>(data: TData): UserError[] { + return Object.values(data).reduce( + (acc: UserError[], mut) => [...acc, ...maybe(() => mut.errors, [])], + [] + ); +} export function getMutationStatus< TData extends Record >(opts: MutationResult): ConfirmButtonTransitionState { - const errors = opts.data - ? Object.values(opts.data).reduce( - (acc: UserError[], mut) => [...acc, ...maybe(() => mut.errors, [])], - [] - ) - : []; + const errors = opts.data ? getMutationErrors(opts.data) : []; return getMutationState(opts.called, opts.loading, errors); } diff --git a/src/shipping/components/OrderValue/OrderValue.tsx b/src/shipping/components/OrderValue/OrderValue.tsx index e5bfd0585..2d1e12467 100644 --- a/src/shipping/components/OrderValue/OrderValue.tsx +++ b/src/shipping/components/OrderValue/OrderValue.tsx @@ -56,7 +56,7 @@ export const OrderValue: React.FC = ({ diff --git a/src/shipping/components/OrderWeight/OrderWeight.tsx b/src/shipping/components/OrderWeight/OrderWeight.tsx index a7a2571f7..69e680410 100644 --- a/src/shipping/components/OrderWeight/OrderWeight.tsx +++ b/src/shipping/components/OrderWeight/OrderWeight.tsx @@ -40,7 +40,7 @@ export const OrderWeight: React.FC = ({ diff --git a/src/shipping/components/ShippingRateZipCodeRangeRemoveDialog/ShippingRateZipCodeRangeRemoveDialog.tsx b/src/shipping/components/ShippingRateZipCodeRangeRemoveDialog/ShippingRateZipCodeRangeRemoveDialog.tsx new file mode 100644 index 000000000..be3fe0e48 --- /dev/null +++ b/src/shipping/components/ShippingRateZipCodeRangeRemoveDialog/ShippingRateZipCodeRangeRemoveDialog.tsx @@ -0,0 +1,42 @@ +import DialogContentText from "@material-ui/core/DialogContentText"; +import ActionDialog from "@saleor/components/ActionDialog"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; +import { DialogProps } from "@saleor/types"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +export interface ShippingRateZipCodeRangeRemoveDialogProps extends DialogProps { + confirmButtonState: ConfirmButtonTransitionState; + onConfirm: () => void; +} + +const ShippingRateZipCodeRangeRemoveDialog: React.FC = ({ + confirmButtonState, + open, + onClose, + onConfirm +}) => { + const intl = useIntl(); + + return ( + + + + + + ); +}; + +ShippingRateZipCodeRangeRemoveDialog.displayName = + "ShippingRateZipCodeRangeRemoveDialog"; +export default ShippingRateZipCodeRangeRemoveDialog; diff --git a/src/shipping/components/ShippingRateZipCodeRangeRemoveDialog/index.ts b/src/shipping/components/ShippingRateZipCodeRangeRemoveDialog/index.ts new file mode 100644 index 000000000..0d9ae9266 --- /dev/null +++ b/src/shipping/components/ShippingRateZipCodeRangeRemoveDialog/index.ts @@ -0,0 +1,2 @@ +export * from "./ShippingRateZipCodeRangeRemoveDialog"; +export { default } from "./ShippingRateZipCodeRangeRemoveDialog"; diff --git a/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.stories.tsx b/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.stories.tsx index abac4a784..2f1ed4686 100644 --- a/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.stories.tsx +++ b/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.stories.tsx @@ -47,37 +47,58 @@ const props: ShippingZoneRatesPageProps = { onChannelsChange: () => undefined, onDelete: () => undefined, onSubmit: () => undefined, + onZipCodeAssign: () => undefined, + onZipCodeUnassign: () => undefined, openChannelsModal: () => undefined, + rate: null, saveButtonBarState: "default", shippingChannels: defaultChannels, - variant: ShippingMethodTypeEnum.PRICE + variant: ShippingMethodTypeEnum.PRICE, + zipCodes: [ + { + __typename: "ShippingMethodZipCodeRule", + end: "51-200", + id: "1", + start: "51-220" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: "31-101", + id: "1", + start: "44-205" + } + ] }; -storiesOf("Shipping / ShippingZoneRates page", module) +storiesOf("Views / Shipping / Shipping rate", module) .addDecorator(Decorator) - .add("default price", () => ) + .add("create price rate", () => ) + .add("create weight rate", () => ( + + )) .add("loading", () => ( )) - .add("update price", () => ( + .add("update price rate", () => ( )) - .add("default weight", () => ( - - )) - .add("update weight", () => ( + .add("update weight rate", () => ( + )) + .add("no zip codes", () => ( + )); diff --git a/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx b/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx index 289847826..d411d7c78 100644 --- a/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx +++ b/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx @@ -10,19 +10,27 @@ import PageHeader from "@saleor/components/PageHeader"; import SaveButtonBar from "@saleor/components/SaveButtonBar"; import { ShippingChannelsErrorFragment } from "@saleor/fragments/types/ShippingChannelsErrorFragment"; import { ShippingErrorFragment } from "@saleor/fragments/types/ShippingErrorFragment"; +import { + ShippingMethodFragment, + ShippingMethodFragment_zipCodeRules +} from "@saleor/fragments/types/ShippingMethodFragment"; import { validatePrice } from "@saleor/products/utils/validation"; import OrderValue from "@saleor/shipping/components/OrderValue"; import OrderWeight from "@saleor/shipping/components/OrderWeight"; import PricingCard from "@saleor/shipping/components/PricingCard"; import ShippingZoneInfo from "@saleor/shipping/components/ShippingZoneInfo"; import { createChannelsChangeHandler } from "@saleor/shipping/handlers"; -import { ShippingZone_shippingZone_shippingMethods } from "@saleor/shipping/types/ShippingZone"; import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; +import ShippingZoneZipCodes, { + ZipCodeInclusion +} from "../ShippingZoneZipCodes"; + export interface FormData { channelListings: ChannelShippingData[]; + includeZipCodes: ZipCodeInclusion; name: string; noLimits: boolean; minValue: string; @@ -35,13 +43,16 @@ export interface ShippingZoneRatesPageProps { shippingChannels: ChannelShippingData[]; disabled: boolean; hasChannelChanged?: boolean; - rate?: ShippingZone_shippingZone_shippingMethods; + rate: ShippingMethodFragment | null; + zipCodes?: ShippingMethodFragment_zipCodeRules[]; channelErrors: ShippingChannelsErrorFragment[]; errors: ShippingErrorFragment[]; saveButtonBarState: ConfirmButtonTransitionState; onBack: () => void; onDelete?: () => void; onSubmit: (data: FormData) => void; + onZipCodeAssign: () => void; + onZipCodeUnassign: (id: string) => void; onChannelsChange: (data: ChannelShippingData[]) => void; openChannelsModal: () => void; variant: ShippingMethodTypeEnum; @@ -58,15 +69,19 @@ export const ShippingZoneRatesPage: React.FC = ({ onDelete, onSubmit, onChannelsChange, + onZipCodeAssign, + onZipCodeUnassign, openChannelsModal, rate, saveButtonBarState, - variant + variant, + zipCodes }) => { const intl = useIntl(); const isPriceVariant = variant === ShippingMethodTypeEnum.PRICE; const initialForm: FormData = { channelListings: shippingChannels, + includeZipCodes: ZipCodeInclusion.Exclude, maxValue: rate?.maximumOrderWeight?.value.toString() || "", minValue: rate?.minimumOrderWeight?.value.toString() || "", name: rate?.name || "", @@ -74,6 +89,8 @@ export const ShippingZoneRatesPage: React.FC = ({ type: rate?.type || null }; + const rateExists = rate !== null; + return (
{({ change, data, hasChanged, submit, triggerChange }) => { @@ -141,6 +158,14 @@ export const ShippingZoneRatesPage: React.FC = ({ errors={channelErrors} /> + undefined} + onZipCodeRangeAdd={onZipCodeAssign} + zipCodes={rateExists ? rate?.zipCodeRules : zipCodes} + />
( + undefined} + onSubmit={() => undefined} + /> + )); diff --git a/src/shipping/components/ShippingZoneZipCodeRangeDialog/ShippingZoneZipCodeRangeDialog.tsx b/src/shipping/components/ShippingZoneZipCodeRangeDialog/ShippingZoneZipCodeRangeDialog.tsx new file mode 100644 index 000000000..ded91d516 --- /dev/null +++ b/src/shipping/components/ShippingZoneZipCodeRangeDialog/ShippingZoneZipCodeRangeDialog.tsx @@ -0,0 +1,109 @@ +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import makeStyles from "@material-ui/core/styles/makeStyles"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import ConfirmButton, { + ConfirmButtonTransitionState +} from "@saleor/components/ConfirmButton"; +import Form from "@saleor/components/Form"; +import Grid from "@saleor/components/Grid"; +import { buttonMessages, commonMessages } from "@saleor/intl"; +import { DialogProps, MinMax } from "@saleor/types"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +export interface ShippingZoneZipCodeRangeDialogProps extends DialogProps { + confirmButtonState: ConfirmButtonTransitionState; + onSubmit: (range: MinMax) => void; +} + +const useStyles = makeStyles( + theme => ({ + info: { + marginBottom: theme.spacing(2) + } + }), + { + name: "ShippingZoneZipCodeRangeDialog" + } +); + +const ShippingZoneZipCodeRangeDialog: React.FC = ({ + confirmButtonState, + open, + onClose, + onSubmit +}) => { + const classes = useStyles({}); + const intl = useIntl(); + + const initial: MinMax = { + max: "", + min: "" + }; + + return ( + + + + + + {({ change, data, hasChanged }) => ( + <> + + + + + + + + + + + + + + + + + )} + + + ); +}; + +ShippingZoneZipCodeRangeDialog.displayName = "ShippingZoneZipCodeRangeDialog"; +export default ShippingZoneZipCodeRangeDialog; diff --git a/src/shipping/components/ShippingZoneZipCodeRangeDialog/index.ts b/src/shipping/components/ShippingZoneZipCodeRangeDialog/index.ts new file mode 100644 index 000000000..4ad017af4 --- /dev/null +++ b/src/shipping/components/ShippingZoneZipCodeRangeDialog/index.ts @@ -0,0 +1,2 @@ +export * from "./ShippingZoneZipCodeRangeDialog"; +export { default } from "./ShippingZoneZipCodeRangeDialog"; diff --git a/src/shipping/components/ShippingZoneZipCodes/ShippingZoneZipCodes.tsx b/src/shipping/components/ShippingZoneZipCodes/ShippingZoneZipCodes.tsx new file mode 100644 index 000000000..b14fb03f4 --- /dev/null +++ b/src/shipping/components/ShippingZoneZipCodes/ShippingZoneZipCodes.tsx @@ -0,0 +1,233 @@ +import Button from "@material-ui/core/Button"; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import IconButton from "@material-ui/core/IconButton"; +import makeStyles from "@material-ui/core/styles/makeStyles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import Typography from "@material-ui/core/Typography"; +import DeleteIcon from "@material-ui/icons/Delete"; +import CardTitle from "@saleor/components/CardTitle"; +import RadioGroupField from "@saleor/components/RadioGroupField"; +import ResponsiveTable from "@saleor/components/ResponsiveTable"; +import Skeleton from "@saleor/components/Skeleton"; +import { ShippingMethodFragment_zipCodeRules } from "@saleor/fragments/types/ShippingMethodFragment"; +import { FormChange } from "@saleor/hooks/useForm"; +import ArrowDropdown from "@saleor/icons/ArrowDropdown"; +import { renderCollection } from "@saleor/misc"; +import classNames from "classnames"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +export enum ZipCodeInclusion { + Include, + Exclude +} + +export interface ShippingZoneZipCodesProps { + data: Record<"includeZipCodes", ZipCodeInclusion>; + disabled: boolean; + initialExpanded?: boolean; + zipCodes: ShippingMethodFragment_zipCodeRules[] | undefined; + onZipCodeInclusionChange: FormChange; + onZipCodeDelete: (id: string) => void; + onZipCodeRangeAdd: () => void; +} + +const useStyles = makeStyles( + theme => ({ + arrow: { + transition: theme.transitions.create("transform") + }, + arrowRotate: { + transform: "scale(-1)" + }, + colAction: { + width: 80 + }, + colCode: {}, + hide: { + display: "none" + }, + option: { + marginBottom: theme.spacing(2), + width: 400 + }, + radioContainer: { + paddingBottom: 0 + }, + skeleton: { + width: 80 + } + }), + { + name: "ShippingZoneZipCodes" + } +); + +const ShippingZoneZipCodes: React.FC = ({ + data, + disabled, + initialExpanded, + zipCodes, + onZipCodeDelete, + onZipCodeInclusionChange, + onZipCodeRangeAdd +}) => { + const [expanded, setExpanded] = React.useState(initialExpanded); + const intl = useIntl(); + const classes = useStyles({}); + + return ( + + + + + } + /> + + + + + + + + +
+ ), + value: ZipCodeInclusion.Exclude + }, + { + label: ( +
+ + + + + + +
+ ), + value: ZipCodeInclusion.Include + } + ]} + name="includeZipCodes" + value={data.includeZipCodes} + onChange={onZipCodeInclusionChange} + /> + + + + + + + {zipCodes === undefined || + (zipCodes.length > 0 && ( + + + + {zipCodes === undefined ? ( + + ) : ( + + + + )} + + + setExpanded(!expanded)}> + + + + + + ))} + {expanded && ( + + {renderCollection( + zipCodes, + zipCodeRange => ( + + + {zipCodeRange?.start ? ( + zipCodeRange?.end ? ( + `${zipCodeRange.start} - ${zipCodeRange.end}` + ) : ( + zipCodeRange.start + ) + ) : ( + + )} + + + onZipCodeDelete(zipCodeRange.id)} + data-test="delete-zip-code" + data-test-id={zipCodeRange?.id} + > + + + + + ), + () => ( + + + + + + + + ) + )} + + )} + +
+ ); +}; + +ShippingZoneZipCodes.displayName = "ShippingZoneZipCodes"; +ShippingZoneZipCodes.defaultProps = { + initialExpanded: true +}; +export default ShippingZoneZipCodes; diff --git a/src/shipping/components/ShippingZoneZipCodes/index.ts b/src/shipping/components/ShippingZoneZipCodes/index.ts new file mode 100644 index 000000000..97d7bc8df --- /dev/null +++ b/src/shipping/components/ShippingZoneZipCodes/index.ts @@ -0,0 +1,2 @@ +export * from "./ShippingZoneZipCodes"; +export { default } from "./ShippingZoneZipCodes"; diff --git a/src/shipping/fixtures.ts b/src/shipping/fixtures.ts index ac4920183..7045d9e0b 100644 --- a/src/shipping/fixtures.ts +++ b/src/shipping/fixtures.ts @@ -1589,7 +1589,27 @@ export const shippingZone: ShippingZoneDetailsFragment = { value: 0 }, name: "DB Schenker", - type: ShippingMethodTypeEnum.WEIGHT + type: ShippingMethodTypeEnum.WEIGHT, + zipCodeRules: [ + { + __typename: "ShippingMethodZipCodeRule", + end: "51-220", + id: "1", + start: "51-210" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: "51-240", + id: "2", + start: "51-235" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: null, + id: "2", + start: "51-274" + } + ] }, { __typename: "ShippingMethod", @@ -1602,7 +1622,27 @@ export const shippingZone: ShippingZoneDetailsFragment = { value: 0 }, name: "Registred priority", - type: ShippingMethodTypeEnum.WEIGHT + type: ShippingMethodTypeEnum.WEIGHT, + zipCodeRules: [ + { + __typename: "ShippingMethodZipCodeRule", + end: "51-220", + id: "1", + start: "51-210" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: "51-240", + id: "2", + start: "51-235" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: null, + id: "2", + start: "51-274" + } + ] }, { __typename: "ShippingMethod", @@ -1614,9 +1654,28 @@ export const shippingZone: ShippingZoneDetailsFragment = { unit: WeightUnitsEnum.KG, value: 0 }, - name: "UPS", - type: ShippingMethodTypeEnum.PRICE + type: ShippingMethodTypeEnum.PRICE, + zipCodeRules: [ + { + __typename: "ShippingMethodZipCodeRule", + end: "51-220", + id: "1", + start: "51-210" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: "51-240", + id: "2", + start: "51-235" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: null, + id: "2", + start: "51-274" + } + ] }, { __typename: "ShippingMethod", @@ -1629,7 +1688,27 @@ export const shippingZone: ShippingZoneDetailsFragment = { value: 0 }, name: "DHL", - type: ShippingMethodTypeEnum.PRICE + type: ShippingMethodTypeEnum.PRICE, + zipCodeRules: [ + { + __typename: "ShippingMethodZipCodeRule", + end: "51-220", + id: "1", + start: "51-210" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: "51-240", + id: "2", + start: "51-235" + }, + { + __typename: "ShippingMethodZipCodeRule", + end: null, + id: "2", + start: "51-274" + } + ] } ], warehouses: [ diff --git a/src/shipping/handlers.ts b/src/shipping/handlers.ts index e367fbe0c..3e34b148c 100644 --- a/src/shipping/handlers.ts +++ b/src/shipping/handlers.ts @@ -1,10 +1,24 @@ import { ChannelShippingData } from "@saleor/channels/utils"; +import { ShippingMethodFragment_zipCodeRules } from "@saleor/fragments/types/ShippingMethodFragment"; +import useNavigator from "@saleor/hooks/useNavigator"; +import useNotifier from "@saleor/hooks/useNotifier"; +import { commonMessages } from "@saleor/intl"; +import { getMutationErrors, getMutationState } from "@saleor/misc"; import { FormData as ShippingZoneRatesPageFormData } from "@saleor/shipping/components/ShippingZoneRatesPage"; import { CreateShippingRateVariables } from "@saleor/shipping/types/CreateShippingRate"; import { ShippingMethodChannelListingUpdateVariables } from "@saleor/shipping/types/ShippingMethodChannelListingUpdate"; import { UpdateShippingRateVariables } from "@saleor/shipping/types/UpdateShippingRate"; import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes"; import { diff } from "fast-array-diff"; +import { useIntl } from "react-intl"; + +import { + useShippingMethodChannelListingUpdate, + useShippingMethodZipCodeRangeAssign, + useShippingRateCreate, + useShippingRateDelete +} from "./mutations"; +import { shippingPriceRatesEditUrl, shippingWeightRatesEditUrl } from "./urls"; export const createChannelsChangeHandler = ( selectedChannels: ChannelShippingData[], @@ -122,3 +136,109 @@ export function getShippingMethodChannelVariables( } }; } + +export function useShippingRateCreator( + shippingZoneId: string, + type: ShippingMethodTypeEnum, + zipCodes: ShippingMethodFragment_zipCodeRules[] +) { + const intl = useIntl(); + const notify = useNotifier(); + const navigate = useNavigator(); + const [ + createBaseShippingRate, + createBaseShippingRateOpts + ] = useShippingRateCreate({}); + const [ + assignZipCodeRules, + assignZipCodeRulesOpts + ] = useShippingMethodZipCodeRangeAssign({}); + const [ + updateShippingMethodChannelListing, + updateShippingMethodChannelListingOpts + ] = useShippingMethodChannelListingUpdate({}); + const [deleteShippingRate] = useShippingRateDelete({}); + + const getVariables = + type === ShippingMethodTypeEnum.PRICE + ? getCreateShippingPriceRateVariables + : getCreateShippingWeightRateVariables; + const getUrl = + type === ShippingMethodTypeEnum.PRICE + ? shippingPriceRatesEditUrl + : shippingWeightRatesEditUrl; + + const createShippingRate = async (data: ShippingZoneRatesPageFormData) => { + const response = await createBaseShippingRate({ + variables: getVariables(data, shippingZoneId) + }); + + const createErrors = response.data.shippingPriceCreate.errors; + if (createErrors.length === 0) { + const rateId = response.data.shippingPriceCreate.shippingMethod.id; + + const mutationResults = await Promise.all([ + updateShippingMethodChannelListing({ + variables: getShippingMethodChannelVariables( + rateId, + data.noLimits, + data.channelListings + ) + }), + assignZipCodeRules({ + variables: { + id: rateId, + input: { + zipCodeRules: zipCodes.map(zipCodeRule => ({ + end: zipCodeRule.end || null, + start: zipCodeRule.start + })) + } + } + }) + ]); + + if ( + mutationResults.find( + result => getMutationErrors(result.data as any).length > 0 + ) + ) { + deleteShippingRate({ + variables: { + id: rateId + } + }); + } else { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + navigate(getUrl(shippingZoneId, rateId)); + } + } + }; + + const called = + createBaseShippingRateOpts.called || + updateShippingMethodChannelListingOpts.called || + assignZipCodeRulesOpts.called; + const loading = + createBaseShippingRateOpts.loading || + updateShippingMethodChannelListingOpts.loading || + assignZipCodeRulesOpts.loading; + const errors = [ + ...(createBaseShippingRateOpts.data?.shippingPriceCreate.errors || []), + ...(assignZipCodeRulesOpts.data?.shippingMethodZipCodeRulesCreate.errors || + []) + ]; + const channelErrors = + updateShippingMethodChannelListingOpts.data + ?.shippingMethodChannelListingUpdate.errors || []; + + return { + channelErrors, + createShippingRate, + errors, + status: getMutationState(called, loading, [...errors, ...channelErrors]) + }; +} diff --git a/src/shipping/index.tsx b/src/shipping/index.tsx index 508241e9d..471a2cf09 100644 --- a/src/shipping/index.tsx +++ b/src/shipping/index.tsx @@ -6,10 +6,12 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { WindowTitle } from "../components/WindowTitle"; import { - shippingPriceRatesEditUrl, - shippingPriceRatesUrl, - shippingWeightRatesEditUrl, - shippingWeightRatesUrl, + shippingPriceRatesEditPath, + shippingPriceRatesPath, + ShippingRateCreateUrlQueryParams, + ShippingRateUrlQueryParams, + shippingWeightRatesEditPath, + shippingWeightRatesPath, shippingZoneAddPath, shippingZonePath, shippingZonesListPath, @@ -48,31 +50,63 @@ const ShippingZoneDetails: React.FC> = ({ match -}) => ; +}) => { + const qs = parseQs(location.search.substr(1)); + const params: ShippingRateCreateUrlQueryParams = qs; + + return ( + + ); +}; const WeightRatesCreate: React.FC> = ({ match -}) => ; +}) => { + const qs = parseQs(location.search.substr(1)); + const params: ShippingRateCreateUrlQueryParams = qs; + + return ( + + ); +}; const WeightRatesUpdate: React.FC> = ({ match }) => ( - -); +}>> = ({ match }) => { + const qs = parseQs(location.search.substr(1)); + const params: ShippingRateUrlQueryParams = qs; + + return ( + + ); +}; const PriceRatesUpdate: React.FC> = ({ match }) => ( - -); +}>> = ({ match }) => { + const qs = parseQs(location.search.substr(1)); + const params: ShippingRateUrlQueryParams = qs; + + return ( + + ); +}; export const ShippingRouter: React.FC = () => { const intl = useIntl(); @@ -97,19 +131,19 @@ export const ShippingRouter: React.FC = () => { component={ShippingZoneDetails} /> diff --git a/src/shipping/mutations.ts b/src/shipping/mutations.ts index d950893e2..35c0f9438 100644 --- a/src/shipping/mutations.ts +++ b/src/shipping/mutations.ts @@ -4,6 +4,7 @@ import { } from "@saleor/fragments/errors"; import { shippingMethodFragment, + shippingMethodWithZipCodesFragment, shippingZoneDetailsFragment } from "@saleor/fragments/shipping"; import { countryFragment } from "@saleor/fragments/taxes"; @@ -38,6 +39,14 @@ import { ShippingMethodChannelListingUpdate, ShippingMethodChannelListingUpdateVariables } from "./types/ShippingMethodChannelListingUpdate"; +import { + ShippingMethodZipCodeRangeAssign, + ShippingMethodZipCodeRangeAssignVariables +} from "./types/ShippingMethodZipCodeRangeAssign"; +import { + ShippingMethodZipCodeRangeUnassign, + ShippingMethodZipCodeRangeUnassignVariables +} from "./types/ShippingMethodZipCodeRangeUnassign"; import { UpdateDefaultWeightUnit, UpdateDefaultWeightUnitVariables @@ -245,3 +254,46 @@ export const useShippingMethodChannelListingUpdate = makeMutation< ShippingMethodChannelListingUpdate, ShippingMethodChannelListingUpdateVariables >(shippingMethodChannelListingUpdate); + +export const shippingMethodZipCodeRangeAssign = gql` + ${shippingChannelsErrorFragment} + ${shippingMethodWithZipCodesFragment} + mutation ShippingMethodZipCodeRangeAssign( + $id: ID! + $input: ShippingZipCodeRulesCreateInput! + ) { + shippingMethodZipCodeRulesCreate(shippingMethodId: $id, input: $input) { + errors: shippingErrors { + ...ShippingChannelsErrorFragment + } + shippingMethod { + ...ShippingMethodWithZipCodesFragment + } + } + } +`; + +export const useShippingMethodZipCodeRangeAssign = makeMutation< + ShippingMethodZipCodeRangeAssign, + ShippingMethodZipCodeRangeAssignVariables +>(shippingMethodZipCodeRangeAssign); + +export const shippingMethodZipCodeRulesDelete = gql` + ${shippingChannelsErrorFragment} + ${shippingMethodWithZipCodesFragment} + mutation ShippingMethodZipCodeRangeUnassign($id: ID!) { + shippingMethodZipCodeRulesDelete(id: $id) { + errors: shippingErrors { + ...ShippingChannelsErrorFragment + } + shippingMethod { + ...ShippingMethodWithZipCodesFragment + } + } + } +`; + +export const useShippingMethodZipCodeRangeUnassign = makeMutation< + ShippingMethodZipCodeRangeUnassign, + ShippingMethodZipCodeRangeUnassignVariables +>(shippingMethodZipCodeRulesDelete); diff --git a/src/shipping/types/CreateShippingRate.ts b/src/shipping/types/CreateShippingRate.ts index 81ad1e07c..dc82e41c8 100644 --- a/src/shipping/types/CreateShippingRate.ts +++ b/src/shipping/types/CreateShippingRate.ts @@ -20,6 +20,13 @@ export interface CreateShippingRate_shippingPriceCreate_shippingZone_countries { country: string; } +export interface CreateShippingRate_shippingPriceCreate_shippingZone_shippingMethods_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface CreateShippingRate_shippingPriceCreate_shippingZone_shippingMethods_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -69,6 +76,7 @@ export interface CreateShippingRate_shippingPriceCreate_shippingZone_shippingMet export interface CreateShippingRate_shippingPriceCreate_shippingZone_shippingMethods { __typename: "ShippingMethod"; id: string; + zipCodeRules: (CreateShippingRate_shippingPriceCreate_shippingZone_shippingMethods_zipCodeRules | null)[] | null; minimumOrderWeight: CreateShippingRate_shippingPriceCreate_shippingZone_shippingMethods_minimumOrderWeight | null; maximumOrderWeight: CreateShippingRate_shippingPriceCreate_shippingZone_shippingMethods_maximumOrderWeight | null; name: string; @@ -92,6 +100,13 @@ export interface CreateShippingRate_shippingPriceCreate_shippingZone { warehouses: (CreateShippingRate_shippingPriceCreate_shippingZone_warehouses | null)[] | null; } +export interface CreateShippingRate_shippingPriceCreate_shippingMethod_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface CreateShippingRate_shippingPriceCreate_shippingMethod_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -141,6 +156,7 @@ export interface CreateShippingRate_shippingPriceCreate_shippingMethod_channelLi export interface CreateShippingRate_shippingPriceCreate_shippingMethod { __typename: "ShippingMethod"; id: string; + zipCodeRules: (CreateShippingRate_shippingPriceCreate_shippingMethod_zipCodeRules | null)[] | null; minimumOrderWeight: CreateShippingRate_shippingPriceCreate_shippingMethod_minimumOrderWeight | null; maximumOrderWeight: CreateShippingRate_shippingPriceCreate_shippingMethod_maximumOrderWeight | null; name: string; diff --git a/src/shipping/types/DeleteShippingRate.ts b/src/shipping/types/DeleteShippingRate.ts index 13cd8ac9a..838db5345 100644 --- a/src/shipping/types/DeleteShippingRate.ts +++ b/src/shipping/types/DeleteShippingRate.ts @@ -20,6 +20,13 @@ export interface DeleteShippingRate_shippingPriceDelete_shippingZone_countries { country: string; } +export interface DeleteShippingRate_shippingPriceDelete_shippingZone_shippingMethods_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface DeleteShippingRate_shippingPriceDelete_shippingZone_shippingMethods_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -69,6 +76,7 @@ export interface DeleteShippingRate_shippingPriceDelete_shippingZone_shippingMet export interface DeleteShippingRate_shippingPriceDelete_shippingZone_shippingMethods { __typename: "ShippingMethod"; id: string; + zipCodeRules: (DeleteShippingRate_shippingPriceDelete_shippingZone_shippingMethods_zipCodeRules | null)[] | null; minimumOrderWeight: DeleteShippingRate_shippingPriceDelete_shippingZone_shippingMethods_minimumOrderWeight | null; maximumOrderWeight: DeleteShippingRate_shippingPriceDelete_shippingZone_shippingMethods_maximumOrderWeight | null; name: string; diff --git a/src/shipping/types/ShippingMethodChannelListingUpdate.ts b/src/shipping/types/ShippingMethodChannelListingUpdate.ts index 89e925294..c197efb7c 100644 --- a/src/shipping/types/ShippingMethodChannelListingUpdate.ts +++ b/src/shipping/types/ShippingMethodChannelListingUpdate.ts @@ -8,6 +8,13 @@ import { ShippingMethodChannelListingInput, WeightUnitsEnum, ShippingMethodTypeE // GraphQL mutation operation: ShippingMethodChannelListingUpdate // ==================================================== +export interface ShippingMethodChannelListingUpdate_shippingMethodChannelListingUpdate_shippingMethod_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface ShippingMethodChannelListingUpdate_shippingMethodChannelListingUpdate_shippingMethod_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -57,6 +64,7 @@ export interface ShippingMethodChannelListingUpdate_shippingMethodChannelListing export interface ShippingMethodChannelListingUpdate_shippingMethodChannelListingUpdate_shippingMethod { __typename: "ShippingMethod"; id: string; + zipCodeRules: (ShippingMethodChannelListingUpdate_shippingMethodChannelListingUpdate_shippingMethod_zipCodeRules | null)[] | null; minimumOrderWeight: ShippingMethodChannelListingUpdate_shippingMethodChannelListingUpdate_shippingMethod_minimumOrderWeight | null; maximumOrderWeight: ShippingMethodChannelListingUpdate_shippingMethodChannelListingUpdate_shippingMethod_maximumOrderWeight | null; name: string; diff --git a/src/shipping/types/ShippingMethodZipCodeRangeAssign.ts b/src/shipping/types/ShippingMethodZipCodeRangeAssign.ts new file mode 100644 index 000000000..e10053f47 --- /dev/null +++ b/src/shipping/types/ShippingMethodZipCodeRangeAssign.ts @@ -0,0 +1,44 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ShippingZipCodeRulesCreateInput, ShippingErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ShippingMethodZipCodeRangeAssign +// ==================================================== + +export interface ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate_errors { + __typename: "ShippingError"; + code: ShippingErrorCode; + field: string | null; + channels: string[] | null; +} + +export interface ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate_shippingMethod_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + +export interface ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate_shippingMethod { + __typename: "ShippingMethod"; + id: string; + zipCodeRules: (ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate_shippingMethod_zipCodeRules | null)[] | null; +} + +export interface ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate { + __typename: "ShippingZipCodeRulesCreate"; + errors: ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate_errors[]; + shippingMethod: ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate_shippingMethod | null; +} + +export interface ShippingMethodZipCodeRangeAssign { + shippingMethodZipCodeRulesCreate: ShippingMethodZipCodeRangeAssign_shippingMethodZipCodeRulesCreate | null; +} + +export interface ShippingMethodZipCodeRangeAssignVariables { + id: string; + input: ShippingZipCodeRulesCreateInput; +} diff --git a/src/shipping/types/ShippingMethodZipCodeRangeUnassign.ts b/src/shipping/types/ShippingMethodZipCodeRangeUnassign.ts new file mode 100644 index 000000000..23fd0ca4a --- /dev/null +++ b/src/shipping/types/ShippingMethodZipCodeRangeUnassign.ts @@ -0,0 +1,43 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ShippingErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ShippingMethodZipCodeRangeUnassign +// ==================================================== + +export interface ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete_errors { + __typename: "ShippingError"; + code: ShippingErrorCode; + field: string | null; + channels: string[] | null; +} + +export interface ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete_shippingMethod_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + +export interface ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete_shippingMethod { + __typename: "ShippingMethod"; + id: string; + zipCodeRules: (ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete_shippingMethod_zipCodeRules | null)[] | null; +} + +export interface ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete { + __typename: "ShippingZipCodeRulesDelete"; + errors: ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete_errors[]; + shippingMethod: ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete_shippingMethod | null; +} + +export interface ShippingMethodZipCodeRangeUnassign { + shippingMethodZipCodeRulesDelete: ShippingMethodZipCodeRangeUnassign_shippingMethodZipCodeRulesDelete | null; +} + +export interface ShippingMethodZipCodeRangeUnassignVariables { + id: string; +} diff --git a/src/shipping/types/ShippingZone.ts b/src/shipping/types/ShippingZone.ts index a7a73c9e1..04bb69905 100644 --- a/src/shipping/types/ShippingZone.ts +++ b/src/shipping/types/ShippingZone.ts @@ -14,6 +14,13 @@ export interface ShippingZone_shippingZone_countries { country: string; } +export interface ShippingZone_shippingZone_shippingMethods_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface ShippingZone_shippingZone_shippingMethods_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -63,6 +70,7 @@ export interface ShippingZone_shippingZone_shippingMethods_channelListings { export interface ShippingZone_shippingZone_shippingMethods { __typename: "ShippingMethod"; id: string; + zipCodeRules: (ShippingZone_shippingZone_shippingMethods_zipCodeRules | null)[] | null; minimumOrderWeight: ShippingZone_shippingZone_shippingMethods_minimumOrderWeight | null; maximumOrderWeight: ShippingZone_shippingZone_shippingMethods_maximumOrderWeight | null; name: string; diff --git a/src/shipping/types/UpdateShippingRate.ts b/src/shipping/types/UpdateShippingRate.ts index f505c129c..56bf1d413 100644 --- a/src/shipping/types/UpdateShippingRate.ts +++ b/src/shipping/types/UpdateShippingRate.ts @@ -14,6 +14,13 @@ export interface UpdateShippingRate_shippingPriceUpdate_errors { field: string | null; } +export interface UpdateShippingRate_shippingPriceUpdate_shippingMethod_zipCodeRules { + __typename: "ShippingMethodZipCodeRule"; + id: string; + start: string | null; + end: string | null; +} + export interface UpdateShippingRate_shippingPriceUpdate_shippingMethod_minimumOrderWeight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -63,6 +70,7 @@ export interface UpdateShippingRate_shippingPriceUpdate_shippingMethod_channelLi export interface UpdateShippingRate_shippingPriceUpdate_shippingMethod { __typename: "ShippingMethod"; id: string; + zipCodeRules: (UpdateShippingRate_shippingPriceUpdate_shippingMethod_zipCodeRules | null)[] | null; minimumOrderWeight: UpdateShippingRate_shippingPriceUpdate_shippingMethod_minimumOrderWeight | null; maximumOrderWeight: UpdateShippingRate_shippingPriceUpdate_shippingMethod_maximumOrderWeight | null; name: string; diff --git a/src/shipping/urls.ts b/src/shipping/urls.ts index 053ca4b6a..2421f18a9 100644 --- a/src/shipping/urls.ts +++ b/src/shipping/urls.ts @@ -36,22 +36,58 @@ export const shippingZoneUrl = ( params?: ShippingZoneUrlQueryParams ) => shippingZonePath(encodeURIComponent(id)) + "?" + stringifyQs(params); -export const shippingPriceRatesUrl = (id: string) => +type ZipCodeRangeActions = "add-range" | "remove-range"; +export type ShippingRateUrlDialog = ZipCodeRangeActions | "remove"; +export type ShippingRateUrlQueryParams = Dialog & + SingleAction; +export type ShippingRateCreateUrlDialog = ZipCodeRangeActions; +export type ShippingRateCreateUrlQueryParams = Dialog< + ShippingRateCreateUrlDialog +> & + SingleAction; + +export const shippingPriceRatesPath = (id: string) => urlJoin(shippingZonePath(id), "price", "add"); +export const shippingPriceRatesUrl = ( + id: string, + params?: ShippingRateCreateUrlQueryParams +) => shippingPriceRatesPath(encodeURIComponent(id)) + "?" + stringifyQs(params); -export const shippingWeightRatesUrl = (id: string) => +export const shippingWeightRatesPath = (id: string) => urlJoin(shippingZonePath(id), "weight", "add"); +export const shippingWeightRatesUrl = ( + id: string, + params?: ShippingRateCreateUrlQueryParams +) => + shippingWeightRatesPath(encodeURIComponent(id)) + "?" + stringifyQs(params); -export const shippingWeightRatesEditUrl = (id: string, rateId: string) => - urlJoin(shippingZonePath(id), "weight", rateId); - -export const shippingPriceRatesEditUrl = (id: string, rateId: string) => +export const shippingPriceRatesEditPath = (id: string, rateId: string) => urlJoin(shippingZonePath(id), "price", rateId); +export const shippingPriceRatesEditUrl = ( + id: string, + rateId: string, + params?: ShippingRateUrlQueryParams +) => + shippingPriceRatesEditPath( + encodeURIComponent(id), + encodeURIComponent(rateId) + ) + + "?" + + stringifyQs(params); + +export const shippingWeightRatesEditPath = (id: string, rateId: string) => + urlJoin(shippingZonePath(id), "weight", rateId); +export const shippingWeightRatesEditUrl = ( + id: string, + rateId: string, + params?: ShippingRateUrlQueryParams +) => + shippingWeightRatesEditPath( + encodeURIComponent(id), + encodeURIComponent(rateId) + ) + + "?" + + stringifyQs(params); export const shippingZoneAddPath = urlJoin(shippingZonesListPath, "add"); -export const shippingZoneAddUrl = shippingZoneAddPath; -export const shippingPriceRatesAddPath = urlJoin( - shippingZonesListPath, - "price", - "add" -); +export const shippingZoneAddUrl = shippingZoneAddPath + "?"; diff --git a/src/shipping/views/PriceRatesCreate/PriceRatesCreate.tsx b/src/shipping/views/PriceRatesCreate/PriceRatesCreate.tsx index a06ef9dbb..3ce8f048c 100644 --- a/src/shipping/views/PriceRatesCreate/PriceRatesCreate.tsx +++ b/src/shipping/views/PriceRatesCreate/PriceRatesCreate.tsx @@ -2,57 +2,49 @@ import { useChannelsList } from "@saleor/channels/queries"; import { createSortedShippingChannels } from "@saleor/channels/utils"; import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog"; import { WindowTitle } from "@saleor/components/WindowTitle"; +import { ShippingMethodFragment_zipCodeRules } from "@saleor/fragments/types/ShippingMethodFragment"; import useChannels from "@saleor/hooks/useChannels"; import useNavigator from "@saleor/hooks/useNavigator"; -import useNotifier from "@saleor/hooks/useNotifier"; -import { commonMessages, sectionNames } from "@saleor/intl"; -import { FormData } from "@saleor/shipping/components/ShippingZoneRatesPage"; +import { sectionNames } from "@saleor/intl"; +import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog"; import ShippingZoneRatesPage from "@saleor/shipping/components/ShippingZoneRatesPage"; +import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog"; +import { useShippingRateCreator } from "@saleor/shipping/handlers"; import { - getCreateShippingPriceRateVariables, - getShippingMethodChannelVariables -} from "@saleor/shipping/handlers"; -import { useShippingMethodChannelListingUpdate } from "@saleor/shipping/mutations"; -import { useShippingRateCreate } from "@saleor/shipping/mutations"; -import { - shippingPriceRatesEditUrl, + shippingPriceRatesUrl, + ShippingRateCreateUrlDialog, + ShippingRateCreateUrlQueryParams, shippingZoneUrl } from "@saleor/shipping/urls"; +import { MinMax } from "@saleor/types"; import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { remove } from "@saleor/utils/lists"; import React from "react"; import { useIntl } from "react-intl"; export interface PriceRatesCreateProps { id: string; + params: ShippingRateCreateUrlQueryParams; } -export const PriceRatesCreate: React.FC = ({ id }) => { +export const PriceRatesCreate: React.FC = ({ + id, + params +}) => { const navigate = useNavigator(); - const notify = useNotifier(); const intl = useIntl(); + const [zipCodes, setZipCodes] = React.useState< + ShippingMethodFragment_zipCodeRules[] + >([]); + const { data: channelsData, loading: channelsLoading } = useChannelsList({}); - const [ - updateShippingMethodChannelListing, - updateShippingMethodChannelListingOpts - ] = useShippingMethodChannelListingUpdate({ - onCompleted: data => { - const errors = data.shippingMethodChannelListingUpdate.errors; - if (errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - navigate( - shippingPriceRatesEditUrl( - id, - data.shippingMethodChannelListingUpdate.shippingMethod.id - ) - ); - } - } - }); + const [openModal, closeModal] = createDialogActionHandlers< + ShippingRateCreateUrlDialog, + ShippingRateCreateUrlQueryParams + >(navigate, params => shippingPriceRatesUrl(id, params), params); const allChannels = createSortedShippingChannels(channelsData?.channels); @@ -69,27 +61,38 @@ export const PriceRatesCreate: React.FC = ({ id }) => { toggleAllChannels } = useChannels(allChannels); - const [createShippingRate, createShippingRateOpts] = useShippingRateCreate( - {} - ); + const { + channelErrors, + createShippingRate, + errors, + status + } = useShippingRateCreator(id, ShippingMethodTypeEnum.PRICE, zipCodes); - const handleSubmit = async (data: FormData) => { - const response = await createShippingRate({ - variables: getCreateShippingPriceRateVariables(data, id) - }); - const errors = response.data.shippingPriceCreate.errors; - if (errors.length === 0) { - updateShippingMethodChannelListing({ - variables: getShippingMethodChannelVariables( - response.data.shippingPriceCreate.shippingMethod.id, - data.noLimits, - data.channelListings - ) - }); - } - }; const handleBack = () => navigate(shippingZoneUrl(id)); + const handleZipCodeRangeAdd = (data: MinMax) => { + setZipCodes(zipCodes => [ + ...zipCodes, + { + __typename: "ShippingMethodZipCodeRule", + end: data.max, + id: zipCodes.length.toString(), + start: data.min + } + ]); + closeModal(); + }; + const handleZipCodeRangeDelete = (id: string) => { + setZipCodes(zipCodes => + remove( + zipCodes.find(zipCode => zipCode.id === id), + zipCodes, + (a, b) => a.id === b.id + ) + ); + closeModal(); + }; + return ( <> @@ -114,23 +117,36 @@ export const PriceRatesCreate: React.FC = ({ id }) => { openModal("add-range")} + onZipCodeUnassign={id => + openModal("remove-range", { + id + }) + } variant={ShippingMethodTypeEnum.PRICE} /> + + handleZipCodeRangeDelete(params.id)} + open={params.action === "remove-range"} + /> ); }; diff --git a/src/shipping/views/PriceRatesUpdate/PriceRatesUpdate.tsx b/src/shipping/views/PriceRatesUpdate/PriceRatesUpdate.tsx index 121669bb1..b83a646ea 100644 --- a/src/shipping/views/PriceRatesUpdate/PriceRatesUpdate.tsx +++ b/src/shipping/views/PriceRatesUpdate/PriceRatesUpdate.tsx @@ -11,32 +11,46 @@ import useNotifier from "@saleor/hooks/useNotifier"; import { sectionNames } from "@saleor/intl"; import { commonMessages } from "@saleor/intl"; import DeleteShippingRateDialog from "@saleor/shipping/components/DeleteShippingRateDialog"; +import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog"; import ShippingZoneRatesPage, { FormData } from "@saleor/shipping/components/ShippingZoneRatesPage"; +import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog"; import { getShippingMethodChannelVariables, getUpdateShippingPriceRateVariables } from "@saleor/shipping/handlers"; -import { useShippingMethodChannelListingUpdate } from "@saleor/shipping/mutations"; +import { + useShippingMethodChannelListingUpdate, + useShippingMethodZipCodeRangeAssign, + useShippingMethodZipCodeRangeUnassign +} from "@saleor/shipping/mutations"; import { useShippingRateDelete, useShippingRateUpdate } from "@saleor/shipping/mutations"; import { useShippingZone } from "@saleor/shipping/queries"; -import { shippingZoneUrl } from "@saleor/shipping/urls"; +import { + shippingPriceRatesEditUrl, + ShippingRateUrlDialog, + ShippingRateUrlQueryParams, + shippingZoneUrl +} from "@saleor/shipping/urls"; import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import React from "react"; import { useIntl } from "react-intl"; export interface PriceRatesUpdateProps { id: string; rateId: string; + params: ShippingRateUrlQueryParams; } export const PriceRatesUpdate: React.FC = ({ id, - rateId + rateId, + params }) => { const navigate = useNavigator(); const notify = useNotifier(); @@ -47,6 +61,11 @@ export const PriceRatesUpdate: React.FC = ({ variables: { id } }); + const [openModal, closeModal] = createDialogActionHandlers< + ShippingRateUrlDialog, + ShippingRateUrlQueryParams + >(navigate, params => shippingPriceRatesEditUrl(id, rateId, params), params); + const rate = data?.shippingZone?.shippingMethods.find( rate => rate.id === rateId ); @@ -57,6 +76,43 @@ export const PriceRatesUpdate: React.FC = ({ updateShippingMethodChannelListingOpts ] = useShippingMethodChannelListingUpdate({}); + const [ + assignZipCodeRange, + assignZipCodeRangeOpts + ] = useShippingMethodZipCodeRangeAssign({ + onCompleted: data => { + if (data.shippingMethodZipCodeRulesCreate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + } else { + notify({ + status: "error", + text: intl.formatMessage({ + defaultMessage: "Cannot add specified zip codes range.", + description: "zip code range add error text" + }) + }); + } + } + }); + const [ + unassignZipCodeRange, + unassignZipCodeRangeOpts + ] = useShippingMethodZipCodeRangeUnassign({ + onCompleted: data => { + if (data.shippingMethodZipCodeRulesDelete.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + } + } + }); + const shippingChannels = createShippingChannelsFromRate( rate?.channelListings ); @@ -75,8 +131,6 @@ export const PriceRatesUpdate: React.FC = ({ toggleAllChannels } = useChannels(shippingChannels); - const [openModal, setOpenModal] = React.useState(false); - const [updateShippingRate, updateShippingRateOpts] = useShippingRateUpdate( {} ); @@ -96,7 +150,6 @@ export const PriceRatesUpdate: React.FC = ({ } }); - const handleDelete = () => setOpenModal(true); const handleSubmit = async (formData: FormData) => { const response = await updateShippingRate({ variables: getUpdateShippingPriceRateVariables(formData, id, rateId) @@ -139,7 +192,7 @@ export const PriceRatesUpdate: React.FC = ({ )} setOpenModal(false)} + onClose={closeModal} handleConfirm={() => deleteShippingRate({ variables: { @@ -147,7 +200,7 @@ export const PriceRatesUpdate: React.FC = ({ } }) } - open={openModal} + open={params.action === "remove"} name={rate?.name} /> = ({ } hasChannelChanged={shippingChannels?.length !== currentChannels?.length} saveButtonBarState={updateShippingRateOpts.status} - onDelete={handleDelete} + onDelete={() => openModal("remove")} onSubmit={handleSubmit} onBack={handleBack} rate={rate} @@ -172,6 +225,44 @@ export const PriceRatesUpdate: React.FC = ({ openChannelsModal={handleChannelsModalOpen} onChannelsChange={setCurrentChannels} variant={ShippingMethodTypeEnum.PRICE} + onZipCodeAssign={() => openModal("add-range")} + onZipCodeUnassign={id => + openModal("remove-range", { + id + }) + } + /> + + assignZipCodeRange({ + variables: { + id: rateId, + input: { + zipCodeRules: [ + { + end: data.max || null, + start: data.min + } + ] + } + } + }) + } + open={params.action === "add-range"} + /> + + unassignZipCodeRange({ + variables: { + id: params.id + } + }) + } + open={params.action === "remove-range"} /> ); diff --git a/src/shipping/views/WeightRatesCreate/WeightRatesCreate.tsx b/src/shipping/views/WeightRatesCreate/WeightRatesCreate.tsx index b8b43880c..16bbd992d 100644 --- a/src/shipping/views/WeightRatesCreate/WeightRatesCreate.tsx +++ b/src/shipping/views/WeightRatesCreate/WeightRatesCreate.tsx @@ -5,57 +5,49 @@ import { } from "@saleor/channels/utils"; import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog"; import { WindowTitle } from "@saleor/components/WindowTitle"; +import { ShippingMethodFragment_zipCodeRules } from "@saleor/fragments/types/ShippingMethodFragment"; import useChannels from "@saleor/hooks/useChannels"; import useNavigator from "@saleor/hooks/useNavigator"; -import useNotifier from "@saleor/hooks/useNotifier"; -import { commonMessages, sectionNames } from "@saleor/intl"; -import { FormData } from "@saleor/shipping/components/ShippingZoneRatesPage"; +import { sectionNames } from "@saleor/intl"; +import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog"; import ShippingZoneRatesPage from "@saleor/shipping/components/ShippingZoneRatesPage"; +import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog"; +import { useShippingRateCreator } from "@saleor/shipping/handlers"; import { - getCreateShippingWeightRateVariables, - getShippingMethodChannelVariables -} from "@saleor/shipping/handlers"; -import { useShippingMethodChannelListingUpdate } from "@saleor/shipping/mutations"; -import { useShippingRateCreate } from "@saleor/shipping/mutations"; -import { - shippingWeightRatesEditUrl, + ShippingRateCreateUrlDialog, + ShippingRateCreateUrlQueryParams, + shippingWeightRatesUrl, shippingZoneUrl } from "@saleor/shipping/urls"; +import { MinMax } from "@saleor/types"; import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { remove } from "@saleor/utils/lists"; import React from "react"; import { useIntl } from "react-intl"; export interface WeightRatesCreateProps { id: string; + params: ShippingRateCreateUrlQueryParams; } -export const WeightRatesCreate: React.FC = ({ id }) => { +export const WeightRatesCreate: React.FC = ({ + id, + params +}) => { const navigate = useNavigator(); - const notify = useNotifier(); const intl = useIntl(); + const [zipCodes, setZipCodes] = React.useState< + ShippingMethodFragment_zipCodeRules[] + >([]); + const { data: channelsData, loading: channelsLoading } = useChannelsList({}); - const [ - updateShippingMethodChannelListing, - updateShippingMethodChannelListingOpts - ] = useShippingMethodChannelListingUpdate({ - onCompleted: data => { - const errors = data.shippingMethodChannelListingUpdate.errors; - if (errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - navigate( - shippingWeightRatesEditUrl( - id, - data.shippingMethodChannelListingUpdate.shippingMethod.id - ) - ); - } - } - }); + const [openModal, closeModal] = createDialogActionHandlers< + ShippingRateCreateUrlDialog, + ShippingRateCreateUrlQueryParams + >(navigate, params => shippingWeightRatesUrl(id, params), params); const shippingChannels = createShippingChannels(channelsData?.channels); const allChannels = createSortedShippingChannels(channelsData?.channels); @@ -73,28 +65,38 @@ export const WeightRatesCreate: React.FC = ({ id }) => { toggleAllChannels } = useChannels(shippingChannels); - const [createShippingRate, createShippingRateOpts] = useShippingRateCreate( - {} - ); - - const handleSubmit = async (data: FormData) => { - const response = await createShippingRate({ - variables: getCreateShippingWeightRateVariables(data, id) - }); - const errors = response.data.shippingPriceCreate.errors; - if (errors.length === 0) { - updateShippingMethodChannelListing({ - variables: getShippingMethodChannelVariables( - response.data.shippingPriceCreate.shippingMethod.id, - data.noLimits, - data.channelListings - ) - }); - } - }; + const { + channelErrors, + createShippingRate, + errors, + status + } = useShippingRateCreator(id, ShippingMethodTypeEnum.WEIGHT, zipCodes); const handleBack = () => navigate(shippingZoneUrl(id)); + const handleZipCodeRangeAdd = (data: MinMax) => { + setZipCodes(zipCodes => [ + ...zipCodes, + { + __typename: "ShippingMethodZipCodeRule", + end: data.max, + id: zipCodes.length.toString(), + start: data.min + } + ]); + closeModal(); + }; + const handleZipCodeRangeDelete = (id: string) => { + setZipCodes(zipCodes => + remove( + zipCodes.find(zipCode => zipCode.id === id), + zipCodes, + (a, b) => a.id === b.id + ) + ); + closeModal(); + }; + return ( <> @@ -118,23 +120,36 @@ export const WeightRatesCreate: React.FC = ({ id }) => { openModal("add-range")} + onZipCodeUnassign={id => + openModal("remove-range", { + id + }) + } variant={ShippingMethodTypeEnum.WEIGHT} /> + + handleZipCodeRangeDelete(params.id)} + open={params.action === "remove-range"} + /> ); }; diff --git a/src/shipping/views/WeightRatesUpdate/WeightRatesUpdate.tsx b/src/shipping/views/WeightRatesUpdate/WeightRatesUpdate.tsx index 714079cd5..2e6cbdb80 100644 --- a/src/shipping/views/WeightRatesUpdate/WeightRatesUpdate.tsx +++ b/src/shipping/views/WeightRatesUpdate/WeightRatesUpdate.tsx @@ -11,32 +11,44 @@ import useNotifier from "@saleor/hooks/useNotifier"; import { sectionNames } from "@saleor/intl"; import { commonMessages } from "@saleor/intl"; import DeleteShippingRateDialog from "@saleor/shipping/components/DeleteShippingRateDialog"; +import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog"; import ShippingZoneRatesPage, { FormData } from "@saleor/shipping/components/ShippingZoneRatesPage"; +import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog"; import { getShippingMethodChannelVariables, getUpdateShippingWeightRateVariables } from "@saleor/shipping/handlers"; import { + useShippingMethodZipCodeRangeAssign, + useShippingMethodZipCodeRangeUnassign, useShippingRateDelete, useShippingRateUpdate } from "@saleor/shipping/mutations"; import { useShippingMethodChannelListingUpdate } from "@saleor/shipping/mutations"; import { useShippingZone } from "@saleor/shipping/queries"; -import { shippingZoneUrl } from "@saleor/shipping/urls"; +import { + ShippingRateUrlDialog, + ShippingRateUrlQueryParams, + shippingWeightRatesEditUrl, + shippingZoneUrl +} from "@saleor/shipping/urls"; import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import React from "react"; import { useIntl } from "react-intl"; export interface WeightRatesUpdateProps { id: string; rateId: string; + params: ShippingRateUrlQueryParams; } export const WeightRatesUpdate: React.FC = ({ id, - rateId + rateId, + params }) => { const navigate = useNavigator(); const notify = useNotifier(); @@ -47,6 +59,11 @@ export const WeightRatesUpdate: React.FC = ({ variables: { id } }); + const [openModal, closeModal] = createDialogActionHandlers< + ShippingRateUrlDialog, + ShippingRateUrlQueryParams + >(navigate, params => shippingWeightRatesEditUrl(id, rateId, params), params); + const rate = data?.shippingZone?.shippingMethods.find( rate => rate.id === rateId ); @@ -74,8 +91,6 @@ export const WeightRatesUpdate: React.FC = ({ toggleAllChannels } = useChannels(shippingChannels); - const [openModal, setOpenModal] = React.useState(false); - const [updateShippingRate, updateShippingRateOpts] = useShippingRateUpdate( {} ); @@ -96,7 +111,43 @@ export const WeightRatesUpdate: React.FC = ({ } }); - const handleDelete = () => setOpenModal(true); + const [ + assignZipCodeRange, + assignZipCodeRangeOpts + ] = useShippingMethodZipCodeRangeAssign({ + onCompleted: data => { + if (data.shippingMethodZipCodeRulesCreate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + } else { + notify({ + status: "error", + text: intl.formatMessage({ + defaultMessage: "Cannot add specified zip codes range.", + description: "zip code range add error text" + }) + }); + } + } + }); + const [ + unassignZipCodeRange, + unassignZipCodeRangeOpts + ] = useShippingMethodZipCodeRangeUnassign({ + onCompleted: data => { + if (data.shippingMethodZipCodeRulesDelete.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + } + } + }); + const handleSubmit = async (data: FormData) => { const response = await updateShippingRate({ variables: getUpdateShippingWeightRateVariables(data, id, rateId) @@ -139,7 +190,7 @@ export const WeightRatesUpdate: React.FC = ({ )} setOpenModal(false)} + onClose={closeModal} handleConfirm={() => deleteShippingRate({ variables: { @@ -147,7 +198,7 @@ export const WeightRatesUpdate: React.FC = ({ } }) } - open={openModal} + open={params.action === "remove"} name={rate?.name} /> = ({ } hasChannelChanged={shippingChannels?.length !== currentChannels?.length} saveButtonBarState={updateShippingRateOpts.status} - onDelete={handleDelete} + onDelete={() => openModal("remove")} onSubmit={handleSubmit} onBack={handleBack} rate={rate} @@ -172,6 +223,44 @@ export const WeightRatesUpdate: React.FC = ({ openChannelsModal={handleChannelsModalOpen} onChannelsChange={setCurrentChannels} variant={ShippingMethodTypeEnum.WEIGHT} + onZipCodeAssign={() => openModal("add-range")} + onZipCodeUnassign={id => + openModal("remove-range", { + id + }) + } + /> + + assignZipCodeRange({ + variables: { + id: rateId, + input: { + zipCodeRules: [ + { + end: data.max || null, + start: data.min + } + ] + } + } + }) + } + open={params.action === "add-range"} + /> + + unassignZipCodeRange({ + variables: { + id: params.id + } + }) + } + open={params.action === "remove-range"} /> ); diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index b2277ea3b..136227fc3 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -12446,6 +12446,12 @@ exports[`Storyshots Product types / ProductTypeDeleteDialog default 1`] = ` exports[`Storyshots Products / ProductVariantImageSelectDialog default 1`] = `null`; +exports[`Storyshots Shipping / Add zip code range default 1`] = ` +
+`; + exports[`Storyshots Shipping / Assign countries default 1`] = `
- Order value + Order Value
- Order value + Order Value
- Order weight + Order Weight
- Order weight + Order Weight
- Order weight + Order Weight
- Order weight + Order Weight
`; -exports[`Storyshots Shipping / ShippingZoneRates page default price 1`] = ` -
-
-
-
-
- Price Rate Create -
-
-
-
-
-
-
-
-
- - General Information - -
-
-
-
-
-
- -
- - -
-
-
-
-
-
-
- - Order value - -
-
-
-
-
-
- -
- Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency -
-
-
- - - - - - - - - - - - - - - -
- - Channel name - - - - Min. value - - - - Max. value - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
- - Pricing - -
-
-
-
-
-
- Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency -
-
- - - - - - - - - - - - - -
- - Channel name - - - - Price - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
-
-
- - Availability - -
-
-
-
-
-
- Available at 1 out of 3 channels -
-
-
-
-
- channel -
-
-
-
-
-
-
-
-
- -
-`; - -exports[`Storyshots Shipping / ShippingZoneRates page default weight 1`] = ` -
-
-
-
-
- Weight Rate Create -
-
-
-
-
-
-
-
-
- - General Information - -
-
-
-
-
-
- -
- - -
-
-
-
-
-
-
- - Order weight - -
-
-
-
-
- -
-
- -
- - -
-
-
- -
- - -
-
-
-
-
-
-
-
- - Pricing - -
-
-
-
-
-
- Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency -
-
- - - - - - - - - - - - - -
- - Channel name - - - - Price - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
-
-
- - Availability - -
-
-
-
-
-
- Available at 1 out of 3 channels -
-
-
-
-
- channel -
-
-
-
-
-
-
-
-
- -
-`; - -exports[`Storyshots Shipping / ShippingZoneRates page loading 1`] = ` -
-
-
-
-
- Price Rate Create -
-
-
-
-
-
-
-
-
- - General Information - -
-
-
-
-
-
- -
- - -
-
-
-
-
-
-
- - Order value - -
-
-
-
-
-
- -
- Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency -
-
-
- - - - - - - - - - - - - - - -
- - Channel name - - - - Min. value - - - - Max. value - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
- - Pricing - -
-
-
-
-
-
- Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency -
-
- - - - - - - - - - - - - -
- - Channel name - - - - Price - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
-
-
- - Availability - -
-
-
-
-
-
- Available at 1 out of 3 channels -
-
-
-
-
- channel -
-
-
-
-
-
-
-
-
- -
-`; - -exports[`Storyshots Shipping / ShippingZoneRates page update price 1`] = ` -
-
-
-
-
- UPS -
-
-
-
-
-
-
-
-
- - General Information - -
-
-
-
-
-
- -
- - -
-
-
-
-
-
-
- - Order value - -
-
-
-
-
-
- -
- Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency -
-
-
- - - - - - - - - - - - - - - - - - - - -
- - Channel name - - - - Min. value - - - - Max. value - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
- -
- -
-
- USD -
-
- -
-
-
-
- test -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
- - Pricing - -
-
-
-
-
-
- Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency -
-
- - - - - - - - - - - - - - - - - -
- - Channel name - - - - Price - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
- test -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
-
-
- - Availability - -
-
-
-
-
-
- Available at 2 out of 3 channels -
-
-
-
-
- channel -
-
-
-
-
-
-
- test -
-
-
-
-
-
-
-
-
- -
-`; - -exports[`Storyshots Shipping / ShippingZoneRates page update weight 1`] = ` -
-
-
-
-
- DB Schenker -
-
-
-
-
-
-
-
-
- - General Information - -
-
-
-
-
-
- -
- - -
-
-
-
-
-
-
- - Order weight - -
-
-
-
-
- -
-
- -
- - -
-
-
- -
- - -
-
-
-
-
-
-
-
- - Pricing - -
-
-
-
-
-
- Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency -
-
- - - - - - - - - - - - - - - - - -
- - Channel name - - - - Price - -
-
- channel -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
- test -
-
-
- -
- -
-
- USD -
-
- -
-
-
-
-
-
-
-
-
-
-
- - Availability - -
-
-
-
-
-
- Available at 2 out of 3 channels -
-
-
-
-
- channel -
-
-
-
-
-
-
- test -
-
-
-
-
-
-
-
-
- -
-`; - exports[`Storyshots SiteSettings / Add key dialog default 1`] = `
`; +exports[`Storyshots Views / Shipping / Shipping rate create price rate 1`] = ` +
+
+
+
+
+ Price Rate Create +
+
+
+
+
+
+
+
+
+ + General Information + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Order Value + +
+
+
+
+
+
+ +
+ Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+
+ + + + + + + + + + + + + + + +
+ + Channel name + + + + Min. value + + + + Max. value + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + Pricing + +
+
+
+
+
+
+ Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+ + + + + + + + + + + + + +
+ + Channel name + + + + Price + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + ZIP-Codes + +
+ +
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ 2 ZIP-Code ranges +
+
+ +
+ 51-220 - 51-200 + + +
+ 44-205 - 31-101 + + +
+
+
+
+
+
+
+ + Availability + +
+
+
+
+
+
+ Available at 1 out of 3 channels +
+
+
+
+
+ channel +
+
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Shipping / Shipping rate create weight rate 1`] = ` +
+
+
+
+
+ Weight Rate Create +
+
+
+
+
+
+
+
+
+ + General Information + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Order Weight + +
+
+
+
+
+ +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ + Pricing + +
+
+
+
+
+
+ Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+ + + + + + + + + + + + + +
+ + Channel name + + + + Price + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + ZIP-Codes + +
+ +
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ 2 ZIP-Code ranges +
+
+ +
+ 51-220 - 51-200 + + +
+ 44-205 - 31-101 + + +
+
+
+
+
+
+
+ + Availability + +
+
+
+
+
+
+ Available at 1 out of 3 channels +
+
+
+
+
+ channel +
+
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Shipping / Shipping rate loading 1`] = ` +
+
+
+
+
+ Price Rate Create +
+
+
+
+
+
+
+
+
+ + General Information + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Order Value + +
+
+
+
+
+
+ +
+ Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+
+ + + + + + + + + + + + + + + +
+ + Channel name + + + + Min. value + + + + Max. value + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + Pricing + +
+
+
+
+
+
+ Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+ + + + + + + + + + + + + +
+ + Channel name + + + + Price + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + ZIP-Codes + +
+ +
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + +
+ + ‌ + + + +
+
+
+
+
+
+
+ + Availability + +
+
+
+
+
+
+ Available at 1 out of 3 channels +
+
+
+
+
+ channel +
+
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Shipping / Shipping rate no zip codes 1`] = ` +
+
+
+
+
+ Price Rate Create +
+
+
+
+
+
+
+
+
+ + General Information + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Order Value + +
+
+
+
+
+
+ +
+ Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+
+ + + + + + + + + + + + + + + +
+ + Channel name + + + + Min. value + + + + Max. value + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + Pricing + +
+
+
+
+
+
+ Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+ + + + + + + + + + + + + +
+ + Channel name + + + + Price + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + ZIP-Codes + +
+ +
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + +
+
+ This shipping rate has no ZIP-codes assigned +
+
+
+
+
+
+
+
+ + Availability + +
+
+
+
+
+
+ Available at 1 out of 3 channels +
+
+
+
+
+ channel +
+
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Shipping / Shipping rate update price rate 1`] = ` +
+
+
+
+
+ UPS +
+
+
+
+
+
+
+
+
+ + General Information + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Order Value + +
+
+
+
+
+
+ +
+ Channels that don’t have assigned discounts will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ + Channel name + + + + Min. value + + + + Max. value + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ test +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + Pricing + +
+
+
+
+
+
+ Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+ + + + + + + + + + + + + + + + + +
+ + Channel name + + + + Price + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ test +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + ZIP-Codes + +
+ +
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 3 ZIP-Code ranges +
+
+ +
+ 51-210 - 51-220 + + +
+ 51-235 - 51-240 + + +
+ 51-274 + + +
+
+
+
+
+
+
+ + Availability + +
+
+
+
+
+
+ Available at 2 out of 3 channels +
+
+
+
+
+ channel +
+
+
+
+
+
+
+ test +
+
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Shipping / Shipping rate update weight rate 1`] = ` +
+
+
+
+
+ DB Schenker +
+
+
+
+
+
+
+
+
+ + General Information + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Order Weight + +
+
+
+
+
+ +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ + Pricing + +
+
+
+
+
+
+ Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency +
+
+ + + + + + + + + + + + + + + + + +
+ + Channel name + + + + Price + +
+
+ channel +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+ test +
+
+
+ +
+ +
+
+ USD +
+
+ +
+
+
+
+
+
+
+
+
+ + ZIP-Codes + +
+ +
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 3 ZIP-Code ranges +
+
+ +
+ 51-210 - 51-220 + + +
+ 51-235 - 51-240 + + +
+ 51-274 + + +
+
+
+
+
+
+
+ + Availability + +
+
+
+
+
+
+ Available at 2 out of 3 channels +
+
+
+
+
+ channel +
+
+
+
+
+
+
+ test +
+
+
+
+
+
+
+
+
+ +
+`; + exports[`Storyshots Views / Shipping / Shipping zone details default 1`] = `