@dismissible/react-client 0.3.2-canary.2.38782c4 → 0.3.2-canary.4.578bcba

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,10 +1,10 @@
1
1
  # @dismissible/react-client
2
2
 
3
- A React component library for creating dismissible UI elements with persistent state management.
3
+ A React component library for creating dismissible UI elements with persistent state management.
4
4
 
5
- Use this in combination with [dismissible.io](https://dismissible.io). Get your free account now!
5
+ **Free and open source** - use with the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api) that you can self-host with Docker or integrate into your NestJS application.
6
6
 
7
- 🌐 **[dismissible.io](https://dismissible.io)** | 💰 **[View Pricing](https://dismissible.io/pricing)** | 📖 **[Documentation](https://docs.dismissible.io)**
7
+ 🌐 **[dismissible.io](https://dismissible.io)** | 📖 **[Documentation](https://dismissible.io/docs)** | 🐙 **[API Server](https://github.com/DismissibleIo/dismissible-api)**
8
8
 
9
9
  [![npm version](https://badge.fury.io/js/@dismissible%2Freact-client.svg)](https://badge.fury.io/js/@dismissible%2Freact-client)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -13,12 +13,14 @@ Use this in combination with [dismissible.io](https://dismissible.io). Get your
13
13
 
14
14
  - 🎯 **Easy to use** - Simple component API for dismissible content
15
15
  - 💾 **Persistent state** - Dismissal state is saved and restored across sessions
16
- - 🔐 **JWT Authentication** - Built-in support for JWT-based user authentication (Enterprise only)
16
+ - 🔄 **Restore support** - Restore previously dismissed items programmatically
17
+ - 🔐 **JWT Authentication** - Built-in support for secure JWT-based authentication
17
18
  - 🎨 **Customizable** - Custom loading, error, and dismiss button components
18
19
  - ♿ **Accessible** - Built with accessibility best practices
19
20
  - 🪝 **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
20
21
  - 📦 **Lightweight** - Minimal bundle size with tree-shaking support
21
22
  - 🔧 **TypeScript** - Full TypeScript support with complete type definitions
23
+ - 🐳 **Self-hosted** - Works with your own Dismissible API server
22
24
 
23
25
  ## Installation
24
26
 
@@ -36,13 +38,69 @@ npm install react react-dom
36
38
 
37
39
  ## Quick Start
38
40
 
41
+ ### 1. Set up the Dismissible API Server
42
+
43
+ First, you need a Dismissible API server running. The easiest way is with Docker:
44
+
45
+ ```yaml
46
+ # docker-compose.yml
47
+ version: '3.8'
48
+ services:
49
+ api:
50
+ image: dismissibleio/dismissible-api:latest
51
+ ports:
52
+ - '3001:3001'
53
+ environment:
54
+ DISMISSIBLE_PORT: 3001
55
+ DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING: postgresql://postgres:postgres@db:5432/dismissible
56
+ depends_on:
57
+ - db
58
+
59
+ db:
60
+ image: postgres:15
61
+ environment:
62
+ POSTGRES_USER: postgres
63
+ POSTGRES_PASSWORD: postgres
64
+ POSTGRES_DB: dismissible
65
+ volumes:
66
+ - postgres_data:/var/lib/postgresql/data
67
+
68
+ volumes:
69
+ postgres_data:
70
+ ```
71
+
72
+ ```bash
73
+ docker-compose up -d
74
+ ```
75
+
76
+ See the [API Server documentation](https://github.com/DismissibleIo/dismissible-api) for more deployment options including NestJS integration.
77
+
78
+ ### 2. Configure the Provider
79
+
80
+ Wrap your app with `DismissibleProvider`. The `userId` prop is **required** to track dismissals per user:
81
+
39
82
  ```tsx
40
- import React from 'react';
41
- import { Dismissible } from '@dismissible/react-client';
83
+ import { DismissibleProvider } from '@dismissible/react-client';
42
84
 
43
85
  function App() {
86
+ const userId = getCurrentUserId(); // Get from your auth system
87
+
88
+ return (
89
+ <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
90
+ <YourApp />
91
+ </DismissibleProvider>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ### 3. Use Dismissible Components
97
+
98
+ ```tsx
99
+ import { Dismissible } from '@dismissible/react-client';
100
+
101
+ function WelcomeBanner() {
44
102
  return (
45
- <Dismissible id="welcome-banner-123-413-31-1">
103
+ <Dismissible itemId="welcome-banner">
46
104
  <div className="banner">
47
105
  <h2>Welcome to our app!</h2>
48
106
  <p>This banner can be dismissed and won't show again.</p>
@@ -56,16 +114,15 @@ function App() {
56
114
 
57
115
  ### `<DismissibleProvider>` Component
58
116
 
59
- Context provider for JWT authentication and configuration. Wrap your app or components that need JWT authentication.
60
-
61
- > **Note:** JWT authentication is only available for **Enterprise customers**. For standard usage, you can use the `<Dismissible>` component directly without a provider. [View pricing →](https://dismissible.io/pricing)
117
+ Context provider that configures authentication and API settings. **Required** - all `<Dismissible>` components must be wrapped in a provider.
62
118
 
63
119
  #### Props
64
120
 
65
121
  | Prop | Type | Required | Description |
66
122
  |------|------|----------|-------------|
67
- | `jwt` | `string \| (() => string) \| (() => Promise<string>)` | | JWT token for user-specific dismissals (**Enterprise only**) |
68
- | `baseUrl` | `string` | ❌ | Custom API base URL override |
123
+ | `userId` | `string` | | User ID for tracking dismissals per user |
124
+ | `jwt` | `string \| (() => string) \| (() => Promise<string>)` | ❌ | JWT token for secure authentication |
125
+ | `baseUrl` | `string` | ❌ | API base URL (defaults to your self-hosted server) |
69
126
  | `children` | `ReactNode` | ✅ | Components that will use the dismissible functionality |
70
127
 
71
128
  #### Example
@@ -73,37 +130,53 @@ Context provider for JWT authentication and configuration. Wrap your app or comp
73
130
  ```tsx
74
131
  import { DismissibleProvider } from '@dismissible/react-client';
75
132
 
76
- // With static JWT
133
+ // Basic setup with userId
77
134
  function App() {
78
135
  return (
79
- <DismissibleProvider jwt="eyJhbGciOiJIUzI1NiIs...">
136
+ <DismissibleProvider userId="user-123" baseUrl="http://localhost:3001">
80
137
  <YourApp />
81
138
  </DismissibleProvider>
82
139
  );
83
140
  }
84
141
 
85
- // With dynamic JWT function
86
- function AppWithDynamicAuth() {
142
+ // With static JWT
143
+ function AppWithJWT() {
87
144
  return (
88
- <DismissibleProvider jwt={() => getAccessToken()}>
145
+ <DismissibleProvider
146
+ userId="user-123"
147
+ jwt="eyJhbGciOiJIUzI1NiIs..."
148
+ baseUrl="https://api.yourapp.com"
149
+ >
89
150
  <YourApp />
90
151
  </DismissibleProvider>
91
152
  );
92
153
  }
93
154
 
94
- // With async JWT function
95
- function AppWithAsyncAuth() {
155
+ // With dynamic JWT function
156
+ function AppWithDynamicAuth() {
157
+ const { user, getAccessToken } = useAuth();
158
+
96
159
  return (
97
- <DismissibleProvider jwt={async () => await fetchAccessToken()}>
160
+ <DismissibleProvider
161
+ userId={user.id}
162
+ jwt={() => getAccessToken()}
163
+ baseUrl="https://api.yourapp.com"
164
+ >
98
165
  <YourApp />
99
166
  </DismissibleProvider>
100
167
  );
101
168
  }
102
169
 
103
- // Without JWT (anonymous/backwards compatible)
104
- function AppWithoutAuth() {
170
+ // With async JWT function
171
+ function AppWithAsyncAuth() {
172
+ const { user, refreshAndGetToken } = useAuth();
173
+
105
174
  return (
106
- <DismissibleProvider>
175
+ <DismissibleProvider
176
+ userId={user.id}
177
+ jwt={async () => await refreshAndGetToken()}
178
+ baseUrl="https://api.yourapp.com"
179
+ >
107
180
  <YourApp />
108
181
  </DismissibleProvider>
109
182
  );
@@ -114,25 +187,49 @@ function AppWithoutAuth() {
114
187
 
115
188
  The main component for creating dismissible content.
116
189
 
190
+ > **Note:** The `<Dismissible>` component renders `null` when an item is dismissed. For restore functionality, use the `useDismissibleItem` hook directly in custom implementations.
191
+
117
192
  #### Props
118
193
 
119
194
  | Prop | Type | Required | Description |
120
195
  |------|------|----------|-------------|
121
- | `id` | `string` | ✅ | Unique identifier for the dismissible item |
196
+ | `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
122
197
  | `children` | `ReactNode` | ✅ | Content to render when not dismissed |
123
198
  | `onDismiss` | `() => void` | ❌ | Callback fired when item is dismissed |
124
- | `LoadingComponent` | `ComponentType<{id: string}>` | ❌ | Custom loading component |
125
- | `ErrorComponent` | `ComponentType<{id: string, error: Error}>` | ❌ | Custom error component |
126
- | `DismissButtonComponent` | `ComponentType<{id: string, onDismiss: () => Promise<void>, ariaLabel: string}>` | ❌ | Custom dismiss button |
199
+ | `LoadingComponent` | `ComponentType<{itemId: string}>` | ❌ | Custom loading component |
200
+ | `ErrorComponent` | `ComponentType<{itemId: string, error: Error}>` | ❌ | Custom error component |
201
+ | `DismissButtonComponent` | `ComponentType<{onDismiss: () => Promise<void>, ariaLabel: string}>` | ❌ | Custom dismiss button |
127
202
  | `ignoreErrors` | `boolean` | ❌ | Ignore errors and display component anyway (default: false) |
203
+ | `enableCache` | `boolean` | ❌ | Enable localStorage caching (default: true) |
204
+ | `cachePrefix` | `string` | ❌ | Cache key prefix (default: 'dismissible') |
205
+ | `cacheExpiration` | `number` | ❌ | Cache expiration time in milliseconds |
206
+ | `metadata` | `string[]` | ❌ | Optional metadata as key:value pairs (can be repeated) |
128
207
 
129
208
  #### Example
130
209
 
131
210
  ```tsx
132
211
  <Dismissible
133
- id="promo-banner"
212
+ itemId="promo-banner"
213
+ onDismiss={() => console.log('Banner dismissed')}
214
+ >
215
+ <div className="promo">
216
+ <h3>Special Offer!</h3>
217
+ <p>Get 50% off your first order</p>
218
+ </div>
219
+ </Dismissible>
220
+ ```
221
+
222
+ #### Example with Metadata
223
+
224
+ ```tsx
225
+ <Dismissible
226
+ itemId="promo-banner"
227
+ metadata={{
228
+ version: 2,
229
+ category: "promotional",
230
+ campaign: "summer-sale"
231
+ }}
134
232
  onDismiss={() => console.log('Banner dismissed')}
135
- onRestore={() => console.log('Banner restored')}
136
233
  >
137
234
  <div className="promo">
138
235
  <h3>Special Offer!</h3>
@@ -149,7 +246,18 @@ For custom implementations and advanced use cases.
149
246
 
150
247
  | Parameter | Type | Required | Description |
151
248
  |-----------|------|----------|-------------|
152
- | `id` | `string` | ✅ | Unique identifier for the dismissible item |
249
+ | `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
250
+ | `options` | `object` | ❌ | Configuration options |
251
+
252
+ #### Options
253
+
254
+ | Option | Type | Required | Description |
255
+ |--------|------|----------|-------------|
256
+ | `enableCache` | `boolean` | ❌ | Enable localStorage caching (default: true) |
257
+ | `cachePrefix` | `string` | ❌ | Cache key prefix (default: 'dismissible') |
258
+ | `cacheExpiration` | `number` | ❌ | Cache expiration time in milliseconds |
259
+ | `metadata` | `IMetadata` | ❌ | Optional metadata object (`{ [key: string]: string \| number }`) |
260
+ | `initialData` | `IDismissibleItem` | ❌ | Initial data for the dismissible item |
153
261
 
154
262
  #### Returns
155
263
 
@@ -157,20 +265,18 @@ For custom implementations and advanced use cases.
157
265
  |----------|------|-------------|
158
266
  | `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
159
267
  | `dismiss` | `() => Promise<void>` | Function to dismiss the item |
268
+ | `restore` | `() => Promise<void>` | Function to restore a dismissed item |
160
269
  | `isLoading` | `boolean` | Loading state indicator |
161
270
  | `error` | `Error \| null` | Error state, if any |
271
+ | `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
162
272
 
163
273
  #### Example
164
274
 
165
275
  ```tsx
166
276
  import { useDismissibleItem } from '@dismissible/react-client';
167
277
 
168
- function CustomDismissible({ id, children }) {
169
- const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(id);
170
-
171
- if (dismissedOn) {
172
- return null; // Item is dismissed
173
- }
278
+ function CustomDismissible({ itemId, children }) {
279
+ const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
174
280
 
175
281
  if (isLoading) {
176
282
  return <div>Loading...</div>;
@@ -180,6 +286,15 @@ function CustomDismissible({ id, children }) {
180
286
  return <div>Error: {error.message}</div>;
181
287
  }
182
288
 
289
+ if (dismissedOn) {
290
+ return (
291
+ <div>
292
+ <p>This item was dismissed.</p>
293
+ <button onClick={restore}>Restore</button>
294
+ </div>
295
+ );
296
+ }
297
+
183
298
  return (
184
299
  <div>
185
300
  {children}
@@ -193,21 +308,16 @@ function CustomDismissible({ id, children }) {
193
308
 
194
309
  ## Usage Examples
195
310
 
196
- ### JWT Authentication Setup (Enterprise Only)
197
-
198
- > **Enterprise Feature:** JWT authentication allows user-specific dismissible state management. This feature requires an Enterprise subscription. [Learn more about Enterprise features →](https://dismissible.io/pricing)
199
-
200
- For enterprise accounts that require user-specific dismissible state, wrap your app with the `DismissibleProvider`:
311
+ ### Basic Dismissible Banner
201
312
 
202
313
  ```tsx
203
314
  import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
204
315
 
205
316
  function App() {
206
- // Example with static JWT token
207
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
317
+ const userId = getCurrentUserId();
208
318
 
209
319
  return (
210
- <DismissibleProvider jwt={jwt}>
320
+ <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
211
321
  <Dashboard />
212
322
  </DismissibleProvider>
213
323
  );
@@ -215,61 +325,48 @@ function App() {
215
325
 
216
326
  function Dashboard() {
217
327
  return (
218
- <div>
219
- {/* These dismissible items will be user-specific */}
220
- <Dismissible id="user-welcome-banner">
221
- <div className="alert alert-info">
222
- <h4>Welcome back!</h4>
223
- <p>You have 3 new notifications.</p>
224
- </div>
225
- </Dismissible>
226
- </div>
328
+ <Dismissible itemId="welcome-banner">
329
+ <div className="alert alert-info">
330
+ <h4>Welcome!</h4>
331
+ <p>Thanks for joining our platform. Here are some quick tips to get started.</p>
332
+ </div>
333
+ </Dismissible>
227
334
  );
228
335
  }
229
336
  ```
230
337
 
231
- ### Dynamic JWT with Authentication Provider
338
+ ### JWT Authentication Setup
339
+
340
+ For secure environments, configure JWT authentication:
232
341
 
233
342
  ```tsx
234
- import { DismissibleProvider } from '@dismissible/react-client';
235
- import { useAuth } from './auth'; // Your auth context
343
+ import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
236
344
 
237
- // Synchronous JWT function
238
345
  function App() {
239
- const { getAccessToken } = useAuth();
240
-
241
- return (
242
- <DismissibleProvider jwt={() => getAccessToken()}>
243
- <YourApp />
244
- </DismissibleProvider>
245
- );
246
- }
247
-
248
- // Asynchronous JWT function
249
- function AppWithAsyncAuth() {
250
- const { refreshAndGetToken } = useAuth();
346
+ const { user, getAccessToken } = useAuth();
251
347
 
252
348
  return (
253
- <DismissibleProvider jwt={async () => await refreshAndGetToken()}>
254
- <YourApp />
349
+ <DismissibleProvider
350
+ userId={user.id}
351
+ jwt={() => getAccessToken()}
352
+ baseUrl="https://api.yourapp.com"
353
+ >
354
+ <Dashboard />
255
355
  </DismissibleProvider>
256
356
  );
257
357
  }
258
- ```
259
358
 
260
- ### Basic Dismissible Banner
261
-
262
- ```tsx
263
- import { Dismissible } from '@dismissible/react-client';
264
-
265
- function WelcomeBanner() {
359
+ function Dashboard() {
266
360
  return (
267
- <Dismissible id="welcome-banner-234-432-432-1">
268
- <div className="alert alert-info">
269
- <h4>Welcome!</h4>
270
- <p>Thanks for joining our platform. Here are some quick tips to get started.</p>
271
- </div>
272
- </Dismissible>
361
+ <div>
362
+ {/* Dismissible state is tracked per user */}
363
+ <Dismissible itemId="user-welcome-banner">
364
+ <div className="alert alert-info">
365
+ <h4>Welcome back!</h4>
366
+ <p>You have 3 new notifications.</p>
367
+ </div>
368
+ </Dismissible>
369
+ </div>
273
370
  );
274
371
  }
275
372
  ```
@@ -292,7 +389,7 @@ const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
292
389
  function CustomBanner() {
293
390
  return (
294
391
  <Dismissible
295
- id="custom-banner"
392
+ itemId="custom-banner"
296
393
  DismissButtonComponent={CustomDismissButton}
297
394
  >
298
395
  <div className="banner">
@@ -308,7 +405,7 @@ function CustomBanner() {
308
405
  ```tsx
309
406
  import { Dismissible } from '@dismissible/react-client';
310
407
 
311
- const CustomLoader = ({ id }) => (
408
+ const CustomLoader = ({ itemId }) => (
312
409
  <div className="spinner">
313
410
  <div className="bounce1"></div>
314
411
  <div className="bounce2"></div>
@@ -329,7 +426,7 @@ const CustomError = ({ error }) => (
329
426
  function AdvancedBanner() {
330
427
  return (
331
428
  <Dismissible
332
- id="advanced-banner"
429
+ itemId="advanced-banner"
333
430
  LoadingComponent={CustomLoader}
334
431
  ErrorComponent={CustomError}
335
432
  >
@@ -349,19 +446,19 @@ import { Dismissible } from '@dismissible/react-client';
349
446
  function Dashboard() {
350
447
  return (
351
448
  <div>
352
- <Dismissible id="feature-announcement-234-432-432-1">
449
+ <Dismissible itemId="feature-announcement">
353
450
  <div className="alert alert-success">
354
451
  🎉 New feature: Dark mode is now available!
355
452
  </div>
356
453
  </Dismissible>
357
454
 
358
- <Dismissible id="maintenance-notice-234-432-432-1">
455
+ <Dismissible itemId="maintenance-notice">
359
456
  <div className="alert alert-warning">
360
457
  ⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
361
458
  </div>
362
459
  </Dismissible>
363
460
 
364
- <Dismissible id="survey-request-234-432-432-1">
461
+ <Dismissible itemId="survey-request">
365
462
  <div className="alert alert-info">
366
463
  📝 Help us improve! Take our 2-minute survey.
367
464
  </div>
@@ -371,35 +468,6 @@ function Dashboard() {
371
468
  }
372
469
  ```
373
470
 
374
- ### User-Specific vs Anonymous Dismissible Items
375
-
376
- The behavior changes based on whether JWT authentication is configured:
377
-
378
- > **Enterprise vs Standard:** JWT authentication for user-specific dismissals is an Enterprise feature. Standard accounts use anonymous (account-level) dismissals. [Compare plans →](https://dismissible.io/pricing)
379
-
380
- ```tsx
381
- import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
382
-
383
- // With JWT - dismissible state is user-specific
384
- function AuthenticatedApp() {
385
- return (
386
- <DismissibleProvider jwt={() => getAccessToken()}>
387
- <div>
388
- {/* Each user will see this banner independently */}
389
- <Dismissible id="feature-announcement">
390
- <div>New feature available!</div>
391
- </Dismissible>
392
-
393
- {/* User A dismissing this won't affect User B */}
394
- <Dismissible id="survey-request">
395
- <div>Please take our survey!</div>
396
- </Dismissible>
397
- </div>
398
- </DismissibleProvider>
399
- );
400
- }
401
- ```
402
-
403
471
  ### Error Handling with ignoreErrors
404
472
 
405
473
  ```tsx
@@ -409,7 +477,7 @@ import { Dismissible } from '@dismissible/react-client';
409
477
  function RobustBanner() {
410
478
  return (
411
479
  <Dismissible
412
- id="important-announcement"
480
+ itemId="important-announcement"
413
481
  ignoreErrors={true}
414
482
  >
415
483
  <div className="important-banner">
@@ -421,57 +489,68 @@ function RobustBanner() {
421
489
  }
422
490
  ```
423
491
 
424
- ### Async JWT Authentication Examples
492
+ ### Integration with Auth Providers
425
493
 
426
494
  ```tsx
427
495
  import { DismissibleProvider } from '@dismissible/react-client';
428
496
 
429
- // With token refresh logic
430
- function AppWithTokenRefresh() {
497
+ // With Firebase Auth
498
+ function AppWithFirebase() {
499
+ const user = firebase.auth().currentUser;
500
+
431
501
  return (
432
502
  <DismissibleProvider
503
+ userId={user.uid}
433
504
  jwt={async () => {
434
- try {
435
- const token = await fetch('/api/auth/refresh', {
436
- method: 'POST',
437
- credentials: 'include'
438
- });
439
- const { accessToken } = await token.json();
440
- return accessToken;
441
- } catch (error) {
442
- console.error('Failed to refresh token:', error);
443
- throw error;
505
+ if (user) {
506
+ return await user.getIdToken();
444
507
  }
508
+ throw new Error('User not authenticated');
445
509
  }}
510
+ baseUrl="https://api.yourapp.com"
446
511
  >
447
512
  <YourApp />
448
513
  </DismissibleProvider>
449
514
  );
450
515
  }
451
516
 
452
- // With Firebase Auth
453
- function AppWithFirebase() {
517
+ // With Auth0
518
+ function AppWithAuth0() {
519
+ const { user, getAccessTokenSilently } = useAuth0();
520
+
454
521
  return (
455
522
  <DismissibleProvider
456
- jwt={async () => {
457
- const user = firebase.auth().currentUser;
458
- if (user) {
459
- return await user.getIdToken();
460
- }
461
- throw new Error('User not authenticated');
462
- }}
523
+ userId={user.sub}
524
+ jwt={async () => await getAccessTokenSilently()}
525
+ baseUrl="https://api.yourapp.com"
463
526
  >
464
527
  <YourApp />
465
528
  </DismissibleProvider>
466
529
  );
467
530
  }
468
531
 
469
- // With Auth0
470
- function AppWithAuth0() {
471
- const { getAccessTokenSilently } = useAuth0();
532
+ // With token refresh logic
533
+ function AppWithTokenRefresh() {
534
+ const { user } = useAuth();
472
535
 
473
536
  return (
474
- <DismissibleProvider jwt={async () => await getAccessTokenSilently()}>
537
+ <DismissibleProvider
538
+ userId={user.id}
539
+ jwt={async () => {
540
+ try {
541
+ const response = await fetch('/api/auth/refresh', {
542
+ method: 'POST',
543
+ credentials: 'include'
544
+ });
545
+ const { accessToken } = await response.json();
546
+ return accessToken;
547
+ } catch (error) {
548
+ console.error('Failed to refresh token:', error);
549
+ throw error;
550
+ }
551
+ }}
552
+ baseUrl="https://api.yourapp.com"
553
+ >
475
554
  <YourApp />
476
555
  </DismissibleProvider>
477
556
  );
@@ -481,11 +560,11 @@ function AppWithAuth0() {
481
560
  ### Using the Hook for Complex Logic
482
561
 
483
562
  ```tsx
484
- import { useDismissibleItem, DismissibleProvider } from '@dismissible/react-client';
563
+ import { useDismissibleItem } from '@dismissible/react-client';
485
564
  import { useState, useEffect } from 'react';
486
565
 
487
- function SmartNotification({ id, message, type = 'info' }) {
488
- const { dismissedOn, dismiss, isLoading } = useDismissibleItem(id);
566
+ function SmartNotification({ itemId, message, type = 'info' }) {
567
+ const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId);
489
568
  const [autoHide, setAutoHide] = useState(false);
490
569
 
491
570
  // Auto-hide after 10 seconds for info messages
@@ -517,17 +596,132 @@ function SmartNotification({ id, message, type = 'info' }) {
517
596
  </div>
518
597
  );
519
598
  }
599
+ ```
520
600
 
521
- // Usage with async authentication
522
- function App() {
601
+ ### Using Metadata with Dismissible Items
602
+
603
+ Metadata allows you to attach additional information to dismissible items, which can be useful for analytics, filtering, or conditional logic:
604
+
605
+ ```tsx
606
+ import { Dismissible, useDismissibleItem } from '@dismissible/react-client';
607
+
608
+ // Using metadata with the Dismissible component (object format)
609
+ function PromotionalBanner({ campaignId, version }) {
523
610
  return (
524
- <DismissibleProvider jwt={async () => await getUserToken()}>
525
- <SmartNotification
526
- id="user-specific-notification"
527
- message="Welcome back!"
528
- type="info"
529
- />
530
- </DismissibleProvider>
611
+ <Dismissible
612
+ itemId={`promo-${campaignId}`}
613
+ metadata={{
614
+ version: version,
615
+ category: "promotional",
616
+ campaign: campaignId,
617
+ }}
618
+ >
619
+ <div className="promo-banner">
620
+ <h3>Special Offer!</h3>
621
+ <p>Limited time promotion</p>
622
+ </div>
623
+ </Dismissible>
624
+ );
625
+ }
626
+
627
+ // Using metadata with the hook (same object format)
628
+ function CustomNotification({ itemId, category, priority }) {
629
+ const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId, {
630
+ metadata: {
631
+ category,
632
+ priority,
633
+ },
634
+ });
635
+
636
+ if (dismissedOn) {
637
+ return null;
638
+ }
639
+
640
+ return (
641
+ <div className={`notification notification-${category}`}>
642
+ <span>Notification content</span>
643
+ <button onClick={dismiss} disabled={isLoading}>
644
+ Dismiss
645
+ </button>
646
+ </div>
647
+ );
648
+ }
649
+ ```
650
+
651
+ ### Restoring Dismissed Items
652
+
653
+ Use the `restore` function to bring back previously dismissed content:
654
+
655
+ ```tsx
656
+ import { useDismissibleItem } from '@dismissible/react-client';
657
+
658
+ function RestorableBanner({ itemId }) {
659
+ const { dismissedOn, dismiss, restore, isLoading } = useDismissibleItem(itemId);
660
+
661
+ if (dismissedOn) {
662
+ return (
663
+ <div className="dismissed-placeholder">
664
+ <p>Banner was dismissed on {new Date(dismissedOn).toLocaleDateString()}</p>
665
+ <button onClick={restore} disabled={isLoading}>
666
+ {isLoading ? 'Restoring...' : 'Show Banner Again'}
667
+ </button>
668
+ </div>
669
+ );
670
+ }
671
+
672
+ return (
673
+ <div className="banner">
674
+ <h3>Welcome!</h3>
675
+ <p>This is a restorable banner.</p>
676
+ <button onClick={dismiss} disabled={isLoading}>
677
+ Dismiss
678
+ </button>
679
+ </div>
680
+ );
681
+ }
682
+ ```
683
+
684
+ ### Admin Panel with Restore Capability
685
+
686
+ ```tsx
687
+ import { useDismissibleItem } from '@dismissible/react-client';
688
+
689
+ function AdminNotificationManager({ notificationId }) {
690
+ const { dismissedOn, dismiss, restore, item, isLoading, error } =
691
+ useDismissibleItem(notificationId);
692
+
693
+ if (error) {
694
+ return <div className="error">Error: {error.message}</div>;
695
+ }
696
+
697
+ return (
698
+ <div className="admin-panel">
699
+ <h4>Notification: {notificationId}</h4>
700
+ <p>Status: {dismissedOn ? 'Dismissed' : 'Active'}</p>
701
+ {dismissedOn && (
702
+ <p>Dismissed at: {new Date(dismissedOn).toLocaleString()}</p>
703
+ )}
704
+
705
+ <div className="actions">
706
+ {dismissedOn ? (
707
+ <button
708
+ onClick={restore}
709
+ disabled={isLoading}
710
+ className="btn-restore"
711
+ >
712
+ Restore
713
+ </button>
714
+ ) : (
715
+ <button
716
+ onClick={dismiss}
717
+ disabled={isLoading}
718
+ className="btn-dismiss"
719
+ >
720
+ Dismiss
721
+ </button>
722
+ )}
723
+ </div>
724
+ </div>
531
725
  );
532
726
  }
533
727
  ```
@@ -560,32 +754,65 @@ The library includes minimal default styles. You can override them or provide yo
560
754
  The library is written in TypeScript and exports all type definitions:
561
755
 
562
756
  ```tsx
563
- import type {
757
+ import type {
564
758
  DismissibleProps,
565
759
  DismissibleProviderProps,
566
760
  JwtToken,
567
- IDismissibleItem
761
+ IMetadata,
568
762
  } from '@dismissible/react-client';
569
763
 
570
- // Dismissible component with custom props
571
- const MyComponent: React.FC<DismissibleProps> = (props) => {
572
- // Your component implementation
573
- };
574
-
575
- // Provider with JWT authentication
576
- const AuthProvider: React.FC<DismissibleProviderProps> = ({ children, jwt }) => {
764
+ // Custom provider wrapper
765
+ const AuthenticatedDismissibleProvider: React.FC<{
766
+ children: React.ReactNode;
767
+ }> = ({ children }) => {
768
+ const { user, getToken } = useAuth();
769
+
577
770
  return (
578
- <DismissibleProvider jwt={jwt}>
771
+ <DismissibleProvider
772
+ userId={user.id}
773
+ jwt={getToken}
774
+ baseUrl={process.env.DISMISSIBLE_API_URL}
775
+ >
579
776
  {children}
580
777
  </DismissibleProvider>
581
778
  );
582
779
  };
583
780
  ```
584
781
 
782
+ ## Self-Hosting
783
+
784
+ Dismissible is designed to be self-hosted. You have full control over your data.
785
+
786
+ ### Option 1: Docker (Recommended)
787
+
788
+ The fastest way to get started:
789
+
790
+ ```bash
791
+ docker run -p 3001:3001 dismissibleio/dismissible-api:latest
792
+ ```
793
+
794
+ See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
795
+
796
+ ### Option 2: NestJS Module
797
+
798
+ Integrate directly into your existing NestJS application:
799
+
800
+ ```bash
801
+ npm install @dismissible/nestjs-api
802
+ ```
803
+
804
+ See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
805
+
585
806
  ## Support
586
807
 
587
- - 📖 [Documentation](https://docs.dismissible.io)
808
+ - 📖 [Documentation](https://dismissible.io/docs)
809
+ - 🐙 [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
810
+ - 🐙 [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
811
+
812
+ ## License
813
+
814
+ MIT © [Dismissible](https://dismissible.io)
588
815
 
589
816
  ## Changelog
590
817
 
591
- See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
818
+ See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.