@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 +286 -261
- package/dist/components/Dismissible.d.ts +3 -4
- package/dist/contexts/DismissibleContext.d.ts +3 -2
- package/dist/contexts/DismissibleProvider.d.ts +3 -1
- package/dist/dismissible-client.es.js +405 -357
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/hooks/useDismissibleItem.d.ts +9 -7
- package/dist/mockServiceWorker.js +17 -12
- package/dist/root.d.ts +0 -1
- package/dist/types/dismissible.types.d.ts +7 -3
- package/dist/utils/auth.utils.d.ts +0 -6
- package/package.json +50 -31
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
|
-
|
|
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
|
[](https://badge.fury.io/js/@dismissible%2Freact-client)
|
|
8
10
|
[](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
|
-
-
|
|
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
|
|
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
|
-
<
|
|
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
|
|
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
|
-
| `
|
|
66
|
-
| `
|
|
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
|
-
//
|
|
133
|
+
// Basic setup with userId
|
|
75
134
|
function App() {
|
|
76
135
|
return (
|
|
77
|
-
<DismissibleProvider
|
|
136
|
+
<DismissibleProvider userId="user-123" baseUrl="http://localhost:3001">
|
|
78
137
|
<YourApp />
|
|
79
138
|
</DismissibleProvider>
|
|
80
139
|
);
|
|
81
140
|
}
|
|
82
141
|
|
|
83
|
-
// With
|
|
84
|
-
function
|
|
142
|
+
// With static JWT
|
|
143
|
+
function AppWithJWT() {
|
|
85
144
|
return (
|
|
86
|
-
<DismissibleProvider
|
|
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
|
|
93
|
-
function
|
|
155
|
+
// With dynamic JWT function
|
|
156
|
+
function AppWithDynamicAuth() {
|
|
157
|
+
const { user, getAccessToken } = useAuth();
|
|
158
|
+
|
|
94
159
|
return (
|
|
95
|
-
<DismissibleProvider
|
|
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
|
-
//
|
|
102
|
-
function
|
|
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
|
-
###
|
|
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
|
-
|
|
205
|
-
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
|
|
287
|
+
const userId = getCurrentUserId();
|
|
206
288
|
|
|
207
289
|
return (
|
|
208
|
-
<DismissibleProvider
|
|
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
|
-
<
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
<
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
###
|
|
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
|
|
252
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
```tsx
|
|
261
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
262
|
-
|
|
263
|
-
function WelcomeBanner() {
|
|
329
|
+
function Dashboard() {
|
|
264
330
|
return (
|
|
265
|
-
<
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
###
|
|
462
|
+
### Integration with Auth Providers
|
|
473
463
|
|
|
474
464
|
```tsx
|
|
475
465
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
476
466
|
|
|
477
|
-
// With
|
|
478
|
-
function
|
|
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
|
-
|
|
483
|
-
|
|
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
|
|
501
|
-
function
|
|
487
|
+
// With Auth0
|
|
488
|
+
function AppWithAuth0() {
|
|
489
|
+
const { user, getAccessTokenSilently } = useAuth0();
|
|
490
|
+
|
|
502
491
|
return (
|
|
503
492
|
<DismissibleProvider
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
|
518
|
-
function
|
|
519
|
-
const {
|
|
502
|
+
// With token refresh logic
|
|
503
|
+
function AppWithTokenRefresh() {
|
|
504
|
+
const { user } = useAuth();
|
|
520
505
|
|
|
521
506
|
return (
|
|
522
|
-
<DismissibleProvider
|
|
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
|
|
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
|
-
<
|
|
573
|
-
<
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
</
|
|
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
|
-
//
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
|
|
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
|
|
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
|
-
##
|
|
701
|
+
## Self-Hosting
|
|
634
702
|
|
|
635
|
-
|
|
703
|
+
Dismissible is designed to be self-hosted. You have full control over your data.
|
|
636
704
|
|
|
637
|
-
|
|
638
|
-
- npm or yarn
|
|
705
|
+
### Option 1: Docker (Recommended)
|
|
639
706
|
|
|
640
|
-
|
|
707
|
+
The fastest way to get started:
|
|
641
708
|
|
|
642
709
|
```bash
|
|
643
|
-
|
|
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
|
-
|
|
713
|
+
See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
|
|
655
714
|
|
|
656
|
-
|
|
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
|
-
|
|
717
|
+
Integrate directly into your existing NestJS application:
|
|
666
718
|
|
|
667
719
|
```bash
|
|
668
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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://
|
|
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.
|