@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
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
import { Box } from "@mui/material";
|
|
2
|
-
import {
|
|
2
|
+
import type { JSX, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
import { useCheckoutCopy } from "./copy-context";
|
|
5
5
|
import { ContentCopyIcon } from "./icons";
|
|
6
|
+
import { StickyPayBar, useCheckoutLayout } from "./payment-step-layout";
|
|
6
7
|
import { Emphasized, PIX_WIDE, PixQr } from "./pix-parts";
|
|
7
8
|
import type { PixCharge } from "./types";
|
|
8
9
|
import { useCheckoutComponents } from "./ui";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* The PIX pane before a copy (the 2026-10-06 Pix redesign
|
|
12
|
+
* The PIX pane before a copy (the 2026-10-06 Pix redesign, laid out again by
|
|
13
|
+
* the 2026-10-11 Pagamento redesign, FUT-3525).
|
|
12
14
|
*
|
|
13
|
-
* Narrow — a phone —
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the QR
|
|
18
|
-
* banking.
|
|
15
|
+
* Narrow — a phone — the code and its button come first and the QR follows,
|
|
16
|
+
* "Ou pague de outro aparelho": the buyer pays on the phone they are holding
|
|
17
|
+
* and cannot scan a QR drawn on that same screen. Wide — a computer — the two
|
|
18
|
+
* stand side by side, the QR on the left. The Copia/QR tabs this replaced hid
|
|
19
|
+
* the QR behind a tap the phone's own buyer never needed.
|
|
19
20
|
*
|
|
20
21
|
* Both panels are always mounted and the switch is CSS (`PIX_WIDE`), so the
|
|
21
|
-
* layout needs no measurement: the first paint is already right
|
|
22
|
-
* in the document whichever tab is showing.
|
|
22
|
+
* layout needs no measurement: the first paint is already right.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
export type PixTab = "copy" | "qr";
|
|
26
|
-
|
|
27
25
|
/** A narrow-only element: shown under the container query's threshold. */
|
|
28
26
|
const NARROW_ONLY = { [PIX_WIDE]: { display: "none" } } as const;
|
|
29
27
|
|
|
@@ -32,120 +30,82 @@ function wideOnly(display: string) {
|
|
|
32
30
|
return { display: "none", [PIX_WIDE]: { display } } as const;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
/**
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** The small capitals over each wide column. Uppercase by CSS only. */
|
|
47
|
-
function ColumnHeading({ children, center }: { children: ReactNode; center?: boolean }): JSX.Element {
|
|
33
|
+
/** The small capitals over each column. Uppercase by CSS only. */
|
|
34
|
+
function ColumnHeading({
|
|
35
|
+
children,
|
|
36
|
+
center,
|
|
37
|
+
sx,
|
|
38
|
+
}: {
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
center?: boolean;
|
|
41
|
+
sx?: object;
|
|
42
|
+
}): JSX.Element {
|
|
48
43
|
const { Text } = useCheckoutComponents();
|
|
49
44
|
return (
|
|
50
|
-
<Box sx={{
|
|
51
|
-
<Text
|
|
45
|
+
<Box sx={{ textAlign: center ? "center" : "left", ...sx }}>
|
|
46
|
+
<Text
|
|
47
|
+
variant="body"
|
|
48
|
+
size="xs"
|
|
49
|
+
weight="bold"
|
|
50
|
+
color="secondary"
|
|
51
|
+
as="p"
|
|
52
|
+
style={{
|
|
53
|
+
textTransform: "uppercase",
|
|
54
|
+
letterSpacing: "0.04em",
|
|
55
|
+
margin: 0,
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
52
58
|
{children}
|
|
53
59
|
</Text>
|
|
54
60
|
</Box>
|
|
55
61
|
);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/** The ARIA tabs pattern's keys: arrows move and select, Home and End jump. */
|
|
61
|
-
function nextTab(key: string, tab: PixTab): PixTab | null {
|
|
62
|
-
const at = TAB_ORDER.indexOf(tab);
|
|
63
|
-
if (key === "ArrowRight") return TAB_ORDER[(at + 1) % TAB_ORDER.length] ?? null;
|
|
64
|
-
if (key === "ArrowLeft") return TAB_ORDER[(at + TAB_ORDER.length - 1) % TAB_ORDER.length] ?? null;
|
|
65
|
-
if (key === "Home") return TAB_ORDER[0] ?? null;
|
|
66
|
-
if (key === "End") return TAB_ORDER[TAB_ORDER.length - 1] ?? null;
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** DOM ids for the tabs and panels, unique per pane (two panes on one page must not collide). */
|
|
71
|
-
function paneIds(base: string, id: PixTab): { tab: string; panel: string } {
|
|
72
|
-
return { tab: `${base}-tab-${id}`, panel: `${base}-panel-${id}` };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function PixTabs({ tab, onTab, idBase }: { tab: PixTab; onTab: (tab: PixTab) => void; idBase: string }): JSX.Element {
|
|
76
|
-
const copy = useCheckoutCopy().screens.pix;
|
|
77
|
-
const tabs: { id: PixTab; label: string }[] = [
|
|
78
|
-
{ id: "copy", label: copy.copyPasteTab },
|
|
79
|
-
{ id: "qr", label: copy.qrTab },
|
|
80
|
-
];
|
|
81
|
-
const onKeyDown = (event: KeyboardEvent<HTMLElement>): void => {
|
|
82
|
-
const next = nextTab(event.key, tab);
|
|
83
|
-
if (!next) return;
|
|
84
|
-
event.preventDefault();
|
|
85
|
-
onTab(next);
|
|
86
|
-
document.getElementById(paneIds(idBase, next).tab)?.focus();
|
|
87
|
-
};
|
|
88
|
-
return (
|
|
89
|
-
<Box
|
|
90
|
-
role="tablist"
|
|
91
|
-
aria-label={copy.tabsLabel}
|
|
92
|
-
onKeyDown={onKeyDown}
|
|
93
|
-
sx={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 1fr))", borderBottom: "1px solid", borderColor: "divider", ...NARROW_ONLY }}
|
|
94
|
-
>
|
|
95
|
-
{tabs.map((item) => {
|
|
96
|
-
const selected = item.id === tab;
|
|
97
|
-
return (
|
|
98
|
-
<Box
|
|
99
|
-
key={item.id}
|
|
100
|
-
component="button"
|
|
101
|
-
type="button"
|
|
102
|
-
role="tab"
|
|
103
|
-
id={paneIds(idBase, item.id).tab}
|
|
104
|
-
aria-selected={selected}
|
|
105
|
-
aria-controls={paneIds(idBase, item.id).panel}
|
|
106
|
-
tabIndex={selected ? 0 : -1}
|
|
107
|
-
data-testid={`pix-tab-${item.id}`}
|
|
108
|
-
onClick={() => onTab(item.id)}
|
|
109
|
-
sx={{
|
|
110
|
-
minHeight: 44,
|
|
111
|
-
border: 0,
|
|
112
|
-
borderBottom: "3px solid",
|
|
113
|
-
borderColor: selected ? "primary.main" : "transparent",
|
|
114
|
-
bgcolor: "transparent",
|
|
115
|
-
font: "inherit",
|
|
116
|
-
fontSize: "0.875rem",
|
|
117
|
-
fontWeight: selected ? 700 : 600,
|
|
118
|
-
color: selected ? "text.primary" : "text.secondary",
|
|
119
|
-
cursor: "pointer",
|
|
120
|
-
"&:focus-visible": { outline: "2px solid", outlineColor: "primary.main", outlineOffset: -2 },
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
{item.label}
|
|
124
|
-
</Box>
|
|
125
|
-
);
|
|
126
|
-
})}
|
|
127
|
-
</Box>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function QrPanel({ pix, shown, idBase }: { pix: PixCharge; shown: boolean; idBase: string }): JSX.Element {
|
|
64
|
+
function QrPanel({ pix }: { pix: PixCharge }): JSX.Element {
|
|
132
65
|
const { Text } = useCheckoutComponents();
|
|
133
66
|
const copy = useCheckoutCopy().screens.pix;
|
|
134
67
|
return (
|
|
135
68
|
<Box
|
|
136
|
-
|
|
137
|
-
role="tabpanel"
|
|
138
|
-
aria-labelledby={paneIds(idBase, "qr").tab}
|
|
69
|
+
data-testid="pix-qr-panel"
|
|
139
70
|
sx={{
|
|
140
|
-
|
|
71
|
+
display: "flex",
|
|
72
|
+
flexDirection: "column",
|
|
141
73
|
alignItems: "center",
|
|
74
|
+
gap: 1.25,
|
|
75
|
+
minWidth: 0,
|
|
142
76
|
textAlign: "center",
|
|
143
|
-
|
|
77
|
+
pt: 2,
|
|
78
|
+
borderTop: "1px solid",
|
|
79
|
+
borderColor: "divider",
|
|
80
|
+
[PIX_WIDE]: {
|
|
81
|
+
gap: 1.75,
|
|
82
|
+
p: 3,
|
|
83
|
+
borderTop: "1px solid",
|
|
84
|
+
borderRadius: 1.75,
|
|
85
|
+
border: "1px solid",
|
|
86
|
+
borderColor: "divider",
|
|
87
|
+
bgcolor: "action.hover",
|
|
88
|
+
},
|
|
144
89
|
}}
|
|
145
90
|
>
|
|
146
|
-
<ColumnHeading center
|
|
147
|
-
|
|
148
|
-
|
|
91
|
+
<ColumnHeading center sx={NARROW_ONLY}>
|
|
92
|
+
{copy.otherDeviceHeading}
|
|
93
|
+
</ColumnHeading>
|
|
94
|
+
<ColumnHeading center sx={wideOnly("block")}>
|
|
95
|
+
{copy.qrHeading}
|
|
96
|
+
</ColumnHeading>
|
|
97
|
+
<Box
|
|
98
|
+
sx={{
|
|
99
|
+
"& svg": { width: 168, height: 168 },
|
|
100
|
+
[PIX_WIDE]: { "& svg": { width: 232, height: 232 } },
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<PixQr
|
|
104
|
+
payload={pix.copyPaste}
|
|
105
|
+
size={232}
|
|
106
|
+
alt={copy.qrAlt}
|
|
107
|
+
testId="pix-qr"
|
|
108
|
+
/>
|
|
149
109
|
</Box>
|
|
150
110
|
<Text variant="body" size="sm" color="secondary" as="p" style={{ margin: 0, maxWidth: 280 }}>
|
|
151
111
|
<Box component="span" sx={NARROW_ONLY}>{copy.qrTabCaption}</Box>
|
|
@@ -157,11 +117,62 @@ function QrPanel({ pix, shown, idBase }: { pix: PixCharge; shown: boolean; idBas
|
|
|
157
117
|
);
|
|
158
118
|
}
|
|
159
119
|
|
|
160
|
-
|
|
161
|
-
|
|
120
|
+
/**
|
|
121
|
+
* The one action on the pane: the prototype's 56px bar (52 wide), whatever
|
|
122
|
+
* the host's lg is — and on the phone's Pagamento step, the sticky pay bar's
|
|
123
|
+
* button (FUT-3525).
|
|
124
|
+
*/
|
|
125
|
+
function CopyButton({ onCopy }: { onCopy: () => void }): JSX.Element {
|
|
126
|
+
const { Button } = useCheckoutComponents();
|
|
127
|
+
const copy = useCheckoutCopy().screens.pix;
|
|
128
|
+
return (
|
|
129
|
+
<StickyPayBar>
|
|
130
|
+
<Box
|
|
131
|
+
sx={{
|
|
132
|
+
"& > *": { minHeight: 56 },
|
|
133
|
+
"& > *, & > * *": { fontWeight: 700 },
|
|
134
|
+
[PIX_WIDE]: { "& > *": { minHeight: 52 } },
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<Button
|
|
138
|
+
variant="solid"
|
|
139
|
+
color="primary"
|
|
140
|
+
size="lg"
|
|
141
|
+
fullWidth
|
|
142
|
+
icon={<ContentCopyIcon fontSize="small" />}
|
|
143
|
+
onClick={onCopy}
|
|
144
|
+
dataTestId="pix-copy"
|
|
145
|
+
>
|
|
146
|
+
{copy.copyAction}
|
|
147
|
+
</Button>
|
|
148
|
+
</Box>
|
|
149
|
+
</StickyPayBar>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function CopyPanel({
|
|
154
|
+
pix,
|
|
155
|
+
onCopy,
|
|
156
|
+
inPanel,
|
|
157
|
+
}: {
|
|
158
|
+
pix: PixCharge;
|
|
159
|
+
onCopy: () => void;
|
|
160
|
+
/** False when the phone's pay bar carries the button, at the pane's foot. */
|
|
161
|
+
inPanel: boolean;
|
|
162
|
+
}): JSX.Element {
|
|
163
|
+
const { Text } = useCheckoutComponents();
|
|
162
164
|
const copy = useCheckoutCopy().screens.pix;
|
|
163
165
|
return (
|
|
164
|
-
<Box
|
|
166
|
+
<Box
|
|
167
|
+
data-testid="pix-copy-panel"
|
|
168
|
+
sx={{
|
|
169
|
+
display: "flex",
|
|
170
|
+
flexDirection: "column",
|
|
171
|
+
gap: 1.75,
|
|
172
|
+
minWidth: 0,
|
|
173
|
+
[PIX_WIDE]: { justifyContent: "center" },
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
165
176
|
<ColumnHeading>{copy.copyPasteHeading}</ColumnHeading>
|
|
166
177
|
{/*
|
|
167
178
|
The box IS the code's frame, so whatever the host draws for inline code
|
|
@@ -183,14 +194,15 @@ function CopyPanel({ pix, shown, onCopy, idBase }: { pix: PixCharge; shown: bool
|
|
|
183
194
|
{pix.copyPaste}
|
|
184
195
|
</Text>
|
|
185
196
|
</Box>
|
|
186
|
-
{
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
{inPanel ? <CopyButton onCopy={onCopy} /> : null}
|
|
198
|
+
<Text
|
|
199
|
+
variant="body"
|
|
200
|
+
size="sm"
|
|
201
|
+
color="secondary"
|
|
202
|
+
as="p"
|
|
203
|
+
style={{ margin: 0 }}
|
|
204
|
+
>
|
|
205
|
+
<Box component="span" sx={{ display: "block", ...NARROW_ONLY }}>
|
|
194
206
|
<Emphasized text={copy.copyPasteHint} />
|
|
195
207
|
</Box>
|
|
196
208
|
<Box component="span" sx={wideOnly("block")}>
|
|
@@ -201,38 +213,51 @@ function CopyPanel({ pix, shown, onCopy, idBase }: { pix: PixCharge; shown: bool
|
|
|
201
213
|
);
|
|
202
214
|
}
|
|
203
215
|
|
|
204
|
-
/**
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
216
|
+
/**
|
|
217
|
+
* The pane itself. Narrow, the code comes first — the buyer pays on the phone
|
|
218
|
+
* they are holding and cannot scan a QR drawn on that same screen — and the
|
|
219
|
+
* QR follows under a hairline, for paying from another device. Wide, the two
|
|
220
|
+
* stand side by side: the QR for the phone in the buyer's hand, the code for
|
|
221
|
+
* their internet banking.
|
|
222
|
+
*/
|
|
223
|
+
export function PixPane({
|
|
224
|
+
pix,
|
|
225
|
+
onCopy,
|
|
226
|
+
}: {
|
|
227
|
+
pix: PixCharge;
|
|
228
|
+
onCopy: () => void;
|
|
229
|
+
}): JSX.Element {
|
|
230
|
+
// The phone's Pagamento step pins the button to the bottom of the screen
|
|
231
|
+
// (FUT-3525), so it goes LAST in the pane — under the QR — where a sticky
|
|
232
|
+
// bar rests when the page is scrolled to its end.
|
|
233
|
+
const layout = useCheckoutLayout();
|
|
234
|
+
const barAtFoot = layout !== null && !layout.wide;
|
|
222
235
|
return (
|
|
223
|
-
|
|
224
|
-
|
|
236
|
+
<Box
|
|
237
|
+
sx={{
|
|
238
|
+
display: "grid",
|
|
239
|
+
gap: 2.5,
|
|
240
|
+
[PIX_WIDE]: {
|
|
241
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
242
|
+
gap: 4,
|
|
243
|
+
alignItems: "stretch",
|
|
244
|
+
},
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
<Box sx={{ minWidth: 0, [PIX_WIDE]: { order: 2 } }}>
|
|
248
|
+
<CopyPanel pix={pix} onCopy={onCopy} inPanel={!barAtFoot} />
|
|
249
|
+
</Box>
|
|
225
250
|
<Box
|
|
226
251
|
sx={{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
252
|
+
minWidth: 0,
|
|
253
|
+
display: "flex",
|
|
254
|
+
flexDirection: "column",
|
|
255
|
+
[PIX_WIDE]: { order: 1 },
|
|
230
256
|
}}
|
|
231
257
|
>
|
|
232
|
-
<QrPanel pix={pix}
|
|
233
|
-
<OrDivider />
|
|
234
|
-
<CopyPanel pix={pix} shown={tab === "copy"} onCopy={onCopy} idBase={idBase} />
|
|
258
|
+
<QrPanel pix={pix} />
|
|
235
259
|
</Box>
|
|
236
|
-
|
|
260
|
+
{barAtFoot ? <CopyButton onCopy={onCopy} /> : null}
|
|
261
|
+
</Box>
|
|
237
262
|
);
|
|
238
263
|
}
|
|
@@ -9,11 +9,13 @@ import QRCode from "react-qr-code";
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The pane switches from
|
|
13
|
-
* window's: the same checkout is mounted
|
|
14
|
-
* phone, and only the room it was given says whether two
|
|
12
|
+
* The pane switches from one column to two by its OWN width, never the
|
|
13
|
+
* window's: the same checkout is mounted beside an order summary, alone in a
|
|
14
|
+
* column and on a phone, and only the room it was given says whether two
|
|
15
|
+
* columns fit. 680px is what the Pagamento step's payment column leaves it
|
|
16
|
+
* beside the summary card (FUT-3525).
|
|
15
17
|
*/
|
|
16
|
-
export const PIX_WIDE = "@container pixpane (min-width:
|
|
18
|
+
export const PIX_WIDE = "@container pixpane (min-width: 680px)";
|
|
17
19
|
|
|
18
20
|
/** The container the {@link PIX_WIDE} query reads. */
|
|
19
21
|
export const PIX_CONTAINER = { containerType: "inline-size", containerName: "pixpane" } as const;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Box } from "@mui/material";
|
|
2
|
-
import { useEffect, useMemo, useRef,
|
|
2
|
+
import { useEffect, useMemo, useRef, type JSX } from "react";
|
|
3
3
|
|
|
4
4
|
import { useCheckoutCopy } from "./copy-context";
|
|
5
5
|
import { PixAfterCopy } from "./pix-after-copy";
|
|
6
|
-
import { PixPane
|
|
6
|
+
import { PixPane } from "./pix-pane";
|
|
7
|
+
import { useCheckoutLayout } from "./payment-step-layout";
|
|
7
8
|
import { PIX_CONTAINER, PIX_WIDE, useClipboardCopy } from "./pix-parts";
|
|
8
9
|
import { usePixStage } from "./pix-stage";
|
|
9
10
|
import type { PixPaneCopy, SettlingCopy } from "./screens-copy";
|
|
@@ -202,11 +203,27 @@ function usePixPolling(order: CheckoutOrder, onResolved: (status: OrderStatus) =
|
|
|
202
203
|
return polling;
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
/**
|
|
207
|
+
* The pane's title, at the prototype's scale: 20px, 26px wide — left out
|
|
208
|
+
* inside the Pagamento step, where the "Pix" tab names the pane (FUT-3525).
|
|
209
|
+
*/
|
|
210
|
+
function PixTitle(): JSX.Element | null {
|
|
206
211
|
const { Text } = useCheckoutComponents();
|
|
212
|
+
const copy = useCheckoutCopy().screens.pix;
|
|
213
|
+
const inStep = useCheckoutLayout() !== null;
|
|
214
|
+
if (inStep) return null;
|
|
215
|
+
return (
|
|
216
|
+
<Box sx={{ "&& > *": { fontSize: "1.25rem", lineHeight: 1.25 }, [PIX_WIDE]: { "&& > *": { fontSize: "1.625rem" } } }}>
|
|
217
|
+
<Text variant="heading" size="md" weight="bold" as="h2" style={{ margin: 0 }}>
|
|
218
|
+
{copy.heading}
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function PixBody({ order, pix, poll }: { order: CheckoutOrder; pix: PixCharge; poll: ReturnType<typeof usePixPolling> }): JSX.Element {
|
|
207
225
|
const copy = useCheckoutCopy().screens.pix;
|
|
208
226
|
const stage = usePixStage();
|
|
209
|
-
const [tab, setTab] = useState<PixTab>("copy");
|
|
210
227
|
const { copy: writeClipboard, justCopied } = useClipboardCopy(pix.copyPaste);
|
|
211
228
|
// Leaving the payment step (a method switch, a terminal status) ends the
|
|
212
229
|
// after-copy face with it: the next pane starts on the code.
|
|
@@ -240,10 +257,8 @@ function PixBody({ order, pix, poll }: { order: CheckoutOrder; pix: PixCharge; p
|
|
|
240
257
|
deadline={time}
|
|
241
258
|
justCopied={justCopied}
|
|
242
259
|
onCopyAgain={() => void writeClipboard()}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
setAfterCopy(false);
|
|
246
|
-
}}
|
|
260
|
+
// Back to the pane, where the QR is always on screen now.
|
|
261
|
+
onShowQr={() => setAfterCopy(false)}
|
|
247
262
|
onPreferCard={stage.preferCard}
|
|
248
263
|
footer={footerLine(copy.verifying)}
|
|
249
264
|
/>
|
|
@@ -251,16 +266,9 @@ function PixBody({ order, pix, poll }: { order: CheckoutOrder; pix: PixCharge; p
|
|
|
251
266
|
}
|
|
252
267
|
return (
|
|
253
268
|
<>
|
|
254
|
-
|
|
255
|
-
<Box sx={{ "&& > *": { fontSize: "1.25rem", lineHeight: 1.25 }, [PIX_WIDE]: { "&& > *": { fontSize: "1.625rem" } } }}>
|
|
256
|
-
<Text variant="heading" size="md" weight="bold" as="h2" style={{ margin: 0 }}>
|
|
257
|
-
{copy.heading}
|
|
258
|
-
</Text>
|
|
259
|
-
</Box>
|
|
269
|
+
<PixTitle />
|
|
260
270
|
<PixPane
|
|
261
271
|
pix={pix}
|
|
262
|
-
tab={tab}
|
|
263
|
-
onTab={setTab}
|
|
264
272
|
onCopy={() => {
|
|
265
273
|
// Only a copy the clipboard TOOK moves on: the next face opens on
|
|
266
274
|
// "Código Pix copiado", and a failed write leaves the code on screen
|
|
@@ -287,6 +295,9 @@ export function PixView({
|
|
|
287
295
|
const { Text } = useCheckoutComponents();
|
|
288
296
|
const copy = useCheckoutCopy().screens.pix;
|
|
289
297
|
const poll = usePixPolling(order, onResolved, pollIntervalMs);
|
|
298
|
+
// The Pagamento step's panel is the card there (FUT-3525); drawing another
|
|
299
|
+
// inside it would be a box in a box.
|
|
300
|
+
const inStep = useCheckoutLayout() !== null;
|
|
290
301
|
const pix = order.pix;
|
|
291
302
|
if (!pix) {
|
|
292
303
|
return (
|
|
@@ -307,7 +318,16 @@ export function PixView({
|
|
|
307
318
|
display: "flex",
|
|
308
319
|
flexDirection: "column",
|
|
309
320
|
gap: 2,
|
|
310
|
-
[PIX_WIDE]:
|
|
321
|
+
[PIX_WIDE]: inStep
|
|
322
|
+
? { gap: 2.5 }
|
|
323
|
+
: {
|
|
324
|
+
gap: 2.5,
|
|
325
|
+
p: 3.5,
|
|
326
|
+
bgcolor: "background.paper",
|
|
327
|
+
border: "1px solid",
|
|
328
|
+
borderColor: "divider",
|
|
329
|
+
borderRadius: 2,
|
|
330
|
+
},
|
|
311
331
|
}}
|
|
312
332
|
>
|
|
313
333
|
<PixBody order={order} pix={pix} poll={poll} />
|
|
@@ -133,17 +133,22 @@ export interface PixPaneCopy {
|
|
|
133
133
|
preferCardAction: string;
|
|
134
134
|
/** The after-copy state's poll line. */
|
|
135
135
|
verifying: string;
|
|
136
|
-
/**
|
|
136
|
+
/** @deprecated Unread since FUT-3525, which took the narrow layout's Copia/QR tabs out. */
|
|
137
137
|
tabsLabel: string;
|
|
138
|
+
/** @deprecated Unread since FUT-3525 — see {@link PixPaneCopy.tabsLabel}. */
|
|
138
139
|
copyPasteTab: string;
|
|
140
|
+
/** @deprecated Unread since FUT-3525 — see {@link PixPaneCopy.tabsLabel}. */
|
|
139
141
|
qrTab: string;
|
|
140
|
-
/** The wide layout's two column headings
|
|
142
|
+
/** The wide layout's two column headings. */
|
|
141
143
|
qrHeading: string;
|
|
144
|
+
/** The phone's heading over the QR, under the code (FUT-3525). */
|
|
145
|
+
otherDeviceHeading: string;
|
|
142
146
|
copyPasteHeading: string;
|
|
147
|
+
/** @deprecated Unread since FUT-3525, which took the "ou" between the wide columns out. */
|
|
143
148
|
or: string;
|
|
144
149
|
/** Under the QR in the wide layout; `**…**` marks the bold span. */
|
|
145
150
|
qrInstructions: string;
|
|
146
|
-
/** Under the QR in the narrow layout
|
|
151
|
+
/** Under the QR in the narrow layout (a tab until FUT-3525, hence the name). */
|
|
147
152
|
qrTabCaption: string;
|
|
148
153
|
/** Under the copy button: narrow and wide. `**…**` marks the bold span. */
|
|
149
154
|
copyPasteHint: string;
|
|
@@ -301,6 +306,29 @@ export interface CheckoutValidationCopy {
|
|
|
301
306
|
required: string;
|
|
302
307
|
}
|
|
303
308
|
|
|
309
|
+
/**
|
|
310
|
+
* The Pagamento step's own frame (FUT-3525): the order summary beside the
|
|
311
|
+
* payment on a computer, and the phone's header, total and pay bar.
|
|
312
|
+
*/
|
|
313
|
+
export interface PaymentLayoutCopy {
|
|
314
|
+
/** Over the method tabs on a computer. */
|
|
315
|
+
payHeading: string;
|
|
316
|
+
/** The summary card's title. */
|
|
317
|
+
summaryTitle: string;
|
|
318
|
+
/** The summary's and the pay bar's label over the amount. */
|
|
319
|
+
total: string;
|
|
320
|
+
/** The phone's caption over the big amount — "Total a pagar · 2 itens". */
|
|
321
|
+
totalDueCaption(items: number): string;
|
|
322
|
+
/** One cart line in the summary — "2× X-Burger". */
|
|
323
|
+
lineLabel(quantity: number, name: string): string;
|
|
324
|
+
/** The phone's payer chip, for a CPF the store already holds — "CPF ✓". A typed one is shown masked. */
|
|
325
|
+
taxIdOnFileShort: string;
|
|
326
|
+
/** The address control's accessible name; its visible word is {@link PayerSummaryCopy.changeAction}. */
|
|
327
|
+
changeAddress: string;
|
|
328
|
+
/** The phone's step line — "Etapa 2 de 3 · Pagamento". */
|
|
329
|
+
stepProgress(step: number, steps: number, label: string): string;
|
|
330
|
+
}
|
|
331
|
+
|
|
304
332
|
/** Everything above, in the shape the checkout context carries. */
|
|
305
333
|
export interface CheckoutScreensCopy {
|
|
306
334
|
method: MethodPickerCopy;
|
|
@@ -308,6 +336,7 @@ export interface CheckoutScreensCopy {
|
|
|
308
336
|
pix: PixPaneCopy;
|
|
309
337
|
card: CardPaneCopy;
|
|
310
338
|
payer: PayerSummaryCopy;
|
|
339
|
+
layout: PaymentLayoutCopy;
|
|
311
340
|
error: PaymentErrorCopy;
|
|
312
341
|
wallet: WalletCopy;
|
|
313
342
|
hosted: HostedHandoverCopy;
|
|
@@ -54,6 +54,7 @@ export const EN_US_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
|
54
54
|
copyPasteTab: 'Copy and paste',
|
|
55
55
|
qrTab: 'QR code',
|
|
56
56
|
qrHeading: 'On your phone',
|
|
57
|
+
otherDeviceHeading: 'Or pay from another device',
|
|
57
58
|
copyPasteHeading: 'Pix copy and paste',
|
|
58
59
|
or: 'or',
|
|
59
60
|
qrInstructions: 'Open your banking app on your phone, choose **Pix › Scan QR code** and point it at the screen.',
|
|
@@ -99,6 +100,16 @@ export const EN_US_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
|
99
100
|
payingWithSavedDetails: 'Paying with your saved details',
|
|
100
101
|
changeAction: 'Change',
|
|
101
102
|
},
|
|
103
|
+
layout: {
|
|
104
|
+
payHeading: 'How would you like to pay?',
|
|
105
|
+
summaryTitle: 'Order summary',
|
|
106
|
+
total: 'Total',
|
|
107
|
+
totalDueCaption: (items) => `Total due · ${items} ${items === 1 ? 'item' : 'items'}`,
|
|
108
|
+
lineLabel: (quantity, name) => `${quantity}× ${name}`,
|
|
109
|
+
taxIdOnFileShort: 'CPF ✓',
|
|
110
|
+
changeAddress: 'Change address',
|
|
111
|
+
stepProgress: (step, steps, label) => `Step ${step} of ${steps} · ${label}`,
|
|
112
|
+
},
|
|
102
113
|
error: {
|
|
103
114
|
confirming: 'We are confirming your payment',
|
|
104
115
|
cannotContinue: 'Could not continue',
|
|
@@ -43,6 +43,7 @@ export const PT_BR_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
|
43
43
|
copyPasteTab: 'Copia e cola',
|
|
44
44
|
qrTab: 'QR code',
|
|
45
45
|
qrHeading: 'Pelo celular',
|
|
46
|
+
otherDeviceHeading: 'Ou pague de outro aparelho',
|
|
46
47
|
copyPasteHeading: 'Pix Copia e Cola',
|
|
47
48
|
or: 'ou',
|
|
48
49
|
qrInstructions: 'Abra o app do seu banco no celular, escolha **Pix › Ler QR code** e aponte para a tela.',
|
|
@@ -88,6 +89,16 @@ export const PT_BR_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
|
88
89
|
payingWithSavedDetails: 'Pagando com os seus dados salvos',
|
|
89
90
|
changeAction: 'Alterar',
|
|
90
91
|
},
|
|
92
|
+
layout: {
|
|
93
|
+
payHeading: 'Como você quer pagar?',
|
|
94
|
+
summaryTitle: 'Resumo do pedido',
|
|
95
|
+
total: 'Total',
|
|
96
|
+
totalDueCaption: (items) => `Total a pagar · ${items} ${items === 1 ? 'item' : 'itens'}`,
|
|
97
|
+
lineLabel: (quantity, name) => `${quantity}× ${name}`,
|
|
98
|
+
taxIdOnFileShort: 'CPF ✓',
|
|
99
|
+
changeAddress: 'Alterar endereço',
|
|
100
|
+
stepProgress: (step, steps, label) => `Etapa ${step} de ${steps} · ${label}`,
|
|
101
|
+
},
|
|
91
102
|
error: {
|
|
92
103
|
confirming: 'Estamos confirmando seu pagamento',
|
|
93
104
|
cannotContinue: 'Não foi possível continuar',
|
package/src/index.ts
CHANGED
|
@@ -60,6 +60,7 @@ export {
|
|
|
60
60
|
type CheckoutFlowProps,
|
|
61
61
|
} from './components/checkout/checkout-flow';
|
|
62
62
|
export type { BackActionEmphasis } from './components/checkout/payment-status-parts';
|
|
63
|
+
export type { CheckoutDeliverySummary, CheckoutSummaryLine } from './components/checkout/payment-summary';
|
|
63
64
|
|
|
64
65
|
// ---------------------------------------------------------------------------
|
|
65
66
|
// The MOUNTED buyer checkout (FUT-741) and the checkout PIPELINE (FUT-1240).
|
|
@@ -126,6 +127,7 @@ export type {
|
|
|
126
127
|
MethodPickerCopy,
|
|
127
128
|
PayerSummaryCopy,
|
|
128
129
|
PaymentErrorCopy,
|
|
130
|
+
PaymentLayoutCopy,
|
|
129
131
|
PixAfterCopyCopy,
|
|
130
132
|
PixPaneCopy,
|
|
131
133
|
SettlingCopy,
|