Order confirmation (#840)
* Order confirmed webhook * Add status chip component and update order details page header * refactor * refactor and update types * Remove rebase leftovers * Create order settings page * Add order setting query * Connct order settings view with api * Show order settings update error message * Popup menu routing to order settings * Fix circular import error with order settings form types * Update order settings story * Update order settings messages * wip * Add order confirm mutation and types * Add confirm order feature to order details * Update global types * refactor after review * refactor after review * update types * Revert settings menu removal * Add changelog entry Co-authored-by: Tomasz Szymanski <lime129@gmail.com> Co-authored-by: Magdalena Markusik <magdalena.markusik@mirumee.com> Co-authored-by: Dawid Tarasiuk <tarasiukdawid@gmail.com>
This commit is contained in:
parent
6456cd21d2
commit
07f8f4b0b8
38 changed files with 2040 additions and 32 deletions
|
@ -4,6 +4,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- Add Order Confirmation settings - #840 by @orzechdev and @mmarkusik
|
||||
- Add Page Types - #807 by @orzechdev
|
||||
- Add shipping methods to translation section - #864 by @marekchoinski
|
||||
|
||||
|
|
|
@ -3093,6 +3093,10 @@
|
|||
"context": "button",
|
||||
"string": "Cancel order"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_3086420445": {
|
||||
"context": "save button",
|
||||
"string": "confirm order"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderDraftCancelDialog_dot_1961675716": {
|
||||
"context": "dialog header",
|
||||
"string": "Delete Daft Order"
|
||||
|
@ -3371,6 +3375,10 @@
|
|||
"context": "order history message",
|
||||
"string": "Order was fully paid"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_276183305": {
|
||||
"context": "order history message",
|
||||
"string": "Order was confirmed"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_2770854455": {
|
||||
"context": "order history message",
|
||||
"string": "Payment was captured"
|
||||
|
@ -3472,6 +3480,10 @@
|
|||
"context": "generate invoice button",
|
||||
"string": "Generate"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderListPage_dot_2225897825": {
|
||||
"context": "button",
|
||||
"string": "Order Settings"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderListPage_dot_2826235371": {
|
||||
"context": "button",
|
||||
"string": "Create order"
|
||||
|
@ -3621,6 +3633,25 @@
|
|||
"src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_353369701": {
|
||||
"string": "No products matching given query"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderSettingsPage_dot_1149215359": {
|
||||
"context": "header",
|
||||
"string": "Order settings"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderSettingsPage_dot_3782847217": {
|
||||
"string": "General Settings"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderSettings_dot_1391686013": {
|
||||
"context": "section header",
|
||||
"string": "Settings"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderSettings_dot_3281882935": {
|
||||
"context": "checkbox label description",
|
||||
"string": "All orders will be automatically confirmed and all payments will be captured."
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderSettings_dot_513393066": {
|
||||
"context": "checkbox label",
|
||||
"string": "Automatically confirm all orders"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderShippingMethodEditDialog_dot_3369240294": {
|
||||
"context": "dialog header",
|
||||
"string": "Edit Shipping Method"
|
||||
|
@ -5934,6 +5965,10 @@
|
|||
"src_dot_translations_dot_components_dot_TranslationsVouchersPage_dot_2599922713": {
|
||||
"string": "Voucher Name"
|
||||
},
|
||||
"src_dot_unconfirmed": {
|
||||
"context": "order status",
|
||||
"string": "Unconfirmed"
|
||||
},
|
||||
"src_dot_undo": {
|
||||
"context": "button",
|
||||
"string": "Undo"
|
||||
|
@ -6229,6 +6264,10 @@
|
|||
"context": "event",
|
||||
"string": "Checkout updated"
|
||||
},
|
||||
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1366770851": {
|
||||
"context": "event",
|
||||
"string": "Order confirmed"
|
||||
},
|
||||
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1368317066": {
|
||||
"context": "event",
|
||||
"string": "Invoice deleted"
|
||||
|
|
|
@ -2554,6 +2554,7 @@ type Mutation {
|
|||
shopFetchTaxRates: ShopFetchTaxRates
|
||||
shopSettingsTranslate(input: ShopSettingsTranslationInput!, languageCode: LanguageCodeEnum!): ShopSettingsTranslate
|
||||
shopAddressUpdate(input: AddressInput): ShopAddressUpdate
|
||||
orderSettingsUpdate(input: OrderSettingsUpdateInput!): OrderSettingsUpdate
|
||||
shippingMethodChannelListingUpdate(id: ID!, input: ShippingMethodChannelListingInput!): ShippingMethodChannelListingUpdate
|
||||
shippingPriceCreate(input: ShippingPriceInput!): ShippingPriceCreate
|
||||
shippingPriceDelete(id: ID!): ShippingPriceDelete
|
||||
|
@ -2643,6 +2644,7 @@ type Mutation {
|
|||
orderAddNote(order: ID!, input: OrderAddNoteInput!): OrderAddNote
|
||||
orderCancel(id: ID!): OrderCancel
|
||||
orderCapture(amount: PositiveDecimal!, id: ID!): OrderCapture
|
||||
orderConfirm(id: ID!): OrderConfirm
|
||||
orderFulfill(input: OrderFulfillInput!, order: ID): OrderFulfill
|
||||
orderFulfillmentCancel(id: ID!, input: FulfillmentCancelInput!): FulfillmentCancel
|
||||
orderFulfillmentUpdateTracking(id: ID!, input: FulfillmentUpdateTrackingInput!): FulfillmentUpdateTracking
|
||||
|
@ -2882,6 +2884,12 @@ type OrderCapture {
|
|||
orderErrors: [OrderError!]!
|
||||
}
|
||||
|
||||
type OrderConfirm {
|
||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||
order: Order
|
||||
orderErrors: [OrderError!]!
|
||||
}
|
||||
|
||||
type OrderCountableConnection {
|
||||
pageInfo: PageInfo!
|
||||
edges: [OrderCountableEdge!]!
|
||||
|
@ -2984,6 +2992,7 @@ type OrderEventOrderLineObject {
|
|||
|
||||
enum OrderEventsEmailsEnum {
|
||||
PAYMENT_CONFIRMATION
|
||||
CONFIRMED
|
||||
SHIPPING_CONFIRMATION
|
||||
TRACKING_UPDATED
|
||||
ORDER_CONFIRMATION
|
||||
|
@ -3005,6 +3014,7 @@ enum OrderEventsEnum {
|
|||
ORDER_FULLY_PAID
|
||||
UPDATED_ADDRESS
|
||||
EMAIL_SENT
|
||||
CONFIRMED
|
||||
PAYMENT_AUTHORIZED
|
||||
PAYMENT_CAPTURED
|
||||
EXTERNAL_SERVICE_NOTIFICATION
|
||||
|
@ -3093,6 +3103,30 @@ type OrderRefund {
|
|||
orderErrors: [OrderError!]!
|
||||
}
|
||||
|
||||
type OrderSettings {
|
||||
automaticallyConfirmAllNewOrders: Boolean!
|
||||
}
|
||||
|
||||
type OrderSettingsError {
|
||||
field: String
|
||||
message: String
|
||||
code: OrderSettingsErrorCode!
|
||||
}
|
||||
|
||||
enum OrderSettingsErrorCode {
|
||||
INVALID
|
||||
}
|
||||
|
||||
type OrderSettingsUpdate {
|
||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||
orderSettings: OrderSettings
|
||||
orderSettingsErrors: [OrderSettingsError!]!
|
||||
}
|
||||
|
||||
input OrderSettingsUpdateInput {
|
||||
automaticallyConfirmAllNewOrders: Boolean!
|
||||
}
|
||||
|
||||
enum OrderSortField {
|
||||
NUMBER
|
||||
CREATION_DATE
|
||||
|
@ -3108,6 +3142,7 @@ input OrderSortingInput {
|
|||
|
||||
enum OrderStatus {
|
||||
DRAFT
|
||||
UNCONFIRMED
|
||||
UNFULFILLED
|
||||
PARTIALLY_FULFILLED
|
||||
FULFILLED
|
||||
|
@ -3118,6 +3153,7 @@ enum OrderStatusFilter {
|
|||
READY_TO_FULFILL
|
||||
READY_TO_CAPTURE
|
||||
UNFULFILLED
|
||||
UNCONFIRMED
|
||||
PARTIALLY_FULFILLED
|
||||
FULFILLED
|
||||
CANCELED
|
||||
|
@ -4291,6 +4327,7 @@ type Query {
|
|||
stock(id: ID!): Stock
|
||||
stocks(filter: StockFilterInput, before: String, after: String, first: Int, last: Int): StockCountableConnection
|
||||
shop: Shop!
|
||||
orderSettings: OrderSettings
|
||||
shippingZone(id: ID!, channel: String): ShippingZone
|
||||
shippingZones(channel: String, before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection
|
||||
digitalContent(id: ID!): DigitalContent
|
||||
|
@ -5598,6 +5635,7 @@ type WebhookEvent {
|
|||
enum WebhookEventTypeEnum {
|
||||
ANY_EVENTS
|
||||
ORDER_CREATED
|
||||
ORDER_CONFIRMED
|
||||
ORDER_FULLY_PAID
|
||||
ORDER_UPDATED
|
||||
ORDER_CANCELLED
|
||||
|
@ -5616,6 +5654,7 @@ enum WebhookEventTypeEnum {
|
|||
|
||||
enum WebhookSampleEventTypeEnum {
|
||||
ORDER_CREATED
|
||||
ORDER_CONFIRMED
|
||||
ORDER_FULLY_PAID
|
||||
ORDER_UPDATED
|
||||
ORDER_CANCELLED
|
||||
|
|
13
src/components/StatusChip/StatusChip.stories.tsx
Normal file
13
src/components/StatusChip/StatusChip.stories.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import StatusChip from "./StatusChip";
|
||||
import { StatusType } from "./types";
|
||||
|
||||
storiesOf("Generics / Status Chip", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("neutral", () => <StatusChip label="label" type={StatusType.NEUTRAL} />)
|
||||
.add("error", () => <StatusChip label="label" type={StatusType.ERROR} />)
|
||||
.add("success", () => <StatusChip label="label" type={StatusType.SUCCESS} />)
|
||||
.add("alert", () => <StatusChip label="label" type={StatusType.ALERT} />);
|
76
src/components/StatusChip/StatusChip.tsx
Normal file
76
src/components/StatusChip/StatusChip.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import { StatusType } from "./types";
|
||||
|
||||
export interface StatusChipProps {
|
||||
type?: StatusType;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const StatusChipStyles = {
|
||||
alert: {
|
||||
background: "#FFF4E4"
|
||||
},
|
||||
alertLabel: {
|
||||
color: "#FFB84E"
|
||||
},
|
||||
error: {
|
||||
backgroundColor: "rgba(254, 110, 118, 0.15)"
|
||||
},
|
||||
errorLabel: {
|
||||
color: "#FE6E76"
|
||||
},
|
||||
neutral: {
|
||||
background: "rgba(40, 35, 74, 0.1)"
|
||||
},
|
||||
neutralLabel: {
|
||||
color: "#28234A"
|
||||
},
|
||||
success: {
|
||||
background: "rgba(93, 194, 146, 0.3)"
|
||||
},
|
||||
successLabel: {
|
||||
color: "#5DC292"
|
||||
}
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
label: {
|
||||
fontSize: "1rem",
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
textTransform: "uppercase"
|
||||
},
|
||||
root: {
|
||||
borderRadius: 22,
|
||||
display: "inline-block",
|
||||
padding: "8px 24px"
|
||||
},
|
||||
...StatusChipStyles
|
||||
}),
|
||||
{ name: "StatusChip" }
|
||||
);
|
||||
|
||||
const StatusChip: React.FC<StatusChipProps> = props => {
|
||||
const { type = StatusType.NEUTRAL, label } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
if (!label) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(classes.root, classes[type])}>
|
||||
<Typography
|
||||
className={classNames(classes.label, classes[`${type}Label`])}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusChip;
|
1
src/components/StatusChip/index.ts
Normal file
1
src/components/StatusChip/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./StatusChip";
|
6
src/components/StatusChip/types.ts
Normal file
6
src/components/StatusChip/types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export enum StatusType {
|
||||
NEUTRAL = "neutral",
|
||||
ERROR = "error",
|
||||
ALERT = "alert",
|
||||
SUCCESS = "success"
|
||||
}
|
|
@ -69,6 +69,13 @@ export const orderErrorFragment = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const orderSettingsErrorFragment = gql`
|
||||
fragment OrderSettingsErrorFragment on OrderSettingsError {
|
||||
code
|
||||
field
|
||||
}
|
||||
`;
|
||||
|
||||
export const pageErrorFragment = gql`
|
||||
fragment PageErrorFragment on PageError {
|
||||
code
|
||||
|
|
|
@ -175,3 +175,9 @@ export const fragmentOrderDetails = gql`
|
|||
isPaid
|
||||
}
|
||||
`;
|
||||
|
||||
export const fragmentOrderSettings = gql`
|
||||
fragment OrderSettingsFragment on OrderSettings {
|
||||
automaticallyConfirmAllNewOrders
|
||||
}
|
||||
`;
|
||||
|
|
15
src/fragments/types/OrderSettingsErrorFragment.ts
Normal file
15
src/fragments/types/OrderSettingsErrorFragment.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { OrderSettingsErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: OrderSettingsErrorFragment
|
||||
// ====================================================
|
||||
|
||||
export interface OrderSettingsErrorFragment {
|
||||
__typename: "OrderSettingsError";
|
||||
code: OrderSettingsErrorCode;
|
||||
field: string | null;
|
||||
}
|
12
src/fragments/types/OrderSettingsFragment.ts
Normal file
12
src/fragments/types/OrderSettingsFragment.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: OrderSettingsFragment
|
||||
// ====================================================
|
||||
|
||||
export interface OrderSettingsFragment {
|
||||
__typename: "OrderSettings";
|
||||
automaticallyConfirmAllNewOrders: boolean;
|
||||
}
|
27
src/misc.ts
27
src/misc.ts
|
@ -4,6 +4,7 @@ import { defineMessages, IntlShape } from "react-intl";
|
|||
import urlJoin from "url-join";
|
||||
|
||||
import { ConfirmButtonTransitionState } from "./components/ConfirmButton/ConfirmButton";
|
||||
import { StatusType } from "./components/StatusChip/types";
|
||||
import { APP_MOUNT_URI } from "./config";
|
||||
import { AddressType, AddressTypeInput } from "./customers/types";
|
||||
import {
|
||||
|
@ -144,43 +145,55 @@ export const orderStatusMessages = defineMessages({
|
|||
defaultMessage: "Ready to fulfill",
|
||||
description: "order status"
|
||||
},
|
||||
unconfirmed: {
|
||||
defaultMessage: "Unconfirmed",
|
||||
description: "order status"
|
||||
},
|
||||
unfulfilled: {
|
||||
defaultMessage: "Unfulfilled",
|
||||
description: "order status"
|
||||
}
|
||||
});
|
||||
|
||||
export const transformOrderStatus = (status: string, intl: IntlShape) => {
|
||||
export const transformOrderStatus = (
|
||||
status: string,
|
||||
intl: IntlShape
|
||||
): { localized: string; status: StatusType } => {
|
||||
switch (status) {
|
||||
case OrderStatus.FULFILLED:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.fulfilled),
|
||||
status: "success"
|
||||
status: StatusType.SUCCESS
|
||||
};
|
||||
case OrderStatus.PARTIALLY_FULFILLED:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.partiallyFulfilled),
|
||||
status: "neutral"
|
||||
status: StatusType.NEUTRAL
|
||||
};
|
||||
case OrderStatus.UNFULFILLED:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.unfulfilled),
|
||||
status: "error"
|
||||
status: StatusType.ERROR
|
||||
};
|
||||
case OrderStatus.CANCELED:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.cancelled),
|
||||
status: "error"
|
||||
status: StatusType.ERROR
|
||||
};
|
||||
case OrderStatus.DRAFT:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.draft),
|
||||
status: "error"
|
||||
status: StatusType.ERROR
|
||||
};
|
||||
case OrderStatus.UNCONFIRMED:
|
||||
return {
|
||||
localized: intl.formatMessage(orderStatusMessages.unconfirmed),
|
||||
status: StatusType.NEUTRAL
|
||||
};
|
||||
}
|
||||
return {
|
||||
localized: status,
|
||||
status: "error"
|
||||
status: StatusType.ERROR
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import OrderHistory, { FormData as HistoryFormData } from "../OrderHistory";
|
|||
import OrderInvoiceList from "../OrderInvoiceList";
|
||||
import OrderPayment from "../OrderPayment/OrderPayment";
|
||||
import OrderUnfulfilledItems from "../OrderUnfulfilledItems/OrderUnfulfilledItems";
|
||||
import Title from "./Title";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -39,6 +40,7 @@ const useStyles = makeStyles(
|
|||
},
|
||||
header: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 0
|
||||
}
|
||||
}),
|
||||
|
@ -136,6 +138,22 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
privateMetadata: order?.privateMetadata.map(mapMetadataItemToInput)
|
||||
};
|
||||
|
||||
const saveLabel =
|
||||
order?.status === OrderStatus.UNCONFIRMED
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "confirm order",
|
||||
description: "save button"
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const allowSave = (hasChanged: boolean) => {
|
||||
if (order?.status !== OrderStatus.UNCONFIRMED) {
|
||||
return disabled || !hasChanged;
|
||||
}
|
||||
|
||||
return disabled;
|
||||
};
|
||||
|
||||
return (
|
||||
<Form initial={initial} onSubmit={handleSubmit}>
|
||||
{({ change, data, hasChanged, submit }) => {
|
||||
|
@ -149,7 +167,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
<PageHeader
|
||||
className={classes.header}
|
||||
inline
|
||||
title={maybe(() => order.number) ? "#" + order.number : undefined}
|
||||
title={<Title order={order} />}
|
||||
>
|
||||
{canCancel && (
|
||||
<CardMenu
|
||||
|
@ -243,10 +261,11 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
labels={{ save: saveLabel }}
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
disabled={allowSave(hasChanged)}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
|
|
46
src/orders/components/OrderDetailsPage/Title.tsx
Normal file
46
src/orders/components/OrderDetailsPage/Title.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import StatusChip from "@saleor/components/StatusChip";
|
||||
import { transformOrderStatus } from "@saleor/misc";
|
||||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
export interface TitleProps {
|
||||
order?: OrderDetails_order;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
container: {
|
||||
alignItems: "center",
|
||||
display: "flex"
|
||||
},
|
||||
statusContainer: {
|
||||
marginLeft: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{ name: "OrderDetailsTitle" }
|
||||
);
|
||||
|
||||
const Title: React.FC<TitleProps> = props => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles(props);
|
||||
const { order } = props;
|
||||
|
||||
if (!order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { localized, status } = transformOrderStatus(order.status, intl);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
{`#${order.number}`}
|
||||
<div className={classes.statusContainer}>
|
||||
<StatusChip label={localized} type={status} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Title;
|
|
@ -226,6 +226,11 @@ const getEventMessage = (event: OrderDetails_order_events, intl: IntlShape) => {
|
|||
defaultMessage: "Payment was authorized",
|
||||
description: "order history message"
|
||||
});
|
||||
case OrderEventsEnum.CONFIRMED:
|
||||
return intl.formatMessage({
|
||||
defaultMessage: "Order was confirmed",
|
||||
description: "order history message"
|
||||
});
|
||||
case OrderEventsEnum.EXTERNAL_SERVICE_NOTIFICATION:
|
||||
return event.message;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import CardMenu from "@saleor/components/CardMenu";
|
||||
import Container from "@saleor/components/Container";
|
||||
import FilterBar from "@saleor/components/FilterBar";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
|
@ -22,8 +24,18 @@ export interface OrderListPageProps
|
|||
FilterPageProps<OrderFilterKeys, OrderListFilterOpts>,
|
||||
SortPage<OrderListUrlSortField> {
|
||||
orders: OrderList_orders_edges_node[];
|
||||
onSettingsOpen: () => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
settings: {
|
||||
marginRight: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{ name: "OrderListPage" }
|
||||
);
|
||||
|
||||
const OrderListPage: React.FC<OrderListPageProps> = ({
|
||||
currentTab,
|
||||
initialSearch,
|
||||
|
@ -32,6 +44,7 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
|
|||
onAdd,
|
||||
onAll,
|
||||
onSearchChange,
|
||||
onSettingsOpen,
|
||||
onFilterChange,
|
||||
onTabChange,
|
||||
onTabDelete,
|
||||
|
@ -39,11 +52,26 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
|
|||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
const filterStructure = createFilterStructure(intl, filterOpts);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.orders)}>
|
||||
{!!onSettingsOpen && (
|
||||
<CardMenu
|
||||
className={classes.settings}
|
||||
menuItems={[
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Order Settings",
|
||||
description: "button"
|
||||
}),
|
||||
onSelect: onSettingsOpen
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Button color="primary" variant="contained" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create order"
|
||||
|
|
61
src/orders/components/OrderSettings/OrderSettings.tsx
Normal file
61
src/orders/components/OrderSettings/OrderSettings.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { OrderSettingsFormData } from "../OrderSettingsPage/form";
|
||||
|
||||
export interface OrderSettingsProps {
|
||||
data: OrderSettingsFormData;
|
||||
disabled: boolean;
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
}
|
||||
|
||||
const OrderSettings: React.FC<OrderSettingsProps> = ({
|
||||
data,
|
||||
disabled,
|
||||
onChange
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card data-test="orderSettings">
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Settings",
|
||||
description: "section header"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<ControlledCheckbox
|
||||
name={
|
||||
"automaticallyConfirmAllNewOrders" as keyof OrderSettingsFormData
|
||||
}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage
|
||||
defaultMessage="Automatically confirm all orders"
|
||||
description="checkbox label"
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage
|
||||
defaultMessage="All orders will be automatically confirmed and all payments will be captured."
|
||||
description="checkbox label description"
|
||||
/>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
checked={data.automaticallyConfirmAllNewOrders}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
data-test="automaticallyConfirmAllNewOrdersCheckbox"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
OrderSettings.displayName = "OrderSettings";
|
||||
export default OrderSettings;
|
2
src/orders/components/OrderSettings/index.ts
Normal file
2
src/orders/components/OrderSettings/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderSettings";
|
||||
export * from "./OrderSettings";
|
|
@ -0,0 +1,21 @@
|
|||
import { orderSettings as orderSettingsFixture } from "@saleor/orders/fixtures";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import Decorator from "../../../storybook/Decorator";
|
||||
import OrderSettings, { OrderSettingsPageProps } from ".";
|
||||
|
||||
const props: OrderSettingsPageProps = {
|
||||
data: orderSettingsFixture,
|
||||
disabled: false,
|
||||
onBack: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
saveButtonBarState: "default"
|
||||
};
|
||||
|
||||
storiesOf("Views / Orders / Order settings", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <OrderSettings {...props} />)
|
||||
.add("loading", () => (
|
||||
<OrderSettings {...props} disabled={true} data={undefined} />
|
||||
));
|
|
@ -0,0 +1,62 @@
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { OrderSettingsFragment } from "@saleor/fragments/types/OrderSettingsFragment";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import OrderSettings from "../OrderSettings/OrderSettings";
|
||||
import OrderSettingsForm, { OrderSettingsFormData } from "./form";
|
||||
|
||||
export interface OrderSettingsPageProps {
|
||||
data: OrderSettingsFragment;
|
||||
disabled: boolean;
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
onBack: () => void;
|
||||
onSubmit: (data: OrderSettingsFormData) => SubmitPromise;
|
||||
}
|
||||
|
||||
const OrderSettingsPage: React.FC<OrderSettingsPageProps> = props => {
|
||||
const { data, disabled, saveButtonBarState, onBack, onSubmit } = props;
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<OrderSettingsForm orderSettings={data} onSubmit={onSubmit}>
|
||||
{({ data, submit, hasChanged, change }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.orders)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Order settings",
|
||||
description: "header"
|
||||
})}
|
||||
/>
|
||||
<Grid variant="inverted">
|
||||
<div>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="General Settings" />
|
||||
</Typography>
|
||||
</div>
|
||||
<OrderSettings data={data} disabled={disabled} onChange={change} />
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
disabled={disabled || !hasChanged}
|
||||
state={saveButtonBarState}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
</OrderSettingsForm>
|
||||
);
|
||||
};
|
||||
OrderSettingsPage.displayName = "OrderSettingsPage";
|
||||
export default OrderSettingsPage;
|
71
src/orders/components/OrderSettingsPage/form.tsx
Normal file
71
src/orders/components/OrderSettingsPage/form.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { OrderSettingsFragment } from "@saleor/fragments/types/OrderSettingsFragment";
|
||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
||||
import React from "react";
|
||||
|
||||
export interface OrderSettingsFormData {
|
||||
automaticallyConfirmAllNewOrders: boolean;
|
||||
}
|
||||
|
||||
export interface UseOrderSettingsFormResult {
|
||||
change: FormChange;
|
||||
data: OrderSettingsFormData;
|
||||
hasChanged: boolean;
|
||||
submit: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface OrderSettingsFormProps {
|
||||
children: (props: UseOrderSettingsFormResult) => React.ReactNode;
|
||||
orderSettings: OrderSettingsFragment;
|
||||
onSubmit: (data: OrderSettingsFormData) => SubmitPromise;
|
||||
}
|
||||
|
||||
function getOrderSeettingsFormData(
|
||||
orderSettings: OrderSettingsFragment
|
||||
): OrderSettingsFormData {
|
||||
return {
|
||||
automaticallyConfirmAllNewOrders:
|
||||
orderSettings?.automaticallyConfirmAllNewOrders
|
||||
};
|
||||
}
|
||||
|
||||
function useOrderSettingsForm(
|
||||
orderSettings: OrderSettingsFragment,
|
||||
onSubmit: (data: OrderSettingsFormData) => SubmitPromise
|
||||
): UseOrderSettingsFormResult {
|
||||
const [changed, setChanged] = React.useState(false);
|
||||
const triggerChange = () => setChanged(true);
|
||||
|
||||
const form = useForm(getOrderSeettingsFormData(orderSettings));
|
||||
|
||||
const handleChange: FormChange = (event, cb) => {
|
||||
form.change(event, cb);
|
||||
triggerChange();
|
||||
};
|
||||
|
||||
const data: OrderSettingsFormData = {
|
||||
...form.data
|
||||
};
|
||||
|
||||
const submit = () => handleFormSubmit(form.data, onSubmit, setChanged);
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data,
|
||||
hasChanged: changed,
|
||||
submit
|
||||
};
|
||||
}
|
||||
|
||||
const OrderSettingsForm: React.FC<OrderSettingsFormProps> = ({
|
||||
children,
|
||||
orderSettings,
|
||||
onSubmit
|
||||
}) => {
|
||||
const props = useOrderSettingsForm(orderSettings, onSubmit);
|
||||
|
||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||
};
|
||||
|
||||
OrderSettingsForm.displayName = "OrderSettingsForm";
|
||||
export default OrderSettingsForm;
|
2
src/orders/components/OrderSettingsPage/index.ts
Normal file
2
src/orders/components/OrderSettingsPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./OrderSettingsPage";
|
||||
export * from "./OrderSettingsPage";
|
|
@ -1,4 +1,5 @@
|
|||
import { InvoiceFragment } from "@saleor/fragments/types/InvoiceFragment";
|
||||
import { OrderSettingsFragment } from "@saleor/fragments/types/OrderSettingsFragment";
|
||||
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
|
||||
import { warehouseList } from "@saleor/warehouses/fixtures";
|
||||
import { MessageDescriptor } from "react-intl";
|
||||
|
@ -1628,3 +1629,8 @@ export const invoices: InvoiceFragment[] = [
|
|||
"http://localhost:8000/media/invoices/invoice-1/07/2020-order-20-0e449e10-ef4b-4066-bebe-361f670b6820.pdf"
|
||||
}
|
||||
];
|
||||
|
||||
export const orderSettings: OrderSettingsFragment = {
|
||||
__typename: "OrderSettings",
|
||||
automaticallyConfirmAllNewOrders: true
|
||||
};
|
||||
|
|
|
@ -15,12 +15,14 @@ import {
|
|||
OrderListUrlQueryParams,
|
||||
OrderListUrlSortField,
|
||||
orderPath,
|
||||
orderSettingsPath,
|
||||
OrderUrlQueryParams
|
||||
} from "./urls";
|
||||
import OrderDetailsComponent from "./views/OrderDetails";
|
||||
import OrderDraftListComponent from "./views/OrderDraftList";
|
||||
import OrderFulfillComponent from "./views/OrderFulfill";
|
||||
import OrderListComponent from "./views/OrderList";
|
||||
import OrderSettings from "./views/OrderSettings";
|
||||
|
||||
const OrderList: React.FC<RouteComponentProps<any>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
|
@ -70,6 +72,7 @@ const Component = () => {
|
|||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.orders)} />
|
||||
<Switch>
|
||||
<Route exact path={orderSettingsPath} component={OrderSettings} />
|
||||
<Route exact path={orderDraftListPath} component={OrderDraftList} />
|
||||
<Route exact path={orderListPath} component={OrderList} />
|
||||
<Route path={orderFulfillPath(":id")} component={OrderFulfill} />
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import {
|
||||
invoiceErrorFragment,
|
||||
orderErrorFragment
|
||||
orderErrorFragment,
|
||||
orderSettingsErrorFragment
|
||||
} from "@saleor/fragments/errors";
|
||||
import {
|
||||
fragmentOrderDetails,
|
||||
fragmentOrderEvent,
|
||||
fragmentOrderSettings,
|
||||
invoiceFragment
|
||||
} from "@saleor/fragments/orders";
|
||||
import makeMutation from "@saleor/hooks/makeMutation";
|
||||
|
@ -23,6 +25,7 @@ import {
|
|||
import { OrderAddNote, OrderAddNoteVariables } from "./types/OrderAddNote";
|
||||
import { OrderCancel, OrderCancelVariables } from "./types/OrderCancel";
|
||||
import { OrderCapture, OrderCaptureVariables } from "./types/OrderCapture";
|
||||
import { OrderConfirm, OrderConfirmVariables } from "./types/OrderConfirm";
|
||||
import {
|
||||
OrderDraftBulkCancel,
|
||||
OrderDraftBulkCancelVariables
|
||||
|
@ -65,6 +68,10 @@ import {
|
|||
OrderMarkAsPaidVariables
|
||||
} from "./types/OrderMarkAsPaid";
|
||||
import { OrderRefund, OrderRefundVariables } from "./types/OrderRefund";
|
||||
import {
|
||||
OrderSettingsUpdate,
|
||||
OrderSettingsUpdateVariables
|
||||
} from "./types/OrderSettingsUpdate";
|
||||
import {
|
||||
OrderShippingMethodUpdate,
|
||||
OrderShippingMethodUpdateVariables
|
||||
|
@ -120,6 +127,27 @@ const orderDraftBulkCancelMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const orderConfirmMutation = gql`
|
||||
${fragmentOrderDetails}
|
||||
${orderErrorFragment}
|
||||
mutation OrderConfirm($id: ID!) {
|
||||
orderConfirm(id: $id) {
|
||||
errors: orderErrors {
|
||||
...OrderErrorFragment
|
||||
}
|
||||
order {
|
||||
...OrderDetailsFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useOrderConfirmMutation = makeMutation<
|
||||
OrderConfirm,
|
||||
OrderConfirmVariables
|
||||
>(orderConfirmMutation);
|
||||
|
||||
export const TypedOrderDraftBulkCancelMutation = TypedMutation<
|
||||
OrderDraftBulkCancel,
|
||||
OrderDraftBulkCancelVariables
|
||||
|
@ -500,3 +528,22 @@ export const TypedInvoiceEmailSendMutation = TypedMutation<
|
|||
InvoiceEmailSend,
|
||||
InvoiceEmailSendVariables
|
||||
>(invoiceEmailSendMutation);
|
||||
|
||||
const orderSettingsUpdateMutation = gql`
|
||||
${fragmentOrderSettings}
|
||||
${orderSettingsErrorFragment}
|
||||
mutation OrderSettingsUpdate($input: OrderSettingsUpdateInput!) {
|
||||
orderSettingsUpdate(input: $input) {
|
||||
errors: orderSettingsErrors {
|
||||
...OrderSettingsErrorFragment
|
||||
}
|
||||
orderSettings {
|
||||
...OrderSettingsFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const useOrderSettingsUpdateMutation = makeMutation<
|
||||
OrderSettingsUpdate,
|
||||
OrderSettingsUpdateVariables
|
||||
>(orderSettingsUpdateMutation);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { fragmentAddress } from "@saleor/fragments/address";
|
||||
import { fragmentOrderDetails } from "@saleor/fragments/orders";
|
||||
import {
|
||||
fragmentOrderDetails,
|
||||
fragmentOrderSettings
|
||||
} from "@saleor/fragments/orders";
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch";
|
||||
import gql from "graphql-tag";
|
||||
|
@ -15,6 +18,7 @@ import {
|
|||
OrderFulfillDataVariables
|
||||
} from "./types/OrderFulfillData";
|
||||
import { OrderList, OrderListVariables } from "./types/OrderList";
|
||||
import { OrderSettings } from "./types/OrderSettings";
|
||||
import {
|
||||
SearchOrderVariant as SearchOrderVariantType,
|
||||
SearchOrderVariantVariables
|
||||
|
@ -238,3 +242,15 @@ export const useOrderFulfillData = makeQuery<
|
|||
OrderFulfillData,
|
||||
OrderFulfillDataVariables
|
||||
>(orderFulfillData);
|
||||
|
||||
export const orderSettingsQuery = gql`
|
||||
${fragmentOrderSettings}
|
||||
query OrderSettings {
|
||||
orderSettings {
|
||||
...OrderSettingsFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const useOrderSettingsQuery = makeQuery<OrderSettings, never>(
|
||||
orderSettingsQuery
|
||||
);
|
||||
|
|
343
src/orders/types/OrderConfirm.ts
Normal file
343
src/orders/types/OrderConfirm.ts
Normal file
|
@ -0,0 +1,343 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { OrderErrorCode, OrderEventsEmailsEnum, OrderEventsEnum, FulfillmentStatus, PaymentChargeStatusEnum, OrderStatus, OrderAction, JobStatusEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: OrderConfirm
|
||||
// ====================================================
|
||||
|
||||
export interface OrderConfirm_orderConfirm_errors {
|
||||
__typename: "OrderError";
|
||||
code: OrderErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_metadata {
|
||||
__typename: "MetadataItem";
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_privateMetadata {
|
||||
__typename: "MetadataItem";
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_billingAddress_country {
|
||||
__typename: "CountryDisplay";
|
||||
code: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_billingAddress {
|
||||
__typename: "Address";
|
||||
city: string;
|
||||
cityArea: string;
|
||||
companyName: string;
|
||||
country: OrderConfirm_orderConfirm_order_billingAddress_country;
|
||||
countryArea: string;
|
||||
firstName: string;
|
||||
id: string;
|
||||
lastName: string;
|
||||
phone: string | null;
|
||||
postalCode: string;
|
||||
streetAddress1: string;
|
||||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_events_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_events {
|
||||
__typename: "OrderEvent";
|
||||
id: string;
|
||||
amount: number | null;
|
||||
date: any | null;
|
||||
email: string | null;
|
||||
emailType: OrderEventsEmailsEnum | null;
|
||||
invoiceNumber: string | null;
|
||||
message: string | null;
|
||||
quantity: number | null;
|
||||
type: OrderEventsEnum | null;
|
||||
user: OrderConfirm_orderConfirm_order_events_user | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_variant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
quantityAvailable: number;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_unitPrice_gross {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_unitPrice_net {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_unitPrice {
|
||||
__typename: "TaxedMoney";
|
||||
gross: OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_unitPrice_gross;
|
||||
net: OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_unitPrice_net;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_thumbnail {
|
||||
__typename: "Image";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine {
|
||||
__typename: "OrderLine";
|
||||
id: string;
|
||||
isShippingRequired: boolean;
|
||||
variant: OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_variant | null;
|
||||
productName: string;
|
||||
productSku: string;
|
||||
quantity: number;
|
||||
quantityFulfilled: number;
|
||||
unitPrice: OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_unitPrice | null;
|
||||
thumbnail: OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine_thumbnail | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_lines {
|
||||
__typename: "FulfillmentLine";
|
||||
id: string;
|
||||
quantity: number;
|
||||
orderLine: OrderConfirm_orderConfirm_order_fulfillments_lines_orderLine | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments_warehouse {
|
||||
__typename: "Warehouse";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_fulfillments {
|
||||
__typename: "Fulfillment";
|
||||
id: string;
|
||||
lines: (OrderConfirm_orderConfirm_order_fulfillments_lines | null)[] | null;
|
||||
fulfillmentOrder: number;
|
||||
status: FulfillmentStatus;
|
||||
trackingNumber: string;
|
||||
warehouse: OrderConfirm_orderConfirm_order_fulfillments_warehouse | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_lines_variant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
quantityAvailable: number;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_lines_unitPrice_gross {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_lines_unitPrice_net {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_lines_unitPrice {
|
||||
__typename: "TaxedMoney";
|
||||
gross: OrderConfirm_orderConfirm_order_lines_unitPrice_gross;
|
||||
net: OrderConfirm_orderConfirm_order_lines_unitPrice_net;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_lines_thumbnail {
|
||||
__typename: "Image";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_lines {
|
||||
__typename: "OrderLine";
|
||||
id: string;
|
||||
isShippingRequired: boolean;
|
||||
variant: OrderConfirm_orderConfirm_order_lines_variant | null;
|
||||
productName: string;
|
||||
productSku: string;
|
||||
quantity: number;
|
||||
quantityFulfilled: number;
|
||||
unitPrice: OrderConfirm_orderConfirm_order_lines_unitPrice | null;
|
||||
thumbnail: OrderConfirm_orderConfirm_order_lines_thumbnail | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_shippingAddress_country {
|
||||
__typename: "CountryDisplay";
|
||||
code: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_shippingAddress {
|
||||
__typename: "Address";
|
||||
city: string;
|
||||
cityArea: string;
|
||||
companyName: string;
|
||||
country: OrderConfirm_orderConfirm_order_shippingAddress_country;
|
||||
countryArea: string;
|
||||
firstName: string;
|
||||
id: string;
|
||||
lastName: string;
|
||||
phone: string | null;
|
||||
postalCode: string;
|
||||
streetAddress1: string;
|
||||
streetAddress2: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_shippingMethod {
|
||||
__typename: "ShippingMethod";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_shippingPrice_gross {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_shippingPrice {
|
||||
__typename: "TaxedMoney";
|
||||
gross: OrderConfirm_orderConfirm_order_shippingPrice_gross;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_subtotal_gross {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_subtotal {
|
||||
__typename: "TaxedMoney";
|
||||
gross: OrderConfirm_orderConfirm_order_subtotal_gross;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_total_gross {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_total_tax {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_total {
|
||||
__typename: "TaxedMoney";
|
||||
gross: OrderConfirm_orderConfirm_order_total_gross;
|
||||
tax: OrderConfirm_orderConfirm_order_total_tax;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_totalAuthorized {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_totalCaptured {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_availableShippingMethods_price {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_availableShippingMethods {
|
||||
__typename: "ShippingMethod";
|
||||
id: string;
|
||||
name: string;
|
||||
price: OrderConfirm_orderConfirm_order_availableShippingMethods_price | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_discount {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_invoices {
|
||||
__typename: "Invoice";
|
||||
id: string;
|
||||
number: string | null;
|
||||
createdAt: any;
|
||||
url: string | null;
|
||||
status: JobStatusEnum;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order_channel {
|
||||
__typename: "Channel";
|
||||
isActive: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm_order {
|
||||
__typename: "Order";
|
||||
id: string;
|
||||
metadata: (OrderConfirm_orderConfirm_order_metadata | null)[];
|
||||
privateMetadata: (OrderConfirm_orderConfirm_order_privateMetadata | null)[];
|
||||
billingAddress: OrderConfirm_orderConfirm_order_billingAddress | null;
|
||||
canFinalize: boolean;
|
||||
created: any;
|
||||
customerNote: string;
|
||||
events: (OrderConfirm_orderConfirm_order_events | null)[] | null;
|
||||
fulfillments: (OrderConfirm_orderConfirm_order_fulfillments | null)[];
|
||||
lines: (OrderConfirm_orderConfirm_order_lines | null)[];
|
||||
number: string | null;
|
||||
paymentStatus: PaymentChargeStatusEnum | null;
|
||||
shippingAddress: OrderConfirm_orderConfirm_order_shippingAddress | null;
|
||||
shippingMethod: OrderConfirm_orderConfirm_order_shippingMethod | null;
|
||||
shippingMethodName: string | null;
|
||||
shippingPrice: OrderConfirm_orderConfirm_order_shippingPrice | null;
|
||||
status: OrderStatus;
|
||||
subtotal: OrderConfirm_orderConfirm_order_subtotal | null;
|
||||
total: OrderConfirm_orderConfirm_order_total | null;
|
||||
actions: (OrderAction | null)[];
|
||||
totalAuthorized: OrderConfirm_orderConfirm_order_totalAuthorized | null;
|
||||
totalCaptured: OrderConfirm_orderConfirm_order_totalCaptured | null;
|
||||
user: OrderConfirm_orderConfirm_order_user | null;
|
||||
userEmail: string | null;
|
||||
availableShippingMethods: (OrderConfirm_orderConfirm_order_availableShippingMethods | null)[] | null;
|
||||
discount: OrderConfirm_orderConfirm_order_discount | null;
|
||||
invoices: (OrderConfirm_orderConfirm_order_invoices | null)[] | null;
|
||||
channel: OrderConfirm_orderConfirm_order_channel;
|
||||
}
|
||||
|
||||
export interface OrderConfirm_orderConfirm {
|
||||
__typename: "OrderConfirm";
|
||||
errors: OrderConfirm_orderConfirm_errors[];
|
||||
order: OrderConfirm_orderConfirm_order | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirm {
|
||||
orderConfirm: OrderConfirm_orderConfirm | null;
|
||||
}
|
||||
|
||||
export interface OrderConfirmVariables {
|
||||
id: string;
|
||||
}
|
16
src/orders/types/OrderSettings.ts
Normal file
16
src/orders/types/OrderSettings.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: OrderSettings
|
||||
// ====================================================
|
||||
|
||||
export interface OrderSettings_orderSettings {
|
||||
__typename: "OrderSettings";
|
||||
automaticallyConfirmAllNewOrders: boolean;
|
||||
}
|
||||
|
||||
export interface OrderSettings {
|
||||
orderSettings: OrderSettings_orderSettings | null;
|
||||
}
|
34
src/orders/types/OrderSettingsUpdate.ts
Normal file
34
src/orders/types/OrderSettingsUpdate.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { OrderSettingsUpdateInput, OrderSettingsErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: OrderSettingsUpdate
|
||||
// ====================================================
|
||||
|
||||
export interface OrderSettingsUpdate_orderSettingsUpdate_errors {
|
||||
__typename: "OrderSettingsError";
|
||||
code: OrderSettingsErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface OrderSettingsUpdate_orderSettingsUpdate_orderSettings {
|
||||
__typename: "OrderSettings";
|
||||
automaticallyConfirmAllNewOrders: boolean;
|
||||
}
|
||||
|
||||
export interface OrderSettingsUpdate_orderSettingsUpdate {
|
||||
__typename: "OrderSettingsUpdate";
|
||||
errors: OrderSettingsUpdate_orderSettingsUpdate_errors[];
|
||||
orderSettings: OrderSettingsUpdate_orderSettingsUpdate_orderSettings | null;
|
||||
}
|
||||
|
||||
export interface OrderSettingsUpdate {
|
||||
orderSettingsUpdate: OrderSettingsUpdate_orderSettingsUpdate | null;
|
||||
}
|
||||
|
||||
export interface OrderSettingsUpdateVariables {
|
||||
input: OrderSettingsUpdateInput;
|
||||
}
|
|
@ -114,3 +114,5 @@ export const orderFulfillPath = (id: string) =>
|
|||
urlJoin(orderPath(id), "fulfill");
|
||||
export const orderFulfillUrl = (id: string) =>
|
||||
orderFulfillPath(encodeURIComponent(id));
|
||||
|
||||
export const orderSettingsPath = urlJoin(orderSectionUrl, "settings");
|
||||
|
|
|
@ -10,8 +10,10 @@ import useUser from "@saleor/hooks/useUser";
|
|||
import { commonMessages } from "@saleor/intl";
|
||||
import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog";
|
||||
import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog";
|
||||
import { useOrderConfirmMutation } from "@saleor/orders/mutations";
|
||||
import { InvoiceRequest } from "@saleor/orders/types/InvoiceRequest";
|
||||
import useCustomerSearch from "@saleor/searches/useCustomerSearch";
|
||||
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||
import {
|
||||
|
@ -70,6 +72,7 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
|
|||
} = useCustomerSearch({
|
||||
variables: DEFAULT_INITIAL_SEARCH_DATA
|
||||
});
|
||||
|
||||
const {
|
||||
loadMore,
|
||||
search: variantSearch,
|
||||
|
@ -100,6 +103,19 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
|
|||
|
||||
const handleBack = () => navigate(orderListUrl());
|
||||
|
||||
const [orderConfirm] = useOrderConfirmMutation({
|
||||
onCompleted: ({ orderConfirm: { errors } }) => {
|
||||
const isError = !!errors.length;
|
||||
|
||||
notify({
|
||||
status: isError ? "error" : "success",
|
||||
text: isError
|
||||
? getOrderErrorMessage(errors[0], intl)
|
||||
: "Confirmed Order"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedOrderDetailsQuery displayLoader variables={{ id }}>
|
||||
{({ data, loading }) => {
|
||||
|
@ -109,6 +125,10 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
|
|||
}
|
||||
|
||||
const handleSubmit = async (data: MetadataFormData) => {
|
||||
if (order?.status === OrderStatus.UNCONFIRMED) {
|
||||
await orderConfirm({ variables: { id: order?.id } });
|
||||
}
|
||||
|
||||
const update = createMetadataUpdateHandler(
|
||||
order,
|
||||
() => Promise.resolve([]),
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
orderListUrl,
|
||||
OrderListUrlDialog,
|
||||
OrderListUrlQueryParams,
|
||||
orderSettingsPath,
|
||||
orderUrl
|
||||
} from "../../urls";
|
||||
import {
|
||||
|
@ -160,6 +161,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
|||
initialSearch={params.query || ""}
|
||||
tabs={getFilterTabs().map(tab => tab.name)}
|
||||
onAll={resetFilters}
|
||||
onSettingsOpen={() => navigate(orderSettingsPath)}
|
||||
/>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
|
|
64
src/orders/views/OrderSettings.tsx
Normal file
64
src/orders/views/OrderSettings.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getMutationState } from "@saleor/misc";
|
||||
import OrderSettingsPage from "@saleor/orders/components/OrderSettingsPage";
|
||||
import { OrderSettingsFormData } from "@saleor/orders/components/OrderSettingsPage/form";
|
||||
import { useOrderSettingsUpdateMutation } from "@saleor/orders/mutations";
|
||||
import { useOrderSettingsQuery } from "@saleor/orders/queries";
|
||||
import { orderListUrl } from "@saleor/orders/urls";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
export const OrderSettings: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
|
||||
const { data, loading } = useOrderSettingsQuery({});
|
||||
|
||||
const [
|
||||
orderSettingsUpdate,
|
||||
orderSettingsUpdateOpts
|
||||
] = useOrderSettingsUpdateMutation({});
|
||||
|
||||
const handleSubmit = async (data: OrderSettingsFormData) => {
|
||||
const result = await orderSettingsUpdate({
|
||||
variables: {
|
||||
input: data
|
||||
}
|
||||
});
|
||||
|
||||
const errors = result.data?.orderSettingsUpdate.errors;
|
||||
if (errors.length) {
|
||||
notify({
|
||||
status: "error",
|
||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
return [];
|
||||
};
|
||||
|
||||
const handleBack = () => navigate(orderListUrl());
|
||||
|
||||
return (
|
||||
<OrderSettingsPage
|
||||
data={data?.orderSettings}
|
||||
disabled={loading || orderSettingsUpdateOpts.loading}
|
||||
onSubmit={handleSubmit}
|
||||
onBack={handleBack}
|
||||
saveButtonBarState={getMutationState(
|
||||
orderSettingsUpdateOpts.called,
|
||||
orderSettingsUpdateOpts.loading,
|
||||
[...(orderSettingsUpdateOpts.data?.orderSettingsUpdate.errors || [])]
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default OrderSettings;
|
|
@ -67,6 +67,7 @@ export interface PageCreate_pageCreate_page_pageType {
|
|||
id: string;
|
||||
name: string;
|
||||
attributes: (PageCreate_pageCreate_page_pageType_attributes | null)[] | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface PageCreate_pageCreate_page_metadata {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -37,6 +37,7 @@ const props: OrderListPageProps = {
|
|||
value: [OrderStatusFilter.CANCELED, OrderStatusFilter.FULFILLED]
|
||||
}
|
||||
},
|
||||
onSettingsOpen: () => undefined,
|
||||
orders,
|
||||
sort: {
|
||||
...sortPageProps.sort,
|
||||
|
|
|
@ -592,6 +592,7 @@ export enum OrderErrorCode {
|
|||
}
|
||||
|
||||
export enum OrderEventsEmailsEnum {
|
||||
CONFIRMED = "CONFIRMED",
|
||||
DIGITAL_LINKS = "DIGITAL_LINKS",
|
||||
FULFILLMENT_CONFIRMATION = "FULFILLMENT_CONFIRMATION",
|
||||
ORDER_CANCEL = "ORDER_CANCEL",
|
||||
|
@ -604,6 +605,7 @@ export enum OrderEventsEmailsEnum {
|
|||
|
||||
export enum OrderEventsEnum {
|
||||
CANCELED = "CANCELED",
|
||||
CONFIRMED = "CONFIRMED",
|
||||
DRAFT_ADDED_PRODUCTS = "DRAFT_ADDED_PRODUCTS",
|
||||
DRAFT_CREATED = "DRAFT_CREATED",
|
||||
DRAFT_REMOVED_PRODUCTS = "DRAFT_REMOVED_PRODUCTS",
|
||||
|
@ -632,6 +634,10 @@ export enum OrderEventsEnum {
|
|||
UPDATED_ADDRESS = "UPDATED_ADDRESS",
|
||||
}
|
||||
|
||||
export enum OrderSettingsErrorCode {
|
||||
INVALID = "INVALID",
|
||||
}
|
||||
|
||||
export enum OrderSortField {
|
||||
CREATION_DATE = "CREATION_DATE",
|
||||
CUSTOMER = "CUSTOMER",
|
||||
|
@ -645,6 +651,7 @@ export enum OrderStatus {
|
|||
DRAFT = "DRAFT",
|
||||
FULFILLED = "FULFILLED",
|
||||
PARTIALLY_FULFILLED = "PARTIALLY_FULFILLED",
|
||||
UNCONFIRMED = "UNCONFIRMED",
|
||||
UNFULFILLED = "UNFULFILLED",
|
||||
}
|
||||
|
||||
|
@ -654,6 +661,7 @@ export enum OrderStatusFilter {
|
|||
PARTIALLY_FULFILLED = "PARTIALLY_FULFILLED",
|
||||
READY_TO_CAPTURE = "READY_TO_CAPTURE",
|
||||
READY_TO_FULFILL = "READY_TO_FULFILL",
|
||||
UNCONFIRMED = "UNCONFIRMED",
|
||||
UNFULFILLED = "UNFULFILLED",
|
||||
}
|
||||
|
||||
|
@ -954,6 +962,7 @@ export enum WebhookEventTypeEnum {
|
|||
INVOICE_REQUESTED = "INVOICE_REQUESTED",
|
||||
INVOICE_SENT = "INVOICE_SENT",
|
||||
ORDER_CANCELLED = "ORDER_CANCELLED",
|
||||
ORDER_CONFIRMED = "ORDER_CONFIRMED",
|
||||
ORDER_CREATED = "ORDER_CREATED",
|
||||
ORDER_FULFILLED = "ORDER_FULFILLED",
|
||||
ORDER_FULLY_PAID = "ORDER_FULLY_PAID",
|
||||
|
@ -1342,6 +1351,10 @@ export interface OrderLineInput {
|
|||
quantity: number;
|
||||
}
|
||||
|
||||
export interface OrderSettingsUpdateInput {
|
||||
automaticallyConfirmAllNewOrders: boolean;
|
||||
}
|
||||
|
||||
export interface OrderSortingInput {
|
||||
direction: OrderDirection;
|
||||
field: OrderSortField;
|
||||
|
|
|
@ -60,6 +60,10 @@ const WebhookEvents: React.FC<WebhookEventsProps> = ({
|
|||
defaultMessage: "Order created",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.ORDER_CONFIRMED]: intl.formatMessage({
|
||||
defaultMessage: "Order confirmed",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.ORDER_FULFILLED]: intl.formatMessage({
|
||||
defaultMessage: "Order fulfilled",
|
||||
description: "event"
|
||||
|
|
Loading…
Reference in a new issue