@dismissible/react-client 0.3.0 → 0.3.2-canary.1.3c5be4b

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