@12-apps/payments-frontend 3.12.0 → 3.13.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/copy.ts +6 -0
- package/src/card/fields.tsx +1 -1
- package/src/card/pt-BR.ts +1 -0
- package/src/components/ProviderSetupGuide.tsx +2 -1
- package/src/components/platform/ConnectApplicationPanel.tsx +20 -5
- package/src/components/platform/ConnectEnvironmentCard.tsx +19 -18
- package/src/components/platform/HomologacaoGuideCard.tsx +12 -11
- package/src/components/platform/HomologacaoOutcomeCard.tsx +19 -21
- package/src/components/platform/PlatformHomologacao.tsx +16 -12
- package/src/components/platform/copy-context.tsx +45 -0
- package/src/components/platform/copy.ts +113 -0
- package/src/components/platform/pt-BR.ts +73 -0
- package/src/components/settings-copy.ts +6 -0
- package/src/components/settings-pt-BR.ts +1 -0
- package/src/index.ts +7 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/payments-frontend",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"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.",
|
|
6
6
|
"exports": {
|
package/src/card/copy.ts
CHANGED
|
@@ -41,6 +41,12 @@ export interface CardFieldCopy {
|
|
|
41
41
|
* flagged it, and getting it wrong makes a buyer type the year first.
|
|
42
42
|
*/
|
|
43
43
|
expiryLabel: string;
|
|
44
|
+
/**
|
|
45
|
+
* The same order, inside the box. It sat here as `"MM/AA"` while the LABEL
|
|
46
|
+
* beside it was already required config — so a host could translate the
|
|
47
|
+
* label and be contradicted by the field one line down.
|
|
48
|
+
*/
|
|
49
|
+
expiryPlaceholder: string;
|
|
44
50
|
cvvLabel: string;
|
|
45
51
|
/**
|
|
46
52
|
* The expiry field holds something that is not yet `MM/AA` at all — too few
|
package/src/card/fields.tsx
CHANGED
package/src/card/pt-BR.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const PT_BR_CARD_COPY: CardCopy = {
|
|
|
19
19
|
holderLabel: 'Nome impresso no cartão',
|
|
20
20
|
holderRequired: 'Informe o nome impresso no cartão.',
|
|
21
21
|
expiryLabel: 'Validade (MM/AA)',
|
|
22
|
+
expiryPlaceholder: 'MM/AA',
|
|
22
23
|
cvvLabel: 'CVV',
|
|
23
24
|
expiryIncomplete: 'Validade incompleta (MM/AA).',
|
|
24
25
|
monthInvalid: 'Mês inválido.',
|
|
@@ -159,9 +159,10 @@ function StepText({ text, link }: { text?: string; link?: SetupStep['link'] }) {
|
|
|
159
159
|
* button gives them nothing to weigh that against.
|
|
160
160
|
*/
|
|
161
161
|
function ConfirmBar({ action }: { action: { label: string; run: () => void } }) {
|
|
162
|
+
const copy = usePaymentsSettingsCopy().setupGuide;
|
|
162
163
|
return (
|
|
163
164
|
<Box sx={BAR_SX} data-testid="payments-setup-confirm-bar">
|
|
164
|
-
<Typography sx={BAR_MSG_SX}>
|
|
165
|
+
<Typography sx={BAR_MSG_SX}>{copy.confirmPrompt}</Typography>
|
|
165
166
|
<Button variant="contained" disableElevation sx={BTN_PRIMARY_SX} onClick={() => action.run()}>
|
|
166
167
|
{action.label}
|
|
167
168
|
</Button>
|
|
@@ -6,6 +6,8 @@ import type { ReactNode } from 'react';
|
|
|
6
6
|
import type { ConnectApplicationReport, PaymentEnvironment } from '@12-apps/payments-backend';
|
|
7
7
|
|
|
8
8
|
import { CARD_SX, ConnectEnvironmentCard } from './ConnectEnvironmentCard';
|
|
9
|
+
import type { PlatformHomologacaoCopy } from './copy';
|
|
10
|
+
import { PlatformCopyProvider, usePlatformCopy } from './copy-context';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* The platform's PagBank Connect application, per environment (FUT-479,
|
|
@@ -25,9 +27,11 @@ import { CARD_SX, ConnectEnvironmentCard } from './ConnectEnvironmentCard';
|
|
|
25
27
|
* here, so the host page is a thin mount — page chrome, auth and loading
|
|
26
28
|
* belong to the host; the screen itself lives in this package.
|
|
27
29
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
30
|
+
* Its words are the HOST's (FUT-760), passed as one required `copy` object.
|
|
31
|
+
* The reader is whoever operates the platform — and a second platform adopting
|
|
32
|
+
* this package has its own operator, who reads whatever it was handed. The
|
|
33
|
+
* literals this screen used to carry were English because they were written
|
|
34
|
+
* for whoever was reading the code, which is a different person.
|
|
31
35
|
*/
|
|
32
36
|
export interface ConnectApplicationPanelProps {
|
|
33
37
|
/** The consult report, as the backend's `consultConnectApplications` answers. */
|
|
@@ -40,15 +44,26 @@ export interface ConnectApplicationPanelProps {
|
|
|
40
44
|
* when provided.
|
|
41
45
|
*/
|
|
42
46
|
configVarsFor?: (environment: PaymentEnvironment) => string[];
|
|
47
|
+
/** Every word this panel and its environment cards render. REQUIRED. */
|
|
48
|
+
copy: PlatformHomologacaoCopy;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
export function ConnectApplicationPanel(props: ConnectApplicationPanelProps): ReactNode {
|
|
52
|
+
return (
|
|
53
|
+
<PlatformCopyProvider copy={props.copy}>
|
|
54
|
+
<ConnectApplicationBody {...props} />
|
|
55
|
+
</PlatformCopyProvider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ConnectApplicationBody(props: ConnectApplicationPanelProps): ReactNode {
|
|
46
60
|
const { report, onRefresh, configVarsFor } = props;
|
|
61
|
+
const copy = usePlatformCopy().connect;
|
|
47
62
|
return (
|
|
48
63
|
<Stack spacing={2} data-testid="connect-application-panel">
|
|
49
64
|
<Stack spacing={0.5} data-testid="connect-expected-redirect" sx={CARD_SX}>
|
|
50
65
|
<Typography variant="caption" color="text.secondary" fontWeight={600}>
|
|
51
|
-
|
|
66
|
+
{copy.expectedRedirectHeading}
|
|
52
67
|
</Typography>
|
|
53
68
|
<Box
|
|
54
69
|
component="code"
|
|
@@ -72,7 +87,7 @@ export function ConnectApplicationPanel(props: ConnectApplicationPanelProps): Re
|
|
|
72
87
|
onClick={() => onRefresh()}
|
|
73
88
|
data-testid="connect-refresh"
|
|
74
89
|
>
|
|
75
|
-
|
|
90
|
+
{copy.consultAgain}
|
|
76
91
|
</Button>
|
|
77
92
|
</Box>
|
|
78
93
|
) : null}
|
|
@@ -4,6 +4,7 @@ import { Alert, Box, Button, Stack, Typography } from '@mui/material';
|
|
|
4
4
|
import { useState, type ReactNode } from 'react';
|
|
5
5
|
|
|
6
6
|
import type { ConnectApplicationStatus, PaymentEnvironment } from '@12-apps/payments-backend';
|
|
7
|
+
import { usePlatformCopy } from './copy-context';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* One environment's Connect application (FUT-479, packaged by FUT-573).
|
|
@@ -46,49 +47,50 @@ function Field({ label, children }: { label: string; children: ReactNode }): Rea
|
|
|
46
47
|
|
|
47
48
|
/** The verdict the screen exists for: does the registered callback match ours? */
|
|
48
49
|
function MismatchAlert({ status }: { status: ConnectApplicationStatus }): ReactNode {
|
|
50
|
+
const copy = usePlatformCopy().connect;
|
|
49
51
|
if (status.application === null) return null;
|
|
50
52
|
if (status.redirectUriMismatch === true) {
|
|
51
53
|
return (
|
|
52
54
|
<Alert severity="error" data-testid={`connect-mismatch-${status.environment}`}>
|
|
53
|
-
|
|
54
|
-
uses. The OAuth authorization flow fails silently until the registration is corrected
|
|
55
|
-
at PagBank.
|
|
55
|
+
{copy.redirectDiffers}
|
|
56
56
|
</Alert>
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
59
|
if (status.redirectUriMismatch === false) {
|
|
60
60
|
return (
|
|
61
61
|
<Alert severity="success" data-testid={`connect-match-${status.environment}`}>
|
|
62
|
-
|
|
62
|
+
{copy.redirectMatches}
|
|
63
63
|
</Alert>
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
return (
|
|
67
67
|
<Alert severity="warning" data-testid={`connect-unknown-${status.environment}`}>
|
|
68
|
-
|
|
69
|
-
callback this deployment uses.
|
|
68
|
+
{copy.redirectUnreported}
|
|
70
69
|
</Alert>
|
|
71
70
|
);
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/** What PagBank reports as registered, plus whatever extra keys came back. */
|
|
75
74
|
function ApplicationFields({ status }: { status: ConnectApplicationStatus }): ReactNode {
|
|
75
|
+
const copy = usePlatformCopy().connect;
|
|
76
76
|
const app = status.application;
|
|
77
77
|
if (app === null) return null;
|
|
78
78
|
const extraKeys = Object.keys(app.extra);
|
|
79
79
|
return (
|
|
80
80
|
<Stack spacing={1.5}>
|
|
81
81
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2.5 }}>
|
|
82
|
-
<Field label=
|
|
83
|
-
<Field label=
|
|
84
|
-
<Field label=
|
|
85
|
-
<Field label=
|
|
86
|
-
<Field label=
|
|
82
|
+
<Field label={copy.fields.name}>{app.name ?? copy.fieldEmpty}</Field>
|
|
83
|
+
<Field label={copy.fields.site}>{app.site ?? copy.fieldEmpty}</Field>
|
|
84
|
+
<Field label={copy.fields.description}>{app.description ?? copy.fieldEmpty}</Field>
|
|
85
|
+
<Field label={copy.fields.logo}>{app.logo ?? copy.fieldEmpty}</Field>
|
|
86
|
+
<Field label={copy.fields.redirectUri}>
|
|
87
|
+
{app.redirectUri ?? copy.redirectNotReported}
|
|
88
|
+
</Field>
|
|
87
89
|
</Box>
|
|
88
90
|
{extraKeys.length > 0 ? (
|
|
89
91
|
<Box data-testid={`connect-extra-${status.environment}`}>
|
|
90
92
|
<Typography variant="caption" color="text.secondary" fontWeight={600}>
|
|
91
|
-
|
|
93
|
+
{copy.extraKeys}
|
|
92
94
|
</Typography>
|
|
93
95
|
<Box component="pre" sx={{ m: 0, fontSize: 12, overflowX: 'auto' }}>
|
|
94
96
|
{JSON.stringify(app.extra, null, 2)}
|
|
@@ -111,6 +113,7 @@ function ConfigHelp({
|
|
|
111
113
|
environment: PaymentEnvironment;
|
|
112
114
|
configVars?: string[];
|
|
113
115
|
}): ReactNode {
|
|
116
|
+
const copy = usePlatformCopy().connect;
|
|
114
117
|
const [open, setOpen] = useState(false);
|
|
115
118
|
if (!configVars || configVars.length === 0) return null;
|
|
116
119
|
return (
|
|
@@ -121,13 +124,12 @@ function ConfigHelp({
|
|
|
121
124
|
onClick={() => setOpen((value) => !value)}
|
|
122
125
|
data-testid={`connect-config-toggle-${environment}`}
|
|
123
126
|
>
|
|
124
|
-
{open ?
|
|
127
|
+
{open ? copy.hideConfig : copy.showConfig}
|
|
125
128
|
</Button>
|
|
126
129
|
{open ? (
|
|
127
130
|
<Box data-testid={`connect-config-details-${environment}`}>
|
|
128
131
|
<Typography variant="caption" color="text.secondary" component="p">
|
|
129
|
-
|
|
130
|
-
fallback between environments:
|
|
132
|
+
{copy.resolvedFrom}
|
|
131
133
|
</Typography>
|
|
132
134
|
<Box component="ul" sx={{ m: 0, pl: 2.5 }}>
|
|
133
135
|
{configVars.map((name) => (
|
|
@@ -151,6 +153,7 @@ export function ConnectEnvironmentCard({
|
|
|
151
153
|
status: ConnectApplicationStatus;
|
|
152
154
|
configVars?: string[];
|
|
153
155
|
}): ReactNode {
|
|
156
|
+
const copy = usePlatformCopy().connect;
|
|
154
157
|
return (
|
|
155
158
|
<Stack spacing={1.5} data-testid={`connect-env-${status.environment}`} sx={CARD_SX}>
|
|
156
159
|
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 1.5, wordBreak: 'break-all' }}>
|
|
@@ -162,9 +165,7 @@ export function ConnectEnvironmentCard({
|
|
|
162
165
|
</Typography>
|
|
163
166
|
</Box>
|
|
164
167
|
{!status.configured ? (
|
|
165
|
-
<Typography variant="body2" color="text.secondary">
|
|
166
|
-
No application configured in this environment.
|
|
167
|
-
</Typography>
|
|
168
|
+
<Typography variant="body2" color="text.secondary">{copy.noApplication}</Typography>
|
|
168
169
|
) : null}
|
|
169
170
|
{status.error !== null ? (
|
|
170
171
|
<Alert severity="warning" data-testid={`connect-error-${status.environment}`}>
|
|
@@ -6,6 +6,7 @@ import type { ReactNode } from 'react';
|
|
|
6
6
|
import type { HomologacaoGuide } from '@12-apps/payments-backend';
|
|
7
7
|
|
|
8
8
|
import { CARD_SX } from './ConnectEnvironmentCard';
|
|
9
|
+
import { usePlatformCopy } from './copy-context';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* The paste-ready homologação answers (FUT-483, packaged by FUT-573) — the
|
|
@@ -49,31 +50,31 @@ function Answer({ label, children }: { label: string; children: ReactNode }): Re
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export function HomologacaoGuideCard({ guide }: { guide: HomologacaoGuide }): ReactNode {
|
|
53
|
+
const copy = usePlatformCopy().guide;
|
|
52
54
|
return (
|
|
53
55
|
<Stack spacing={1.5} data-testid="homologacao-guide-card" sx={CARD_SX}>
|
|
54
56
|
<Typography variant="body2" fontWeight={600}>
|
|
55
|
-
|
|
57
|
+
{copy.heading}
|
|
56
58
|
</Typography>
|
|
57
59
|
<Typography variant="body2" color="text.secondary" component="p">
|
|
58
|
-
|
|
60
|
+
{copy.ledeBeforeForm}
|
|
59
61
|
<Link
|
|
60
62
|
href={guide.formUrl}
|
|
61
63
|
target="_blank"
|
|
62
64
|
rel="noreferrer"
|
|
63
65
|
data-testid="homologacao-form-link"
|
|
64
66
|
>
|
|
65
|
-
|
|
66
|
-
</Link>
|
|
67
|
-
|
|
67
|
+
{copy.formLink}
|
|
68
|
+
</Link>
|
|
69
|
+
{copy.ledeBeforeSupport}
|
|
68
70
|
<Link href={guide.supportFormUrl} target="_blank" rel="noreferrer">
|
|
69
|
-
|
|
70
|
-
</Link>
|
|
71
|
-
|
|
72
|
-
covers Connect. Documentation:{' '}
|
|
71
|
+
{copy.supportLink}
|
|
72
|
+
</Link>
|
|
73
|
+
{copy.ledeBeforeDocs}
|
|
73
74
|
<Link href={guide.docsUrl} target="_blank" rel="noreferrer">
|
|
74
|
-
|
|
75
|
+
{copy.docsLink}
|
|
75
76
|
</Link>
|
|
76
|
-
.
|
|
77
|
+
{copy.ledeAfterDocs}
|
|
77
78
|
</Typography>
|
|
78
79
|
<Answer label={guide.fieldLabels.integrationType}>{guide.integrationType}</Answer>
|
|
79
80
|
<Box data-testid="homologacao-services">
|
|
@@ -6,6 +6,7 @@ import { useState, type ReactNode } from 'react';
|
|
|
6
6
|
import type { PlatformHomologationStatus } from '@12-apps/payments-backend';
|
|
7
7
|
|
|
8
8
|
import { CARD_SX } from './ConnectEnvironmentCard';
|
|
9
|
+
import { usePlatformCopy } from './copy-context';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* The homologação outcome record (FUT-483, packaged by FUT-573) — the durable
|
|
@@ -44,12 +45,6 @@ export interface HomologacaoSaveState {
|
|
|
44
45
|
success: boolean;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
const STATUS_LABEL: Record<PlatformHomologationStatus, string> = {
|
|
48
|
-
SUBMITTED: 'Submitted',
|
|
49
|
-
APPROVED: 'Approved',
|
|
50
|
-
REJECTED: 'Rejected',
|
|
51
|
-
};
|
|
52
|
-
|
|
53
48
|
const STATUS_COLOR: Record<PlatformHomologationStatus, 'warning' | 'success' | 'error'> = {
|
|
54
49
|
SUBMITTED: 'warning',
|
|
55
50
|
APPROVED: 'success',
|
|
@@ -57,10 +52,11 @@ const STATUS_COLOR: Record<PlatformHomologationStatus, 'warning' | 'success' | '
|
|
|
57
52
|
};
|
|
58
53
|
|
|
59
54
|
function StatusChip({ record }: { record: PlatformHomologationRecordView | null }): ReactNode {
|
|
55
|
+
const copy = usePlatformCopy().outcome;
|
|
60
56
|
if (record === null) {
|
|
61
57
|
return (
|
|
62
58
|
<Chip
|
|
63
|
-
label=
|
|
59
|
+
label={copy.notSubmitted}
|
|
64
60
|
size="small"
|
|
65
61
|
variant="outlined"
|
|
66
62
|
data-testid="homologacao-status-chip"
|
|
@@ -69,7 +65,7 @@ function StatusChip({ record }: { record: PlatformHomologationRecordView | null
|
|
|
69
65
|
}
|
|
70
66
|
return (
|
|
71
67
|
<Chip
|
|
72
|
-
label={
|
|
68
|
+
label={copy.statuses[record.status] ?? record.status}
|
|
73
69
|
size="small"
|
|
74
70
|
color={STATUS_COLOR[record.status]}
|
|
75
71
|
data-testid="homologacao-status-chip"
|
|
@@ -88,11 +84,12 @@ function StatusChip({ record }: { record: PlatformHomologationRecordView | null
|
|
|
88
84
|
const formatDateTime = (iso: string): string => new Date(iso).toLocaleString();
|
|
89
85
|
|
|
90
86
|
function RecordTrail({ record }: { record: PlatformHomologationRecordView }): ReactNode {
|
|
87
|
+
const copy = usePlatformCopy().outcome;
|
|
91
88
|
return (
|
|
92
89
|
<Typography variant="caption" color="text.secondary" component="p">
|
|
93
|
-
{record.submittedAt ?
|
|
94
|
-
{record.decidedAt ?
|
|
95
|
-
{record.updatedBy ?
|
|
90
|
+
{record.submittedAt ? copy.submittedAt(formatDateTime(record.submittedAt)) : ''}
|
|
91
|
+
{record.decidedAt ? copy.decidedAt(formatDateTime(record.decidedAt)) : ''}
|
|
92
|
+
{record.updatedBy ? copy.recordedBy(record.updatedBy) : ''}
|
|
96
93
|
</Typography>
|
|
97
94
|
);
|
|
98
95
|
}
|
|
@@ -106,6 +103,7 @@ interface HomologacaoOutcomeCardProps {
|
|
|
106
103
|
|
|
107
104
|
export function HomologacaoOutcomeCard(props: HomologacaoOutcomeCardProps): ReactNode {
|
|
108
105
|
const { record, onSave, save } = props;
|
|
106
|
+
const copy = usePlatformCopy().outcome;
|
|
109
107
|
const [status, setStatus] = useState<PlatformHomologationStatus>(record?.status ?? 'SUBMITTED');
|
|
110
108
|
const [protocol, setProtocol] = useState(record?.protocol ?? '');
|
|
111
109
|
const [notes, setNotes] = useState(record?.notes ?? '');
|
|
@@ -114,7 +112,7 @@ export function HomologacaoOutcomeCard(props: HomologacaoOutcomeCardProps): Reac
|
|
|
114
112
|
<Stack spacing={1.5} data-testid="homologacao-outcome-card" sx={CARD_SX}>
|
|
115
113
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
116
114
|
<Typography variant="body2" fontWeight={600}>
|
|
117
|
-
|
|
115
|
+
{copy.heading}
|
|
118
116
|
</Typography>
|
|
119
117
|
<StatusChip record={record} />
|
|
120
118
|
</Box>
|
|
@@ -123,7 +121,7 @@ export function HomologacaoOutcomeCard(props: HomologacaoOutcomeCardProps): Reac
|
|
|
123
121
|
<TextField
|
|
124
122
|
select
|
|
125
123
|
size="small"
|
|
126
|
-
label=
|
|
124
|
+
label={copy.statusLabel}
|
|
127
125
|
value={status}
|
|
128
126
|
onChange={(event) => setStatus(event.target.value as PlatformHomologationStatus)}
|
|
129
127
|
slotProps={{
|
|
@@ -131,24 +129,24 @@ export function HomologacaoOutcomeCard(props: HomologacaoOutcomeCardProps): Reac
|
|
|
131
129
|
htmlInput: { 'data-testid': 'homologacao-status-select' },
|
|
132
130
|
}}
|
|
133
131
|
>
|
|
134
|
-
{(Object.keys(
|
|
132
|
+
{(Object.keys(STATUS_COLOR) as PlatformHomologationStatus[]).map((key) => (
|
|
135
133
|
<option key={key} value={key}>
|
|
136
|
-
{
|
|
134
|
+
{copy.statuses[key] ?? key}
|
|
137
135
|
</option>
|
|
138
136
|
))}
|
|
139
137
|
</TextField>
|
|
140
138
|
<TextField
|
|
141
139
|
size="small"
|
|
142
|
-
aria-label=
|
|
143
|
-
placeholder=
|
|
140
|
+
aria-label={copy.protocolLabel}
|
|
141
|
+
placeholder={copy.protocolPlaceholder}
|
|
144
142
|
value={protocol}
|
|
145
143
|
onChange={(event) => setProtocol(event.target.value)}
|
|
146
144
|
slotProps={{ htmlInput: { 'data-testid': 'homologacao-protocol' } }}
|
|
147
145
|
/>
|
|
148
146
|
<TextField
|
|
149
147
|
size="small"
|
|
150
|
-
aria-label=
|
|
151
|
-
placeholder=
|
|
148
|
+
aria-label={copy.notesLabel}
|
|
149
|
+
placeholder={copy.notesPlaceholder}
|
|
152
150
|
value={notes}
|
|
153
151
|
onChange={(event) => setNotes(event.target.value)}
|
|
154
152
|
slotProps={{ htmlInput: { 'data-testid': 'homologacao-notes' } }}
|
|
@@ -160,7 +158,7 @@ export function HomologacaoOutcomeCard(props: HomologacaoOutcomeCardProps): Reac
|
|
|
160
158
|
onClick={() => onSave({ status, protocol, notes })}
|
|
161
159
|
data-testid="homologacao-save"
|
|
162
160
|
>
|
|
163
|
-
|
|
161
|
+
{copy.save}
|
|
164
162
|
</Button>
|
|
165
163
|
</Box>
|
|
166
164
|
{save.error !== null ? (
|
|
@@ -170,7 +168,7 @@ export function HomologacaoOutcomeCard(props: HomologacaoOutcomeCardProps): Reac
|
|
|
170
168
|
) : null}
|
|
171
169
|
{save.success ? (
|
|
172
170
|
<Alert severity="success" data-testid="homologacao-save-ok">
|
|
173
|
-
|
|
171
|
+
{copy.saved}
|
|
174
172
|
</Alert>
|
|
175
173
|
) : null}
|
|
176
174
|
</Stack>
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
type HomologacaoSaveState,
|
|
14
14
|
type PlatformHomologationRecordView,
|
|
15
15
|
} from './HomologacaoOutcomeCard';
|
|
16
|
+
import type { PlatformHomologacaoCopy } from './copy';
|
|
17
|
+
import { PlatformCopyProvider, usePlatformCopy } from './copy-context';
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* The PLATFORM's PagBank homologação screen (FUT-483, packaged by FUT-573).
|
|
@@ -49,10 +51,13 @@ export interface PlatformHomologacaoProps {
|
|
|
49
51
|
* card shows it verbatim.
|
|
50
52
|
*/
|
|
51
53
|
onGenerateAnexo: () => Promise<void>;
|
|
54
|
+
/** Every word these three cards render. REQUIRED — no default copy. */
|
|
55
|
+
copy: PlatformHomologacaoCopy;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/** The evidence-file half: real sandbox calls, downloaded as a text file. */
|
|
55
59
|
function AnexoCard({ onGenerate }: { onGenerate: () => Promise<void> }): ReactNode {
|
|
60
|
+
const copy = usePlatformCopy().anexo;
|
|
56
61
|
const [error, setError] = useState<string | null>(null);
|
|
57
62
|
const [busy, setBusy] = useState(false);
|
|
58
63
|
|
|
@@ -62,7 +67,7 @@ function AnexoCard({ onGenerate }: { onGenerate: () => Promise<void> }): ReactNo
|
|
|
62
67
|
try {
|
|
63
68
|
await onGenerate();
|
|
64
69
|
} catch (cause) {
|
|
65
|
-
setError(cause instanceof Error ? cause.message :
|
|
70
|
+
setError(cause instanceof Error ? cause.message : copy.generateFailed);
|
|
66
71
|
} finally {
|
|
67
72
|
setBusy(false);
|
|
68
73
|
}
|
|
@@ -71,13 +76,10 @@ function AnexoCard({ onGenerate }: { onGenerate: () => Promise<void> }): ReactNo
|
|
|
71
76
|
return (
|
|
72
77
|
<Stack spacing={1.5} data-testid="homologacao-anexo-card" sx={CARD_SX}>
|
|
73
78
|
<Typography variant="body2" fontWeight={600}>
|
|
74
|
-
|
|
79
|
+
{copy.heading}
|
|
75
80
|
</Typography>
|
|
76
81
|
<Typography variant="body2" color="text.secondary" component="p">
|
|
77
|
-
|
|
78
|
-
button below makes those calls for real against the test environment (Sandbox), on the
|
|
79
|
-
platform's own token — nothing is actually charged — and downloads the file ready to
|
|
80
|
-
attach, with the token redacted.
|
|
82
|
+
{copy.body}
|
|
81
83
|
</Typography>
|
|
82
84
|
<Box>
|
|
83
85
|
<Button
|
|
@@ -87,7 +89,7 @@ function AnexoCard({ onGenerate }: { onGenerate: () => Promise<void> }): ReactNo
|
|
|
87
89
|
onClick={() => void generate()}
|
|
88
90
|
data-testid="homologacao-anexo-button"
|
|
89
91
|
>
|
|
90
|
-
|
|
92
|
+
{copy.generate}
|
|
91
93
|
</Button>
|
|
92
94
|
</Box>
|
|
93
95
|
{error !== null ? (
|
|
@@ -102,10 +104,12 @@ function AnexoCard({ onGenerate }: { onGenerate: () => Promise<void> }): ReactNo
|
|
|
102
104
|
export function PlatformHomologacao(props: PlatformHomologacaoProps): ReactNode {
|
|
103
105
|
const { record, guide, onSaveRecord, save, onGenerateAnexo } = props;
|
|
104
106
|
return (
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
<PlatformCopyProvider copy={props.copy}>
|
|
108
|
+
<Stack spacing={2} data-testid="platform-homologacao">
|
|
109
|
+
<HomologacaoOutcomeCard record={record} onSave={onSaveRecord} save={save} />
|
|
110
|
+
<HomologacaoGuideCard guide={guide} />
|
|
111
|
+
<AnexoCard onGenerate={onGenerateAnexo} />
|
|
112
|
+
</Stack>
|
|
113
|
+
</PlatformCopyProvider>
|
|
110
114
|
);
|
|
111
115
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type JSX, type ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import type { PlatformHomologacaoCopy } from './copy';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The platform screens' words, for the eight components that render them.
|
|
9
|
+
*
|
|
10
|
+
* A CONTEXT rather than a prop, for the same reason the settings surface uses
|
|
11
|
+
* one: this is a tree of small cards — the outcome form, the paste-ready
|
|
12
|
+
* answers, the anexo, the environment cards and the fields inside them — and
|
|
13
|
+
* threading one object through all of them as props is how a copy port comes
|
|
14
|
+
* to exist, be required, and go unread.
|
|
15
|
+
*
|
|
16
|
+
* It stays a single REQUIRED prop at the two mounts (`PlatformHomologacao` and
|
|
17
|
+
* `ConnectApplicationPanel`), which is the only place a host has to answer.
|
|
18
|
+
*/
|
|
19
|
+
const PlatformCopyContext = createContext<PlatformHomologacaoCopy | null>(null);
|
|
20
|
+
|
|
21
|
+
export function PlatformCopyProvider({
|
|
22
|
+
copy,
|
|
23
|
+
children,
|
|
24
|
+
}: {
|
|
25
|
+
copy: PlatformHomologacaoCopy;
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}): JSX.Element {
|
|
28
|
+
return <PlatformCopyContext.Provider value={copy}>{children}</PlatformCopyContext.Provider>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The words these screens render — THROWS outside a provider rather than
|
|
33
|
+
* falling back.
|
|
34
|
+
*
|
|
35
|
+
* A fallback could only be this package's own answer, handed silently to the
|
|
36
|
+
* next platform's operator. Failing at the mount is the point: it is the one
|
|
37
|
+
* moment a host can still be told it forgot.
|
|
38
|
+
*/
|
|
39
|
+
export function usePlatformCopy(): PlatformHomologacaoCopy {
|
|
40
|
+
const copy = useContext(PlatformCopyContext);
|
|
41
|
+
if (!copy) {
|
|
42
|
+
throw new Error('usePlatformCopy must be rendered inside a <PlatformCopyProvider>');
|
|
43
|
+
}
|
|
44
|
+
return copy;
|
|
45
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every word the PLATFORM's homologação screens render.
|
|
3
|
+
*
|
|
4
|
+
* The reader here is not a buyer and not a merchant: it is whoever operates
|
|
5
|
+
* the platform, submitting a vendor homologação on behalf of every tenant on
|
|
6
|
+
* it. That made this surface look internal enough to write literals into for a
|
|
7
|
+
* long time, and it is not — a second platform adopting
|
|
8
|
+
* `@12-apps/payments-frontend` mounts these same four cards, and its operator
|
|
9
|
+
* reads whatever this package decided, in whatever language it decided.
|
|
10
|
+
*
|
|
11
|
+
* So the same rule as every other surface here: REQUIRED, no defaults. A pack
|
|
12
|
+
* for one language ships as `PT_BR_PLATFORM_HOMOLOGACAO_COPY` and a host
|
|
13
|
+
* passes it by hand.
|
|
14
|
+
*
|
|
15
|
+
* What is NOT here, deliberately: `HomologacaoGuide.fieldLabels` and the guide's
|
|
16
|
+
* three URLs. Those are PagBank's own form field names and pages, answered by
|
|
17
|
+
* the backend adapter, and a translation of them produces a submission their
|
|
18
|
+
* form does not accept.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The outcome card: what was submitted, and what came back. */
|
|
22
|
+
export interface HomologacaoOutcomeCopy {
|
|
23
|
+
heading: string;
|
|
24
|
+
/** The status a record carries — and what "no record at all" reads as. */
|
|
25
|
+
statusLabel: string;
|
|
26
|
+
notSubmitted: string;
|
|
27
|
+
/** The two free-text fields, each with a visible-less input. */
|
|
28
|
+
protocolLabel: string;
|
|
29
|
+
protocolPlaceholder: string;
|
|
30
|
+
notesLabel: string;
|
|
31
|
+
notesPlaceholder: string;
|
|
32
|
+
/** Save, and the confirmation after it. */
|
|
33
|
+
save: string;
|
|
34
|
+
saved: string;
|
|
35
|
+
/** The three statuses a record can carry, keyed by the stored value. */
|
|
36
|
+
statuses: Readonly<Record<string, string>>;
|
|
37
|
+
/**
|
|
38
|
+
* The trail under the heading, one clause per timestamp the record has. Each
|
|
39
|
+
* takes its own value because the ORDER of word and date differs by language
|
|
40
|
+
* and this line concatenates whichever clauses exist.
|
|
41
|
+
*/
|
|
42
|
+
submittedAt(when: string): string;
|
|
43
|
+
decidedAt(when: string): string;
|
|
44
|
+
recordedBy(who: string): string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The paste-ready answers card, and the three links woven through its lede. */
|
|
48
|
+
export interface HomologacaoGuideCopy {
|
|
49
|
+
heading: string;
|
|
50
|
+
/**
|
|
51
|
+
* The instruction paragraph, in the runs it renders as: text, link, text,
|
|
52
|
+
* link, text, link, text. Split because two of the links sit MID-SENTENCE,
|
|
53
|
+
* so a single string with placeholders could not carry them.
|
|
54
|
+
*/
|
|
55
|
+
ledeBeforeForm: string;
|
|
56
|
+
formLink: string;
|
|
57
|
+
ledeBeforeSupport: string;
|
|
58
|
+
supportLink: string;
|
|
59
|
+
ledeBeforeDocs: string;
|
|
60
|
+
docsLink: string;
|
|
61
|
+
ledeAfterDocs: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The evidence-file card. */
|
|
65
|
+
export interface HomologacaoAnexoCopy {
|
|
66
|
+
heading: string;
|
|
67
|
+
body: string;
|
|
68
|
+
generate: string;
|
|
69
|
+
/** A generate that threw something carrying no message of its own. */
|
|
70
|
+
generateFailed: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The Connect application panel, and the environment card under it. */
|
|
74
|
+
export interface ConnectApplicationCopy {
|
|
75
|
+
/** The callback the deployment uses — the value that must be registered. */
|
|
76
|
+
expectedRedirectHeading: string;
|
|
77
|
+
consultAgain: string;
|
|
78
|
+
/** No application resolved for this environment at all. */
|
|
79
|
+
noApplication: string;
|
|
80
|
+
/**
|
|
81
|
+
* The three verdicts on the registered `redirect_uri`: it matches, it does
|
|
82
|
+
* not, or PagBank reported none to compare against. A mismatch is the whole
|
|
83
|
+
* reason this card exists, so all three read as findings rather than states.
|
|
84
|
+
*/
|
|
85
|
+
redirectMatches: string;
|
|
86
|
+
redirectDiffers: string;
|
|
87
|
+
redirectUnreported: string;
|
|
88
|
+
/** The application's own fields, as PagBank reports them. */
|
|
89
|
+
fields: {
|
|
90
|
+
name: string;
|
|
91
|
+
site: string;
|
|
92
|
+
description: string;
|
|
93
|
+
logo: string;
|
|
94
|
+
redirectUri: string;
|
|
95
|
+
};
|
|
96
|
+
/** What a field with no value reads as, and an unreported redirect_uri. */
|
|
97
|
+
fieldEmpty: string;
|
|
98
|
+
redirectNotReported: string;
|
|
99
|
+
/** Where the environment's application comes from, and its disclosure. */
|
|
100
|
+
resolvedFrom: string;
|
|
101
|
+
showConfig: string;
|
|
102
|
+
hideConfig: string;
|
|
103
|
+
/** The heading above whatever extra keys PagBank's response carried. */
|
|
104
|
+
extraKeys: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The whole platform surface, in one object a host passes at the mount. */
|
|
108
|
+
export interface PlatformHomologacaoCopy {
|
|
109
|
+
outcome: HomologacaoOutcomeCopy;
|
|
110
|
+
guide: HomologacaoGuideCopy;
|
|
111
|
+
anexo: HomologacaoAnexoCopy;
|
|
112
|
+
connect: ConnectApplicationCopy;
|
|
113
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { PlatformHomologacaoCopy } from './copy';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The pt-BR pack for the platform's homologação screens.
|
|
5
|
+
*
|
|
6
|
+
* A NAMED pack, which is how this repo ships a language: a host imports it and
|
|
7
|
+
* passes it by hand, so choosing Portuguese is a line in that host's diff
|
|
8
|
+
* rather than a silence in this package.
|
|
9
|
+
*
|
|
10
|
+
* The English these screens rendered as literals is deliberately NOT what this
|
|
11
|
+
* pack says. Those sentences were written for a developer reading the code;
|
|
12
|
+
* these are written for the operator filling PagBank's form, which is the
|
|
13
|
+
* register the surface is actually in.
|
|
14
|
+
*/
|
|
15
|
+
export const PT_BR_PLATFORM_HOMOLOGACAO_COPY: PlatformHomologacaoCopy = {
|
|
16
|
+
outcome: {
|
|
17
|
+
heading: 'Situação da homologação',
|
|
18
|
+
statusLabel: 'Situação',
|
|
19
|
+
notSubmitted: 'Não enviada',
|
|
20
|
+
protocolLabel: 'Protocolo',
|
|
21
|
+
protocolPlaceholder: 'Protocolo (card do Pipefy / chamado)',
|
|
22
|
+
notesLabel: 'Observações',
|
|
23
|
+
notesPlaceholder: 'Observações (resposta do PagBank, contexto…)',
|
|
24
|
+
save: 'Registrar',
|
|
25
|
+
saved: 'Registro atualizado.',
|
|
26
|
+
statuses: { SUBMITTED: 'Enviada', APPROVED: 'Aprovada', REJECTED: 'Recusada' },
|
|
27
|
+
submittedAt: (when) => `Enviada em ${when}. `,
|
|
28
|
+
decidedAt: (when) => `Decidida em ${when}. `,
|
|
29
|
+
recordedBy: (who) => `Registrado por ${who}.`,
|
|
30
|
+
},
|
|
31
|
+
guide: {
|
|
32
|
+
heading: 'Formulário de homologação — respostas prontas para colar',
|
|
33
|
+
ledeBeforeForm: 'Abra o ',
|
|
34
|
+
formLink: 'formulário oficial de homologação',
|
|
35
|
+
ledeBeforeSupport: ' e preencha com os valores abaixo. Em paralelo, abra um chamado no ',
|
|
36
|
+
supportLink: 'SIP — suporte de integração PagBank',
|
|
37
|
+
ledeBeforeDocs:
|
|
38
|
+
' citando o 403 ACCESS_DENIED: o que responder primeiro define se o formulário cobre o Connect. Documentação: ',
|
|
39
|
+
docsLink: 'solicitar homologação',
|
|
40
|
+
ledeAfterDocs: '.',
|
|
41
|
+
},
|
|
42
|
+
anexo: {
|
|
43
|
+
heading: 'Anexo de evidências',
|
|
44
|
+
body:
|
|
45
|
+
'O formulário pede as requisições e as respostas das chamadas enviadas às APIs do PagBank. O arquivo é gerado a partir das chamadas reais desta plataforma, com o token redigido.',
|
|
46
|
+
generate: 'Gerar anexo',
|
|
47
|
+
generateFailed: 'Não foi possível gerar o anexo.',
|
|
48
|
+
},
|
|
49
|
+
connect: {
|
|
50
|
+
expectedRedirectHeading: 'Callback que este deploy usa (o valor que precisa estar registrado)',
|
|
51
|
+
consultAgain: 'Consultar de novo',
|
|
52
|
+
noApplication: 'Nenhuma aplicação configurada neste ambiente.',
|
|
53
|
+
redirectMatches: 'O redirect_uri registrado confere com o callback que este deploy usa.',
|
|
54
|
+
redirectDiffers:
|
|
55
|
+
'O redirect_uri registrado no PagBank é diferente do callback que este deploy usa.',
|
|
56
|
+
redirectUnreported:
|
|
57
|
+
'A resposta do PagBank não trouxe redirect_uri, então não foi possível comparar com o callback.',
|
|
58
|
+
fields: {
|
|
59
|
+
name: 'Nome (exibido ao lojista)',
|
|
60
|
+
site: 'Site',
|
|
61
|
+
description: 'Descrição',
|
|
62
|
+
logo: 'Logo',
|
|
63
|
+
redirectUri: 'redirect_uri registrado',
|
|
64
|
+
},
|
|
65
|
+
fieldEmpty: '—',
|
|
66
|
+
redirectNotReported: 'não informado',
|
|
67
|
+
resolvedFrom:
|
|
68
|
+
'A aplicação deste ambiente é resolvida estritamente a partir destas variáveis, sem fallback entre ambientes:',
|
|
69
|
+
showConfig: 'Mostrar variáveis de ambiente',
|
|
70
|
+
hideConfig: 'Ocultar variáveis de ambiente',
|
|
71
|
+
extraKeys: 'Outros campos retornados',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -289,6 +289,12 @@ export interface SetupGuideCopy {
|
|
|
289
289
|
confirmedByYou: string;
|
|
290
290
|
/** Reopen a section the owner already confirmed. */
|
|
291
291
|
reviewAction: string;
|
|
292
|
+
/**
|
|
293
|
+
* The sentence beside the confirm button. It exists because the owner is
|
|
294
|
+
* vouching for work done in the PROVIDER's dashboard, not here — a bare
|
|
295
|
+
* button gives them nothing to weigh that against.
|
|
296
|
+
*/
|
|
297
|
+
confirmPrompt: string;
|
|
292
298
|
/**
|
|
293
299
|
* The copy-to-clipboard button beside a reference value, and what it says
|
|
294
300
|
* for the two seconds after a successful copy.
|
|
@@ -166,6 +166,7 @@ export const PT_BR_PAYMENTS_SETTINGS_COPY: PaymentsSettingsCopy = {
|
|
|
166
166
|
defaultConfirmLabel: 'Já habilitei o Checkout Integrado',
|
|
167
167
|
confirmedByYou: 'Confirmado por você',
|
|
168
168
|
reviewAction: 'Revisar',
|
|
169
|
+
confirmPrompt: 'Confirme quando terminar do lado do provedor.',
|
|
169
170
|
copyValue: (fieldLabel) => `Copiar ${fieldLabel}`,
|
|
170
171
|
copied: 'Copiado',
|
|
171
172
|
},
|
package/src/index.ts
CHANGED
|
@@ -306,23 +306,23 @@ export {
|
|
|
306
306
|
// The PLATFORM operations screens (FUT-479 / FUT-483, packaged by FUT-573) —
|
|
307
307
|
// the Connect-application consult and the homologação, as dumb components a
|
|
308
308
|
// host page mounts with data + callbacks from its own routes. Their backend
|
|
309
|
-
// halves
|
|
310
|
-
// `
|
|
311
|
-
// `buildPlatformHomologacaoAnexo`).
|
|
309
|
+
// halves are `consultConnectApplications`, `platformHomologacaoGuide`,
|
|
310
|
+
// `createHomologationRecordService` and `buildPlatformHomologacaoAnexo`.
|
|
312
311
|
// ---------------------------------------------------------------------------
|
|
313
312
|
export {
|
|
314
313
|
ConnectApplicationPanel,
|
|
315
314
|
type ConnectApplicationPanelProps,
|
|
316
315
|
} from './components/platform/ConnectApplicationPanel';
|
|
317
|
-
export {
|
|
318
|
-
PlatformHomologacao,
|
|
319
|
-
type PlatformHomologacaoProps,
|
|
320
|
-
} from './components/platform/PlatformHomologacao';
|
|
316
|
+
export { PlatformHomologacao, type PlatformHomologacaoProps } from './components/platform/PlatformHomologacao';
|
|
321
317
|
export {
|
|
322
318
|
type HomologacaoSaveInput,
|
|
323
319
|
type HomologacaoSaveState,
|
|
324
320
|
type PlatformHomologationRecordView,
|
|
325
321
|
} from './components/platform/HomologacaoOutcomeCard';
|
|
322
|
+
// Both mounts require `copy`, so the contract and its pt-BR pack are part of
|
|
323
|
+
// the port — a required port a host cannot import is not one.
|
|
324
|
+
export type * from './components/platform/copy';
|
|
325
|
+
export { PT_BR_PLATFORM_HOMOLOGACAO_COPY } from './components/platform/pt-BR';
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Re-exported because it appears in the `prepareConnect` prop a host must
|