@dismissible/react-client 0.3.2 → 1.0.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/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
+
44
88
  return (
45
- <Dismissible id="welcome-banner-123-413-31-1">
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() {
102
+ return (
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,29 @@ 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 |
128
206
 
129
207
  #### Example
130
208
 
131
209
  ```tsx
132
210
  <Dismissible
133
- id="promo-banner"
211
+ itemId="promo-banner"
134
212
  onDismiss={() => console.log('Banner dismissed')}
135
- onRestore={() => console.log('Banner restored')}
136
213
  >
137
214
  <div className="promo">
138
215
  <h3>Special Offer!</h3>
@@ -149,7 +226,17 @@ For custom implementations and advanced use cases.
149
226
 
150
227
  | Parameter | Type | Required | Description |
151
228
  |-----------|------|----------|-------------|
152
- | `id` | `string` | ✅ | Unique identifier for the dismissible item |
229
+ | `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
230
+ | `options` | `object` | ❌ | Configuration options |
231
+
232
+ #### Options
233
+
234
+ | Option | Type | Required | Description |
235
+ |--------|------|----------|-------------|
236
+ | `enableCache` | `boolean` | ❌ | Enable localStorage caching (default: true) |
237
+ | `cachePrefix` | `string` | ❌ | Cache key prefix (default: 'dismissible') |
238
+ | `cacheExpiration` | `number` | ❌ | Cache expiration time in milliseconds |
239
+ | `initialData` | `IDismissibleItem` | ❌ | Initial data for the dismissible item |
153
240
 
154
241
  #### Returns
155
242
 
@@ -157,20 +244,18 @@ For custom implementations and advanced use cases.
157
244
  |----------|------|-------------|
158
245
  | `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
159
246
  | `dismiss` | `() => Promise<void>` | Function to dismiss the item |
247
+ | `restore` | `() => Promise<void>` | Function to restore a dismissed item |
160
248
  | `isLoading` | `boolean` | Loading state indicator |
161
249
  | `error` | `Error \| null` | Error state, if any |
250
+ | `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
162
251
 
163
252
  #### Example
164
253
 
165
254
  ```tsx
166
255
  import { useDismissibleItem } from '@dismissible/react-client';
167
256
 
168
- function CustomDismissible({ id, children }) {
169
- const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(id);
170
-
171
- if (dismissedOn) {
172
- return null; // Item is dismissed
173
- }
257
+ function CustomDismissible({ itemId, children }) {
258
+ const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
174
259
 
175
260
  if (isLoading) {
176
261
  return <div>Loading...</div>;
@@ -180,6 +265,15 @@ function CustomDismissible({ id, children }) {
180
265
  return <div>Error: {error.message}</div>;
181
266
  }
182
267
 
268
+ if (dismissedOn) {
269
+ return (
270
+ <div>
271
+ <p>This item was dismissed.</p>
272
+ <button onClick={restore}>Restore</button>
273
+ </div>
274
+ );
275
+ }
276
+
183
277
  return (
184
278
  <div>
185
279
  {children}
@@ -193,21 +287,16 @@ function CustomDismissible({ id, children }) {
193
287
 
194
288
  ## Usage Examples
195
289
 
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`:
290
+ ### Basic Dismissible Banner
201
291
 
202
292
  ```tsx
203
293
  import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
204
294
 
205
295
  function App() {
206
- // Example with static JWT token
207
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
296
+ const userId = getCurrentUserId();
208
297
 
209
298
  return (
210
- <DismissibleProvider jwt={jwt}>
299
+ <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
211
300
  <Dashboard />
212
301
  </DismissibleProvider>
213
302
  );
@@ -215,61 +304,48 @@ function App() {
215
304
 
216
305
  function Dashboard() {
217
306
  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>
307
+ <Dismissible itemId="welcome-banner">
308
+ <div className="alert alert-info">
309
+ <h4>Welcome!</h4>
310
+ <p>Thanks for joining our platform. Here are some quick tips to get started.</p>
311
+ </div>
312
+ </Dismissible>
227
313
  );
228
314
  }
229
315
  ```
230
316
 
231
- ### Dynamic JWT with Authentication Provider
317
+ ### JWT Authentication Setup
318
+
319
+ For secure environments, configure JWT authentication:
232
320
 
233
321
  ```tsx
234
- import { DismissibleProvider } from '@dismissible/react-client';
235
- import { useAuth } from './auth'; // Your auth context
322
+ import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
236
323
 
237
- // Synchronous JWT function
238
324
  function App() {
239
- const { getAccessToken } = useAuth();
325
+ const { user, getAccessToken } = useAuth();
240
326
 
241
327
  return (
242
- <DismissibleProvider jwt={() => getAccessToken()}>
243
- <YourApp />
244
- </DismissibleProvider>
245
- );
246
- }
247
-
248
- // Asynchronous JWT function
249
- function AppWithAsyncAuth() {
250
- const { refreshAndGetToken } = useAuth();
251
-
252
- return (
253
- <DismissibleProvider jwt={async () => await refreshAndGetToken()}>
254
- <YourApp />
328
+ <DismissibleProvider
329
+ userId={user.id}
330
+ jwt={() => getAccessToken()}
331
+ baseUrl="https://api.yourapp.com"
332
+ >
333
+ <Dashboard />
255
334
  </DismissibleProvider>
256
335
  );
257
336
  }
258
- ```
259
-
260
- ### Basic Dismissible Banner
261
-
262
- ```tsx
263
- import { Dismissible } from '@dismissible/react-client';
264
337
 
265
- function WelcomeBanner() {
338
+ function Dashboard() {
266
339
  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>
340
+ <div>
341
+ {/* Dismissible state is tracked per user */}
342
+ <Dismissible itemId="user-welcome-banner">
343
+ <div className="alert alert-info">
344
+ <h4>Welcome back!</h4>
345
+ <p>You have 3 new notifications.</p>
346
+ </div>
347
+ </Dismissible>
348
+ </div>
273
349
  );
274
350
  }
275
351
  ```
@@ -292,7 +368,7 @@ const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
292
368
  function CustomBanner() {
293
369
  return (
294
370
  <Dismissible
295
- id="custom-banner"
371
+ itemId="custom-banner"
296
372
  DismissButtonComponent={CustomDismissButton}
297
373
  >
298
374
  <div className="banner">
@@ -308,7 +384,7 @@ function CustomBanner() {
308
384
  ```tsx
309
385
  import { Dismissible } from '@dismissible/react-client';
310
386
 
311
- const CustomLoader = ({ id }) => (
387
+ const CustomLoader = ({ itemId }) => (
312
388
  <div className="spinner">
313
389
  <div className="bounce1"></div>
314
390
  <div className="bounce2"></div>
@@ -329,7 +405,7 @@ const CustomError = ({ error }) => (
329
405
  function AdvancedBanner() {
330
406
  return (
331
407
  <Dismissible
332
- id="advanced-banner"
408
+ itemId="advanced-banner"
333
409
  LoadingComponent={CustomLoader}
334
410
  ErrorComponent={CustomError}
335
411
  >
@@ -349,19 +425,19 @@ import { Dismissible } from '@dismissible/react-client';
349
425
  function Dashboard() {
350
426
  return (
351
427
  <div>
352
- <Dismissible id="feature-announcement-234-432-432-1">
428
+ <Dismissible itemId="feature-announcement">
353
429
  <div className="alert alert-success">
354
430
  🎉 New feature: Dark mode is now available!
355
431
  </div>
356
432
  </Dismissible>
357
433
 
358
- <Dismissible id="maintenance-notice-234-432-432-1">
434
+ <Dismissible itemId="maintenance-notice">
359
435
  <div className="alert alert-warning">
360
436
  ⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
361
437
  </div>
362
438
  </Dismissible>
363
439
 
364
- <Dismissible id="survey-request-234-432-432-1">
440
+ <Dismissible itemId="survey-request">
365
441
  <div className="alert alert-info">
366
442
  📝 Help us improve! Take our 2-minute survey.
367
443
  </div>
@@ -371,35 +447,6 @@ function Dashboard() {
371
447
  }
372
448
  ```
373
449
 
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
450
  ### Error Handling with ignoreErrors
404
451
 
405
452
  ```tsx
@@ -409,7 +456,7 @@ import { Dismissible } from '@dismissible/react-client';
409
456
  function RobustBanner() {
410
457
  return (
411
458
  <Dismissible
412
- id="important-announcement"
459
+ itemId="important-announcement"
413
460
  ignoreErrors={true}
414
461
  >
415
462
  <div className="important-banner">
@@ -421,57 +468,68 @@ function RobustBanner() {
421
468
  }
422
469
  ```
423
470
 
424
- ### Async JWT Authentication Examples
471
+ ### Integration with Auth Providers
425
472
 
426
473
  ```tsx
427
474
  import { DismissibleProvider } from '@dismissible/react-client';
428
475
 
429
- // With token refresh logic
430
- function AppWithTokenRefresh() {
476
+ // With Firebase Auth
477
+ function AppWithFirebase() {
478
+ const user = firebase.auth().currentUser;
479
+
431
480
  return (
432
481
  <DismissibleProvider
482
+ userId={user.uid}
433
483
  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;
484
+ if (user) {
485
+ return await user.getIdToken();
444
486
  }
487
+ throw new Error('User not authenticated');
445
488
  }}
489
+ baseUrl="https://api.yourapp.com"
446
490
  >
447
491
  <YourApp />
448
492
  </DismissibleProvider>
449
493
  );
450
494
  }
451
495
 
452
- // With Firebase Auth
453
- function AppWithFirebase() {
496
+ // With Auth0
497
+ function AppWithAuth0() {
498
+ const { user, getAccessTokenSilently } = useAuth0();
499
+
454
500
  return (
455
501
  <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
- }}
502
+ userId={user.sub}
503
+ jwt={async () => await getAccessTokenSilently()}
504
+ baseUrl="https://api.yourapp.com"
463
505
  >
464
506
  <YourApp />
465
507
  </DismissibleProvider>
466
508
  );
467
509
  }
468
510
 
469
- // With Auth0
470
- function AppWithAuth0() {
471
- const { getAccessTokenSilently } = useAuth0();
511
+ // With token refresh logic
512
+ function AppWithTokenRefresh() {
513
+ const { user } = useAuth();
472
514
 
473
515
  return (
474
- <DismissibleProvider jwt={async () => await getAccessTokenSilently()}>
516
+ <DismissibleProvider
517
+ userId={user.id}
518
+ jwt={async () => {
519
+ try {
520
+ const response = await fetch('/api/auth/refresh', {
521
+ method: 'POST',
522
+ credentials: 'include'
523
+ });
524
+ const { accessToken } = await response.json();
525
+ return accessToken;
526
+ } catch (error) {
527
+ console.error('Failed to refresh token:', error);
528
+ throw error;
529
+ }
530
+ }}
531
+ baseUrl="https://api.yourapp.com"
532
+ >
475
533
  <YourApp />
476
534
  </DismissibleProvider>
477
535
  );
@@ -481,11 +539,11 @@ function AppWithAuth0() {
481
539
  ### Using the Hook for Complex Logic
482
540
 
483
541
  ```tsx
484
- import { useDismissibleItem, DismissibleProvider } from '@dismissible/react-client';
542
+ import { useDismissibleItem } from '@dismissible/react-client';
485
543
  import { useState, useEffect } from 'react';
486
544
 
487
- function SmartNotification({ id, message, type = 'info' }) {
488
- const { dismissedOn, dismiss, isLoading } = useDismissibleItem(id);
545
+ function SmartNotification({ itemId, message, type = 'info' }) {
546
+ const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId);
489
547
  const [autoHide, setAutoHide] = useState(false);
490
548
 
491
549
  // Auto-hide after 10 seconds for info messages
@@ -517,17 +575,82 @@ function SmartNotification({ id, message, type = 'info' }) {
517
575
  </div>
518
576
  );
519
577
  }
578
+ ```
579
+
580
+ ### Restoring Dismissed Items
581
+
582
+ Use the `restore` function to bring back previously dismissed content:
583
+
584
+ ```tsx
585
+ import { useDismissibleItem } from '@dismissible/react-client';
586
+
587
+ function RestorableBanner({ itemId }) {
588
+ const { dismissedOn, dismiss, restore, isLoading } = useDismissibleItem(itemId);
589
+
590
+ if (dismissedOn) {
591
+ return (
592
+ <div className="dismissed-placeholder">
593
+ <p>Banner was dismissed on {new Date(dismissedOn).toLocaleDateString()}</p>
594
+ <button onClick={restore} disabled={isLoading}>
595
+ {isLoading ? 'Restoring...' : 'Show Banner Again'}
596
+ </button>
597
+ </div>
598
+ );
599
+ }
520
600
 
521
- // Usage with async authentication
522
- function App() {
523
601
  return (
524
- <DismissibleProvider jwt={async () => await getUserToken()}>
525
- <SmartNotification
526
- id="user-specific-notification"
527
- message="Welcome back!"
528
- type="info"
529
- />
530
- </DismissibleProvider>
602
+ <div className="banner">
603
+ <h3>Welcome!</h3>
604
+ <p>This is a restorable banner.</p>
605
+ <button onClick={dismiss} disabled={isLoading}>
606
+ Dismiss
607
+ </button>
608
+ </div>
609
+ );
610
+ }
611
+ ```
612
+
613
+ ### Admin Panel with Restore Capability
614
+
615
+ ```tsx
616
+ import { useDismissibleItem } from '@dismissible/react-client';
617
+
618
+ function AdminNotificationManager({ notificationId }) {
619
+ const { dismissedOn, dismiss, restore, item, isLoading, error } =
620
+ useDismissibleItem(notificationId);
621
+
622
+ if (error) {
623
+ return <div className="error">Error: {error.message}</div>;
624
+ }
625
+
626
+ return (
627
+ <div className="admin-panel">
628
+ <h4>Notification: {notificationId}</h4>
629
+ <p>Status: {dismissedOn ? 'Dismissed' : 'Active'}</p>
630
+ {dismissedOn && (
631
+ <p>Dismissed at: {new Date(dismissedOn).toLocaleString()}</p>
632
+ )}
633
+
634
+ <div className="actions">
635
+ {dismissedOn ? (
636
+ <button
637
+ onClick={restore}
638
+ disabled={isLoading}
639
+ className="btn-restore"
640
+ >
641
+ Restore
642
+ </button>
643
+ ) : (
644
+ <button
645
+ onClick={dismiss}
646
+ disabled={isLoading}
647
+ className="btn-dismiss"
648
+ >
649
+ Dismiss
650
+ </button>
651
+ )}
652
+ </div>
653
+ </div>
531
654
  );
532
655
  }
533
656
  ```
@@ -560,32 +683,64 @@ The library includes minimal default styles. You can override them or provide yo
560
683
  The library is written in TypeScript and exports all type definitions:
561
684
 
562
685
  ```tsx
563
- import type {
686
+ import type {
564
687
  DismissibleProps,
565
688
  DismissibleProviderProps,
566
689
  JwtToken,
567
- IDismissibleItem
568
690
  } from '@dismissible/react-client';
569
691
 
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 }) => {
692
+ // Custom provider wrapper
693
+ const AuthenticatedDismissibleProvider: React.FC<{
694
+ children: React.ReactNode;
695
+ }> = ({ children }) => {
696
+ const { user, getToken } = useAuth();
697
+
577
698
  return (
578
- <DismissibleProvider jwt={jwt}>
699
+ <DismissibleProvider
700
+ userId={user.id}
701
+ jwt={getToken}
702
+ baseUrl={process.env.DISMISSIBLE_API_URL}
703
+ >
579
704
  {children}
580
705
  </DismissibleProvider>
581
706
  );
582
707
  };
583
708
  ```
584
709
 
710
+ ## Self-Hosting
711
+
712
+ Dismissible is designed to be self-hosted. You have full control over your data.
713
+
714
+ ### Option 1: Docker (Recommended)
715
+
716
+ The fastest way to get started:
717
+
718
+ ```bash
719
+ docker run -p 3001:3001 dismissibleio/dismissible-api:latest
720
+ ```
721
+
722
+ See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
723
+
724
+ ### Option 2: NestJS Module
725
+
726
+ Integrate directly into your existing NestJS application:
727
+
728
+ ```bash
729
+ npm install @dismissible/nestjs-api
730
+ ```
731
+
732
+ See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
733
+
585
734
  ## Support
586
735
 
587
- - 📖 [Documentation](https://docs.dismissible.io)
736
+ - 📖 [Documentation](https://dismissible.io/docs)
737
+ - 🐙 [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
738
+ - 🐙 [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
739
+
740
+ ## License
741
+
742
+ MIT © [Dismissible](https://dismissible.io)
588
743
 
589
744
  ## Changelog
590
745
 
591
- See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
746
+ See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.