@12-apps/payments-frontend 3.34.0 → 3.34.2
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/payments-frontend",
|
|
3
|
-
"version": "3.34.
|
|
3
|
+
"version": "3.34.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Browser half of the vendor-agnostic payments platform: plug-and-play MUI components for the per-provider settings page (credential form from each provider's schema, masked hints, verify/enable) and the checkout page (PIX QR + polling, card tokenization, hosted-checkout redirect), plus the headless hooks and fetch clients they build on. Talks only to the host's payments HTTP surface — never to a provider directly. Microfrontend-ready: no app coupling, host injects theme and auth.",
|
|
@@ -22,6 +22,9 @@ import { useCheckoutComponents } from "./ui";
|
|
|
22
22
|
|
|
23
23
|
type StepState = "done" | "current" | "pending";
|
|
24
24
|
|
|
25
|
+
/** The way to the card is a link: underlined, so it does not lean on colour alone. */
|
|
26
|
+
const LINK_SX = { "& button": { textDecoration: "underline", textUnderlineOffset: "3px" }, "& button, & button *": { fontWeight: 600 } } as const;
|
|
27
|
+
|
|
25
28
|
function StepMark({ state, index }: { state: StepState; index: number }): JSX.Element {
|
|
26
29
|
const size = { width: 26, height: 26, [PIX_WIDE]: { width: 28, height: 28 } };
|
|
27
30
|
if (state === "done") {
|
|
@@ -92,7 +95,7 @@ function Heading({ words, totalLabel }: { words: PixAfterCopyCopy; totalLabel: s
|
|
|
92
95
|
</Box>
|
|
93
96
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.5 }}>
|
|
94
97
|
{/* The screen's title outranks the checklist: 22px, 28px wide (the prototype's scale). */}
|
|
95
|
-
<Box sx={{ "
|
|
98
|
+
<Box sx={{ "&& > *": { fontSize: "1.375rem", lineHeight: 1.25 }, [PIX_WIDE]: { "&& > *": { fontSize: "1.75rem" } } }}>
|
|
96
99
|
<Text variant="heading" size="md" weight="bold" as="h2" style={{ margin: 0 }} data-testid="pix-after-copy-title">
|
|
97
100
|
{words.title}
|
|
98
101
|
</Text>
|
|
@@ -126,7 +129,7 @@ function Actions({ justCopied, onCopyAgain, onShowQr, onPreferCard }: Pick<PixAf
|
|
|
126
129
|
const { Button } = useCheckoutComponents();
|
|
127
130
|
const copy = useCheckoutCopy().screens.pix;
|
|
128
131
|
return (
|
|
129
|
-
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 1fr))", gap: 1.25, "& button": { minHeight: 48, height: "100%" }, [PIX_WIDE]: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 1.5, "& button": { height: "auto" } } }}>
|
|
132
|
+
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 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" } } }}>
|
|
130
133
|
{/* Narrow, a grid cell; wide, its own width beside the link (the prototype's row). */}
|
|
131
134
|
<Box sx={{ minWidth: 0, [PIX_WIDE]: { flex: "none", "& > *": { width: "auto" } } }}>
|
|
132
135
|
<Button variant="outline" color="neutral" size="md" fullWidth icon={<ContentCopyIcon fontSize="small" />} onClick={onCopyAgain} dataTestId="pix-copy-again">
|
|
@@ -139,7 +142,7 @@ function Actions({ justCopied, onCopyAgain, onShowQr, onPreferCard }: Pick<PixAf
|
|
|
139
142
|
</Button>
|
|
140
143
|
</Box>
|
|
141
144
|
{onPreferCard ? (
|
|
142
|
-
<Box sx={{ display: "none", [PIX_WIDE]: { display: "block" } }}>
|
|
145
|
+
<Box sx={{ ...LINK_SX, display: "none", [PIX_WIDE]: { display: "block" } }}>
|
|
143
146
|
<Button variant="text" color="primary" size="md" icon={<CreditCardIcon fontSize="small" />} onClick={onPreferCard} dataTestId="pix-prefer-card-wide">
|
|
144
147
|
{copy.preferCardAction}
|
|
145
148
|
</Button>
|
|
@@ -161,7 +164,7 @@ export function PixAfterCopy(props: PixAfterCopyProps): JSX.Element {
|
|
|
161
164
|
<Actions {...props} />
|
|
162
165
|
{footer}
|
|
163
166
|
{onPreferCard ? (
|
|
164
|
-
<Box sx={{ display: "flex", justifyContent: "center", [PIX_WIDE]: { display: "none" } }}>
|
|
167
|
+
<Box sx={{ ...LINK_SX, display: "flex", justifyContent: "center", [PIX_WIDE]: { display: "none" } }}>
|
|
165
168
|
<Button variant="text" color="primary" size="md" icon={<CreditCardIcon fontSize="small" />} onClick={onPreferCard} dataTestId="pix-prefer-card">
|
|
166
169
|
{copy.preferCardAction}
|
|
167
170
|
</Button>
|
|
@@ -163,15 +163,28 @@ function CopyPanel({ pix, shown, onCopy, idBase }: { pix: PixCharge; shown: bool
|
|
|
163
163
|
return (
|
|
164
164
|
<Box id={paneIds(idBase, "copy").panel} role="tabpanel" aria-labelledby={paneIds(idBase, "copy").tab} sx={{ ...panelSx(shown), [PIX_WIDE]: { display: "flex", justifyContent: "center" } }}>
|
|
165
165
|
<ColumnHeading>{copy.copyPasteHeading}</ColumnHeading>
|
|
166
|
+
{/*
|
|
167
|
+
The box IS the code's frame, so whatever the host draws for inline code
|
|
168
|
+
(a tinted chip, its own border and padding) is undone inside it:
|
|
169
|
+
otherwise a wrapped code reads as a box inside a box.
|
|
170
|
+
*/}
|
|
166
171
|
<Box
|
|
167
|
-
sx={{
|
|
172
|
+
sx={{
|
|
173
|
+
px: 2,
|
|
174
|
+
py: 1.5,
|
|
175
|
+
border: "1px solid",
|
|
176
|
+
borderColor: "divider",
|
|
177
|
+
borderRadius: 1.5,
|
|
178
|
+
bgcolor: "action.hover",
|
|
179
|
+
"&& > *": { bgcolor: "transparent", border: 0, borderRadius: 0, p: 0 },
|
|
180
|
+
}}
|
|
168
181
|
>
|
|
169
182
|
<Text variant="code" size="xs" as="p" data-testid="pix-code" style={{ margin: 0, wordBreak: "break-all", lineHeight: 1.55 }}>
|
|
170
183
|
{pix.copyPaste}
|
|
171
184
|
</Text>
|
|
172
185
|
</Box>
|
|
173
186
|
{/* The one action on the pane: the prototype's 56px bar (52 wide), whatever the host's lg is. */}
|
|
174
|
-
<Box sx={{ "& > *": { minHeight: 56 }, [PIX_WIDE]: { "& > *": { minHeight: 52 } } }}>
|
|
187
|
+
<Box sx={{ "& > *": { minHeight: 56 }, "& > *, & > * *": { fontWeight: 700 }, [PIX_WIDE]: { "& > *": { minHeight: 52 } } }}>
|
|
175
188
|
<Button variant="solid" color="primary" size="lg" fullWidth icon={<ContentCopyIcon fontSize="small" />} onClick={onCopy} dataTestId="pix-copy">
|
|
176
189
|
{copy.copyAction}
|
|
177
190
|
</Button>
|
|
@@ -251,9 +251,12 @@ function PixBody({ order, pix, poll }: { order: CheckoutOrder; pix: PixCharge; p
|
|
|
251
251
|
}
|
|
252
252
|
return (
|
|
253
253
|
<>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
{/* The pane's title, at the prototype's scale: 20px, 26px wide. */}
|
|
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>
|
|
257
260
|
<PixPane
|
|
258
261
|
pix={pix}
|
|
259
262
|
tab={tab}
|
|
@@ -293,8 +296,22 @@ export function PixView({
|
|
|
293
296
|
);
|
|
294
297
|
}
|
|
295
298
|
return (
|
|
296
|
-
<Box data-testid="pix-view" sx={
|
|
297
|
-
|
|
299
|
+
<Box data-testid="pix-view" sx={PIX_CONTAINER}>
|
|
300
|
+
{/*
|
|
301
|
+
Wide, the pane is one white card on the page (the prototype's desktop
|
|
302
|
+
frame): heading, the two columns and the footer read as one unit. The
|
|
303
|
+
card is a child because a container query cannot style its container.
|
|
304
|
+
*/}
|
|
305
|
+
<Box
|
|
306
|
+
sx={{
|
|
307
|
+
display: "flex",
|
|
308
|
+
flexDirection: "column",
|
|
309
|
+
gap: 2,
|
|
310
|
+
[PIX_WIDE]: { gap: 2.5, p: 3.5, bgcolor: "background.paper", border: "1px solid", borderColor: "divider", borderRadius: 2 },
|
|
311
|
+
}}
|
|
312
|
+
>
|
|
313
|
+
<PixBody order={order} pix={pix} poll={poll} />
|
|
314
|
+
</Box>
|
|
298
315
|
</Box>
|
|
299
316
|
);
|
|
300
317
|
}
|
|
@@ -19,8 +19,9 @@ import { claimRearm, REARM_QUIET_MS } from "./poll-rearm";
|
|
|
19
19
|
*
|
|
20
20
|
* The mark is cleared by `restart`, by a `poke` that supersedes the marked
|
|
21
21
|
* ask, by sending its follow-up and by a terminal answer. The deadline does
|
|
22
|
-
* NOT clear it: a marked ask answering after the deadline is
|
|
23
|
-
*
|
|
22
|
+
* NOT clear it: a marked ask answering after the deadline is stopped by the
|
|
23
|
+
* loop's `carriesOn` (FUT-3222) before the mark is read, and neither asks nor
|
|
24
|
+
* schedules; the next `restart` clears the mark.
|
|
24
25
|
*
|
|
25
26
|
* Structural state, as in `poll-rearm.ts`: the loop's run satisfies it.
|
|
26
27
|
*/
|
|
@@ -47,12 +48,12 @@ export function claimHint(run: HintState): boolean {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
|
-
* After a non-terminal answer
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* After a non-terminal answer to a wait that is still running: whether a mark
|
|
52
|
+
* owes one more ask now, rather than the usual schedule. Clears the mark. A
|
|
53
|
+
* wait that stopped while the ask was out never gets here (`carriesOn`).
|
|
53
54
|
*/
|
|
54
|
-
export function owedAfterAnswer(run: Pick<HintState, "hinted"
|
|
55
|
-
if (!run.hinted) return
|
|
55
|
+
export function owedAfterAnswer(run: Pick<HintState, "hinted">): boolean {
|
|
56
|
+
if (!run.hinted) return false;
|
|
56
57
|
run.hinted = false;
|
|
57
|
-
return
|
|
58
|
+
return true;
|
|
58
59
|
}
|
|
@@ -280,11 +280,21 @@ function askNow(run: PollRun, tick: () => Promise<void>): void {
|
|
|
280
280
|
void tick();
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Whether the ask that just answered may book what comes next. Not when a newer
|
|
285
|
+
* ask superseded it, and not when the wall clock ended the wait while it was out
|
|
286
|
+
* (FUT-3222): live, `outOfTime` never ends a wait, so an ask the deadline
|
|
287
|
+
* overtook kept booking more behind the timed-out panel. Read AFTER `absorb`, so
|
|
288
|
+
* a PAID landing just past the deadline is still written.
|
|
289
|
+
*/
|
|
290
|
+
function carriesOn(run: PollRun, mine: number): boolean {
|
|
291
|
+
return run.attempt === mine && !run.stopped;
|
|
292
|
+
}
|
|
293
|
+
|
|
283
294
|
/** After a non-terminal answer: the ask a hint owes, or the usual schedule. */
|
|
284
295
|
function continueAfter(run: PollRun, tick: () => Promise<void>, schedule: () => void): void {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
else if (owed === "none") schedule();
|
|
296
|
+
if (owedAfterAnswer(run)) askNow(run, tick);
|
|
297
|
+
else schedule();
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
/** The handle the hook holds on one running wait. */
|
|
@@ -332,7 +342,7 @@ export function createPollLoop(
|
|
|
332
342
|
clearDeadline(run);
|
|
333
343
|
return;
|
|
334
344
|
}
|
|
335
|
-
if (run
|
|
345
|
+
if (!carriesOn(run, mine)) return;
|
|
336
346
|
continueAfter(run, tick, () => scheduleNext(run, options, sink, () => void tick()));
|
|
337
347
|
};
|
|
338
348
|
|