Add zip code exclusion (#877)

* Clean up stories

* Add missing props

* Add zip codes section (#861)

* Add zip code listing

* Add list wrapping

* Update snapshots

* Set up API data

* Fix lgtm warning

* Update snapshots

* Run Actions on all PR

* Checks on PR

* Test envs on PR

* Cleanup action on PR

* Update messages

Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>

* Add zip code range dialog

* Fix path management

* Use query params to handle modal actions

* Allow zip codes to be assigned to shipping method

* Make params optional

* Fix types

* Add zip code deletion (#871)

* Add zip code range dialog

* Fix path management

* Use query params to handle modal actions

* Allow zip codes to be assigned to shipping method

* Make params optional

* Fix types

* Clean up urls

* Add zip code range delete action

* Update snapshots and messages

* Update testing and changelog

* Update schema

* Simplify code

* Refresh zip code list after assigning them

* Update view after zip code deletion

* Update types and snapshots

* Update snapshots

* Fix error message, checkbox default value (#880)

* Fix error message, checkbox default value

* Update snapshots

* Use price instead of weight variant

* Update schema and types

* Hide exclude/include zip codes section

* Update stories

Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
Co-authored-by: Tomasz Szymański <lime129@gmail.com>
This commit is contained in:
Dominik Żegleń 2020-12-01 16:42:25 +01:00 committed by GitHub
parent eb351b396a
commit e55805a79d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 6588 additions and 2979 deletions

View file

@ -4,7 +4,6 @@ name: TEST-ENV-CLEANUP
on:
pull_request:
types: [closed]
branches: ["*"]
jobs:
cleanup:

View file

@ -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:

View file

@ -1,8 +1,6 @@
name: QA
on:
pull_request:
branches: ["*"]
on: [pull_request]
jobs:
check-lock:

View file

@ -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

View file

@ -5292,9 +5292,9 @@
"context": "channels discount info",
"string": "Channels that dont have assigned discounts will use their parent channel to define the price. Price will be converted to channels 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"
},

View file

@ -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!]!

View file

@ -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<RadioGroupFieldProps> = props => {
const {
alignTop,
className,
disabled,
error,
@ -107,7 +114,14 @@ export const RadioGroupField: React.FC<RadioGroupFieldProps> = props => {
[classes.radioLabel]: variant !== "inline",
[classes.radioLabelInline]: variant === "inline"
})}
control={<Radio color="primary" />}
control={
<Radio
className={classNames({
[classes.alignTop]: alignTop
})}
color="primary"
/>
}
label={choice.label}
key={choice.value}
/>

View file

@ -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

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -264,15 +264,18 @@ export function getMutationState(
interface SaleorMutationResult {
errors?: UserError[];
}
export function getMutationErrors<
TData extends Record<string, SaleorMutationResult>
>(data: TData): UserError[] {
return Object.values(data).reduce(
(acc: UserError[], mut) => [...acc, ...maybe(() => mut.errors, [])],
[]
);
}
export function getMutationStatus<
TData extends Record<string, SaleorMutationResult | any>
>(opts: MutationResult<TData>): 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);
}

View file

@ -56,7 +56,7 @@ export const OrderValue: React.FC<OrderValueProps> = ({
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Order value",
defaultMessage: "Order Value",
description: "card title"
})}
/>

View file

@ -40,7 +40,7 @@ export const OrderWeight: React.FC<OrderWeightProps> = ({
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Order weight",
defaultMessage: "Order Weight",
description: "card title"
})}
/>

View file

@ -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<ShippingRateZipCodeRangeRemoveDialogProps> = ({
confirmButtonState,
open,
onClose,
onConfirm
}) => {
const intl = useIntl();
return (
<ActionDialog
confirmButtonState={confirmButtonState}
open={open}
onClose={onClose}
onConfirm={onConfirm}
title={intl.formatMessage({
defaultMessage: "Remove ZIP-codes from Shipping Rate",
description: "header"
})}
variant="delete"
>
<DialogContentText>
<FormattedMessage defaultMessage="Are you sure you want to remove this ZIP-code rule?" />
</DialogContentText>
</ActionDialog>
);
};
ShippingRateZipCodeRangeRemoveDialog.displayName =
"ShippingRateZipCodeRangeRemoveDialog";
export default ShippingRateZipCodeRangeRemoveDialog;

View file

@ -0,0 +1,2 @@
export * from "./ShippingRateZipCodeRangeRemoveDialog";
export { default } from "./ShippingRateZipCodeRangeRemoveDialog";

View file

@ -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", () => <ShippingZoneRatesPage {...props} />)
.add("create price rate", () => <ShippingZoneRatesPage {...props} />)
.add("create weight rate", () => (
<ShippingZoneRatesPage {...props} variant={ShippingMethodTypeEnum.WEIGHT} />
))
.add("loading", () => (
<ShippingZoneRatesPage
{...props}
disabled={true}
rate={undefined}
saveButtonBarState={"loading"}
/>
))
.add("update price", () => (
.add("update price rate", () => (
<ShippingZoneRatesPage
{...props}
shippingChannels={channels}
rate={shippingZone.shippingMethods[2]}
/>
))
.add("default weight", () => (
<ShippingZoneRatesPage {...props} variant={ShippingMethodTypeEnum.WEIGHT} />
))
.add("update weight", () => (
.add("update weight rate", () => (
<ShippingZoneRatesPage
{...props}
shippingChannels={channels}
rate={shippingZone.shippingMethods[0]}
variant={ShippingMethodTypeEnum.WEIGHT}
/>
))
.add("no zip codes", () => (
<ShippingZoneRatesPage {...props} zipCodes={[]} />
));

View file

@ -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<ShippingZoneRatesPageProps> = ({
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<ShippingZoneRatesPageProps> = ({
type: rate?.type || null
};
const rateExists = rate !== null;
return (
<Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit, triggerChange }) => {
@ -141,6 +158,14 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
errors={channelErrors}
/>
<CardSpacer />
<ShippingZoneZipCodes
data={data}
disabled={disabled}
onZipCodeDelete={onZipCodeUnassign}
onZipCodeInclusionChange={() => undefined}
onZipCodeRangeAdd={onZipCodeAssign}
zipCodes={rateExists ? rate?.zipCodeRules : zipCodes}
/>
</div>
<div>
<ChannelsAvailability

View file

@ -0,0 +1,16 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import ShippingZoneZipCodeRangeDialog from "./ShippingZoneZipCodeRangeDialog";
storiesOf("Shipping / Add zip code range", module)
.addDecorator(Decorator)
.add("default", () => (
<ShippingZoneZipCodeRangeDialog
confirmButtonState="default"
open={true}
onClose={() => undefined}
onSubmit={() => undefined}
/>
));

View file

@ -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<ShippingZoneZipCodeRangeDialogProps> = ({
confirmButtonState,
open,
onClose,
onSubmit
}) => {
const classes = useStyles({});
const intl = useIntl();
const initial: MinMax = {
max: "",
min: ""
};
return (
<Dialog open={open}>
<DialogTitle>
<FormattedMessage
defaultMessage="Add ZIP-Codes"
description="dialog header"
/>
</DialogTitle>
<Form initial={initial} onSubmit={onSubmit}>
{({ change, data, hasChanged }) => (
<>
<DialogContent>
<Typography className={classes.info}>
<FormattedMessage defaultMessage="Please provide range of ZIP codes you want to add to the include/exclude list." />
</Typography>
<Grid variant="uniform">
<TextField
label={intl.formatMessage({
defaultMessage: "Zip Codes (Start)",
description: "range input label"
})}
name="min"
value={data.min}
onChange={change}
/>
<TextField
label={intl.formatMessage({
defaultMessage: "Zip Codes (End)",
description: "range input label"
})}
name="max"
helperText={intl.formatMessage(commonMessages.optionalField)}
value={data.max}
onChange={change}
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>
<FormattedMessage {...buttonMessages.back} />
</Button>
<ConfirmButton
disabled={!hasChanged || !data.min}
transitionState={confirmButtonState}
type="submit"
>
<FormattedMessage
defaultMessage="Add"
description="add zip code range, button"
/>
</ConfirmButton>
</DialogActions>
</>
)}
</Form>
</Dialog>
);
};
ShippingZoneZipCodeRangeDialog.displayName = "ShippingZoneZipCodeRangeDialog";
export default ShippingZoneZipCodeRangeDialog;

View file

@ -0,0 +1,2 @@
export * from "./ShippingZoneZipCodeRangeDialog";
export { default } from "./ShippingZoneZipCodeRangeDialog";

View file

@ -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<ShippingZoneZipCodesProps> = ({
data,
disabled,
initialExpanded,
zipCodes,
onZipCodeDelete,
onZipCodeInclusionChange,
onZipCodeRangeAdd
}) => {
const [expanded, setExpanded] = React.useState(initialExpanded);
const intl = useIntl();
const classes = useStyles({});
return (
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "ZIP-Codes",
description: "postal codes, header"
})}
toolbar={
<Button
color="primary"
onClick={onZipCodeRangeAdd}
data-test="add-zip-code-range"
>
<FormattedMessage
defaultMessage="Add ZIP-Code range"
description="button"
/>
</Button>
}
/>
<CardContent className={classNames(classes.radioContainer, classes.hide)}>
<RadioGroupField
alignTop
choices={[
{
disabled: true,
label: (
<div className={classes.option}>
<Typography color="textSecondary" variant="body1">
<FormattedMessage
defaultMessage="Exclude ZIP-codes"
description="action"
/>
</Typography>
<Typography color="textSecondary" variant="caption">
<FormattedMessage defaultMessage="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" />
</Typography>
</div>
),
value: ZipCodeInclusion.Exclude
},
{
label: (
<div className={classes.option}>
<Typography variant="body1">
<FormattedMessage
defaultMessage="Include ZIP-codes"
description="action"
/>
</Typography>
<Typography color="textSecondary" variant="caption">
<FormattedMessage defaultMessage="Only added ZIP-codes will be able to use this shipping rate" />
</Typography>
</div>
),
value: ZipCodeInclusion.Include
}
]}
name="includeZipCodes"
value={data.includeZipCodes}
onChange={onZipCodeInclusionChange}
/>
</CardContent>
<ResponsiveTable>
<colgroup>
<col />
<col className={classes.colAction} />
</colgroup>
{zipCodes === undefined ||
(zipCodes.length > 0 && (
<TableHead>
<TableRow>
<TableCell>
{zipCodes === undefined ? (
<Skeleton className={classes.skeleton} />
) : (
<Typography variant="caption">
<FormattedMessage
defaultMessage="{number} ZIP-Code ranges"
description="number of zip code ranges"
values={{
number: zipCodes.length
}}
/>
</Typography>
)}
</TableCell>
<TableCell>
<IconButton onClick={() => setExpanded(!expanded)}>
<ArrowDropdown
className={classNames(classes.arrow, {
[classes.arrowRotate]: expanded
})}
/>
</IconButton>
</TableCell>
</TableRow>
</TableHead>
))}
{expanded && (
<TableBody>
{renderCollection(
zipCodes,
zipCodeRange => (
<TableRow key={zipCodeRange?.id}>
<TableCell>
{zipCodeRange?.start ? (
zipCodeRange?.end ? (
`${zipCodeRange.start} - ${zipCodeRange.end}`
) : (
zipCodeRange.start
)
) : (
<Skeleton />
)}
</TableCell>
<TableCell>
<IconButton
disabled={disabled}
color="primary"
onClick={() => onZipCodeDelete(zipCodeRange.id)}
data-test="delete-zip-code"
data-test-id={zipCodeRange?.id}
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
),
() => (
<TableRow>
<TableCell colSpan={2}>
<Typography color="textSecondary">
<FormattedMessage defaultMessage="This shipping rate has no ZIP-codes assigned" />
</Typography>
</TableCell>
</TableRow>
)
)}
</TableBody>
)}
</ResponsiveTable>
</Card>
);
};
ShippingZoneZipCodes.displayName = "ShippingZoneZipCodes";
ShippingZoneZipCodes.defaultProps = {
initialExpanded: true
};
export default ShippingZoneZipCodes;

View file

@ -0,0 +1,2 @@
export * from "./ShippingZoneZipCodes";
export { default } from "./ShippingZoneZipCodes";

View file

@ -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: [

View file

@ -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])
};
}

View file

@ -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<RouteComponentProps<
const PriceRatesCreate: React.FC<RouteComponentProps<{ id: string }>> = ({
match
}) => <PriceRatesCreateComponent id={decodeURIComponent(match.params.id)} />;
}) => {
const qs = parseQs(location.search.substr(1));
const params: ShippingRateCreateUrlQueryParams = qs;
return (
<PriceRatesCreateComponent
id={decodeURIComponent(match.params.id)}
params={params}
/>
);
};
const WeightRatesCreate: React.FC<RouteComponentProps<{ id: string }>> = ({
match
}) => <WeightRatesCreateComponent id={decodeURIComponent(match.params.id)} />;
}) => {
const qs = parseQs(location.search.substr(1));
const params: ShippingRateCreateUrlQueryParams = qs;
return (
<WeightRatesCreateComponent
id={decodeURIComponent(match.params.id)}
params={params}
/>
);
};
const WeightRatesUpdate: React.FC<RouteComponentProps<{
id: string;
rateId: string;
}>> = ({ match }) => (
<WeightRatesUpdateComponent
id={decodeURIComponent(match.params.id)}
rateId={decodeURIComponent(match.params.rateId)}
/>
);
}>> = ({ match }) => {
const qs = parseQs(location.search.substr(1));
const params: ShippingRateUrlQueryParams = qs;
return (
<WeightRatesUpdateComponent
id={decodeURIComponent(match.params.id)}
rateId={decodeURIComponent(match.params.rateId)}
params={params}
/>
);
};
const PriceRatesUpdate: React.FC<RouteComponentProps<{
id: string;
rateId: string;
}>> = ({ match }) => (
<PriceRatesUpdateComponent
id={decodeURIComponent(match.params.id)}
rateId={decodeURIComponent(match.params.rateId)}
/>
);
}>> = ({ match }) => {
const qs = parseQs(location.search.substr(1));
const params: ShippingRateUrlQueryParams = qs;
return (
<PriceRatesUpdateComponent
id={decodeURIComponent(match.params.id)}
rateId={decodeURIComponent(match.params.rateId)}
params={params}
/>
);
};
export const ShippingRouter: React.FC = () => {
const intl = useIntl();
@ -97,19 +131,19 @@ export const ShippingRouter: React.FC = () => {
component={ShippingZoneDetails}
/>
<Route
path={shippingPriceRatesUrl(":id")}
path={shippingPriceRatesPath(":id")}
component={PriceRatesCreate}
/>
<Route
path={shippingWeightRatesUrl(":id")}
path={shippingWeightRatesPath(":id")}
component={WeightRatesCreate}
/>
<Route
path={shippingWeightRatesEditUrl(":id", ":rateId")}
path={shippingWeightRatesEditPath(":id", ":rateId")}
component={WeightRatesUpdate}
/>
<Route
path={shippingPriceRatesEditUrl(":id", ":rateId")}
path={shippingPriceRatesEditPath(":id", ":rateId")}
component={PriceRatesUpdate}
/>
</Switch>

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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<ShippingRateUrlDialog> &
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 + "?";

View file

@ -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<PriceRatesCreateProps> = ({ id }) => {
export const PriceRatesCreate: React.FC<PriceRatesCreateProps> = ({
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<PriceRatesCreateProps> = ({ 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 (
<>
<WindowTitle title={intl.formatMessage(sectionNames.shipping)} />
@ -114,23 +117,36 @@ export const PriceRatesCreate: React.FC<PriceRatesCreateProps> = ({ id }) => {
<ShippingZoneRatesPage
allChannelsCount={allChannels?.length}
shippingChannels={currentChannels}
disabled={
channelsLoading ||
createShippingRateOpts?.status === "loading" ||
updateShippingMethodChannelListingOpts?.status === "loading"
}
saveButtonBarState={createShippingRateOpts?.status}
onSubmit={handleSubmit}
disabled={channelsLoading || status === "loading"}
saveButtonBarState={status}
onSubmit={createShippingRate}
onBack={handleBack}
errors={createShippingRateOpts.data?.shippingPriceCreate.errors || []}
channelErrors={
updateShippingMethodChannelListingOpts?.data
?.shippingMethodChannelListingUpdate?.errors || []
}
errors={errors}
channelErrors={channelErrors}
rate={null}
zipCodes={zipCodes}
openChannelsModal={handleChannelsModalOpen}
onChannelsChange={setCurrentChannels}
onZipCodeAssign={() => openModal("add-range")}
onZipCodeUnassign={id =>
openModal("remove-range", {
id
})
}
variant={ShippingMethodTypeEnum.PRICE}
/>
<ShippingZoneZipCodeRangeDialog
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleZipCodeRangeAdd}
open={params.action === "add-range"}
/>
<ShippingRateZipCodeRangeRemoveDialog
confirmButtonState="default"
onClose={closeModal}
onConfirm={() => handleZipCodeRangeDelete(params.id)}
open={params.action === "remove-range"}
/>
</>
);
};

View file

@ -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<PriceRatesUpdateProps> = ({
id,
rateId
rateId,
params
}) => {
const navigate = useNavigator();
const notify = useNotifier();
@ -47,6 +61,11 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
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<PriceRatesUpdateProps> = ({
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<PriceRatesUpdateProps> = ({
toggleAllChannels
} = useChannels(shippingChannels);
const [openModal, setOpenModal] = React.useState(false);
const [updateShippingRate, updateShippingRateOpts] = useShippingRateUpdate(
{}
);
@ -96,7 +150,6 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
}
});
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<PriceRatesUpdateProps> = ({
)}
<DeleteShippingRateDialog
confirmButtonState={deleteShippingRateOpts.status}
onClose={() => setOpenModal(false)}
onClose={closeModal}
handleConfirm={() =>
deleteShippingRate({
variables: {
@ -147,7 +200,7 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
}
})
}
open={openModal}
open={params.action === "remove"}
name={rate?.name}
/>
<ShippingZoneRatesPage
@ -160,7 +213,7 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
}
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<PriceRatesUpdateProps> = ({
openChannelsModal={handleChannelsModalOpen}
onChannelsChange={setCurrentChannels}
variant={ShippingMethodTypeEnum.PRICE}
onZipCodeAssign={() => openModal("add-range")}
onZipCodeUnassign={id =>
openModal("remove-range", {
id
})
}
/>
<ShippingZoneZipCodeRangeDialog
confirmButtonState={assignZipCodeRangeOpts.status}
onClose={closeModal}
onSubmit={data =>
assignZipCodeRange({
variables: {
id: rateId,
input: {
zipCodeRules: [
{
end: data.max || null,
start: data.min
}
]
}
}
})
}
open={params.action === "add-range"}
/>
<ShippingRateZipCodeRangeRemoveDialog
confirmButtonState={unassignZipCodeRangeOpts.status}
onClose={closeModal}
onConfirm={() =>
unassignZipCodeRange({
variables: {
id: params.id
}
})
}
open={params.action === "remove-range"}
/>
</>
);

View file

@ -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<WeightRatesCreateProps> = ({ id }) => {
export const WeightRatesCreate: React.FC<WeightRatesCreateProps> = ({
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<WeightRatesCreateProps> = ({ 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 (
<>
<WindowTitle title={intl.formatMessage(sectionNames.shipping)} />
@ -118,23 +120,36 @@ export const WeightRatesCreate: React.FC<WeightRatesCreateProps> = ({ id }) => {
<ShippingZoneRatesPage
allChannelsCount={allChannels?.length}
shippingChannels={currentChannels}
disabled={
channelsLoading ||
createShippingRateOpts?.status === "loading" ||
updateShippingMethodChannelListingOpts?.status === "loading"
}
saveButtonBarState={createShippingRateOpts?.status}
onSubmit={handleSubmit}
disabled={channelsLoading || status === "loading"}
saveButtonBarState={status}
onSubmit={createShippingRate}
onBack={handleBack}
errors={createShippingRateOpts.data?.shippingPriceCreate.errors || []}
channelErrors={
updateShippingMethodChannelListingOpts?.data
?.shippingMethodChannelListingUpdate?.errors || []
}
errors={errors}
channelErrors={channelErrors}
rate={null}
zipCodes={zipCodes}
openChannelsModal={handleChannelsModalOpen}
onChannelsChange={setCurrentChannels}
onZipCodeAssign={() => openModal("add-range")}
onZipCodeUnassign={id =>
openModal("remove-range", {
id
})
}
variant={ShippingMethodTypeEnum.WEIGHT}
/>
<ShippingZoneZipCodeRangeDialog
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleZipCodeRangeAdd}
open={params.action === "add-range"}
/>
<ShippingRateZipCodeRangeRemoveDialog
confirmButtonState="default"
onClose={closeModal}
onConfirm={() => handleZipCodeRangeDelete(params.id)}
open={params.action === "remove-range"}
/>
</>
);
};

View file

@ -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<WeightRatesUpdateProps> = ({
id,
rateId
rateId,
params
}) => {
const navigate = useNavigator();
const notify = useNotifier();
@ -47,6 +59,11 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
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<WeightRatesUpdateProps> = ({
toggleAllChannels
} = useChannels(shippingChannels);
const [openModal, setOpenModal] = React.useState(false);
const [updateShippingRate, updateShippingRateOpts] = useShippingRateUpdate(
{}
);
@ -96,7 +111,43 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
}
});
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<WeightRatesUpdateProps> = ({
)}
<DeleteShippingRateDialog
confirmButtonState={deleteShippingRateOpts.status}
onClose={() => setOpenModal(false)}
onClose={closeModal}
handleConfirm={() =>
deleteShippingRate({
variables: {
@ -147,7 +198,7 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
}
})
}
open={openModal}
open={params.action === "remove"}
name={rate?.name}
/>
<ShippingZoneRatesPage
@ -160,7 +211,7 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
}
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<WeightRatesUpdateProps> = ({
openChannelsModal={handleChannelsModalOpen}
onChannelsChange={setCurrentChannels}
variant={ShippingMethodTypeEnum.WEIGHT}
onZipCodeAssign={() => openModal("add-range")}
onZipCodeUnassign={id =>
openModal("remove-range", {
id
})
}
/>
<ShippingZoneZipCodeRangeDialog
confirmButtonState={assignZipCodeRangeOpts.status}
onClose={closeModal}
onSubmit={data =>
assignZipCodeRange({
variables: {
id: rateId,
input: {
zipCodeRules: [
{
end: data.max || null,
start: data.min
}
]
}
}
})
}
open={params.action === "add-range"}
/>
<ShippingRateZipCodeRangeRemoveDialog
confirmButtonState={unassignZipCodeRangeOpts.status}
onClose={closeModal}
onConfirm={() =>
unassignZipCodeRange({
variables: {
id: params.id
}
})
}
open={params.action === "remove-range"}
/>
</>
);

File diff suppressed because it is too large Load diff

View file

@ -1357,19 +1357,18 @@ export interface OrderLineInput {
}
export interface OrderRefundFulfillmentLineInput {
fulfillmentLineId?: string | null;
fulfillmentLineId: string;
quantity: number;
}
export interface OrderRefundLineInput {
orderLineId?: string | null;
orderLineId: string;
quantity: number;
}
export interface OrderRefundProductsInput {
orderLines?: OrderRefundLineInput[] | null;
fulfillmentLines?: OrderRefundFulfillmentLineInput[] | null;
notifyCustomer?: boolean | null;
amountToRefund?: any | null;
includeShippingCosts?: boolean | null;
}
@ -1698,6 +1697,15 @@ export interface ShippingPriceInput {
shippingZone?: string | null;
}
export interface ShippingZipCodeRulesCreateInput {
zipCodeRules: (ShippingZipCodeRulesCreateInputRange | null)[];
}
export interface ShippingZipCodeRulesCreateInputRange {
start: string;
end?: string | null;
}
export interface ShippingZoneCreateInput {
name?: string | null;
countries?: (string | null)[] | null;