@dismissible/react-client 0.3.1 → 0.3.2-canary.3.c1b8c41

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 id="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,6 +187,8 @@ 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 |
@@ -123,6 +200,9 @@ The main component for creating dismissible content.
123
200
  | `ErrorComponent` | `ComponentType<{id: string, error: Error}>` | ❌ | Custom error component |
124
201
  | `DismissButtonComponent` | `ComponentType<{id: string, 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
 
@@ -130,7 +210,6 @@ The main component for creating dismissible content.
130
210
  <Dismissible
131
211
  id="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>
@@ -148,6 +227,7 @@ For custom implementations and advanced use cases.
148
227
  | Parameter | Type | Required | Description |
149
228
  |-----------|------|----------|-------------|
150
229
  | `id` | `string` | ✅ | Unique identifier for the dismissible item |
230
+ | `options` | `object` | ❌ | Cache configuration options |
151
231
 
152
232
  #### Returns
153
233
 
@@ -155,8 +235,10 @@ For custom implementations and advanced use cases.
155
235
  |----------|------|-------------|
156
236
  | `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
157
237
  | `dismiss` | `() => Promise<void>` | Function to dismiss the item |
238
+ | `restore` | `() => Promise<void>` | Function to restore a dismissed item |
158
239
  | `isLoading` | `boolean` | Loading state indicator |
159
240
  | `error` | `Error \| null` | Error state, if any |
241
+ | `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
160
242
 
161
243
  #### Example
162
244
 
@@ -164,11 +246,7 @@ For custom implementations and advanced use cases.
164
246
  import { useDismissibleItem } from '@dismissible/react-client';
165
247
 
166
248
  function CustomDismissible({ id, children }) {
167
- const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(id);
168
-
169
- if (dismissedOn) {
170
- return null; // Item is dismissed
171
- }
249
+ const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(id);
172
250
 
173
251
  if (isLoading) {
174
252
  return <div>Loading...</div>;
@@ -178,6 +256,15 @@ function CustomDismissible({ id, children }) {
178
256
  return <div>Error: {error.message}</div>;
179
257
  }
180
258
 
259
+ if (dismissedOn) {
260
+ return (
261
+ <div>
262
+ <p>This item was dismissed.</p>
263
+ <button onClick={restore}>Restore</button>
264
+ </div>
265
+ );
266
+ }
267
+
181
268
  return (
182
269
  <div>
183
270
  {children}
@@ -191,21 +278,16 @@ function CustomDismissible({ id, children }) {
191
278
 
192
279
  ## Usage Examples
193
280
 
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`:
281
+ ### Basic Dismissible Banner
199
282
 
200
283
  ```tsx
201
284
  import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
202
285
 
203
286
  function App() {
204
- // Example with static JWT token
205
- const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
287
+ const userId = getCurrentUserId();
206
288
 
207
289
  return (
208
- <DismissibleProvider jwt={jwt}>
290
+ <DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
209
291
  <Dashboard />
210
292
  </DismissibleProvider>
211
293
  );
@@ -213,61 +295,48 @@ function App() {
213
295
 
214
296
  function Dashboard() {
215
297
  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>
298
+ <Dismissible id="welcome-banner">
299
+ <div className="alert alert-info">
300
+ <h4>Welcome!</h4>
301
+ <p>Thanks for joining our platform. Here are some quick tips to get started.</p>
302
+ </div>
303
+ </Dismissible>
225
304
  );
226
305
  }
227
306
  ```
228
307
 
229
- ### Dynamic JWT with Authentication Provider
308
+ ### JWT Authentication Setup
309
+
310
+ For secure environments, configure JWT authentication:
230
311
 
231
312
  ```tsx
232
- import { DismissibleProvider } from '@dismissible/react-client';
233
- import { useAuth } from './auth'; // Your auth context
313
+ import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
234
314
 
235
- // Synchronous JWT function
236
315
  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();
316
+ const { user, getAccessToken } = useAuth();
249
317
 
250
318
  return (
251
- <DismissibleProvider jwt={async () => await refreshAndGetToken()}>
252
- <YourApp />
319
+ <DismissibleProvider
320
+ userId={user.id}
321
+ jwt={() => getAccessToken()}
322
+ baseUrl="https://api.yourapp.com"
323
+ >
324
+ <Dashboard />
253
325
  </DismissibleProvider>
254
326
  );
255
327
  }
256
- ```
257
328
 
258
- ### Basic Dismissible Banner
259
-
260
- ```tsx
261
- import { Dismissible } from '@dismissible/react-client';
262
-
263
- function WelcomeBanner() {
329
+ function Dashboard() {
264
330
  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>
331
+ <div>
332
+ {/* Dismissible state is tracked per user */}
333
+ <Dismissible id="user-welcome-banner">
334
+ <div className="alert alert-info">
335
+ <h4>Welcome back!</h4>
336
+ <p>You have 3 new notifications.</p>
337
+ </div>
338
+ </Dismissible>
339
+ </div>
271
340
  );
272
341
  }
273
342
  ```
@@ -347,19 +416,19 @@ import { Dismissible } from '@dismissible/react-client';
347
416
  function Dashboard() {
348
417
  return (
349
418
  <div>
350
- <Dismissible id="feature-announcement-234-432-432-1">
419
+ <Dismissible id="feature-announcement">
351
420
  <div className="alert alert-success">
352
421
  🎉 New feature: Dark mode is now available!
353
422
  </div>
354
423
  </Dismissible>
355
424
 
356
- <Dismissible id="maintenance-notice-234-432-432-1">
425
+ <Dismissible id="maintenance-notice">
357
426
  <div className="alert alert-warning">
358
427
  ⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
359
428
  </div>
360
429
  </Dismissible>
361
430
 
362
- <Dismissible id="survey-request-234-432-432-1">
431
+ <Dismissible id="survey-request">
363
432
  <div className="alert alert-info">
364
433
  📝 Help us improve! Take our 2-minute survey.
365
434
  </div>
@@ -369,73 +438,6 @@ function Dashboard() {
369
438
  }
370
439
  ```
371
440
 
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
441
  ### Error Handling with ignoreErrors
440
442
 
441
443
  ```tsx
@@ -455,71 +457,70 @@ function RobustBanner() {
455
457
  </Dismissible>
456
458
  );
457
459
  }
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
460
  ```
471
461
 
472
- ### Async JWT Authentication Examples
462
+ ### Integration with Auth Providers
473
463
 
474
464
  ```tsx
475
465
  import { DismissibleProvider } from '@dismissible/react-client';
476
466
 
477
- // With token refresh logic
478
- function AppWithTokenRefresh() {
467
+ // With Firebase Auth
468
+ function AppWithFirebase() {
469
+ const user = firebase.auth().currentUser;
470
+
479
471
  return (
480
472
  <DismissibleProvider
473
+ userId={user.uid}
481
474
  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;
475
+ if (user) {
476
+ return await user.getIdToken();
492
477
  }
478
+ throw new Error('User not authenticated');
493
479
  }}
480
+ baseUrl="https://api.yourapp.com"
494
481
  >
495
482
  <YourApp />
496
483
  </DismissibleProvider>
497
484
  );
498
485
  }
499
486
 
500
- // With Firebase Auth
501
- function AppWithFirebase() {
487
+ // With Auth0
488
+ function AppWithAuth0() {
489
+ const { user, getAccessTokenSilently } = useAuth0();
490
+
502
491
  return (
503
492
  <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
- }}
493
+ userId={user.sub}
494
+ jwt={async () => await getAccessTokenSilently()}
495
+ baseUrl="https://api.yourapp.com"
511
496
  >
512
497
  <YourApp />
513
498
  </DismissibleProvider>
514
499
  );
515
500
  }
516
501
 
517
- // With Auth0
518
- function AppWithAuth0() {
519
- const { getAccessTokenSilently } = useAuth0();
502
+ // With token refresh logic
503
+ function AppWithTokenRefresh() {
504
+ const { user } = useAuth();
520
505
 
521
506
  return (
522
- <DismissibleProvider jwt={async () => await getAccessTokenSilently()}>
507
+ <DismissibleProvider
508
+ userId={user.id}
509
+ jwt={async () => {
510
+ try {
511
+ const response = await fetch('/api/auth/refresh', {
512
+ method: 'POST',
513
+ credentials: 'include'
514
+ });
515
+ const { accessToken } = await response.json();
516
+ return accessToken;
517
+ } catch (error) {
518
+ console.error('Failed to refresh token:', error);
519
+ throw error;
520
+ }
521
+ }}
522
+ baseUrl="https://api.yourapp.com"
523
+ >
523
524
  <YourApp />
524
525
  </DismissibleProvider>
525
526
  );
@@ -529,7 +530,7 @@ function AppWithAuth0() {
529
530
  ### Using the Hook for Complex Logic
530
531
 
531
532
  ```tsx
532
- import { useDismissibleItem, DismissibleProvider } from '@dismissible/react-client';
533
+ import { useDismissibleItem } from '@dismissible/react-client';
533
534
  import { useState, useEffect } from 'react';
534
535
 
535
536
  function SmartNotification({ id, message, type = 'info' }) {
@@ -565,17 +566,82 @@ function SmartNotification({ id, message, type = 'info' }) {
565
566
  </div>
566
567
  );
567
568
  }
569
+ ```
570
+
571
+ ### Restoring Dismissed Items
572
+
573
+ Use the `restore` function to bring back previously dismissed content:
574
+
575
+ ```tsx
576
+ import { useDismissibleItem } from '@dismissible/react-client';
577
+
578
+ function RestorableBanner({ id }) {
579
+ const { dismissedOn, dismiss, restore, isLoading } = useDismissibleItem(id);
580
+
581
+ if (dismissedOn) {
582
+ return (
583
+ <div className="dismissed-placeholder">
584
+ <p>Banner was dismissed on {new Date(dismissedOn).toLocaleDateString()}</p>
585
+ <button onClick={restore} disabled={isLoading}>
586
+ {isLoading ? 'Restoring...' : 'Show Banner Again'}
587
+ </button>
588
+ </div>
589
+ );
590
+ }
568
591
 
569
- // Usage with async authentication
570
- function App() {
571
592
  return (
572
- <DismissibleProvider jwt={async () => await getUserToken()}>
573
- <SmartNotification
574
- id="user-specific-notification"
575
- message="Welcome back!"
576
- type="info"
577
- />
578
- </DismissibleProvider>
593
+ <div className="banner">
594
+ <h3>Welcome!</h3>
595
+ <p>This is a restorable banner.</p>
596
+ <button onClick={dismiss} disabled={isLoading}>
597
+ Dismiss
598
+ </button>
599
+ </div>
600
+ );
601
+ }
602
+ ```
603
+
604
+ ### Admin Panel with Restore Capability
605
+
606
+ ```tsx
607
+ import { useDismissibleItem } from '@dismissible/react-client';
608
+
609
+ function AdminNotificationManager({ notificationId }) {
610
+ const { dismissedOn, dismiss, restore, item, isLoading, error } =
611
+ useDismissibleItem(notificationId);
612
+
613
+ if (error) {
614
+ return <div className="error">Error: {error.message}</div>;
615
+ }
616
+
617
+ return (
618
+ <div className="admin-panel">
619
+ <h4>Notification: {notificationId}</h4>
620
+ <p>Status: {dismissedOn ? 'Dismissed' : 'Active'}</p>
621
+ {dismissedOn && (
622
+ <p>Dismissed at: {new Date(dismissedOn).toLocaleString()}</p>
623
+ )}
624
+
625
+ <div className="actions">
626
+ {dismissedOn ? (
627
+ <button
628
+ onClick={restore}
629
+ disabled={isLoading}
630
+ className="btn-restore"
631
+ >
632
+ Restore
633
+ </button>
634
+ ) : (
635
+ <button
636
+ onClick={dismiss}
637
+ disabled={isLoading}
638
+ className="btn-dismiss"
639
+ >
640
+ Dismiss
641
+ </button>
642
+ )}
643
+ </div>
644
+ </div>
579
645
  );
580
646
  }
581
647
  ```
@@ -612,101 +678,60 @@ import type {
612
678
  DismissibleProps,
613
679
  DismissibleProviderProps,
614
680
  JwtToken,
615
- IDismissibleItem
616
681
  } from '@dismissible/react-client';
617
682
 
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 }) => {
683
+ // Custom provider wrapper
684
+ const AuthenticatedDismissibleProvider: React.FC<{
685
+ children: React.ReactNode;
686
+ }> = ({ children }) => {
687
+ const { user, getToken } = useAuth();
688
+
625
689
  return (
626
- <DismissibleProvider jwt={jwt}>
690
+ <DismissibleProvider
691
+ userId={user.id}
692
+ jwt={getToken}
693
+ baseUrl={process.env.DISMISSIBLE_API_URL}
694
+ >
627
695
  {children}
628
696
  </DismissibleProvider>
629
697
  );
630
698
  };
631
699
  ```
632
700
 
633
- ## Development
701
+ ## Self-Hosting
634
702
 
635
- ### Prerequisites
703
+ Dismissible is designed to be self-hosted. You have full control over your data.
636
704
 
637
- - Node.js 18+
638
- - npm or yarn
705
+ ### Option 1: Docker (Recommended)
639
706
 
640
- ### Setup
707
+ The fastest way to get started:
641
708
 
642
709
  ```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
710
+ docker run -p 3001:3001 dismissibleio/dismissible-api:latest
652
711
  ```
653
712
 
654
- ### Available Scripts
713
+ See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
655
714
 
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
715
+ ### Option 2: NestJS Module
664
716
 
665
- ### Testing
717
+ Integrate directly into your existing NestJS application:
666
718
 
667
719
  ```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
720
+ npm install @dismissible/nestjs-api
676
721
  ```
677
722
 
678
- ### Storybook
679
-
680
- The library includes Storybook for component development and documentation:
723
+ See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
681
724
 
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
725
+ ## Support
691
726
 
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
727
+ - 📖 [Documentation](https://dismissible.io/docs)
728
+ - 🐙 [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
729
+ - 🐙 [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
701
730
 
702
731
  ## License
703
732
 
704
- MIT © [Dismissible](https://github.com/joshystuart)
705
-
706
- ## Support
707
-
708
- - 📖 [Documentation](https://docs.dismissible.io)
733
+ MIT © [Dismissible](https://dismissible.io)
709
734
 
710
735
  ## Changelog
711
736
 
712
- See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
737
+ See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.