@redacto.io/consent-sdk-react 9.2.0-beta.1 → 9.2.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/privacy-center.d.mts +6 -10
  3. package/dist/privacy-center.d.ts +6 -10
  4. package/dist/privacy-center.js +208 -850
  5. package/dist/privacy-center.mjs +208 -850
  6. package/package.json +1 -1
  7. package/src/RedactoPrivacyCenter/components/DataRequest/ProductSelectionSection.tsx +4 -4
  8. package/src/RedactoPrivacyCenter/components/Form.tsx +214 -866
  9. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +44 -219
  10. package/src/RedactoPrivacyCenter/components/types.ts +14 -20
  11. package/src/RedactoPrivacyCenter/lib/api.ts +23 -58
  12. package/src/RedactoPrivacyCenter/lib/types.ts +0 -31
  13. package/src/RedactoPrivacyCenter/locales/as/translation.json +2 -2
  14. package/src/RedactoPrivacyCenter/locales/bho/translation.json +2 -2
  15. package/src/RedactoPrivacyCenter/locales/bn/translation.json +2 -2
  16. package/src/RedactoPrivacyCenter/locales/brx/translation.json +2 -2
  17. package/src/RedactoPrivacyCenter/locales/doi/translation.json +2 -2
  18. package/src/RedactoPrivacyCenter/locales/en/translation.json +2 -2
  19. package/src/RedactoPrivacyCenter/locales/gom/translation.json +2 -2
  20. package/src/RedactoPrivacyCenter/locales/gu/translation.json +2 -2
  21. package/src/RedactoPrivacyCenter/locales/hi/translation.json +2 -2
  22. package/src/RedactoPrivacyCenter/locales/kn/translation.json +2 -2
  23. package/src/RedactoPrivacyCenter/locales/ks/translation.json +2 -2
  24. package/src/RedactoPrivacyCenter/locales/mai/translation.json +2 -2
  25. package/src/RedactoPrivacyCenter/locales/ml/translation.json +2 -2
  26. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +2 -2
  27. package/src/RedactoPrivacyCenter/locales/mr/translation.json +2 -2
  28. package/src/RedactoPrivacyCenter/locales/ne/translation.json +2 -2
  29. package/src/RedactoPrivacyCenter/locales/or/translation.json +2 -2
  30. package/src/RedactoPrivacyCenter/locales/pa/translation.json +2 -2
  31. package/src/RedactoPrivacyCenter/locales/sa/translation.json +2 -2
  32. package/src/RedactoPrivacyCenter/locales/sat/translation.json +2 -2
  33. package/src/RedactoPrivacyCenter/locales/sd/translation.json +2 -2
  34. package/src/RedactoPrivacyCenter/locales/ta/translation.json +2 -2
  35. package/src/RedactoPrivacyCenter/locales/te/translation.json +2 -2
  36. package/src/RedactoPrivacyCenter/locales/ur/translation.json +2 -2
  37. package/tests/Form.test.tsx +123 -149
  38. package/tests/ProductSelectionSection.test.tsx +39 -4
  39. package/tests/RequestSubmit.test.tsx +27 -187
@@ -4,18 +4,13 @@ import { describe, it, expect, vi } from 'vitest';
4
4
  // react-i18next is an optional peer dep that is not installed in this
5
5
  // package's node_modules. Stub it with a minimal `useTranslation` that
6
6
  // resolves the en/translation.json keys we use here, with interpolation
7
- // and `_one` / `_other` plural suffix support.
7
+ // support.
8
8
  vi.mock('react-i18next', () => {
9
9
  const en: Record<string, string> = {
10
10
  requestSubmittedSuccessfully: 'Request Submitted Successfully',
11
11
  thankYouForSubmittingYourRequest: 'Thank you for submitting your request',
12
12
  caseID: 'Case ID',
13
13
  saveThisID: 'Please save this ID for future reference',
14
- casesCreatedCount_one: '{{count}} case created',
15
- casesCreatedCount_other: '{{count}} cases created',
16
- someRequestsFailed: 'Some requests failed',
17
- retry: 'Retry',
18
- retrying: 'Retrying...',
19
14
  confirmationSentTo: 'Confirmation sent to',
20
15
  processRequest: 'We will process your request as per laws',
21
16
  backToHome: 'Back to homepage',
@@ -29,14 +24,7 @@ vi.mock('react-i18next', () => {
29
24
  return value === undefined || value === null ? '' : String(value);
30
25
  });
31
26
  const t = (key: string, opts?: Record<string, unknown>) => {
32
- const count = typeof opts?.count === 'number' ? opts.count : undefined;
33
- const pluralKey =
34
- count !== undefined
35
- ? count === 1
36
- ? `${key}_one`
37
- : `${key}_other`
38
- : key;
39
- const template = en[pluralKey] ?? en[key] ?? key;
27
+ const template = en[key] ?? key;
40
28
  return interpolate(template, opts);
41
29
  };
42
30
  return {
@@ -50,191 +38,72 @@ vi.mock('lucide-react', () => ({
50
38
  }));
51
39
 
52
40
  import RequestSubmit from '../src/RedactoPrivacyCenter/components/RequestSubmit';
53
- import type { BatchCaseSubmitResult } from '../src/RedactoPrivacyCenter/lib/types';
41
+ import type { RequestSubmitResult } from '../src/RedactoPrivacyCenter/components/types';
54
42
 
55
- const makeResult = (
56
- overrides: Partial<BatchCaseSubmitResult> = {},
57
- ): BatchCaseSubmitResult => ({
58
- succeeded: [],
59
- failed: [],
60
- ...overrides,
61
- });
62
-
63
- const singleSucceeded: BatchCaseSubmitResult = {
64
- succeeded: [
65
- {
66
- group_key: '',
67
- product_uuid: '',
68
- product_name: '',
69
- case_id: 'CASE-ONLY',
70
- uuid: 'uuid-only',
71
- },
72
- ],
73
- failed: [],
74
- };
75
-
76
- const multiSucceeded: BatchCaseSubmitResult = {
77
- succeeded: [
78
- {
79
- group_key: 'prod-1',
80
- product_uuid: 'prod-1',
81
- product_name: 'Acme Loyalty',
82
- case_id: 'CASE-1',
83
- uuid: 'uuid-1',
84
- },
85
- {
86
- group_key: 'prod-2',
87
- product_uuid: 'prod-2',
88
- product_name: 'Acme Rewards',
89
- case_id: 'CASE-2',
90
- uuid: 'uuid-2',
91
- },
92
- ],
93
- failed: [],
43
+ const productScopedResult: RequestSubmitResult = {
44
+ case_id: 'CASE-PROD',
45
+ uuid: 'uuid-prod',
46
+ product_name: 'Acme Loyalty',
47
+ product_uuid: 'prod-1',
94
48
  };
95
49
 
96
- const partialResult: BatchCaseSubmitResult = {
97
- succeeded: [
98
- {
99
- group_key: 'prod-ok',
100
- product_uuid: 'prod-ok',
101
- product_name: 'Working Product',
102
- case_id: 'CASE-OK',
103
- uuid: 'uuid-ok',
104
- },
105
- ],
106
- failed: [
107
- {
108
- group_key: 'prod-fail',
109
- product_uuid: 'prod-fail',
110
- product_name: 'Broken Product',
111
- error: 'Network unreachable',
112
- },
113
- ],
50
+ const orgLevelResult: RequestSubmitResult = {
51
+ case_id: 'CASE-ORG',
52
+ uuid: 'uuid-org',
53
+ product_name: '',
54
+ product_uuid: '',
114
55
  };
115
56
 
116
57
  describe('RequestSubmit', () => {
117
- it('renders a single-case success card (legacy / nomination / grievance shape)', () => {
58
+ it('renders the case id and product label for a product-scoped result', () => {
118
59
  render(
119
60
  <RequestSubmit
120
- result={singleSucceeded}
61
+ result={productScopedResult}
121
62
  contact="alice@example.com"
122
63
  onCreateRequest={vi.fn()}
123
64
  />,
124
65
  );
125
66
 
126
- // Single-case branch: one "Case ID: ..." row, no plural header, no
127
- // failure block.
128
67
  expect(
129
68
  screen.getByText('Request Submitted Successfully'),
130
69
  ).toBeInTheDocument();
131
- expect(screen.getByText(/Case ID:\s*CASE-ONLY/)).toBeInTheDocument();
132
- expect(screen.queryByText(/cases created/)).not.toBeInTheDocument();
133
- expect(
134
- screen.queryByText('Some requests failed'),
135
- ).not.toBeInTheDocument();
136
- expect(screen.queryByRole('button', { name: 'Retry' })).toBeNull();
70
+ expect(screen.getByText(/Case ID:\s*CASE-PROD/)).toBeInTheDocument();
71
+ expect(screen.getByText('Acme Loyalty')).toBeInTheDocument();
137
72
  });
138
73
 
139
- it('renders a multi-case success list with product names and case IDs', () => {
74
+ it('omits the product label when the result has no product anchor', () => {
140
75
  render(
141
76
  <RequestSubmit
142
- result={multiSucceeded}
77
+ result={orgLevelResult}
143
78
  contact="alice@example.com"
144
79
  onCreateRequest={vi.fn()}
145
80
  />,
146
81
  );
147
82
 
148
- // Header reflects pluralised count.
149
- expect(screen.getByText('2 cases created')).toBeInTheDocument();
150
- // Each entry renders product name + case id.
151
- expect(screen.getByText('Acme Loyalty')).toBeInTheDocument();
152
- expect(screen.getByText('Acme Rewards')).toBeInTheDocument();
153
- expect(screen.getByText(/Case ID:\s*CASE-1/)).toBeInTheDocument();
154
- expect(screen.getByText(/Case ID:\s*CASE-2/)).toBeInTheDocument();
155
- // No failure block when all succeeded.
156
- expect(
157
- screen.queryByText('Some requests failed'),
158
- ).not.toBeInTheDocument();
83
+ expect(screen.getByText(/Case ID:\s*CASE-ORG/)).toBeInTheDocument();
84
+ // Empty product_name means the product line is not rendered.
85
+ expect(screen.queryByText('Acme Loyalty')).not.toBeInTheDocument();
159
86
  });
160
87
 
161
- it('renders a failed-cases block with per-row retry buttons when failures exist', () => {
88
+ it('shows the contact in the confirmation line', () => {
162
89
  render(
163
90
  <RequestSubmit
164
- result={partialResult}
91
+ result={productScopedResult}
165
92
  contact="alice@example.com"
166
93
  onCreateRequest={vi.fn()}
167
- onRetry={vi.fn()}
168
94
  />,
169
95
  );
170
96
 
171
- // Succeeded row still present.
172
- expect(screen.getByText('Working Product')).toBeInTheDocument();
173
- expect(screen.getByText(/Case ID:\s*CASE-OK/)).toBeInTheDocument();
174
-
175
- // Failure block with product name, error message, retry button.
176
- expect(screen.getByText('Some requests failed')).toBeInTheDocument();
177
- expect(screen.getByText('Broken Product')).toBeInTheDocument();
178
- expect(screen.getByText('Network unreachable')).toBeInTheDocument();
179
97
  expect(
180
- screen.getByRole('button', { name: 'Retry' }),
98
+ screen.getByText(/Confirmation sent to:\s*alice@example\.com/),
181
99
  ).toBeInTheDocument();
182
100
  });
183
101
 
184
- it('does not render retry buttons when onRetry is not provided', () => {
185
- render(
186
- <RequestSubmit
187
- result={partialResult}
188
- contact="alice@example.com"
189
- onCreateRequest={vi.fn()}
190
- />,
191
- );
192
-
193
- expect(screen.getByText('Some requests failed')).toBeInTheDocument();
194
- expect(screen.queryByRole('button', { name: 'Retry' })).toBeNull();
195
- });
196
-
197
- it('invokes onRetry with the failed entry group_key when retry is clicked', () => {
198
- const onRetry = vi.fn();
199
- render(
200
- <RequestSubmit
201
- result={partialResult}
202
- contact="alice@example.com"
203
- onCreateRequest={vi.fn()}
204
- onRetry={onRetry}
205
- />,
206
- );
207
-
208
- fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
209
-
210
- expect(onRetry).toHaveBeenCalledTimes(1);
211
- expect(onRetry).toHaveBeenCalledWith('prod-fail');
212
- });
213
-
214
- it('shows the Retrying… label and disables the button for rows in retryingGroupKeys', () => {
215
- const onRetry = vi.fn();
216
- render(
217
- <RequestSubmit
218
- result={partialResult}
219
- contact="alice@example.com"
220
- onCreateRequest={vi.fn()}
221
- onRetry={onRetry}
222
- retryingGroupKeys={new Set(['prod-fail'])}
223
- />,
224
- );
225
-
226
- const button = screen.getByRole('button', {
227
- name: /Retrying/,
228
- }) as HTMLButtonElement;
229
- expect(button).toBeInTheDocument();
230
- expect(button.disabled).toBe(true);
231
- });
232
-
233
102
  it('invokes onCreateRequest when "Back to homepage" is clicked', () => {
234
103
  const onCreateRequest = vi.fn();
235
104
  render(
236
105
  <RequestSubmit
237
- result={singleSucceeded}
106
+ result={productScopedResult}
238
107
  contact="alice@example.com"
239
108
  onCreateRequest={onCreateRequest}
240
109
  />,
@@ -251,7 +120,7 @@ describe('RequestSubmit', () => {
251
120
  it('renders the erasure-revoke notice when revokeConsentOnFulfilment is true', () => {
252
121
  render(
253
122
  <RequestSubmit
254
- result={singleSucceeded}
123
+ result={productScopedResult}
255
124
  contact="alice@example.com"
256
125
  onCreateRequest={vi.fn()}
257
126
  revokeConsentOnFulfilment
@@ -263,33 +132,4 @@ describe('RequestSubmit', () => {
263
132
  screen.getByText(/Marketing, Analytics/),
264
133
  ).toBeInTheDocument();
265
134
  });
266
-
267
- it('renders nothing in the case-list area when succeeded is empty (failures-only)', () => {
268
- // Edge case: every product failed. Header still shows the success
269
- // banner (parent decides whether to render); the case-list area is
270
- // omitted and the failure block is the only middle content.
271
- const failuresOnly = makeResult({
272
- failed: [
273
- {
274
- group_key: 'prod-a',
275
- product_uuid: 'prod-a',
276
- product_name: 'Product A',
277
- error: 'Timeout',
278
- },
279
- ],
280
- });
281
- render(
282
- <RequestSubmit
283
- result={failuresOnly}
284
- contact="alice@example.com"
285
- onCreateRequest={vi.fn()}
286
- onRetry={vi.fn()}
287
- />,
288
- );
289
-
290
- expect(screen.queryByText(/Case ID:/)).not.toBeInTheDocument();
291
- expect(screen.queryByText(/cases created/)).not.toBeInTheDocument();
292
- expect(screen.getByText('Some requests failed')).toBeInTheDocument();
293
- expect(screen.getByText('Product A')).toBeInTheDocument();
294
- });
295
135
  });