@sudeep7353/grantly-react-consent 0.1.5 → 0.1.6

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/README.md CHANGED
@@ -1,643 +1,636 @@
1
- # React Consent Widget | Gfox · Grantly
2
-
3
- A React component library that simplifies user consent, offering components for both management and revocation.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @gfox/grantly-react-consent
9
- ```
10
-
11
- ```bash
12
- yarn add @gfox/grantly-react-consent
13
- ```
14
-
15
- ```bash
16
- pnpm add @gfox/grantly-react-consent
17
- ```
18
-
19
- ## Quick Start
20
-
21
- Get started with the Consent Widget in just a few steps:
22
-
23
- 1. **Install the package** (see installation above)
24
- 2. **Import the component** in your React application
25
- 3. **Configure the required props**
26
- 4. **Add your styling** (optional)
27
-
28
- ### Basic Example
29
-
30
- ```jsx
31
- import React from 'react';
32
- import { ConsentWidget } from '@gfox/grantly-react-consent';
33
-
34
- function App() {
35
- const handleStatusChange = (data) => {
36
- console.log('Consent status changed:', data.status, data);
37
-
38
- if (data.status === 'accepted') {
39
- console.log('User accepted purposes:', data.acceptedPurposes);
40
- } else if (data.status === 'already-consented') {
41
- console.log('User already consented');
42
- }
43
- };
44
-
45
- return (
46
- <div className="App">
47
- <ConsentWidget
48
- clientId="your-client-id"
49
- xUserId="user-123"
50
- xAuthToken="your-auth-token"
51
- onStatusChange={handleStatusChange}
52
- />
53
- </div>
54
- );
55
- }
56
-
57
- export default App;
58
- ```
59
-
60
- **Alternative:** You can also provide the auth token via URL query parameter:
61
- ```jsx
62
- // If xAuthToken prop is not provided, widget will check ?token=... in URL
63
- <ConsentWidget
64
- clientId="your-client-id"
65
- xUserId="user-123"
66
- // xAuthToken can be omitted if provided via ?token=... in URL
67
- />
68
- ```
69
-
70
- ### Environment Setup
71
-
72
- For production use, make sure to set up your environment variables:
73
-
74
- ```bash
75
- # .env
76
- REACT_APP_CLIENT_ID=your-client-id
77
- ```
78
-
79
- ---
80
-
81
- ## ConsentWidget
82
-
83
- A React component for managing user consent and data agreements with multiple display modes and customizable styling.
84
-
85
- ### Description
86
-
87
- The Consent Widget is a flexible React component that handles user consent management for data processing purposes. It supports three display modes: popup (default), inline, and plain, making it suitable for various integration scenarios. The widget automatically fetches consent agreements from an API and provides an intuitive interface for users to select their consent preferences.
88
-
89
- **Key Features:**
90
- - Automatic scroll locking in popup mode
91
- - URL token parameter fallback support (`?token=...`)
92
- - Consent modification mode with smart button enabling
93
- - Programmatic consent submission via ref
94
- - Comprehensive status callbacks
95
- - Multi-language support via `acceptLanguage` prop
96
-
97
- ### Usage
98
-
99
- ```jsx
100
- import { ConsentWidget } from '@gfox/grantly-react-consent';
101
-
102
- function App() {
103
- const handleStatusChange = (data) => {
104
- console.log('Consent status:', data.status);
105
- };
106
-
107
- return (
108
- <ConsentWidget
109
- clientId="your-client-id"
110
- xUserId="user-123"
111
- xAuthToken="auth-token"
112
- acceptLanguage="en"
113
- onStatusChange={handleStatusChange}
114
- />
115
- );
116
- }
117
- ```
118
-
119
- ### Props
120
-
121
- | Prop | Type | Required | Default | Description |
122
- |------|------|----------|---------|-------------|
123
- | `clientId` | `string` | Required | - | Client identifier for the consent request |
124
- | `xUserId` | `string` | Required | - | User identifier |
125
- | `xAuthToken` | `string` | Optional* | - | Authentication token. Falls back to URL query parameter `token` if not provided |
126
- | `acceptLanguage` | `string` | Optional | `"en"` | Language code for content localization (e.g., "en", "en-US", "hi") |
127
- | `displayMode` | `string` | Optional | `""` | Display mode flags (see Display Modes below) |
128
- | `modifyConsent` | `boolean` | Optional | `false` | Enable consent modification mode. When enabled, the submit button is only enabled when consent has been modified |
129
- | `onStatusChange` | `function` | Optional | - | Callback for consent status updates (see Status Callbacks below) |
130
-
131
- \* `xAuthToken` is required, but can be provided via URL query parameter `?token=...` as a fallback
132
-
133
- #### Display Modes
134
-
135
- The `displayMode` prop accepts space-separated flags:
136
-
137
- | Flag | Description |
138
- |------|-------------|
139
- | **Default (popup)** | Modal overlay with full consent interface. Automatically locks background scroll when open |
140
- | `plain` | Inline display without modal styling. No scroll locking |
141
- | `inline` | Compact inline display (hides title, agreement text, and action buttons) |
142
- | `no-agreement-title` | Hide the agreement title |
143
- | `no-agreement-text` | Hide the agreement description text |
144
- | `no-change-summary` | Hide purpose change summaries (shown by default in all modes except `inline`) |
145
-
146
- **Note:** In default (popup) mode, the widget automatically locks background scrolling when the modal is open and restores it when closed. This ensures a clean user experience where users can only interact with the consent modal when it's open.
147
-
148
- **Examples:**
149
- ```jsx
150
- // Modal popup (default)
151
- <ConsentWidget displayMode="" />
152
-
153
- // Plain inline mode
154
- <ConsentWidget displayMode="plain" />
155
-
156
- // Compact inline with hidden title
157
- <ConsentWidget displayMode="inline no-agreement-title" />
158
-
159
- // Plain mode with hidden change summary
160
- <ConsentWidget displayMode="plain no-change-summary" />
161
- ```
162
-
163
- ### Ref Methods
164
-
165
- The component supports ref forwarding and exposes the following method:
166
-
167
- | Method | Description |
168
- |--------|-------------|
169
- | `submitConsent()` | Programmatically submit the consent. Returns a Promise that resolves with the consent result |
170
-
171
- **Example:**
172
- ```jsx
173
- import { useRef } from 'react';
174
-
175
- const consentRef = useRef();
176
-
177
- const handleSubmit = async () => {
178
- try {
179
- const result = await consentRef.current.submitConsent();
180
- console.log('Consent submitted:', result);
181
- } catch (error) {
182
- console.error('Failed to submit:', error);
183
- }
184
- };
185
-
186
- <ConsentWidget
187
- ref={consentRef}
188
- clientId="your-client-id"
189
- xUserId="user-123"
190
- // ... other props
191
- />
192
- ```
193
-
194
- ### Status Callbacks
195
-
196
- The `onStatusChange` callback receives status updates with the following structure:
197
-
198
- ```jsx
199
- onStatusChange({
200
- status: 'in-progress' | 'already-consented' | 'accepted' | 'declined' | 'error',
201
- clientId: string,
202
- userId: string,
203
- // Conditional fields based on status:
204
- acceptedPurposes?: string[], // present when status is 'accepted'
205
- agreementVersionId?: string, // present when status is 'accepted'
206
- expiresAt?: string, // present when status is 'accepted'
207
- autoAccepted?: boolean, // ONLY present when status is 'already-consented' (always true)
208
- error?: string // ONLY present when status is 'error' (contains error message)
209
- })
210
- ```
211
-
212
- | Status | Description | Additional Fields |
213
- |--------|-------------|-------------------|
214
- | `"in-progress"` | Widget is fetching consent agreement data | None |
215
- | `"already-consented"` | User has already consented (204 response). Widget won't open in popup mode | `autoAccepted: true` |
216
- | `"accepted"` | User has successfully submitted consent | `acceptedPurposes`, `agreementVersionId`, `expiresAt` |
217
- | `"declined"` | User closed the modal without submitting consent | None |
218
- | `"error"` | An error occurred during fetch or submission | `error` (contains error message string) |
219
-
220
- ### Styling
221
-
222
- The widget exposes CSS custom properties for complete customization:
223
-
224
- #### Base Properties
225
- ```css
226
- --consent-widget-width: 100%;
227
- --consent-widget-font-family: inherit;
228
- --consent-widget-font-size: inherit;
229
- --consent-widget-text-color: #f9fafb;
230
- ```
231
-
232
- #### Modal Properties
233
- ```css
234
- --consent-widget-modal-bg: rgba(0, 0, 0, 0.8);
235
- --consent-widget-modal-content-bg: #1f2937;
236
- --consent-widget-modal-z-index: 9999;
237
- ```
238
-
239
- #### Container Properties
240
- ```css
241
- --consent-widget-container-bg: #1f2937;
242
- --consent-widget-container-padding: 2rem;
243
- --consent-widget-container-border-radius: 16px;
244
- --consent-widget-container-max-width: 800px;
245
- ```
246
-
247
- #### Button Properties
248
- ```css
249
- --consent-widget-primary-bg: #8b5cf6;
250
- --consent-widget-secondary-bg: transparent;
251
- --consent-widget-button-padding: 0.75rem 1.5rem;
252
- --consent-widget-button-border-radius: 6px;
253
- ```
254
-
255
- #### Spacing Properties
256
- ```css
257
- --consent-widget-spacing-xs: 0.25rem;
258
- --consent-widget-spacing-sm: 0.5rem;
259
- --consent-widget-spacing-md: 1rem;
260
- --consent-widget-spacing-lg: 1.5rem;
261
- --consent-widget-spacing-xl: 2rem;
262
- ```
263
-
264
- Override these variables in your CSS to customize the widget appearance:
265
-
266
- ```css
267
- .consent-widget {
268
- --consent-widget-primary-bg: #007bff;
269
- --consent-widget-container-border-radius: 8px;
270
- --consent-widget-text-color: #333;
271
- }
272
- ```
273
-
274
- ---
275
-
276
- ## RevokeWidget
277
-
278
- A React component that allows users to view and withdraw their existing consents. The widget displays consent information in a clean, customizable interface with support for both modal and inline display modes.
279
-
280
- **Key Features:**
281
- - Automatic modal opening when consents are loaded (default mode)
282
- - ESC key support to close modal
283
- - Automatic scroll locking in modal mode
284
- - Status callback deduplication to prevent duplicate events
285
- - Auto-detection of display mode from consent data
286
- - Multi-language support via `acceptLanguage` prop
287
-
288
- **Note:** The widget returns `null` (doesn't render) when loading or when there are no consents. Use the `onStatusChange` callback to handle these states.
289
-
290
- ### Usage
291
-
292
- ```jsx
293
- import { RevokeWidget } from '@gfox/grantly-react-consent';
294
-
295
- function App() {
296
- const handleStatusChange = (status, data) => {
297
- console.log('Widget status:', status, data);
298
- };
299
-
300
- const handleModifyConsent = (consent) => {
301
- console.log('Modify consent:', consent);
302
- };
303
-
304
- return (
305
- <RevokeWidget
306
- clientId="your-client-id"
307
- authToken="user-auth-token"
308
- onStatusChange={handleStatusChange}
309
- onModifyConsent={handleModifyConsent}
310
- />
311
- );
312
- }
313
- ```
314
-
315
- ### Props
316
-
317
- | Prop | Type | Required | Default | Description |
318
- |------|------|----------|---------|-------------|
319
- | `clientId` | `string` | Required | - | Client identifier for the application |
320
- | `authToken` | `string` | Required | - | User authentication token |
321
- | `displayMode` | `string` | Optional | `""` | Display mode flags (see Display Modes) |
322
- | `acceptLanguage` | `string` | Optional | `"en"` | Language code for content localization (e.g., "en", "en-US", "hi") |
323
- | `onStatusChange` | `function` | Optional | - | Callback for widget status changes (see Status Callbacks below) |
324
- | `onModifyConsent` | `function` | Optional | - | Callback when user clicks modify consent. Receives the consent object as parameter |
325
-
326
- #### Display Modes
327
-
328
- The `displayMode` prop accepts space-separated flags:
329
-
330
- | Flag | Description |
331
- |------|-------------|
332
- | **Default (modal)** | Modal overlay with full interface. Automatically opens when consents are loaded. Locks background scroll and supports ESC key to close |
333
- | `plain` | Inline display without modal. No scroll locking |
334
- | `no-agreement-title` | Hide agreement titles |
335
- | `no-agreement-text` | Hide agreement descriptions |
336
- | `no-consent-timestamps` | Hide consent timestamps (consented_at, expires_at, withdrawn_at) |
337
- | `no-modify-button` | Hide modify consent buttons |
338
-
339
- **Note:**
340
- - In default (modal) mode, the widget automatically opens the modal when consents are successfully loaded
341
- - The modal locks background scrolling when open and restores it when closed
342
- - Press ESC key to close the modal in default mode
343
- - The widget automatically determines the display mode from the first consent's `display_mode` if not explicitly provided via props
344
-
345
- **Examples:**
346
- ```jsx
347
- // Modal mode (default)
348
- <RevokeWidget displayMode="" />
349
-
350
- // Plain inline mode
351
- <RevokeWidget displayMode="plain" />
352
-
353
- // Plain mode with hidden titles and timestamps
354
- <RevokeWidget displayMode="plain no-agreement-title no-consent-timestamps" />
355
-
356
- // Modal mode without modify buttons
357
- <RevokeWidget displayMode="no-modify-button" />
358
- ```
359
-
360
- ### Styling
361
-
362
- The widget exposes CSS variables for complete customization. All variables are prefixed with `--rw-`:
363
-
364
- #### Layout & Spacing
365
- ```css
366
- --rw-max-width: 800px;
367
- --rw-padding: 20px;
368
- --rw-margin: 0 auto;
369
- --rw-border-radius: 8px;
370
- --rw-spacing-xs: 4px;
371
- --rw-spacing-sm: 8px;
372
- --rw-spacing-md: 12px;
373
- --rw-spacing-lg: 16px;
374
- --rw-spacing-xl: 20px;
375
- --rw-spacing-2xl: 24px;
376
- --rw-spacing-3xl: 40px;
377
- ```
378
-
379
- #### Colors
380
- ```css
381
- --rw-bg-primary: #ffffff;
382
- --rw-bg-secondary: #f9fafb;
383
- --rw-text-primary: #1f2937;
384
- --rw-text-secondary: #4b5563;
385
- --rw-primary: #8b5cf6;
386
- --rw-danger: #8b5cf6;
387
- --rw-success: #059669;
388
- ```
389
-
390
- #### Typography
391
- ```css
392
- --rw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
393
- --rw-font-size-base: 14px;
394
- --rw-font-size-small: 12px;
395
- --rw-font-size-large: 18px;
396
- --rw-font-size-xl: 24px;
397
- --rw-font-weight-normal: 400;
398
- --rw-font-weight-medium: 500;
399
- --rw-font-weight-semibold: 600;
400
- ```
401
-
402
- #### Shadows & Transitions
403
- ```css
404
- --rw-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);
405
- --rw-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
406
- --rw-shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.15);
407
- --rw-transition-fast: 0.15s ease;
408
- --rw-transition-normal: 0.2s ease;
409
- --rw-transition-slow: 0.3s ease;
410
- ```
411
-
412
- #### Plain Mode Variables
413
- ```css
414
- --rw-plain-bg: transparent;
415
- --rw-plain-shadow: none;
416
- --rw-plain-border: none;
417
- --rw-plain-padding: 0;
418
- --rw-plain-margin: 0;
419
- --rw-plain-max-width: none;
420
- --rw-plain-min-height: auto;
421
- ```
422
-
423
- ### Status Callbacks
424
-
425
- The `onStatusChange` callback receives status updates with the following structure:
426
-
427
- ```jsx
428
- onStatusChange({
429
- status: 'in-progress' | 'consents-loaded' | 'no-consents' | 'withdrawing' | 'withdrawn' | 'error',
430
- clientId: string,
431
- // Additional fields based on status:
432
- consents?: Consent[], // present when status is 'consents-loaded' or 'withdrawn'
433
- totalCount?: number, // present when status is 'consents-loaded' or 'no-consents'
434
- withdrawnConsent?: Consent, // present when status is 'withdrawing' or 'withdrawn'
435
- error?: string // present when status is 'error'
436
- })
437
- ```
438
-
439
- | Status | Description |
440
- |--------|-------------|
441
- | `"in-progress"` | Widget is fetching consents from the API |
442
- | `"consents-loaded"` | Consents successfully loaded. Includes `consents` array and `totalCount` |
443
- | `"no-consents"` | User has no active consents. Includes `totalCount: 0` |
444
- | `"withdrawing"` | Consent withdrawal in progress. Includes `withdrawnConsent` |
445
- | `"withdrawn"` | Consent successfully withdrawn. Includes updated `consents` array and `withdrawnConsent` |
446
- | `"error"` | Error occurred during operation. Includes `error` message |
447
-
448
- **Note:** The callback uses deduplication to avoid firing duplicate status updates for the same state.
449
-
450
- **Example:**
451
- ```jsx
452
- <RevokeWidget
453
- onStatusChange={(data) => {
454
- switch (data.status) {
455
- case 'consents-loaded':
456
- console.log('Loaded consents:', data.consents);
457
- console.log('Total count:', data.totalCount);
458
- break;
459
- case 'withdrawn':
460
- console.log('Consent withdrawn:', data.withdrawnConsent);
461
- console.log('Remaining consents:', data.consents);
462
- break;
463
- case 'no-consents':
464
- console.log('User has no active consents');
465
- break;
466
- case 'error':
467
- console.error('Error:', data.error);
468
- break;
469
- }
470
- }}
471
- />
472
- ```
473
-
474
- ## Examples
475
-
476
- ### Advanced ConsentWidget Usage
477
-
478
- #### Inline Mode with Custom Styling
479
-
480
- ```jsx
481
- import React from 'react';
482
- import { ConsentWidget } from '@gfox/grantly-react-consent';
483
- import './ConsentWidget.css';
484
-
485
- function InlineConsent() {
486
- return (
487
- <div className="consent-section">
488
- <h2>Data Processing Consent</h2>
489
- <ConsentWidget
490
- clientId="your-client-id"
491
- xUserId="user-123"
492
- xAuthToken="your-auth-token"
493
- displayMode="inline no-agreement-title"
494
- acceptLanguage="en"
495
- onStatusChange={(data) => {
496
- if (data.status === 'accepted') {
497
- console.log('User has given consent');
498
- }
499
- }}
500
- />
501
- </div>
502
- );
503
- }
504
- ```
505
-
506
- #### Plain Mode for Embedded Use
507
-
508
- ```jsx
509
- import React from 'react';
510
- import { ConsentWidget } from '@gfox/grantly-react-consent';
511
-
512
- function EmbeddedConsent() {
513
- return (
514
- <div className="embedded-consent">
515
- <ConsentWidget
516
- clientId="your-client-id"
517
- xUserId="user-123"
518
- xAuthToken="your-auth-token"
519
- displayMode="plain no-change-summary"
520
- modifyConsent={true}
521
- onStatusChange={(data) => {
522
- // Handle consent modifications
523
- if (data.status === 'accepted') {
524
- console.log('Consent modified:', data.acceptedPurposes);
525
- }
526
- }}
527
- />
528
- </div>
529
- );
530
- }
531
- ```
532
-
533
- **Note:** When `modifyConsent={true}`, the submit button is only enabled when the user has actually changed their consent selection from the initial state. This prevents unnecessary API calls when no changes are made.
534
-
535
- ### Advanced RevokeWidget Usage
536
-
537
- #### Modal Mode with Custom Callbacks
538
-
539
- ```jsx
540
- import React, { useState } from 'react';
541
- import { RevokeWidget } from '@gfox/grantly-react-consent';
542
-
543
- function ConsentManagement() {
544
- const [consentData, setConsentData] = useState(null);
545
-
546
- const handleStatusChange = (data) => {
547
- switch (data.status) {
548
- case 'consents-loaded':
549
- setConsentData(data.consents);
550
- break;
551
- case 'withdrawn':
552
- alert('Consent successfully withdrawn');
553
- setConsentData(data.consents); // Update with remaining consents
554
- break;
555
- case 'error':
556
- console.error('Error:', data.error);
557
- break;
558
- }
559
- };
560
-
561
- const handleModifyConsent = (consent) => {
562
- // Redirect to consent modification page
563
- window.location.href = `/modify-consent/${consent.id}`;
564
- };
565
-
566
- return (
567
- <div>
568
- <h1>Manage Your Consents</h1>
569
- <RevokeWidget
570
- clientId="your-client-id"
571
- authToken="user-auth-token"
572
- onStatusChange={handleStatusChange}
573
- onModifyConsent={handleModifyConsent}
574
- />
575
- </div>
576
- );
577
- }
578
- ```
579
-
580
- #### Plain Mode for Settings Page
581
-
582
- ```jsx
583
- import React from 'react';
584
- import { RevokeWidget } from '@gfox/grantly-react-consent';
585
-
586
- function SettingsPage() {
587
- return (
588
- <div className="settings-page">
589
- <h2>Privacy Settings</h2>
590
- <div className="consent-management">
591
- <RevokeWidget
592
- clientId="your-client-id"
593
- authToken="user-auth-token"
594
- displayMode="plain no-modify-button"
595
- acceptLanguage="en"
596
- onStatusChange={(data) => {
597
- if (data.status === 'no-consents') {
598
- console.log('User has no active consents');
599
- }
600
- }}
601
- />
602
- </div>
603
- </div>
604
- );
605
- }
606
- ```
607
-
608
- ### Custom Styling Examples
609
-
610
- #### Dark Theme
611
-
612
- ```css
613
- .consent-widget {
614
- --consent-widget-container-bg: #1a1a1a;
615
- --consent-widget-text-color: #ffffff;
616
- --consent-widget-primary-bg: #6366f1;
617
- --consent-widget-modal-bg: rgba(0, 0, 0, 0.9);
618
- --consent-widget-modal-content-bg: #2d2d2d;
619
- }
620
- ```
621
-
622
- #### Brand Colors
623
-
624
- ```css
625
- .consent-widget {
626
- --consent-widget-primary-bg: #007bff;
627
- --consent-widget-secondary-bg: #6c757d;
628
- --consent-widget-container-border-radius: 12px;
629
- --consent-widget-button-padding: 1rem 2rem;
630
- }
631
- ```
632
-
633
- #### Minimal Design
634
-
635
- ```css
636
- .consent-widget {
637
- --consent-widget-container-bg: transparent;
638
- --consent-widget-container-padding: 1rem;
639
- --consent-widget-container-border-radius: 4px;
640
- --consent-widget-button-border-radius: 4px;
641
- --consent-widget-spacing-md: 0.5rem;
642
- }
643
- ```
1
+ # @gfox/grantly-react-consent
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@gfox/grantly-react-consent.svg)](https://www.npmjs.com/package/@gfox/grantly-react-consent)
4
+ [![npm license](https://img.shields.io/npm/l/@gfox/grantly-react-consent.svg)](https://www.npmjs.com/package/@gfox/grantly-react-consent)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@gfox/grantly-react-consent.svg)](https://www.npmjs.com/package/@gfox/grantly-react-consent)
6
+
7
+ A React component library that simplifies user consent management, offering components for both consent collection and revocation. Built with TypeScript support and fully customizable styling.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Requirements](#requirements)
14
+ - [TypeScript Support](#typescript-support)
15
+ - [ConsentWidget](#consentwidget)
16
+ - [Props](#consentwidget-props)
17
+ - [Display Modes](#display-modes)
18
+ - [Ref Methods](#ref-methods)
19
+ - [Status Callbacks](#status-callbacks)
20
+ - [Styling](#consentwidget-styling)
21
+ - [WithdrawConsentWidget](#withdrawconsentwidget)
22
+ - [Props](#withdrawconsentwidget-props)
23
+ - [Display Modes](#withdrawconsentwidget-display-modes)
24
+ - [Status Callbacks](#withdrawconsentwidget-status-callbacks)
25
+ - [Styling](#withdrawconsentwidget-styling)
26
+ - [Examples](#examples)
27
+ - [Troubleshooting](#troubleshooting)
28
+ - [License](#license)
29
+
30
+ ## Installation
31
+
32
+ Install the package using your preferred package manager:
33
+
34
+ ```bash
35
+ npm install @gfox/grantly-react-consent
36
+ ```
37
+
38
+ ```bash
39
+ yarn add @gfox/grantly-react-consent
40
+ ```
41
+
42
+ ```bash
43
+ pnpm add @gfox/grantly-react-consent
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ Get started with the Consent Widget in just a few steps:
49
+
50
+ 1. **Install the package** (see installation above)
51
+ 2. **Import the component** in your React application
52
+ 3. **Configure the required props**
53
+
54
+ ### Basic Example
55
+
56
+ ```jsx
57
+ import React from 'react';
58
+ import { ConsentWidget } from '@gfox/grantly-react-consent';
59
+
60
+ function App() {
61
+ const handleStatusChange = (data) => {
62
+ console.log('Consent status changed:', data.status, data);
63
+
64
+ if (data.status === 'accepted') {
65
+ console.log('User accepted purposes:', data.acceptedPurposes);
66
+ } else if (data.status === 'already-consented') {
67
+ console.log('User already consented');
68
+ }
69
+ };
70
+
71
+ return (
72
+ <div className="App">
73
+ <ConsentWidget
74
+ clientId="your-client-id"
75
+ xUserId="user-123"
76
+ onStatusChange={handleStatusChange}
77
+ />
78
+ </div>
79
+ );
80
+ }
81
+
82
+ export default App;
83
+ ```
84
+
85
+ ## Requirements
86
+
87
+ - **React** >= 17.0.0
88
+ - **React-DOM** >= 17.0.0
89
+
90
+ These are peer dependencies and must be installed in your project.
91
+
92
+ ## TypeScript Support
93
+
94
+ This package includes full TypeScript definitions. No additional `@types` package is required.
95
+
96
+ ```tsx
97
+ import { ConsentWidget, WithdrawConsentWidget, ConsentStatusDetail, WithdrawConsentStatusDetail } from '@gfox/grantly-react-consent';
98
+
99
+ function App() {
100
+ const handleStatusChange = (data: ConsentStatusDetail) => {
101
+ // TypeScript will provide full type checking and autocomplete
102
+ if (data.status === 'accepted') {
103
+ console.log(data.acceptedPurposes); // TypeScript knows this exists
104
+ }
105
+ };
106
+
107
+ return (
108
+ <ConsentWidget
109
+ clientId="your-client-id"
110
+ xUserId="user-123"
111
+ onStatusChange={handleStatusChange}
112
+ />
113
+ );
114
+ }
115
+ ```
116
+
117
+ ## ConsentWidget
118
+
119
+ A React component for managing user consent and data agreements with multiple display modes and customizable styling.
120
+
121
+ ### Description
122
+
123
+ The Consent Widget is a flexible React component that handles user consent management for data processing purposes. It supports three display modes: popup (default), inline, and plain, making it suitable for various integration scenarios. The widget automatically fetches consent agreements from an API and provides an intuitive interface for users to select their consent preferences.
124
+
125
+ **Key Features:**
126
+ - Automatic scroll locking in popup mode
127
+ - Consent modification mode with smart button enabling
128
+ - Programmatic consent submission via ref
129
+ - Comprehensive status callbacks
130
+ - Multi-language support via `acceptLanguage` prop
131
+ - Markdown support for agreement text
132
+ - XSS protection via DOMPurify
133
+
134
+ ### ConsentWidget Props
135
+
136
+ | Prop | Type | Required | Default | Description |
137
+ |------|------|----------|---------|-------------|
138
+ | `clientId` | `string` | Required | - | Client identifier for the consent request |
139
+ | `xUserId` | `string` | Required | - | User identifier |
140
+ | `acceptLanguage` | `string` | Optional | `"en"` | Language code for content localization (e.g., "en", "en-US", "hi") |
141
+ | `displayMode` | `string` | Optional | `""` | Display mode flags (see Display Modes below) |
142
+ | `modifyConsent` | `boolean` | Optional | `false` | Enable consent modification mode. When enabled, the submit button is only enabled when consent has been modified |
143
+ | `onStatusChange` | `function` | Optional | - | Callback for consent status updates (see Status Callbacks below) |
144
+
145
+ ### Display Modes
146
+
147
+ The `displayMode` prop accepts space-separated flags:
148
+
149
+ | Flag | Description |
150
+ |------|-------------|
151
+ | **Default (popup)** | Modal overlay with full consent interface. Automatically locks background scroll when open |
152
+ | `plain` | Inline display without modal styling. No scroll locking |
153
+ | `inline` | Compact inline display (hides title, agreement text, and action buttons) |
154
+ | `no-agreement-title` | Hide the agreement title |
155
+ | `no-agreement-text` | Hide the agreement description text |
156
+ | `no-change-summary` | Hide purpose change summaries (shown by default in all modes except `inline`) |
157
+
158
+ **Note:** In default (popup) mode, the widget automatically locks background scrolling when the modal is open and restores it when closed. This ensures a clean user experience where users can only interact with the consent modal when it's open.
159
+
160
+ **Examples:**
161
+ ```jsx
162
+ // Modal popup (default)
163
+ <ConsentWidget displayMode="" />
164
+
165
+ // Plain inline mode
166
+ <ConsentWidget displayMode="plain" />
167
+
168
+ // Compact inline with hidden title
169
+ <ConsentWidget displayMode="inline no-agreement-title" />
170
+
171
+ // Plain mode with hidden change summary
172
+ <ConsentWidget displayMode="plain no-change-summary" />
173
+ ```
174
+
175
+ ### Ref Methods
176
+
177
+ The component supports ref forwarding and exposes the following method:
178
+
179
+ | Method | Description |
180
+ |--------|-------------|
181
+ | `submitConsent()` | Programmatically submit the consent. Returns a Promise that resolves with the consent result |
182
+
183
+ **Example:**
184
+ ```jsx
185
+ import { useRef } from 'react';
186
+ import { ConsentWidget } from '@gfox/grantly-react-consent';
187
+
188
+ const consentRef = useRef();
189
+
190
+ const handleSubmit = async () => {
191
+ try {
192
+ const result = await consentRef.current.submitConsent();
193
+ console.log('Consent submitted:', result);
194
+ } catch (error) {
195
+ console.error('Failed to submit:', error);
196
+ }
197
+ };
198
+
199
+ <ConsentWidget
200
+ ref={consentRef}
201
+ clientId="your-client-id"
202
+ xUserId="user-123"
203
+ // ... other props
204
+ />
205
+ ```
206
+
207
+ ### Status Callbacks
208
+
209
+ The `onStatusChange` callback receives status updates with the following structure:
210
+
211
+ ```tsx
212
+ onStatusChange({
213
+ status: 'in-progress' | 'already-consented' | 'accepted' | 'declined' | 'error',
214
+ clientId: string,
215
+ userId: string,
216
+ // Conditional fields based on status:
217
+ acceptedPurposes?: string[], // present when status is 'accepted' or 'declined'
218
+ agreementVersionId?: string, // present when status is 'accepted' or 'declined'
219
+ error?: string // ONLY present when status is 'error' (contains error message)
220
+ })
221
+ ```
222
+
223
+ | Status | Description | Additional Fields |
224
+ |--------|-------------|-------------------|
225
+ | `"in-progress"` | Widget is fetching consent agreement data | None |
226
+ | `"already-consented"` | User has already consented (204 response). Widget won't open in popup mode | None |
227
+ | `"accepted"` | User has successfully submitted consent | `acceptedPurposes`, `agreementVersionId` |
228
+ | `"declined"` | User closed the modal without submitting consent | `acceptedPurposes: []`, `agreementVersionId` |
229
+ | `"error"` | An error occurred during fetch or submission | `error` (contains error message string) |
230
+
231
+ ### ConsentWidget Styling
232
+
233
+ The widget exposes CSS custom properties for complete customization:
234
+
235
+ #### Base Properties
236
+ ```css
237
+ --consent-widget-width: 100%;
238
+ --consent-widget-font-family: inherit;
239
+ --consent-widget-font-size: inherit;
240
+ --consent-widget-text-color: #f9fafb;
241
+ ```
242
+
243
+ #### Modal Properties
244
+ ```css
245
+ --consent-widget-modal-bg: rgba(0, 0, 0, 0.8);
246
+ --consent-widget-modal-content-bg: #1f2937;
247
+ --consent-widget-modal-z-index: 9999;
248
+ ```
249
+
250
+ #### Container Properties
251
+ ```css
252
+ --consent-widget-container-bg: #1f2937;
253
+ --consent-widget-container-padding: 2rem;
254
+ --consent-widget-container-border-radius: 16px;
255
+ --consent-widget-container-max-width: 800px;
256
+ ```
257
+
258
+ #### Button Properties
259
+ ```css
260
+ --consent-widget-primary-bg: #8b5cf6;
261
+ --consent-widget-secondary-bg: transparent;
262
+ --consent-widget-button-padding: 0.75rem 1.5rem;
263
+ --consent-widget-button-border-radius: 6px;
264
+ ```
265
+
266
+ #### Spacing Properties
267
+ ```css
268
+ --consent-widget-spacing-xs: 0.25rem;
269
+ --consent-widget-spacing-sm: 0.5rem;
270
+ --consent-widget-spacing-md: 1rem;
271
+ --consent-widget-spacing-lg: 1.5rem;
272
+ --consent-widget-spacing-xl: 2rem;
273
+ ```
274
+
275
+ Override these variables in your CSS to customize the widget appearance:
276
+
277
+ ```css
278
+ .consent-widget {
279
+ --consent-widget-primary-bg: #007bff;
280
+ --consent-widget-container-border-radius: 8px;
281
+ --consent-widget-text-color: #333;
282
+ }
283
+ ```
284
+
285
+ ## WithdrawConsentWidget
286
+
287
+ A React component that allows users to view and withdraw their existing consents. The widget displays consent information in a clean, customizable interface with support for both modal and inline display modes.
288
+
289
+ **Key Features:**
290
+ - Automatic modal opening when consents are loaded (default mode)
291
+ - ESC key support to close modal
292
+ - Automatic scroll locking in modal mode
293
+ - Status callback deduplication to prevent duplicate events
294
+ - Auto-detection of display mode from consent data
295
+ - Multi-language support via `acceptLanguage` prop
296
+ - Markdown support for agreement text
297
+ - XSS protection via DOMPurify
298
+
299
+ **Note:** The widget returns `null` (doesn't render) when loading or when there are no consents. Use the `onStatusChange` callback to handle these states.
300
+
301
+ ### WithdrawConsentWidget Props
302
+
303
+ | Prop | Type | Required | Default | Description |
304
+ |------|------|----------|---------|-------------|
305
+ | `clientId` | `string` | Required | - | Client identifier for the application |
306
+ | `userId` | `string` | Required | - | User identifier |
307
+ | `displayMode` | `string` | Optional | `""` | Display mode flags (see Display Modes) |
308
+ | `acceptLanguage` | `string` | Optional | `"en"` | Language code for content localization (e.g., "en", "en-US", "hi") |
309
+ | `onStatusChange` | `function` | Optional | - | Callback for widget status changes (see Status Callbacks below) |
310
+ | `onModifyConsent` | `function` | Optional | - | Callback when user clicks modify consent. Receives the consent object as parameter |
311
+
312
+ ### WithdrawConsentWidget Display Modes
313
+
314
+ The `displayMode` prop accepts space-separated flags:
315
+
316
+ | Flag | Description |
317
+ |------|-------------|
318
+ | **Default (modal)** | Modal overlay with full interface. Automatically opens when consents are loaded. Locks background scroll and supports ESC key to close |
319
+ | `plain` | Inline display without modal. No scroll locking |
320
+ | `no-agreement-title` | Hide agreement titles |
321
+ | `no-agreement-text` | Hide agreement descriptions |
322
+ | `no-consent-timestamps` | Hide consent timestamps (consented_at, expires_at, withdrawn_at) |
323
+ | `no-modify-button` | Hide modify consent buttons |
324
+
325
+ **Note:**
326
+ - In default (modal) mode, the widget automatically opens the modal when consents are successfully loaded
327
+ - The modal locks background scrolling when open and restores it when closed
328
+ - Press ESC key to close the modal in default mode
329
+ - The widget automatically determines the display mode from the first consent's `display_mode` if not explicitly provided via props
330
+
331
+ **Examples:**
332
+ ```jsx
333
+ // Modal mode (default)
334
+ <WithdrawConsentWidget displayMode="" />
335
+
336
+ // Plain inline mode
337
+ <WithdrawConsentWidget displayMode="plain" />
338
+
339
+ // Plain mode with hidden titles and timestamps
340
+ <WithdrawConsentWidget displayMode="plain no-agreement-title no-consent-timestamps" />
341
+
342
+ // Modal mode without modify buttons
343
+ <WithdrawConsentWidget displayMode="no-modify-button" />
344
+ ```
345
+
346
+ ### WithdrawConsentWidget Styling
347
+
348
+ The widget exposes CSS variables for complete customization. All variables are prefixed with `--wcw-`:
349
+
350
+ #### Layout & Spacing
351
+ ```css
352
+ --wcw-max-width: 800px;
353
+ --wcw-padding: 20px;
354
+ --wcw-margin: 0 auto;
355
+ --wcw-border-radius: 8px;
356
+ --wcw-spacing-xs: 4px;
357
+ --wcw-spacing-sm: 8px;
358
+ --wcw-spacing-md: 12px;
359
+ --wcw-spacing-lg: 16px;
360
+ --wcw-spacing-xl: 20px;
361
+ --wcw-spacing-2xl: 24px;
362
+ --wcw-spacing-3xl: 40px;
363
+ ```
364
+
365
+ #### Colors
366
+ ```css
367
+ --wcw-bg-primary: #ffffff;
368
+ --wcw-bg-secondary: #f9fafb;
369
+ --wcw-text-primary: #1f2937;
370
+ --wcw-text-secondary: #4b5563;
371
+ --wcw-primary: #8b5cf6;
372
+ --wcw-danger: #8b5cf6;
373
+ --wcw-success: #059669;
374
+ ```
375
+
376
+ #### Typography
377
+ ```css
378
+ --wcw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
379
+ --wcw-font-size-base: 14px;
380
+ --wcw-font-size-small: 12px;
381
+ --wcw-font-size-large: 18px;
382
+ --wcw-font-size-xl: 24px;
383
+ --wcw-font-weight-normal: 400;
384
+ --wcw-font-weight-medium: 500;
385
+ --wcw-font-weight-semibold: 600;
386
+ ```
387
+
388
+ #### Shadows & Transitions
389
+ ```css
390
+ --wcw-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);
391
+ --wcw-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
392
+ --wcw-shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.15);
393
+ --wcw-transition-fast: 0.15s ease;
394
+ --wcw-transition-normal: 0.2s ease;
395
+ --wcw-transition-slow: 0.3s ease;
396
+ ```
397
+
398
+ #### Plain Mode Variables
399
+ ```css
400
+ --wcw-plain-bg: transparent;
401
+ --wcw-plain-shadow: none;
402
+ --wcw-plain-border: none;
403
+ --wcw-plain-padding: 0;
404
+ --wcw-plain-margin: 0;
405
+ --wcw-plain-max-width: none;
406
+ --wcw-plain-min-height: auto;
407
+ ```
408
+
409
+ ### WithdrawConsentWidget Status Callbacks
410
+
411
+ The `onStatusChange` callback receives status updates with the following structure:
412
+
413
+ ```tsx
414
+ onStatusChange({
415
+ status: 'in-progress' | 'consents-loaded' | 'no-consents' | 'withdrawing' | 'withdrawn' | 'error',
416
+ clientId: string,
417
+ // Additional fields based on status:
418
+ consents?: Consent[], // present when status is 'consents-loaded' or 'withdrawn'
419
+ totalCount?: number, // present when status is 'consents-loaded' or 'no-consents'
420
+ withdrawnConsent?: Consent, // present when status is 'withdrawing' or 'withdrawn'
421
+ error?: string // present when status is 'error'
422
+ })
423
+ ```
424
+
425
+ | Status | Description |
426
+ |--------|-------------|
427
+ | `"in-progress"` | Widget is fetching consents from the API |
428
+ | `"consents-loaded"` | Consents successfully loaded. Includes `consents` array and `totalCount` |
429
+ | `"no-consents"` | User has no active consents. Includes `totalCount: 0` |
430
+ | `"withdrawing"` | Consent withdrawal in progress. Includes `withdrawnConsent` |
431
+ | `"withdrawn"` | Consent successfully withdrawn. Includes updated `consents` array and `withdrawnConsent` |
432
+ | `"error"` | Error occurred during operation. Includes `error` message |
433
+
434
+ **Note:** The callback uses deduplication to avoid firing duplicate status updates for the same state.
435
+
436
+ **Example:**
437
+ ```jsx
438
+ <WithdrawConsentWidget
439
+ onStatusChange={(data) => {
440
+ switch (data.status) {
441
+ case 'consents-loaded':
442
+ console.log('Loaded consents:', data.consents);
443
+ console.log('Total count:', data.totalCount);
444
+ break;
445
+ case 'withdrawn':
446
+ console.log('Consent withdrawn:', data.withdrawnConsent);
447
+ console.log('Remaining consents:', data.consents);
448
+ break;
449
+ case 'no-consents':
450
+ console.log('User has no active consents');
451
+ break;
452
+ case 'error':
453
+ console.error('Error:', data.error);
454
+ break;
455
+ }
456
+ }}
457
+ />
458
+ ```
459
+
460
+ ## Examples
461
+
462
+ ### Advanced ConsentWidget Usage
463
+
464
+ #### Inline Mode with Custom Styling
465
+
466
+ ```jsx
467
+ import React from 'react';
468
+ import { ConsentWidget } from '@gfox/grantly-react-consent';
469
+ import './ConsentWidget.css';
470
+
471
+ function InlineConsent() {
472
+ return (
473
+ <div className="consent-section">
474
+ <h2>Data Processing Consent</h2>
475
+ <ConsentWidget
476
+ clientId="your-client-id"
477
+ xUserId="user-123"
478
+ displayMode="inline no-agreement-title"
479
+ acceptLanguage="en"
480
+ onStatusChange={(data) => {
481
+ if (data.status === 'accepted') {
482
+ console.log('User has given consent');
483
+ }
484
+ }}
485
+ />
486
+ </div>
487
+ );
488
+ }
489
+ ```
490
+
491
+ #### Plain Mode for Embedded Use
492
+
493
+ ```jsx
494
+ import React from 'react';
495
+ import { ConsentWidget } from '@gfox/grantly-react-consent';
496
+
497
+ function EmbeddedConsent() {
498
+ return (
499
+ <div className="embedded-consent">
500
+ <ConsentWidget
501
+ clientId="your-client-id"
502
+ xUserId="user-123"
503
+ displayMode="plain no-change-summary"
504
+ modifyConsent={true}
505
+ onStatusChange={(data) => {
506
+ // Handle consent modifications
507
+ if (data.status === 'accepted') {
508
+ console.log('Consent modified:', data.acceptedPurposes);
509
+ }
510
+ }}
511
+ />
512
+ </div>
513
+ );
514
+ }
515
+ ```
516
+
517
+ **Note:** When `modifyConsent={true}`, the submit button is only enabled when the user has actually changed their consent selection from the initial state. This prevents unnecessary API calls when no changes are made.
518
+
519
+ ### Advanced WithdrawConsentWidget Usage
520
+
521
+ #### Modal Mode with Custom Callbacks
522
+
523
+ ```jsx
524
+ import React, { useState } from 'react';
525
+ import { WithdrawConsentWidget } from '@gfox/grantly-react-consent';
526
+
527
+ function ConsentManagement() {
528
+ const [consentData, setConsentData] = useState(null);
529
+
530
+ const handleStatusChange = (data) => {
531
+ switch (data.status) {
532
+ case 'consents-loaded':
533
+ setConsentData(data.consents);
534
+ break;
535
+ case 'withdrawn':
536
+ alert('Consent successfully withdrawn');
537
+ setConsentData(data.consents); // Update with remaining consents
538
+ break;
539
+ case 'error':
540
+ console.error('Error:', data.error);
541
+ break;
542
+ }
543
+ };
544
+
545
+ const handleModifyConsent = (consent) => {
546
+ // Redirect to consent modification page
547
+ window.location.href = `/modify-consent/${consent.id}`;
548
+ };
549
+
550
+ return (
551
+ <div>
552
+ <h1>Manage Your Consents</h1>
553
+ <WithdrawConsentWidget
554
+ clientId="your-client-id"
555
+ userId="user-123"
556
+ onStatusChange={handleStatusChange}
557
+ onModifyConsent={handleModifyConsent}
558
+ />
559
+ </div>
560
+ );
561
+ }
562
+ ```
563
+
564
+ #### Plain Mode for Settings Page
565
+
566
+ ```jsx
567
+ import React from 'react';
568
+ import { WithdrawConsentWidget } from '@gfox/grantly-react-consent';
569
+
570
+ function SettingsPage() {
571
+ return (
572
+ <div className="settings-page">
573
+ <h2>Privacy Settings</h2>
574
+ <div className="consent-management">
575
+ <WithdrawConsentWidget
576
+ clientId="your-client-id"
577
+ userId="user-123"
578
+ displayMode="plain no-modify-button"
579
+ acceptLanguage="en"
580
+ onStatusChange={(data) => {
581
+ if (data.status === 'no-consents') {
582
+ console.log('User has no active consents');
583
+ }
584
+ }}
585
+ />
586
+ </div>
587
+ </div>
588
+ );
589
+ }
590
+ ```
591
+
592
+ ### Custom Styling Examples
593
+
594
+ #### Dark Theme
595
+
596
+ ```css
597
+ .consent-widget {
598
+ --consent-widget-container-bg: #1a1a1a;
599
+ --consent-widget-text-color: #ffffff;
600
+ --consent-widget-primary-bg: #6366f1;
601
+ --consent-widget-modal-bg: rgba(0, 0, 0, 0.9);
602
+ --consent-widget-modal-content-bg: #2d2d2d;
603
+ }
604
+ ```
605
+
606
+ #### Brand Colors
607
+
608
+ ```css
609
+ .consent-widget {
610
+ --consent-widget-primary-bg: #007bff;
611
+ --consent-widget-secondary-bg: #6c757d;
612
+ --consent-widget-container-border-radius: 12px;
613
+ --consent-widget-button-padding: 1rem 2rem;
614
+ }
615
+ ```
616
+
617
+ #### Minimal Design
618
+
619
+ ```css
620
+ .consent-widget {
621
+ --consent-widget-container-bg: transparent;
622
+ --consent-widget-container-padding: 1rem;
623
+ --consent-widget-container-border-radius: 4px;
624
+ --consent-widget-button-border-radius: 4px;
625
+ --consent-widget-spacing-md: 0.5rem;
626
+ }
627
+ ```
628
+
629
+
630
+ ## License
631
+
632
+ This project is licensed under the Apache License 2.0.
633
+
634
+ ## Links
635
+
636
+ - [NPM Package](https://www.npmjs.com/package/@gfox/grantly-react-consent)