Payment status card improvements (#2222)

* Change table layout to flexbox

* Move taxes before total

* Move discounts above subtotal

* Add type to util function

* Remove maybes

* Improve discounts

* Change taxes message

* Make font smaller

* Query total balance in order details

* Use totalBalance from api

* Show refunded when more than 0

* Make taxes grey

* Delete unused import

* Update fixtures

* Update snapshots

* Extract messages

* Move styles to seperate file

* Fix refunded amount

* Add settled message for zero balances

* Change payment status to payment balance

* Update snapshots

* Fix messages post rebase
This commit is contained in:
Michał Droń 2022-09-01 13:47:13 +02:00 committed by GitHub
parent 24d77b984a
commit c1185198f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1778 additions and 2370 deletions

View file

@ -2481,7 +2481,7 @@
{
"kind": "ENUM",
"name": "AllocationStrategyEnum",
"description": "Determine the allocation strategy for the channel.\n\n PRIORITIZE_SORTING_ORDER - the allocation is prioritized by the warehouses' sort\n order within the channel\n\n PRIORITIZE_HIGH_STOCK - the allocation is prioritized by the highest available\n quantity in stocks\n ",
"description": "Determine the allocation strategy for the channel.\n\n PRIORITIZE_SORTING_ORDER - allocate stocks according to the warehouses' order\n within the channel\n\n PRIORITIZE_HIGH_STOCK - allocate stock in a warehouse with the most stock\n ",
"fields": null,
"inputFields": null,
"interfaces": null,
@ -51802,7 +51802,7 @@
},
{
"name": "moves",
"description": "The list of reordering operations for given channel warehouses.",
"description": "The list of reordering operations for the given channel warehouses.",
"type": {
"kind": "NON_NULL",
"name": null,
@ -67124,7 +67124,7 @@
},
{
"name": "variants",
"description": "List of varint IDs which causes the error.",
"description": "List of variant IDs which causes the error.",
"args": [],
"type": {
"kind": "LIST",
@ -93930,7 +93930,7 @@
"fields": [
{
"name": "allocationStrategy",
"description": "Allocation strategy options. Strategy defines the preference of warehouses for allocations and reservations.",
"description": "Allocation strategy defines the preference of warehouses for allocations and reservations.",
"args": [],
"type": {
"kind": "NON_NULL",
@ -102023,7 +102023,7 @@
},
{
"name": "shippingZones",
"description": "Shipping zones supported by the warehouse.",
"description": "Shipping zones supported by the warehouse.\n\nDEPRECATED: this field will be removed in Saleor 4.0. Providing the zone ids will raise a ValidationError.",
"type": {
"kind": "LIST",
"name": null,
@ -102315,6 +102315,26 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "shippingZones",
"description": "List of shipping zones IDs which causes the error.",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,

View file

@ -1131,6 +1131,10 @@
"context": "product field",
"string": "Export Product Weight"
},
"7KRGqz": {
"context": "Payment card title",
"string": "Payment balance"
},
"7NFfmz": {
"string": "Products"
},
@ -3806,10 +3810,6 @@
"context": "unassign attribute from product type, button",
"string": "Unassign"
},
"SC/eNC": {
"context": "Payment card title",
"string": "Payment status"
},
"SHm7ee": {
"string": "Search by product name, attribute, product type etc..."
},
@ -3888,6 +3888,10 @@
"context": "checkbox label, fulfillment approval",
"string": "Send shipment details to customer"
},
"Sxzua5": {
"context": "order payment",
"string": "Settled"
},
"T/5OyA": {
"string": "No translation yet"
},
@ -6570,6 +6574,10 @@
"context": "tab name",
"string": "All Vouchers"
},
"pPef6L": {
"context": "order payment",
"string": "Included in subtotal"
},
"pRYGUR": {
"context": "section description",
"string": "This address will be used to generate invoices and calculate shipping rates. Email address you provide here will be used as a contact address for your customers."
@ -7236,6 +7244,10 @@
"context": "voucher start date",
"string": "Started"
},
"ukYopn": {
"context": "order payment",
"string": "Included in prices"
},
"ukdRUv": {
"context": "delete variant dialog subtitle",
"string": "{counter,plural,one{Are you sure you want to delete this variant?} other{Are you sure you want to delete {displayQuantity} variants?}}"

View file

@ -414,11 +414,10 @@ type Allocation implements Node {
"""
Determine the allocation strategy for the channel.
PRIORITIZE_SORTING_ORDER - the allocation is prioritized by the warehouses' sort
order within the channel
PRIORITIZE_SORTING_ORDER - allocate stocks according to the warehouses' order
within the channel
PRIORITIZE_HIGH_STOCK - the allocation is prioritized by the highest available
quantity in stocks
PRIORITIZE_HIGH_STOCK - allocate stock in a warehouse with the most stock
"""
enum AllocationStrategyEnum {
@ -11557,7 +11556,7 @@ type Mutation {
"""ID of a channel."""
channelId: ID!
"""The list of reordering operations for given channel warehouses."""
"""The list of reordering operations for the given channel warehouses."""
moves: [ReorderInput!]!
): ChannelReorderWarehouses
@ -14909,7 +14908,7 @@ type PaymentError {
"""The error code."""
code: PaymentErrorCode!
"""List of varint IDs which causes the error."""
"""List of variant IDs which causes the error."""
variants: [ID!]
}
@ -21151,7 +21150,7 @@ Note: this API is currently in Feature Preview and can be subject to changes at
"""
type StockSettings {
"""
Allocation strategy options. Strategy defines the preference of warehouses for allocations and reservations.
Allocation strategy defines the preference of warehouses for allocations and reservations.
"""
allocationStrategy: AllocationStrategyEnum!
}
@ -22856,7 +22855,11 @@ input WarehouseCreateInput {
"""Address of the warehouse."""
address: AddressInput!
"""Shipping zones supported by the warehouse."""
"""
Shipping zones supported by the warehouse.
DEPRECATED: this field will be removed in Saleor 4.0. Providing the zone ids will raise a ValidationError.
"""
shippingZones: [ID!]
}
@ -22930,6 +22933,9 @@ type WarehouseError {
"""The error code."""
code: WarehouseErrorCode!
"""List of shipping zones IDs which causes the error."""
shippingZones: [ID!]
}
"""An enumeration."""

View file

@ -287,6 +287,9 @@ export const fragmentOrderDetails = gql`
totalCaptured {
...Money
}
totalBalance {
...Money
}
undiscountedTotal {
net {
...Money

View file

@ -1424,6 +1424,9 @@ export const OrderDetailsFragmentDoc = gql`
totalCaptured {
...Money
}
totalBalance {
...Money
}
undiscountedTotal {
net {
...Money

View file

@ -5090,11 +5090,12 @@ export type WarehouseDeletedFieldPolicy = {
recipient?: FieldPolicy<any> | FieldReadFunction<any>,
warehouse?: FieldPolicy<any> | FieldReadFunction<any>
};
export type WarehouseErrorKeySpecifier = ('field' | 'message' | 'code' | WarehouseErrorKeySpecifier)[];
export type WarehouseErrorKeySpecifier = ('field' | 'message' | 'code' | 'shippingZones' | WarehouseErrorKeySpecifier)[];
export type WarehouseErrorFieldPolicy = {
field?: FieldPolicy<any> | FieldReadFunction<any>,
message?: FieldPolicy<any> | FieldReadFunction<any>,
code?: FieldPolicy<any> | FieldReadFunction<any>
code?: FieldPolicy<any> | FieldReadFunction<any>,
shippingZones?: FieldPolicy<any> | FieldReadFunction<any>
};
export type WarehouseShippingZoneAssignKeySpecifier = ('warehouseErrors' | 'errors' | 'warehouse' | WarehouseShippingZoneAssignKeySpecifier)[];
export type WarehouseShippingZoneAssignFieldPolicy = {

File diff suppressed because one or more lines are too long

View file

@ -11,41 +11,15 @@ import {
OrderDiscountType,
OrderStatus,
} from "@saleor/graphql";
import { makeStyles, Pill } from "@saleor/macaw-ui";
import { Pill } from "@saleor/macaw-ui";
import clsx from "clsx";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { maybe, transformPaymentStatus } from "../../../misc";
import { transformPaymentStatus } from "../../../misc";
import { orderPaymentMessages, paymentButtonMessages } from "./messages";
import {
extractOrderGiftCardUsedAmount,
extractOutstandingBalance,
extractRefundedAmount,
} from "./utils";
const useStyles = makeStyles(
theme => ({
header: {
display: "flex",
justifyContent: "space-between",
},
root: {
...theme.typography.body1,
lineHeight: 1.9,
width: "100%",
},
textRight: {
textAlign: "right",
},
totalRow: {
fontWeight: 600,
},
titleContainer: {
display: "flex",
},
}),
{ name: "OrderPayment" },
);
import { useStyles } from "./styles";
import { extractOrderGiftCardUsedAmount, extractRefundedAmount } from "./utils";
interface OrderPaymentProps {
order: OrderDetailsFragment;
@ -61,20 +35,17 @@ const OrderPayment: React.FC<OrderPaymentProps> = props => {
const intl = useIntl();
const canCapture = maybe(() => order.actions, []).includes(
OrderAction.CAPTURE,
);
const canVoid = maybe(() => order.actions, []).includes(OrderAction.VOID);
const canRefund = maybe(() => order.actions, []).includes(OrderAction.REFUND);
const canMarkAsPaid = maybe(() => order.actions, []).includes(
const canCapture = (order?.actions ?? []).includes(OrderAction.CAPTURE);
const canVoid = (order?.actions ?? []).includes(OrderAction.VOID);
const canRefund = (order?.actions ?? []).includes(OrderAction.REFUND);
const canMarkAsPaid = (order?.actions ?? []).includes(
OrderAction.MARK_AS_PAID,
);
const payment = transformPaymentStatus(order?.paymentStatus, intl);
const refundedAmount = extractRefundedAmount(order);
const outstandingBalance = extractOutstandingBalance(order);
const usedGiftCardAmount = extractOrderGiftCardUsedAmount(order);
const getDeliveryMethodName = order => {
const getDeliveryMethodName = (order: OrderDetailsFragment) => {
if (
order?.shippingMethodName === undefined &&
order?.shippingPrice === undefined &&
@ -102,13 +73,15 @@ const OrderPayment: React.FC<OrderPaymentProps> = props => {
!order?.paymentStatus ? (
<Skeleton />
) : (
<div className={classes.header}>
<div className={classes.titleContainer}>
<FormattedMessage {...orderPaymentMessages.paymentTitle} />
<HorizontalSpacer spacing={2} />
<Pill label={payment.localized} color={payment.status} />
</div>
{maybe(() => order.status) !== OrderStatus.CANCELED &&
<Pill
className={classes.rightmostLeftAlignedElement}
label={payment.localized}
color={payment.status}
/>
{order?.status !== OrderStatus.CANCELED &&
(canCapture || canRefund || canVoid || canMarkAsPaid) && (
<div>
{canCapture && (
@ -144,172 +117,142 @@ const OrderPayment: React.FC<OrderPaymentProps> = props => {
}
/>
<CardContent>
<table className={classes.root}>
<tbody>
<tr>
<td>
<FormattedMessage {...orderPaymentMessages.subtotal} />
</td>
<td>
{maybe(() => order.lines) === undefined ? (
<Skeleton />
) : (
<FormattedMessage
{...orderPaymentMessages.itemCount}
values={{
quantity: order.lines
.map(line => line.quantity)
.reduce((curr, prev) => prev + curr, 0),
}}
/>
)}
</td>
<td className={classes.textRight}>
{maybe(() => order.subtotal.gross) === undefined ? (
<Skeleton />
) : (
<Money money={order.subtotal.gross} />
)}
</td>
</tr>
<tr>
<td>
<FormattedMessage {...orderPaymentMessages.taxes} />
</td>
<td>
{maybe(() => order.total.tax) === undefined ? (
<Skeleton />
) : order.total.tax.amount > 0 ? (
intl.formatMessage(orderPaymentMessages.vatIncluded)
) : (
intl.formatMessage(orderPaymentMessages.vatNotIncluded)
)}
</td>
<td className={classes.textRight}>
{maybe(() => order.total.tax) === undefined ? (
<Skeleton />
) : (
<Money money={order.total.tax} />
)}
</td>
</tr>
<tr>
<td>
<FormattedMessage {...orderPaymentMessages.shipping} />
</td>
<td>{getDeliveryMethodName(order)}</td>
<td className={classes.textRight}>
{maybe(() => order.shippingPrice.gross) === undefined ? (
<Skeleton />
) : (
<Money money={order.shippingPrice.gross} />
)}
</td>
</tr>
<div className={classes.root}>
{order?.discounts?.map(discount => (
<tr>
<td>
<div>
<FormattedMessage {...orderPaymentMessages.discount} />
</td>
<td>
<HorizontalSpacer spacing={4} />
<span className={classes.supportText}>
{discount.type === OrderDiscountType.MANUAL ? (
<FormattedMessage {...orderPaymentMessages.staffAdded} />
) : (
<FormattedMessage {...orderPaymentMessages.voucher} />
)}
</td>
<td className={classes.textRight}>
-<Money money={discount.amount} />
</td>
</tr>
))}
<tr className={classes.totalRow}>
<td>
<FormattedMessage {...orderPaymentMessages.total} />
</td>
<td />
<td className={classes.textRight}>
{maybe(() => order.total.gross) === undefined ? (
<Skeleton />
) : (
<Money money={order.total.gross} />
</span>
<span
className={clsx(
classes.leftmostRightAlignedElement,
classes.smallFont,
classes.supportText,
)}
</td>
</tr>
</tbody>
</table>
>
<FormattedMessage
{...orderPaymentMessages.includedInSubtotal}
/>
</span>
<HorizontalSpacer spacing={2} />
<div className={classes.supportText}>
-<Money money={discount.amount} />
</div>
</div>
))}
<div>
<FormattedMessage {...orderPaymentMessages.subtotal} />
<div className={classes.leftmostRightAlignedElement}>
{<Money money={order?.subtotal.gross} /> ?? <Skeleton />}
</div>
</div>
<div>
<FormattedMessage {...orderPaymentMessages.shipping} />
<HorizontalSpacer spacing={4} />
<div className={classes.supportText}>
{getDeliveryMethodName(order)}
</div>
<div className={classes.leftmostRightAlignedElement}>
{<Money money={order?.shippingPrice.gross} /> ?? <Skeleton />}
</div>
</div>
<div>
<FormattedMessage {...orderPaymentMessages.taxes} />
{order?.total.tax.amount > 0 && (
<>
<div
className={clsx(
classes.supportText,
classes.smallFont,
classes.leftmostRightAlignedElement,
)}
>
<FormattedMessage
{...orderPaymentMessages.includedInPrices}
/>{" "}
</div>
<HorizontalSpacer spacing={2} />
</>
)}
<div
className={clsx(
{
[classes.leftmostRightAlignedElement]:
order?.total.tax.amount === 0,
},
classes.supportText,
)}
>
{<Money money={order?.total.tax} /> ?? <Skeleton />}
</div>
</div>
<div className={classes.totalRow}>
<FormattedMessage {...orderPaymentMessages.total} />
<div className={classes.leftmostRightAlignedElement}>
{<Money money={order?.total.gross} /> ?? <Skeleton />}
</div>
</div>
</div>
</CardContent>
<Hr />
<CardContent>
<table className={classes.root}>
<tbody>
<div className={classes.root}>
{!!usedGiftCardAmount && (
<tr>
<td>
<FormattedMessage
{...orderPaymentMessages.paidWithGiftCard}
/>
</td>
<td className={classes.textRight}>
<div>
<FormattedMessage {...orderPaymentMessages.paidWithGiftCard} />
<div className={classes.leftmostRightAlignedElement}>
<Money
money={{
amount: usedGiftCardAmount,
currency: order?.total?.gross?.currency,
}}
/>
</td>
</tr>
</div>
</div>
)}
<tr>
<td>
<div>
<FormattedMessage {...orderPaymentMessages.preauthorized} />
</td>
<td className={classes.textRight}>
{maybe(() => order.totalAuthorized.amount) === undefined ? (
<Skeleton />
) : (
<Money money={order.totalAuthorized} />
)}
</td>
</tr>
<tr>
<td>
<div className={classes.leftmostRightAlignedElement}>
{<Money money={order?.totalAuthorized} /> ?? <Skeleton />}
</div>
</div>
<div>
<FormattedMessage {...orderPaymentMessages.captured} />
</td>
<td className={classes.textRight}>
{maybe(() => order.totalCaptured.amount) === undefined ? (
<Skeleton />
) : (
<Money money={order.totalCaptured} />
)}
</td>
</tr>
<tr>
<td>
<div className={classes.leftmostRightAlignedElement}>
{<Money money={order?.totalCaptured} /> ?? <Skeleton />}
</div>
</div>
{!!refundedAmount?.amount && (
<div>
<FormattedMessage {...orderPaymentMessages.refunded} />
</td>
<td className={classes.textRight}>
{refundedAmount?.amount === undefined ? (
<Skeleton />
) : (
<Money money={refundedAmount} />
<div className={classes.leftmostRightAlignedElement}>
{<Money money={refundedAmount} />}
</div>
</div>
)}
</td>
</tr>
<tr className={classes.totalRow}>
<td>
<div
className={clsx(
{ [classes.success]: order?.totalBalance.amount === 0 },
classes.totalRow,
)}
>
<FormattedMessage {...orderPaymentMessages.outstanding} />
</td>
<td className={classes.textRight}>
{outstandingBalance?.amount === undefined ? (
<Skeleton />
<div className={classes.leftmostRightAlignedElement}>
{order?.totalBalance.amount === 0 ? (
<FormattedMessage {...orderPaymentMessages.settled} />
) : (
<Money money={outstandingBalance} />
<Money money={order?.totalBalance} /> ?? <Skeleton />
)}
</td>
</tr>
</tbody>
</table>
{}
</div>
</div>
</div>
</CardContent>
</Card>
);

View file

@ -12,8 +12,8 @@ export const orderPaymentMessages = defineMessages({
description: "OrderPayment does not require shipping",
},
paymentTitle: {
id: "SC/eNC",
defaultMessage: "Payment status",
id: "7KRGqz",
defaultMessage: "Payment balance",
description: "Payment card title",
},
subtotal: {
@ -95,6 +95,21 @@ export const orderPaymentMessages = defineMessages({
defaultMessage: "Paid with Gift Card",
description: "order payment",
},
includedInSubtotal: {
id: "pPef6L",
defaultMessage: "Included in subtotal",
description: "order payment",
},
includedInPrices: {
id: "ukYopn",
defaultMessage: "Included in prices",
description: "order payment",
},
settled: {
id: "Sxzua5",
defaultMessage: "Settled",
description: "order payment",
},
});
export const paymentButtonMessages = defineMessages({

View file

@ -0,0 +1,41 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
theme => ({
header: {
display: "flex",
justifyContent: "space-between",
},
root: {
...theme.typography.body1,
lineHeight: 1.9,
width: "100%",
"& > div": {
display: "flex",
justifyContent: "flex-end",
},
},
leftmostRightAlignedElement: {
marginLeft: "auto",
},
rightmostLeftAlignedElement: {
marginRight: "auto",
},
totalRow: {
fontWeight: 600,
},
titleContainer: {
display: "flex",
},
supportText: {
color: theme.palette.saleor.main[3],
},
smallFont: {
fontSize: theme.typography.body2.fontSize,
},
success: {
color: theme.palette.success.dark,
},
}),
{ name: "OrderPayment" },
);

View file

@ -1586,6 +1586,11 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
amount: 0,
currency: "USD",
},
totalBalance: {
__typename: "Money",
amount: 0,
currency: "USD",
},
undiscountedTotal: {
__typename: "TaxedMoney",
gross: {
@ -1891,6 +1896,11 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
},
totalAuthorized: null,
totalCaptured: null,
totalBalance: {
__typename: "Money" as "Money",
amount: 168.3,
currency: "USD",
},
undiscountedTotal: {
__typename: "TaxedMoney",
gross: {

File diff suppressed because it is too large Load diff