@dismissible/react-client 0.3.1 → 0.3.2-canary.2.371afda

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
@@ -2,7 +2,9 @@
2
2
 
3
3
  A React component library for creating dismissible UI elements with persistent state management.
4
4
 
5
- 🌐 **[dismissible.io](https://dismissible.io)** | 💰 **[View Pricing](https://dismissible.io/pricing)** | 📖 **[Documentation](https://docs.dismissible.io)**
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
+
7
+ 🌐 **[dismissible.io](https://dismissible.io)** | 📖 **[Documentation](https://dismissible.io/docs)** | 🐙 **[API Server](https://github.com/DismissibleIo/dismissible-api)**
6
8
 
7
9
  [![npm version](https://badge.fury.io/js/@dismissible%2Freact-client.svg)](https://badge.fury.io/js/@dismissible%2Freact-client)
8
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -11,12 +13,14 @@ A React component library for creating dismissible UI elements with persistent s
11
13
 
12
14
  - 🎯 **Easy to use** - Simple component API for dismissible content
13
15
  - 💾 **Persistent state** - Dismissal state is saved and restored across sessions
14
- - 🔐 **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
15
18
  - 🎨 **Customizable** - Custom loading, error, and dismiss button components
16
19
  - ♿ **Accessible** - Built with accessibility best practices
17
20
  - 🪝 **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
18
21
  - 📦 **Lightweight** - Minimal bundle size with tree-shaking support
19
22
  - 🔧 **TypeScript** - Full TypeScript support with complete type definitions
23
+ - 🐳 **Self-hosted** - Works with your own Dismissible API server
20
24
 
21
25
  ## Installation
22
26
 
@@ -34,13 +38,69 @@ npm install react react-dom
34
38
 
35
39
  ## Quick Start
36
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
+
37
82
  ```tsx
38
- import React from 'react';
39
- import { Dismissible } from '@dismissible/react-client';
83
+ import { DismissibleProvider } from '@dismissible/react-client';
40
84
 
41
85
  function App() {
86
+ const userId = getCurrentUserId(); // Get from your auth system
87
+
42
88
  return (
43
- <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">
44
104
  <div className="banner">
45
105
  <h2>Welcome to our app!</h2>
46
106
  <p>This banner can be dismissed and won't show again.</p>
@@ -54,16 +114,15 @@ function App() {
54
114
 
55
115
  ### `<DismissibleProvider>` Component
56
116
 
57
- Context provider for JWT authentication and configuration. Wrap your app or components that need JWT authentication.
58
-
59
- > **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.
60
118
 
61
119
  #### Props
62
120
 
63
121
  | Prop | Type | Required | Description |
64
122
  |------|------|----------|-------------|
65
- | `jwt` | `string \| (() => string) \| (() => Promise<string>)` | | JWT token for user-specific dismissals (**Enterprise only**) |
66
- | `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) |
67
126
  | `children` | `ReactNode` | ✅ | Components that will use the dismissible functionality |
68
127
 
69
128
  #### Example
@@ -71,37 +130,53 @@ Context provider for JWT authentication and configuration. Wrap your app or comp
71
130
  ```tsx
72
131
  import { DismissibleProvider } from '@dismissible/react-client';
73
132
 
74
- // With static JWT
133
+ // Basic setup with userId
75
134
  function App() {
76
135
  return (
77
- <DismissibleProvider jwt="eyJhbGciOiJIUzI1NiIs...">
136
+ <DismissibleProvider userId="user-123" baseUrl="http://localhost:3001">
78
137
  <YourApp />
79
138
  </DismissibleProvider>
80
139
  );
81
140
  }
82
141
 
83
- // With dynamic JWT function
84
- function AppWithDynamicAuth() {
142
+ // With static JWT
143
+ function AppWithJWT() {
85
144
  return (
86
- <DismissibleProvider jwt={() => getAccessToken()}>
145
+ <DismissibleProvider
146
+ userId="user-123"
147
+ jwt="eyJhbGciOiJIUzI1NiIs..."
148
+ baseUrl="https://api.yourapp.com"
149
+ >
87
150
  <YourApp />
88
151
  </DismissibleProvider>
89
152
  );
90
153
  }
91
154
 
92
- // With async JWT function
93
- function AppWithAsyncAuth() {
155
+ // With dynamic JWT function
156
+ function AppWithDynamicAuth() {
157
+ const { user, getAccessToken } = useAuth();
158
+
94
159
  return (
95
- <DismissibleProvider jwt={async () => await fetchAccessToken()}>
160
+ <DismissibleProvider
161
+ userId={user.id}
162
+ jwt={() => getAccessToken()}
163
+ baseUrl="https://api.yourapp.com"
164
+ >
96
165
  <YourApp />
97
166
  </DismissibleProvider>
98
167
  );
99
168
  }
100
169
 
101
- // Without JWT (anonymous/backwards compatible)
102
- function AppWithoutAuth() {
170
+ // With async JWT function
171
+ function AppWithAsyncAuth() {
172
+ const { user, refreshAndGetToken } = useAuth();
173
+
103
174
  return (
104
- <DismissibleProvider>
175
+ <DismissibleProvider
176
+ userId={user.id}
177
+ jwt={async () => await refreshAndGetToken()}
178
+ baseUrl="https://api.yourapp.com"
179
+ >
105
180
  <YourApp />
106
181
  </DismissibleProvider>
107
182
  );
@@ -112,25 +187,29 @@ function AppWithoutAuth() {
112
187
 
113
188
  The main component for creating dismissible content.
114
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
+
115
192
  #### Props
116
193
 
117
194
  | Prop | Type | Required | Description |
118
195
  |------|------|----------|-------------|
119
- | `id` | `string` | ✅ | Unique identifier for the dismissible item |
196
+ | `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
120
197
  | `children` | `ReactNode` | ✅ | Content to render when not dismissed |
121
198
  | `onDismiss` | `() => void` | ❌ | Callback fired when item is dismissed |
122
- | `LoadingComponent` | `ComponentType<{id: string}>` | ❌ | Custom loading component |
123
- | `ErrorComponent` | `ComponentType<{id: string, error: Error}>` | ❌ | Custom error component |
124
- | `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 |
125
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 |
126
206
 
127
207
  #### Example
128
208
 
129
209
  ```tsx
130
210
  <Dismissible
131
- id="promo-banner"
211
+ itemId="promo-banner"
132
212
  onDismiss={() => console.log('Banner dismissed')}
133
- onRestore={() => console.log('Banner restored')}
134
213
  >
135
214
  <div className="promo">
136
215
  <h3>Special Offer!</h3>
@@ -147,7 +226,17 @@ For custom implementations and advanced use cases.
147
226
 
148
227
  | Parameter | Type | Required | Description |
149
228
  |-----------|------|----------|-------------|
150
- | `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 |
151
240
 
152
241
  #### Returns
153
242
 
@@ -155,20 +244,18 @@ For custom implementations and advanced use cases.
155
244
  |----------|------|-------------|
156
245
  | `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
157
246
  | `dismiss` | `() => Promise<void>` | Function to dismiss the item |
247
+ | `restore` | `() => Promise<void>` | Function to restore a dismissed item |
158
248
  | `isLoading` | `boolean` | Loading state indicator |
159
249
  | `error` | `Error \| null` | Error state, if any |
250
+ | `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
160
251
 
161
252
  #### Example
162
253
 
163
254
  ```tsx
164
255
  import { useDismissibleItem } from '@dismissible/react-client';
165
256
 
166
- function CustomDismissible({ id, children }) {
167
- const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(id);
168
-
169
- if (dismissedOn) {
170
- return null; // Item is dismissed
171
- }
257
+ function CustomDismissible({ itemId, children }) {
258
+ const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
172
259
 
173
260
  if (isLoading) {
174
261
  return <div>Loading...</div>;
@@ -178,6 +265,15 @@ function CustomDismissible({ id, children }) {
178
265
  return <div>Error: {error.message}</div>;
179
266
  }
180
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
+
181
277
  return (
182
278
  <div>
183
279
  {children}
@@ -191,21 +287,16 @@ function CustomDismissible({ id, children }) {
191
287
 
192
288
  ## Usage Examples
193
289
 
194
- ### JWT Authentication Setup (Enterprise Only)
195
-
196
- > **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)
197
-
198
- For enterprise accounts that require user-specific dismissible state, wrap your app with the `DismissibleProvider`:
290
+ ### Basic Dismissible Banner
199
291
 
200
292
  ```tsx
201
293
  import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
202
294
 
203
295
  function App() {
204
- // Example with static JWT token
205
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
296
+ const userId = getCurrentUserId();
206
297
 
207
298
  return (
208
- <DismissibleProvider jwt={jwt}>
299
+ <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
209
300
  <Dashboard />
210
301
  </DismissibleProvider>
211
302
  );
@@ -213,61 +304,48 @@ function App() {
213
304
 
214
305
  function Dashboard() {
215
306
  return (
216
- <div>
217
- {/* These dismissible items will be user-specific */}
218
- <Dismissible id="user-welcome-banner">
219
- <div className="alert alert-info">
220
- <h4>Welcome back!</h4>
221
- <p>You have 3 new notifications.</p>
222
- </div>
223
- </Dismissible>
224
- </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>
225
313
  );
226
314
  }
227
315
  ```
228
316
 
229
- ### Dynamic JWT with Authentication Provider
317
+ ### JWT Authentication Setup
318
+
319
+ For secure environments, configure JWT authentication:
230
320
 
231
321
  ```tsx
232
- import { DismissibleProvider } from '@dismissible/react-client';
233
- import { useAuth } from './auth'; // Your auth context
322
+ import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
234
323
 
235
- // Synchronous JWT function
236
324
  function App() {
237
- const { getAccessToken } = useAuth();
238
-
239
- return (
240
- <DismissibleProvider jwt={() => getAccessToken()}>
241
- <YourApp />
242
- </DismissibleProvider>
243
- );
244
- }
245
-
246
- // Asynchronous JWT function
247
- function AppWithAsyncAuth() {
248
- const { refreshAndGetToken } = useAuth();
325
+ const { user, getAccessToken } = useAuth();
249
326
 
250
327
  return (
251
- <DismissibleProvider jwt={async () => await refreshAndGetToken()}>
252
- <YourApp />
328
+ <DismissibleProvider
329
+ userId={user.id}
330
+ jwt={() => getAccessToken()}
331
+ baseUrl="https://api.yourapp.com"
332
+ >
333
+ <Dashboard />
253
334
  </DismissibleProvider>
254
335
  );
255
336
  }
256
- ```
257
-
258
- ### Basic Dismissible Banner
259
337
 
260
- ```tsx
261
- import { Dismissible } from '@dismissible/react-client';
262
-
263
- function WelcomeBanner() {
338
+ function Dashboard() {
264
339
  return (
265
- <Dismissible id="welcome-banner-234-432-432-1">
266
- <div className="alert alert-info">
267
- <h4>Welcome!</h4>
268
- <p>Thanks for joining our platform. Here are some quick tips to get started.</p>
269
- </div>
270
- </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>
271
349
  );
272
350
  }
273
351
  ```
@@ -290,7 +368,7 @@ const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
290
368
  function CustomBanner() {
291
369
  return (
292
370
  <Dismissible
293
- id="custom-banner"
371
+ itemId="custom-banner"
294
372
  DismissButtonComponent={CustomDismissButton}
295
373
  >
296
374
  <div className="banner">
@@ -306,7 +384,7 @@ function CustomBanner() {
306
384
  ```tsx
307
385
  import { Dismissible } from '@dismissible/react-client';
308
386
 
309
- const CustomLoader = ({ id }) => (
387
+ const CustomLoader = ({ itemId }) => (
310
388
  <div className="spinner">
311
389
  <div className="bounce1"></div>
312
390
  <div className="bounce2"></div>
@@ -327,7 +405,7 @@ const CustomError = ({ error }) => (
327
405
  function AdvancedBanner() {
328
406
  return (
329
407
  <Dismissible
330
- id="advanced-banner"
408
+ itemId="advanced-banner"
331
409
  LoadingComponent={CustomLoader}
332
410
  ErrorComponent={CustomError}
333
411
  >
@@ -347,19 +425,19 @@ import { Dismissible } from '@dismissible/react-client';
347
425
  function Dashboard() {
348
426
  return (
349
427
  <div>
350
- <Dismissible id="feature-announcement-234-432-432-1">
428
+ <Dismissible itemId="feature-announcement">
351
429
  <div className="alert alert-success">
352
430
  🎉 New feature: Dark mode is now available!
353
431
  </div>
354
432
  </Dismissible>
355
433
 
356
- <Dismissible id="maintenance-notice-234-432-432-1">
434
+ <Dismissible itemId="maintenance-notice">
357
435
  <div className="alert alert-warning">
358
436
  ⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
359
437
  </div>
360
438
  </Dismissible>
361
439
 
362
- <Dismissible id="survey-request-234-432-432-1">
440
+ <Dismissible itemId="survey-request">
363
441
  <div className="alert alert-info">
364
442
  📝 Help us improve! Take our 2-minute survey.
365
443
  </div>
@@ -369,73 +447,6 @@ function Dashboard() {
369
447
  }
370
448
  ```
371
449
 
372
- ### Conditional Dismissible Content
373
-
374
- ```tsx
375
- import { Dismissible } from '@dismissible/react-client';
376
-
377
- function ConditionalBanner({ user }) {
378
- // Only show to new users
379
- if (user.isReturning) {
380
- return null;
381
- }
382
-
383
- return (
384
- <Dismissible id={`onboarding-${user.id}`}>
385
- <div className="onboarding-tips">
386
- <h3>Getting Started</h3>
387
- <ul>
388
- <li>Complete your profile</li>
389
- <li>Connect with friends</li>
390
- <li>Explore our features</li>
391
- </ul>
392
- </div>
393
- </Dismissible>
394
- );
395
- }
396
- ```
397
-
398
- ### User-Specific vs Anonymous Dismissible Items
399
-
400
- The behavior changes based on whether JWT authentication is configured:
401
-
402
- > **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)
403
-
404
- ```tsx
405
- import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
406
-
407
- // With JWT - dismissible state is user-specific
408
- function AuthenticatedApp() {
409
- return (
410
- <DismissibleProvider jwt={() => getAccessToken()}>
411
- <div>
412
- {/* Each user will see this banner independently */}
413
- <Dismissible id="feature-announcement">
414
- <div>New feature available!</div>
415
- </Dismissible>
416
-
417
- {/* User A dismissing this won't affect User B */}
418
- <Dismissible id="survey-request">
419
- <div>Please take our survey!</div>
420
- </Dismissible>
421
- </div>
422
- </DismissibleProvider>
423
- );
424
- }
425
-
426
- // Without JWT - dismissible state is account-level (anonymous)
427
- function AnonymousApp() {
428
- return (
429
- <div>
430
- {/* These will be dismissed for all users of this account */}
431
- <Dismissible id="general-announcement">
432
- <div>Site maintenance scheduled</div>
433
- </Dismissible>
434
- </div>
435
- );
436
- }
437
- ```
438
-
439
450
  ### Error Handling with ignoreErrors
440
451
 
441
452
  ```tsx
@@ -445,7 +456,7 @@ import { Dismissible } from '@dismissible/react-client';
445
456
  function RobustBanner() {
446
457
  return (
447
458
  <Dismissible
448
- id="important-announcement"
459
+ itemId="important-announcement"
449
460
  ignoreErrors={true}
450
461
  >
451
462
  <div className="important-banner">
@@ -455,71 +466,70 @@ function RobustBanner() {
455
466
  </Dismissible>
456
467
  );
457
468
  }
458
-
459
- // Default behavior - hide content on errors
460
- function StandardBanner() {
461
- return (
462
- <Dismissible id="promo-banner">
463
- <div className="promo">
464
- <h3>Special Offer!</h3>
465
- <p>Get 50% off your first order</p>
466
- </div>
467
- </Dismissible>
468
- );
469
- }
470
469
  ```
471
470
 
472
- ### Async JWT Authentication Examples
471
+ ### Integration with Auth Providers
473
472
 
474
473
  ```tsx
475
474
  import { DismissibleProvider } from '@dismissible/react-client';
476
475
 
477
- // With token refresh logic
478
- function AppWithTokenRefresh() {
476
+ // With Firebase Auth
477
+ function AppWithFirebase() {
478
+ const user = firebase.auth().currentUser;
479
+
479
480
  return (
480
481
  <DismissibleProvider
482
+ userId={user.uid}
481
483
  jwt={async () => {
482
- try {
483
- const token = await fetch('/api/auth/refresh', {
484
- method: 'POST',
485
- credentials: 'include'
486
- });
487
- const { accessToken } = await token.json();
488
- return accessToken;
489
- } catch (error) {
490
- console.error('Failed to refresh token:', error);
491
- throw error;
484
+ if (user) {
485
+ return await user.getIdToken();
492
486
  }
487
+ throw new Error('User not authenticated');
493
488
  }}
489
+ baseUrl="https://api.yourapp.com"
494
490
  >
495
491
  <YourApp />
496
492
  </DismissibleProvider>
497
493
  );
498
494
  }
499
495
 
500
- // With Firebase Auth
501
- function AppWithFirebase() {
496
+ // With Auth0
497
+ function AppWithAuth0() {
498
+ const { user, getAccessTokenSilently } = useAuth0();
499
+
502
500
  return (
503
501
  <DismissibleProvider
504
- jwt={async () => {
505
- const user = firebase.auth().currentUser;
506
- if (user) {
507
- return await user.getIdToken();
508
- }
509
- throw new Error('User not authenticated');
510
- }}
502
+ userId={user.sub}
503
+ jwt={async () => await getAccessTokenSilently()}
504
+ baseUrl="https://api.yourapp.com"
511
505
  >
512
506
  <YourApp />
513
507
  </DismissibleProvider>
514
508
  );
515
509
  }
516
510
 
517
- // With Auth0
518
- function AppWithAuth0() {
519
- const { getAccessTokenSilently } = useAuth0();
511
+ // With token refresh logic
512
+ function AppWithTokenRefresh() {
513
+ const { user } = useAuth();
520
514
 
521
515
  return (
522
- <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
+ >
523
533
  <YourApp />
524
534
  </DismissibleProvider>
525
535
  );
@@ -529,11 +539,11 @@ function AppWithAuth0() {
529
539
  ### Using the Hook for Complex Logic
530
540
 
531
541
  ```tsx
532
- import { useDismissibleItem, DismissibleProvider } from '@dismissible/react-client';
542
+ import { useDismissibleItem } from '@dismissible/react-client';
533
543
  import { useState, useEffect } from 'react';
534
544
 
535
- function SmartNotification({ id, message, type = 'info' }) {
536
- const { dismissedOn, dismiss, isLoading } = useDismissibleItem(id);
545
+ function SmartNotification({ itemId, message, type = 'info' }) {
546
+ const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId);
537
547
  const [autoHide, setAutoHide] = useState(false);
538
548
 
539
549
  // Auto-hide after 10 seconds for info messages
@@ -565,17 +575,82 @@ function SmartNotification({ id, message, type = 'info' }) {
565
575
  </div>
566
576
  );
567
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
+ }
568
600
 
569
- // Usage with async authentication
570
- function App() {
571
601
  return (
572
- <DismissibleProvider jwt={async () => await getUserToken()}>
573
- <SmartNotification
574
- id="user-specific-notification"
575
- message="Welcome back!"
576
- type="info"
577
- />
578
- </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>
579
654
  );
580
655
  }
581
656
  ```
@@ -608,105 +683,64 @@ The library includes minimal default styles. You can override them or provide yo
608
683
  The library is written in TypeScript and exports all type definitions:
609
684
 
610
685
  ```tsx
611
- import type {
686
+ import type {
612
687
  DismissibleProps,
613
688
  DismissibleProviderProps,
614
689
  JwtToken,
615
- IDismissibleItem
616
690
  } from '@dismissible/react-client';
617
691
 
618
- // Dismissible component with custom props
619
- const MyComponent: React.FC<DismissibleProps> = (props) => {
620
- // Your component implementation
621
- };
622
-
623
- // Provider with JWT authentication
624
- 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
+
625
698
  return (
626
- <DismissibleProvider jwt={jwt}>
699
+ <DismissibleProvider
700
+ userId={user.id}
701
+ jwt={getToken}
702
+ baseUrl={process.env.DISMISSIBLE_API_URL}
703
+ >
627
704
  {children}
628
705
  </DismissibleProvider>
629
706
  );
630
707
  };
631
708
  ```
632
709
 
633
- ## Development
710
+ ## Self-Hosting
634
711
 
635
- ### Prerequisites
712
+ Dismissible is designed to be self-hosted. You have full control over your data.
636
713
 
637
- - Node.js 18+
638
- - npm or yarn
714
+ ### Option 1: Docker (Recommended)
639
715
 
640
- ### Setup
716
+ The fastest way to get started:
641
717
 
642
718
  ```bash
643
- # Clone the repository
644
- git clone https://github.com/your-org/dismissible.git
645
- cd dismissible/react-client
646
-
647
- # Install dependencies
648
- npm install
649
-
650
- # Start development server
651
- npm run dev
719
+ docker run -p 3001:3001 dismissibleio/dismissible-api:latest
652
720
  ```
653
721
 
654
- ### Available Scripts
722
+ See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
655
723
 
656
- - `npm run dev` - Start development server with Vite
657
- - `npm run build` - Build the library for production
658
- - `npm run test` - Run tests with Vitest
659
- - `npm run test:watch` - Run tests in watch mode
660
- - `npm run lint` - Run ESLint
661
- - `npm run format` - Format code with Prettier
662
- - `npm run storybook` - Start Storybook development server
663
- - `npm run build-storybook` - Build Storybook for production
724
+ ### Option 2: NestJS Module
664
725
 
665
- ### Testing
726
+ Integrate directly into your existing NestJS application:
666
727
 
667
728
  ```bash
668
- # Run all tests
669
- npm run test
670
-
671
- # Run tests in watch mode
672
- npm run test:watch
673
-
674
- # Run tests with coverage
675
- npm run test -- --coverage
729
+ npm install @dismissible/nestjs-api
676
730
  ```
677
731
 
678
- ### Storybook
679
-
680
- The library includes Storybook for component development and documentation:
732
+ See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
681
733
 
682
- ```bash
683
- npm run storybook
684
- ```
685
-
686
- ## Contributing
687
-
688
- We welcome contributions! Please see our [Contributing Guide](../CONTRIBUTING.md) for details.
689
-
690
- ### Development Workflow
734
+ ## Support
691
735
 
692
- 1. Fork the repository
693
- 2. Create a feature branch: `git checkout -b feature/my-new-feature`
694
- 3. Make your changes
695
- 4. Add tests for new functionality
696
- 5. Run tests: `npm run test`
697
- 6. Run linting: `npm run lint`
698
- 7. Commit your changes: `git commit -am 'Add new feature'`
699
- 8. Push to the branch: `git push origin feature/my-new-feature`
700
- 9. Submit a pull request
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)
701
739
 
702
740
  ## License
703
741
 
704
- MIT © [Dismissible](https://github.com/joshystuart)
705
-
706
- ## Support
707
-
708
- - 📖 [Documentation](https://docs.dismissible.io)
742
+ MIT © [Dismissible](https://dismissible.io)
709
743
 
710
744
  ## Changelog
711
745
 
712
- See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
746
+ See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.