@12-apps/payments-frontend 3.35.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/card/fields.tsx +33 -14
- package/src/components/checkout/card-view.tsx +19 -6
- package/src/components/checkout/checkout-flow.tsx +63 -79
- package/src/components/checkout/checkout-frame.tsx +175 -0
- package/src/components/checkout/checkout-steps.tsx +34 -66
- package/src/components/checkout/icons.tsx +5 -0
- package/src/components/checkout/method-tabs.tsx +249 -0
- package/src/components/checkout/payment-status-parts.tsx +13 -3
- package/src/components/checkout/payment-step-frame.tsx +206 -0
- package/src/components/checkout/payment-step-layout.tsx +169 -14
- package/src/components/checkout/payment-summary-phone.tsx +158 -0
- package/src/components/checkout/payment-summary.tsx +290 -0
- package/src/components/checkout/pix-after-copy.tsx +1 -1
- package/src/components/checkout/pix-pane.tsx +172 -147
- package/src/components/checkout/pix-parts.tsx +6 -4
- package/src/components/checkout/pix-view.tsx +37 -17
- package/src/components/checkout/screens-copy.ts +32 -3
- package/src/components/checkout/screens-en-US.ts +11 -0
- package/src/components/checkout/screens-pt-BR.ts +11 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Box } from "@mui/material";
|
|
2
|
+
import type { JSX, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { formatCpf } from "../../card";
|
|
5
|
+
|
|
6
|
+
import { useCheckoutCopy } from "./copy-context";
|
|
7
|
+
import { PersonOutlineIcon, PlaceOutlinedIcon } from "./icons";
|
|
8
|
+
import { NoteIcon, TotalAmount, type CheckoutDeliverySummary, type OrderSummaryParts } from "./payment-summary";
|
|
9
|
+
import { useCheckoutComponents } from "./ui";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The phone's head of the Pagamento step (FUT-3525, layout C): the amount big
|
|
13
|
+
* at the top, then who pays, where it goes and the note as chips that wrap.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const CHIP_ICON = { fontSize: 18, color: "text.secondary" } as const;
|
|
17
|
+
|
|
18
|
+
const CHIP_SX = {
|
|
19
|
+
display: "inline-flex",
|
|
20
|
+
alignItems: "center",
|
|
21
|
+
gap: 1,
|
|
22
|
+
minHeight: 44,
|
|
23
|
+
maxWidth: "100%",
|
|
24
|
+
px: 1.75,
|
|
25
|
+
border: "1px solid",
|
|
26
|
+
borderColor: "divider",
|
|
27
|
+
borderRadius: 999,
|
|
28
|
+
bgcolor: "background.paper",
|
|
29
|
+
color: "text.primary",
|
|
30
|
+
font: "inherit",
|
|
31
|
+
fontSize: "0.8125rem",
|
|
32
|
+
cursor: "pointer",
|
|
33
|
+
"&:focus-visible": { outline: "2px solid", outlineColor: "primary.main", outlineOffset: 2 },
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
/** One chip: an icon, a fact, and the word that changes it. */
|
|
37
|
+
function Chip({
|
|
38
|
+
icon,
|
|
39
|
+
text,
|
|
40
|
+
action,
|
|
41
|
+
onClick,
|
|
42
|
+
label,
|
|
43
|
+
testId,
|
|
44
|
+
}: {
|
|
45
|
+
icon: JSX.Element;
|
|
46
|
+
text: ReactNode;
|
|
47
|
+
action: string;
|
|
48
|
+
onClick: () => void;
|
|
49
|
+
label: string;
|
|
50
|
+
testId: string;
|
|
51
|
+
}): JSX.Element {
|
|
52
|
+
return (
|
|
53
|
+
<Box component="button" type="button" onClick={onClick} aria-label={label} data-testid={testId} sx={CHIP_SX}>
|
|
54
|
+
{icon}
|
|
55
|
+
<Box component="span" sx={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 }}>
|
|
56
|
+
{text}
|
|
57
|
+
</Box>
|
|
58
|
+
<Box component="span" sx={{ color: "primary.main", fontWeight: 600, flex: "none" }}>
|
|
59
|
+
{action}
|
|
60
|
+
</Box>
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Who pays: the name and "CPF ✓", or the CPF typed this time, masked. */
|
|
66
|
+
function PayerChip({ payer }: { payer: NonNullable<OrderSummaryParts["payer"]> }): JSX.Element {
|
|
67
|
+
const screens = useCheckoutCopy().screens;
|
|
68
|
+
const cpf = payer.taxId?.trim() ? screens.payer.taxId(formatCpf(payer.taxId)) : screens.layout.taxIdOnFileShort;
|
|
69
|
+
const who = payer.name ? screens.payer.payingAs(payer.name) : screens.payer.payingWithSavedDetails;
|
|
70
|
+
return (
|
|
71
|
+
<Box data-testid="checkout-payer" sx={{ display: "contents" }}>
|
|
72
|
+
<Chip
|
|
73
|
+
icon={<PersonOutlineIcon sx={CHIP_ICON} />}
|
|
74
|
+
text={
|
|
75
|
+
<>
|
|
76
|
+
{payer.name ? `${payer.name} · ` : null}
|
|
77
|
+
<span data-testid="checkout-payer-cpf">{cpf}</span>
|
|
78
|
+
</>
|
|
79
|
+
}
|
|
80
|
+
action={screens.payer.changeAction}
|
|
81
|
+
onClick={payer.onEdit}
|
|
82
|
+
label={`${who} — ${screens.payer.changeAction}`}
|
|
83
|
+
testId="checkout-payer-edit"
|
|
84
|
+
/>
|
|
85
|
+
</Box>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Where it goes: the street line only, and the way back to the address step. */
|
|
90
|
+
function AddressChip({ delivery }: { delivery: CheckoutDeliverySummary }): JSX.Element {
|
|
91
|
+
const screens = useCheckoutCopy().screens;
|
|
92
|
+
return (
|
|
93
|
+
<Box data-testid="checkout-address" sx={{ display: "contents" }}>
|
|
94
|
+
<Chip
|
|
95
|
+
icon={<PlaceOutlinedIcon sx={CHIP_ICON} />}
|
|
96
|
+
text={delivery.line1}
|
|
97
|
+
action={screens.payer.changeAction}
|
|
98
|
+
onClick={delivery.onEdit}
|
|
99
|
+
label={`${delivery.line1} — ${screens.layout.changeAddress}`}
|
|
100
|
+
testId="checkout-address-edit"
|
|
101
|
+
/>
|
|
102
|
+
</Box>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The host's note control, drawn as the third chip while it is closed; once it
|
|
108
|
+
* opens into a field, that field takes the whole row.
|
|
109
|
+
*/
|
|
110
|
+
function NoteChip({ note }: { note: ReactNode }): JSX.Element {
|
|
111
|
+
return (
|
|
112
|
+
<Box
|
|
113
|
+
data-testid="checkout-note-slot"
|
|
114
|
+
sx={{
|
|
115
|
+
...CHIP_SX,
|
|
116
|
+
gap: 0.5,
|
|
117
|
+
pr: 0.5,
|
|
118
|
+
cursor: "auto",
|
|
119
|
+
"&:has(textarea)": { flexBasis: "100%", display: "block", p: 0, border: 0, bgcolor: "transparent" },
|
|
120
|
+
"&:has(textarea) > svg": { display: "none" },
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
<NoteIcon size={18} />
|
|
124
|
+
{note}
|
|
125
|
+
</Box>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The amount, big, under "Total a pagar · N itens", the host's money rows under it. */
|
|
130
|
+
function PhoneTotal({ parts }: { parts: OrderSummaryParts }): JSX.Element {
|
|
131
|
+
const { Text } = useCheckoutComponents();
|
|
132
|
+
const copy = useCheckoutCopy().screens.layout;
|
|
133
|
+
return (
|
|
134
|
+
<Box data-testid="payment-step-total" sx={{ display: "flex", flexDirection: "column", gap: 0.5, py: 1 }}>
|
|
135
|
+
<Text variant="body" size="sm" color="secondary" as="span">
|
|
136
|
+
{copy.totalDueCaption(parts.totalItems)}
|
|
137
|
+
</Text>
|
|
138
|
+
<TotalAmount totalLabel={parts.totalLabel} big />
|
|
139
|
+
{parts.discountLines}
|
|
140
|
+
</Box>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function PhoneOrderSummary({ parts }: { parts: OrderSummaryParts }): JSX.Element {
|
|
145
|
+
const hasChips = Boolean(parts.payer || parts.delivery || parts.orderNote);
|
|
146
|
+
return (
|
|
147
|
+
<Box sx={{ display: "flex", flexDirection: "column", gap: 2.5 }}>
|
|
148
|
+
<PhoneTotal parts={parts} />
|
|
149
|
+
{hasChips ? (
|
|
150
|
+
<Box data-testid="checkout-chips" sx={{ display: "flex", flexWrap: "wrap", gap: 1, alignItems: "center" }}>
|
|
151
|
+
{parts.payer ? <PayerChip payer={parts.payer} /> : null}
|
|
152
|
+
{parts.delivery ? <AddressChip delivery={parts.delivery} /> : null}
|
|
153
|
+
{parts.orderNote ? <NoteChip note={parts.orderNote} /> : null}
|
|
154
|
+
</Box>
|
|
155
|
+
) : null}
|
|
156
|
+
</Box>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { Box } from "@mui/material";
|
|
2
|
+
import type { JSX, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { formatCpf } from "../../card";
|
|
5
|
+
|
|
6
|
+
import { useCheckoutCopy } from "./copy-context";
|
|
7
|
+
import { PersonOutlineIcon, PlaceOutlinedIcon } from "./icons";
|
|
8
|
+
import { useCheckoutComponents } from "./ui";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What the Pagamento step says about the ORDER around the payment (FUT-3525):
|
|
12
|
+
* its lines and total, who pays, where it goes and the note. On a computer
|
|
13
|
+
* it is a card beside the payment column ({@link OrderSummaryCard}); on a
|
|
14
|
+
* phone the total heads the page and the rest are chips
|
|
15
|
+
* ({@link PhoneOrderSummary}).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** One cart line, as the host words it — the package does no money math. */
|
|
19
|
+
export interface CheckoutSummaryLine {
|
|
20
|
+
key: string;
|
|
21
|
+
quantity: number;
|
|
22
|
+
name: string;
|
|
23
|
+
/** The line's total, formatted by the host. */
|
|
24
|
+
totalLabel: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Where a delivery goes, and the host's way back to change it. */
|
|
28
|
+
export interface CheckoutDeliverySummary {
|
|
29
|
+
/** "Rua Augusta, 1500 · ap 42" — street, number and complement. */
|
|
30
|
+
line1: string;
|
|
31
|
+
/** "Consolação, São Paulo" — district and city. Optional. */
|
|
32
|
+
line2?: string;
|
|
33
|
+
/** Back to the host's address step. */
|
|
34
|
+
onEdit: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Everything the summary shows. Each part is optional and hides itself when absent. */
|
|
38
|
+
export interface OrderSummaryParts {
|
|
39
|
+
totalLabel: string;
|
|
40
|
+
totalItems: number;
|
|
41
|
+
/** The host's discount, fee and delivery lines under the total (FUT-246). */
|
|
42
|
+
discountLines?: ReactNode;
|
|
43
|
+
lines?: CheckoutSummaryLine[];
|
|
44
|
+
/** Who is paying — only for a flow whose Dados step was skipped (FUT-465). */
|
|
45
|
+
payer?: { name?: string; taxId?: string; onEdit: () => void };
|
|
46
|
+
delivery?: CheckoutDeliverySummary;
|
|
47
|
+
/** The host's note control, mounted as it is. */
|
|
48
|
+
orderNote?: ReactNode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Read by a screen reader, not drawn. */
|
|
52
|
+
const VISUALLY_HIDDEN = {
|
|
53
|
+
position: "absolute",
|
|
54
|
+
width: "1px",
|
|
55
|
+
height: "1px",
|
|
56
|
+
overflow: "hidden",
|
|
57
|
+
clip: "rect(0 0 0 0)",
|
|
58
|
+
whiteSpace: "nowrap",
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
/** "Alterar" as the prototype's top-aligned link on the card's edge, not a padded button. */
|
|
62
|
+
const EDIT_SX = { flex: "none", alignSelf: "flex-start", mt: -0.75, mr: -1 } as const;
|
|
63
|
+
|
|
64
|
+
const ROW_SX = { display: "flex", gap: 1.5, alignItems: "flex-start" } as const;
|
|
65
|
+
const ICON_SX = {
|
|
66
|
+
fontSize: 20,
|
|
67
|
+
color: "text.secondary",
|
|
68
|
+
flex: "0 0 auto",
|
|
69
|
+
mt: 0.25,
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
export function NoteIcon({ size = 20 }: { size?: number }): JSX.Element {
|
|
73
|
+
return (
|
|
74
|
+
<Box
|
|
75
|
+
component="svg"
|
|
76
|
+
aria-hidden
|
|
77
|
+
viewBox="0 0 24 24"
|
|
78
|
+
sx={{
|
|
79
|
+
width: size,
|
|
80
|
+
height: size,
|
|
81
|
+
flex: "0 0 auto",
|
|
82
|
+
fill: "none",
|
|
83
|
+
stroke: "currentColor",
|
|
84
|
+
strokeWidth: 2,
|
|
85
|
+
color: "text.secondary",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<path d="M4 4h16v12H8l-4 4z" />
|
|
89
|
+
</Box>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The amount, the one number the whole step is about. */
|
|
94
|
+
export function TotalAmount({
|
|
95
|
+
totalLabel,
|
|
96
|
+
big,
|
|
97
|
+
}: {
|
|
98
|
+
totalLabel: string;
|
|
99
|
+
big: boolean;
|
|
100
|
+
}): JSX.Element {
|
|
101
|
+
const { Text } = useCheckoutComponents();
|
|
102
|
+
return (
|
|
103
|
+
<Box
|
|
104
|
+
sx={{
|
|
105
|
+
"&& > *": {
|
|
106
|
+
fontSize: big ? "2.5rem" : "1.625rem",
|
|
107
|
+
lineHeight: 1.1,
|
|
108
|
+
letterSpacing: big ? "-0.02em" : 0,
|
|
109
|
+
},
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Text
|
|
113
|
+
variant="heading"
|
|
114
|
+
size="md"
|
|
115
|
+
weight="bold"
|
|
116
|
+
color={big ? undefined : "primary"}
|
|
117
|
+
as="span"
|
|
118
|
+
data-testid="pay-bar-total"
|
|
119
|
+
>
|
|
120
|
+
{totalLabel}
|
|
121
|
+
</Text>
|
|
122
|
+
</Box>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function PayerRow({
|
|
127
|
+
payer,
|
|
128
|
+
}: {
|
|
129
|
+
payer: NonNullable<OrderSummaryParts["payer"]>;
|
|
130
|
+
}): JSX.Element {
|
|
131
|
+
const { Button, Text } = useCheckoutComponents();
|
|
132
|
+
const copy = useCheckoutCopy().screens.payer;
|
|
133
|
+
const cpf = payer.taxId?.trim()
|
|
134
|
+
? copy.taxId(formatCpf(payer.taxId))
|
|
135
|
+
: copy.taxIdAlreadyKnown;
|
|
136
|
+
return (
|
|
137
|
+
<Box
|
|
138
|
+
data-testid="checkout-payer"
|
|
139
|
+
sx={ROW_SX}
|
|
140
|
+
aria-label={
|
|
141
|
+
payer.name ? copy.payingAs(payer.name) : copy.payingWithSavedDetails
|
|
142
|
+
}
|
|
143
|
+
role="group"
|
|
144
|
+
>
|
|
145
|
+
<PersonOutlineIcon sx={ICON_SX} />
|
|
146
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
147
|
+
<Text variant="body" size="sm" as="p" data-testid="checkout-payer-name">
|
|
148
|
+
{payer.name ?? copy.payingWithSavedDetails}
|
|
149
|
+
</Text>
|
|
150
|
+
<Text variant="body" size="xs" as="p" data-testid="checkout-payer-cpf">
|
|
151
|
+
{cpf}
|
|
152
|
+
</Text>
|
|
153
|
+
</Box>
|
|
154
|
+
<Box sx={EDIT_SX}>
|
|
155
|
+
<Button
|
|
156
|
+
variant="text"
|
|
157
|
+
color="primary"
|
|
158
|
+
size="sm"
|
|
159
|
+
onClick={payer.onEdit}
|
|
160
|
+
dataTestId="checkout-payer-edit"
|
|
161
|
+
>
|
|
162
|
+
{copy.changeAction}
|
|
163
|
+
</Button>
|
|
164
|
+
</Box>
|
|
165
|
+
</Box>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function AddressRow({
|
|
170
|
+
delivery,
|
|
171
|
+
}: {
|
|
172
|
+
delivery: CheckoutDeliverySummary;
|
|
173
|
+
}): JSX.Element {
|
|
174
|
+
const { Button, Text } = useCheckoutComponents();
|
|
175
|
+
const copy = useCheckoutCopy().screens;
|
|
176
|
+
return (
|
|
177
|
+
<Box data-testid="checkout-address" sx={ROW_SX}>
|
|
178
|
+
<PlaceOutlinedIcon sx={ICON_SX} />
|
|
179
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
180
|
+
<Text
|
|
181
|
+
variant="body"
|
|
182
|
+
size="sm"
|
|
183
|
+
as="p"
|
|
184
|
+
data-testid="checkout-address-line1"
|
|
185
|
+
>
|
|
186
|
+
{delivery.line1}
|
|
187
|
+
</Text>
|
|
188
|
+
{delivery.line2 ? (
|
|
189
|
+
<Text variant="body" size="xs" as="p">
|
|
190
|
+
{delivery.line2}
|
|
191
|
+
</Text>
|
|
192
|
+
) : null}
|
|
193
|
+
</Box>
|
|
194
|
+
<Box sx={EDIT_SX}>
|
|
195
|
+
<Button
|
|
196
|
+
variant="text"
|
|
197
|
+
color="primary"
|
|
198
|
+
size="sm"
|
|
199
|
+
onClick={delivery.onEdit}
|
|
200
|
+
dataTestId="checkout-address-edit"
|
|
201
|
+
>
|
|
202
|
+
<span aria-hidden>{copy.payer.changeAction}</span>
|
|
203
|
+
<Box component="span" sx={VISUALLY_HIDDEN}>
|
|
204
|
+
{copy.layout.changeAddress}
|
|
205
|
+
</Box>
|
|
206
|
+
</Button>
|
|
207
|
+
</Box>
|
|
208
|
+
</Box>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const DIVIDER = <Box sx={{ height: "1px", bgcolor: "divider" }} />;
|
|
213
|
+
|
|
214
|
+
/** The cart's lines, "2× X-Burger … R$ 36,00", when the host passed them. */
|
|
215
|
+
function SummaryLines({ lines }: { lines: CheckoutSummaryLine[] }): JSX.Element {
|
|
216
|
+
const { Text } = useCheckoutComponents();
|
|
217
|
+
const copy = useCheckoutCopy().screens.layout;
|
|
218
|
+
return (
|
|
219
|
+
<Box component="ul" data-testid="checkout-summary-lines" sx={{ m: 0, p: 0, listStyle: "none", display: "flex", flexDirection: "column", gap: 1 }}>
|
|
220
|
+
{lines.map((line) => (
|
|
221
|
+
<Box component="li" key={line.key} sx={{ display: "flex", justifyContent: "space-between", gap: 2 }}>
|
|
222
|
+
<Text variant="body" size="sm" as="span">
|
|
223
|
+
{copy.lineLabel(line.quantity, line.name)}
|
|
224
|
+
</Text>
|
|
225
|
+
<Text variant="body" size="sm" as="span">
|
|
226
|
+
{line.totalLabel}
|
|
227
|
+
</Text>
|
|
228
|
+
</Box>
|
|
229
|
+
))}
|
|
230
|
+
</Box>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** "Total" and the amount on one line, the host's money rows under it. */
|
|
235
|
+
function SummaryTotal({ totalLabel, discountLines }: { totalLabel: string; discountLines?: ReactNode }): JSX.Element {
|
|
236
|
+
const { Text } = useCheckoutComponents();
|
|
237
|
+
const copy = useCheckoutCopy().screens.layout;
|
|
238
|
+
return (
|
|
239
|
+
<Box data-testid="payment-step-total" sx={{ display: "flex", flexDirection: "column", gap: 0.5 }}>
|
|
240
|
+
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 2 }}>
|
|
241
|
+
<Text variant="body" size="md" weight="semibold" as="span">
|
|
242
|
+
{copy.total}
|
|
243
|
+
</Text>
|
|
244
|
+
<TotalAmount totalLabel={totalLabel} big={false} />
|
|
245
|
+
</Box>
|
|
246
|
+
{discountLines}
|
|
247
|
+
</Box>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** The host's note control, on a row of its own with the note icon. */
|
|
252
|
+
function NoteRow({ note }: { note: ReactNode }): JSX.Element {
|
|
253
|
+
return (
|
|
254
|
+
<Box sx={{ ...ROW_SX, alignItems: "center" }}>
|
|
255
|
+
<NoteIcon />
|
|
256
|
+
<Box sx={{ flex: 1, minWidth: 0, "& .MuiButton-root, & button": { px: 0 } }}>{note}</Box>
|
|
257
|
+
</Box>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The computer's summary card, beside the payment (FUT-3525, layout A).
|
|
263
|
+
* Sticky, so the total and the address stay in view while the buyer fills
|
|
264
|
+
* the card form.
|
|
265
|
+
*/
|
|
266
|
+
export function OrderSummaryCard({ parts }: { parts: OrderSummaryParts }): JSX.Element {
|
|
267
|
+
const { Text } = useCheckoutComponents();
|
|
268
|
+
const copy = useCheckoutCopy().screens.layout;
|
|
269
|
+
const lines = parts.lines ?? [];
|
|
270
|
+
const hasWho = Boolean(parts.payer || parts.delivery || parts.orderNote);
|
|
271
|
+
return (
|
|
272
|
+
<Box
|
|
273
|
+
component="aside"
|
|
274
|
+
aria-label={copy.summaryTitle}
|
|
275
|
+
data-testid="checkout-summary"
|
|
276
|
+
sx={{ position: "sticky", top: 24, display: "flex", flexDirection: "column", gap: 2, p: 2.75, bgcolor: "background.paper", border: "1px solid", borderColor: "divider", borderRadius: 2.5 }}
|
|
277
|
+
>
|
|
278
|
+
<Text variant="heading" size="md" weight="bold" as="h2">
|
|
279
|
+
{copy.summaryTitle}
|
|
280
|
+
</Text>
|
|
281
|
+
{lines.length > 0 ? <SummaryLines lines={lines} /> : null}
|
|
282
|
+
{lines.length > 0 ? DIVIDER : null}
|
|
283
|
+
<SummaryTotal totalLabel={parts.totalLabel} discountLines={parts.discountLines} />
|
|
284
|
+
{hasWho ? DIVIDER : null}
|
|
285
|
+
{parts.payer ? <PayerRow payer={parts.payer} /> : null}
|
|
286
|
+
{parts.delivery ? <AddressRow delivery={parts.delivery} /> : null}
|
|
287
|
+
{parts.orderNote ? <NoteRow note={parts.orderNote} /> : null}
|
|
288
|
+
</Box>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
@@ -129,7 +129,7 @@ function Actions({ justCopied, onCopyAgain, onShowQr, onPreferCard }: Pick<PixAf
|
|
|
129
129
|
const { Button } = useCheckoutComponents();
|
|
130
130
|
const copy = useCheckoutCopy().screens.pix;
|
|
131
131
|
return (
|
|
132
|
-
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(
|
|
132
|
+
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 1.25, "& button": { minHeight: 48, height: "100%" }, "& button, & button *": { fontWeight: 700 }, [PIX_WIDE]: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 1.5, "& button": { height: "auto" } } }}>
|
|
133
133
|
{/* Narrow, a grid cell; wide, its own width beside the link (the prototype's row). */}
|
|
134
134
|
<Box sx={{ minWidth: 0, [PIX_WIDE]: { flex: "none", "& > *": { width: "auto" } } }}>
|
|
135
135
|
<Button variant="outline" color="neutral" size="md" fullWidth icon={<ContentCopyIcon fontSize="small" />} onClick={onCopyAgain} dataTestId="pix-copy-again">
|