@dismissible/react-client 0.3.2-canary.2.38782c4 → 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 +302 -156
- package/dist/components/Dismissible.d.ts +3 -4
- package/dist/dismissible-client.es.js +367 -341
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/hooks/useDismissibleItem.d.ts +2 -1
- package/dist/root.d.ts +0 -1
- package/dist/types/dismissible.types.d.ts +3 -3
- package/dist/utils/auth.utils.d.ts +0 -6
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @dismissible/react-client
|
|
2
2
|
|
|
3
|
-
A React component library for creating dismissible UI elements with persistent state management.
|
|
3
|
+
A React component library for creating dismissible UI elements with persistent state management.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Free and open source** - use with the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api) that you can self-host with Docker or integrate into your NestJS application.
|
|
6
6
|
|
|
7
|
-
🌐 **[dismissible.io](https://dismissible.io)** |
|
|
7
|
+
🌐 **[dismissible.io](https://dismissible.io)** | 📖 **[Documentation](https://dismissible.io/docs)** | 🐙 **[API Server](https://github.com/DismissibleIo/dismissible-api)**
|
|
8
8
|
|
|
9
9
|
[](https://badge.fury.io/js/@dismissible%2Freact-client)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -13,12 +13,14 @@ Use this in combination with [dismissible.io](https://dismissible.io). Get your
|
|
|
13
13
|
|
|
14
14
|
- 🎯 **Easy to use** - Simple component API for dismissible content
|
|
15
15
|
- 💾 **Persistent state** - Dismissal state is saved and restored across sessions
|
|
16
|
-
-
|
|
16
|
+
- 🔄 **Restore support** - Restore previously dismissed items programmatically
|
|
17
|
+
- 🔐 **JWT Authentication** - Built-in support for secure JWT-based authentication
|
|
17
18
|
- 🎨 **Customizable** - Custom loading, error, and dismiss button components
|
|
18
19
|
- ♿ **Accessible** - Built with accessibility best practices
|
|
19
20
|
- 🪝 **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
|
|
20
21
|
- 📦 **Lightweight** - Minimal bundle size with tree-shaking support
|
|
21
22
|
- 🔧 **TypeScript** - Full TypeScript support with complete type definitions
|
|
23
|
+
- 🐳 **Self-hosted** - Works with your own Dismissible API server
|
|
22
24
|
|
|
23
25
|
## Installation
|
|
24
26
|
|
|
@@ -36,13 +38,69 @@ npm install react react-dom
|
|
|
36
38
|
|
|
37
39
|
## Quick Start
|
|
38
40
|
|
|
41
|
+
### 1. Set up the Dismissible API Server
|
|
42
|
+
|
|
43
|
+
First, you need a Dismissible API server running. The easiest way is with Docker:
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
# docker-compose.yml
|
|
47
|
+
version: '3.8'
|
|
48
|
+
services:
|
|
49
|
+
api:
|
|
50
|
+
image: dismissibleio/dismissible-api:latest
|
|
51
|
+
ports:
|
|
52
|
+
- '3001:3001'
|
|
53
|
+
environment:
|
|
54
|
+
DISMISSIBLE_PORT: 3001
|
|
55
|
+
DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING: postgresql://postgres:postgres@db:5432/dismissible
|
|
56
|
+
depends_on:
|
|
57
|
+
- db
|
|
58
|
+
|
|
59
|
+
db:
|
|
60
|
+
image: postgres:15
|
|
61
|
+
environment:
|
|
62
|
+
POSTGRES_USER: postgres
|
|
63
|
+
POSTGRES_PASSWORD: postgres
|
|
64
|
+
POSTGRES_DB: dismissible
|
|
65
|
+
volumes:
|
|
66
|
+
- postgres_data:/var/lib/postgresql/data
|
|
67
|
+
|
|
68
|
+
volumes:
|
|
69
|
+
postgres_data:
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
docker-compose up -d
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
See the [API Server documentation](https://github.com/DismissibleIo/dismissible-api) for more deployment options including NestJS integration.
|
|
77
|
+
|
|
78
|
+
### 2. Configure the Provider
|
|
79
|
+
|
|
80
|
+
Wrap your app with `DismissibleProvider`. The `userId` prop is **required** to track dismissals per user:
|
|
81
|
+
|
|
39
82
|
```tsx
|
|
40
|
-
import
|
|
41
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
83
|
+
import { DismissibleProvider } from '@dismissible/react-client';
|
|
42
84
|
|
|
43
85
|
function App() {
|
|
86
|
+
const userId = getCurrentUserId(); // Get from your auth system
|
|
87
|
+
|
|
44
88
|
return (
|
|
45
|
-
<
|
|
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">
|
|
46
104
|
<div className="banner">
|
|
47
105
|
<h2>Welcome to our app!</h2>
|
|
48
106
|
<p>This banner can be dismissed and won't show again.</p>
|
|
@@ -56,16 +114,15 @@ function App() {
|
|
|
56
114
|
|
|
57
115
|
### `<DismissibleProvider>` Component
|
|
58
116
|
|
|
59
|
-
Context provider
|
|
60
|
-
|
|
61
|
-
> **Note:** JWT authentication is only available for **Enterprise customers**. For standard usage, you can use the `<Dismissible>` component directly without a provider. [View pricing →](https://dismissible.io/pricing)
|
|
117
|
+
Context provider that configures authentication and API settings. **Required** - all `<Dismissible>` components must be wrapped in a provider.
|
|
62
118
|
|
|
63
119
|
#### Props
|
|
64
120
|
|
|
65
121
|
| Prop | Type | Required | Description |
|
|
66
122
|
|------|------|----------|-------------|
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
123
|
+
| `userId` | `string` | ✅ | User ID for tracking dismissals per user |
|
|
124
|
+
| `jwt` | `string \| (() => string) \| (() => Promise<string>)` | ❌ | JWT token for secure authentication |
|
|
125
|
+
| `baseUrl` | `string` | ❌ | API base URL (defaults to your self-hosted server) |
|
|
69
126
|
| `children` | `ReactNode` | ✅ | Components that will use the dismissible functionality |
|
|
70
127
|
|
|
71
128
|
#### Example
|
|
@@ -73,37 +130,53 @@ Context provider for JWT authentication and configuration. Wrap your app or comp
|
|
|
73
130
|
```tsx
|
|
74
131
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
75
132
|
|
|
76
|
-
//
|
|
133
|
+
// Basic setup with userId
|
|
77
134
|
function App() {
|
|
78
135
|
return (
|
|
79
|
-
<DismissibleProvider
|
|
136
|
+
<DismissibleProvider userId="user-123" baseUrl="http://localhost:3001">
|
|
80
137
|
<YourApp />
|
|
81
138
|
</DismissibleProvider>
|
|
82
139
|
);
|
|
83
140
|
}
|
|
84
141
|
|
|
85
|
-
// With
|
|
86
|
-
function
|
|
142
|
+
// With static JWT
|
|
143
|
+
function AppWithJWT() {
|
|
87
144
|
return (
|
|
88
|
-
<DismissibleProvider
|
|
145
|
+
<DismissibleProvider
|
|
146
|
+
userId="user-123"
|
|
147
|
+
jwt="eyJhbGciOiJIUzI1NiIs..."
|
|
148
|
+
baseUrl="https://api.yourapp.com"
|
|
149
|
+
>
|
|
89
150
|
<YourApp />
|
|
90
151
|
</DismissibleProvider>
|
|
91
152
|
);
|
|
92
153
|
}
|
|
93
154
|
|
|
94
|
-
// With
|
|
95
|
-
function
|
|
155
|
+
// With dynamic JWT function
|
|
156
|
+
function AppWithDynamicAuth() {
|
|
157
|
+
const { user, getAccessToken } = useAuth();
|
|
158
|
+
|
|
96
159
|
return (
|
|
97
|
-
<DismissibleProvider
|
|
160
|
+
<DismissibleProvider
|
|
161
|
+
userId={user.id}
|
|
162
|
+
jwt={() => getAccessToken()}
|
|
163
|
+
baseUrl="https://api.yourapp.com"
|
|
164
|
+
>
|
|
98
165
|
<YourApp />
|
|
99
166
|
</DismissibleProvider>
|
|
100
167
|
);
|
|
101
168
|
}
|
|
102
169
|
|
|
103
|
-
//
|
|
104
|
-
function
|
|
170
|
+
// With async JWT function
|
|
171
|
+
function AppWithAsyncAuth() {
|
|
172
|
+
const { user, refreshAndGetToken } = useAuth();
|
|
173
|
+
|
|
105
174
|
return (
|
|
106
|
-
<DismissibleProvider
|
|
175
|
+
<DismissibleProvider
|
|
176
|
+
userId={user.id}
|
|
177
|
+
jwt={async () => await refreshAndGetToken()}
|
|
178
|
+
baseUrl="https://api.yourapp.com"
|
|
179
|
+
>
|
|
107
180
|
<YourApp />
|
|
108
181
|
</DismissibleProvider>
|
|
109
182
|
);
|
|
@@ -114,6 +187,8 @@ function AppWithoutAuth() {
|
|
|
114
187
|
|
|
115
188
|
The main component for creating dismissible content.
|
|
116
189
|
|
|
190
|
+
> **Note:** The `<Dismissible>` component renders `null` when an item is dismissed. For restore functionality, use the `useDismissibleItem` hook directly in custom implementations.
|
|
191
|
+
|
|
117
192
|
#### Props
|
|
118
193
|
|
|
119
194
|
| Prop | Type | Required | Description |
|
|
@@ -125,6 +200,9 @@ The main component for creating dismissible content.
|
|
|
125
200
|
| `ErrorComponent` | `ComponentType<{id: string, error: Error}>` | ❌ | Custom error component |
|
|
126
201
|
| `DismissButtonComponent` | `ComponentType<{id: string, onDismiss: () => Promise<void>, ariaLabel: string}>` | ❌ | Custom dismiss button |
|
|
127
202
|
| `ignoreErrors` | `boolean` | ❌ | Ignore errors and display component anyway (default: false) |
|
|
203
|
+
| `enableCache` | `boolean` | ❌ | Enable localStorage caching (default: true) |
|
|
204
|
+
| `cachePrefix` | `string` | ❌ | Cache key prefix (default: 'dismissible') |
|
|
205
|
+
| `cacheExpiration` | `number` | ❌ | Cache expiration time in milliseconds |
|
|
128
206
|
|
|
129
207
|
#### Example
|
|
130
208
|
|
|
@@ -132,7 +210,6 @@ The main component for creating dismissible content.
|
|
|
132
210
|
<Dismissible
|
|
133
211
|
id="promo-banner"
|
|
134
212
|
onDismiss={() => console.log('Banner dismissed')}
|
|
135
|
-
onRestore={() => console.log('Banner restored')}
|
|
136
213
|
>
|
|
137
214
|
<div className="promo">
|
|
138
215
|
<h3>Special Offer!</h3>
|
|
@@ -150,6 +227,7 @@ For custom implementations and advanced use cases.
|
|
|
150
227
|
| Parameter | Type | Required | Description |
|
|
151
228
|
|-----------|------|----------|-------------|
|
|
152
229
|
| `id` | `string` | ✅ | Unique identifier for the dismissible item |
|
|
230
|
+
| `options` | `object` | ❌ | Cache configuration options |
|
|
153
231
|
|
|
154
232
|
#### Returns
|
|
155
233
|
|
|
@@ -157,8 +235,10 @@ For custom implementations and advanced use cases.
|
|
|
157
235
|
|----------|------|-------------|
|
|
158
236
|
| `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
|
|
159
237
|
| `dismiss` | `() => Promise<void>` | Function to dismiss the item |
|
|
238
|
+
| `restore` | `() => Promise<void>` | Function to restore a dismissed item |
|
|
160
239
|
| `isLoading` | `boolean` | Loading state indicator |
|
|
161
240
|
| `error` | `Error \| null` | Error state, if any |
|
|
241
|
+
| `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
|
|
162
242
|
|
|
163
243
|
#### Example
|
|
164
244
|
|
|
@@ -166,11 +246,7 @@ For custom implementations and advanced use cases.
|
|
|
166
246
|
import { useDismissibleItem } from '@dismissible/react-client';
|
|
167
247
|
|
|
168
248
|
function CustomDismissible({ id, children }) {
|
|
169
|
-
const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(id);
|
|
170
|
-
|
|
171
|
-
if (dismissedOn) {
|
|
172
|
-
return null; // Item is dismissed
|
|
173
|
-
}
|
|
249
|
+
const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(id);
|
|
174
250
|
|
|
175
251
|
if (isLoading) {
|
|
176
252
|
return <div>Loading...</div>;
|
|
@@ -180,6 +256,15 @@ function CustomDismissible({ id, children }) {
|
|
|
180
256
|
return <div>Error: {error.message}</div>;
|
|
181
257
|
}
|
|
182
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
|
+
|
|
183
268
|
return (
|
|
184
269
|
<div>
|
|
185
270
|
{children}
|
|
@@ -193,21 +278,16 @@ function CustomDismissible({ id, children }) {
|
|
|
193
278
|
|
|
194
279
|
## Usage Examples
|
|
195
280
|
|
|
196
|
-
###
|
|
197
|
-
|
|
198
|
-
> **Enterprise Feature:** JWT authentication allows user-specific dismissible state management. This feature requires an Enterprise subscription. [Learn more about Enterprise features →](https://dismissible.io/pricing)
|
|
199
|
-
|
|
200
|
-
For enterprise accounts that require user-specific dismissible state, wrap your app with the `DismissibleProvider`:
|
|
281
|
+
### Basic Dismissible Banner
|
|
201
282
|
|
|
202
283
|
```tsx
|
|
203
284
|
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
204
285
|
|
|
205
286
|
function App() {
|
|
206
|
-
|
|
207
|
-
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
|
|
287
|
+
const userId = getCurrentUserId();
|
|
208
288
|
|
|
209
289
|
return (
|
|
210
|
-
<DismissibleProvider
|
|
290
|
+
<DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
|
|
211
291
|
<Dashboard />
|
|
212
292
|
</DismissibleProvider>
|
|
213
293
|
);
|
|
@@ -215,61 +295,48 @@ function App() {
|
|
|
215
295
|
|
|
216
296
|
function Dashboard() {
|
|
217
297
|
return (
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</div>
|
|
225
|
-
</Dismissible>
|
|
226
|
-
</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>
|
|
227
304
|
);
|
|
228
305
|
}
|
|
229
306
|
```
|
|
230
307
|
|
|
231
|
-
###
|
|
308
|
+
### JWT Authentication Setup
|
|
309
|
+
|
|
310
|
+
For secure environments, configure JWT authentication:
|
|
232
311
|
|
|
233
312
|
```tsx
|
|
234
|
-
import { DismissibleProvider } from '@dismissible/react-client';
|
|
235
|
-
import { useAuth } from './auth'; // Your auth context
|
|
313
|
+
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
236
314
|
|
|
237
|
-
// Synchronous JWT function
|
|
238
315
|
function App() {
|
|
239
|
-
const { getAccessToken } = useAuth();
|
|
316
|
+
const { user, getAccessToken } = useAuth();
|
|
240
317
|
|
|
241
318
|
return (
|
|
242
|
-
<DismissibleProvider
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// Asynchronous JWT function
|
|
249
|
-
function AppWithAsyncAuth() {
|
|
250
|
-
const { refreshAndGetToken } = useAuth();
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<DismissibleProvider jwt={async () => await refreshAndGetToken()}>
|
|
254
|
-
<YourApp />
|
|
319
|
+
<DismissibleProvider
|
|
320
|
+
userId={user.id}
|
|
321
|
+
jwt={() => getAccessToken()}
|
|
322
|
+
baseUrl="https://api.yourapp.com"
|
|
323
|
+
>
|
|
324
|
+
<Dashboard />
|
|
255
325
|
</DismissibleProvider>
|
|
256
326
|
);
|
|
257
327
|
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Basic Dismissible Banner
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
264
328
|
|
|
265
|
-
function
|
|
329
|
+
function Dashboard() {
|
|
266
330
|
return (
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
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>
|
|
273
340
|
);
|
|
274
341
|
}
|
|
275
342
|
```
|
|
@@ -349,19 +416,19 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
349
416
|
function Dashboard() {
|
|
350
417
|
return (
|
|
351
418
|
<div>
|
|
352
|
-
<Dismissible id="feature-announcement
|
|
419
|
+
<Dismissible id="feature-announcement">
|
|
353
420
|
<div className="alert alert-success">
|
|
354
421
|
🎉 New feature: Dark mode is now available!
|
|
355
422
|
</div>
|
|
356
423
|
</Dismissible>
|
|
357
424
|
|
|
358
|
-
<Dismissible id="maintenance-notice
|
|
425
|
+
<Dismissible id="maintenance-notice">
|
|
359
426
|
<div className="alert alert-warning">
|
|
360
427
|
⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
|
|
361
428
|
</div>
|
|
362
429
|
</Dismissible>
|
|
363
430
|
|
|
364
|
-
<Dismissible id="survey-request
|
|
431
|
+
<Dismissible id="survey-request">
|
|
365
432
|
<div className="alert alert-info">
|
|
366
433
|
📝 Help us improve! Take our 2-minute survey.
|
|
367
434
|
</div>
|
|
@@ -371,35 +438,6 @@ function Dashboard() {
|
|
|
371
438
|
}
|
|
372
439
|
```
|
|
373
440
|
|
|
374
|
-
### User-Specific vs Anonymous Dismissible Items
|
|
375
|
-
|
|
376
|
-
The behavior changes based on whether JWT authentication is configured:
|
|
377
|
-
|
|
378
|
-
> **Enterprise vs Standard:** JWT authentication for user-specific dismissals is an Enterprise feature. Standard accounts use anonymous (account-level) dismissals. [Compare plans →](https://dismissible.io/pricing)
|
|
379
|
-
|
|
380
|
-
```tsx
|
|
381
|
-
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
382
|
-
|
|
383
|
-
// With JWT - dismissible state is user-specific
|
|
384
|
-
function AuthenticatedApp() {
|
|
385
|
-
return (
|
|
386
|
-
<DismissibleProvider jwt={() => getAccessToken()}>
|
|
387
|
-
<div>
|
|
388
|
-
{/* Each user will see this banner independently */}
|
|
389
|
-
<Dismissible id="feature-announcement">
|
|
390
|
-
<div>New feature available!</div>
|
|
391
|
-
</Dismissible>
|
|
392
|
-
|
|
393
|
-
{/* User A dismissing this won't affect User B */}
|
|
394
|
-
<Dismissible id="survey-request">
|
|
395
|
-
<div>Please take our survey!</div>
|
|
396
|
-
</Dismissible>
|
|
397
|
-
</div>
|
|
398
|
-
</DismissibleProvider>
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
```
|
|
402
|
-
|
|
403
441
|
### Error Handling with ignoreErrors
|
|
404
442
|
|
|
405
443
|
```tsx
|
|
@@ -421,57 +459,68 @@ function RobustBanner() {
|
|
|
421
459
|
}
|
|
422
460
|
```
|
|
423
461
|
|
|
424
|
-
###
|
|
462
|
+
### Integration with Auth Providers
|
|
425
463
|
|
|
426
464
|
```tsx
|
|
427
465
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
428
466
|
|
|
429
|
-
// With
|
|
430
|
-
function
|
|
467
|
+
// With Firebase Auth
|
|
468
|
+
function AppWithFirebase() {
|
|
469
|
+
const user = firebase.auth().currentUser;
|
|
470
|
+
|
|
431
471
|
return (
|
|
432
472
|
<DismissibleProvider
|
|
473
|
+
userId={user.uid}
|
|
433
474
|
jwt={async () => {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
method: 'POST',
|
|
437
|
-
credentials: 'include'
|
|
438
|
-
});
|
|
439
|
-
const { accessToken } = await token.json();
|
|
440
|
-
return accessToken;
|
|
441
|
-
} catch (error) {
|
|
442
|
-
console.error('Failed to refresh token:', error);
|
|
443
|
-
throw error;
|
|
475
|
+
if (user) {
|
|
476
|
+
return await user.getIdToken();
|
|
444
477
|
}
|
|
478
|
+
throw new Error('User not authenticated');
|
|
445
479
|
}}
|
|
480
|
+
baseUrl="https://api.yourapp.com"
|
|
446
481
|
>
|
|
447
482
|
<YourApp />
|
|
448
483
|
</DismissibleProvider>
|
|
449
484
|
);
|
|
450
485
|
}
|
|
451
486
|
|
|
452
|
-
// With
|
|
453
|
-
function
|
|
487
|
+
// With Auth0
|
|
488
|
+
function AppWithAuth0() {
|
|
489
|
+
const { user, getAccessTokenSilently } = useAuth0();
|
|
490
|
+
|
|
454
491
|
return (
|
|
455
492
|
<DismissibleProvider
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return await user.getIdToken();
|
|
460
|
-
}
|
|
461
|
-
throw new Error('User not authenticated');
|
|
462
|
-
}}
|
|
493
|
+
userId={user.sub}
|
|
494
|
+
jwt={async () => await getAccessTokenSilently()}
|
|
495
|
+
baseUrl="https://api.yourapp.com"
|
|
463
496
|
>
|
|
464
497
|
<YourApp />
|
|
465
498
|
</DismissibleProvider>
|
|
466
499
|
);
|
|
467
500
|
}
|
|
468
501
|
|
|
469
|
-
// With
|
|
470
|
-
function
|
|
471
|
-
const {
|
|
502
|
+
// With token refresh logic
|
|
503
|
+
function AppWithTokenRefresh() {
|
|
504
|
+
const { user } = useAuth();
|
|
472
505
|
|
|
473
506
|
return (
|
|
474
|
-
<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
|
+
>
|
|
475
524
|
<YourApp />
|
|
476
525
|
</DismissibleProvider>
|
|
477
526
|
);
|
|
@@ -481,7 +530,7 @@ function AppWithAuth0() {
|
|
|
481
530
|
### Using the Hook for Complex Logic
|
|
482
531
|
|
|
483
532
|
```tsx
|
|
484
|
-
import { useDismissibleItem
|
|
533
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
485
534
|
import { useState, useEffect } from 'react';
|
|
486
535
|
|
|
487
536
|
function SmartNotification({ id, message, type = 'info' }) {
|
|
@@ -517,17 +566,82 @@ function SmartNotification({ id, message, type = 'info' }) {
|
|
|
517
566
|
</div>
|
|
518
567
|
);
|
|
519
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
|
+
}
|
|
520
591
|
|
|
521
|
-
// Usage with async authentication
|
|
522
|
-
function App() {
|
|
523
592
|
return (
|
|
524
|
-
<
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
</
|
|
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>
|
|
531
645
|
);
|
|
532
646
|
}
|
|
533
647
|
```
|
|
@@ -564,28 +678,60 @@ import type {
|
|
|
564
678
|
DismissibleProps,
|
|
565
679
|
DismissibleProviderProps,
|
|
566
680
|
JwtToken,
|
|
567
|
-
IDismissibleItem
|
|
568
681
|
} from '@dismissible/react-client';
|
|
569
682
|
|
|
570
|
-
//
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
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
|
+
|
|
577
689
|
return (
|
|
578
|
-
<DismissibleProvider
|
|
690
|
+
<DismissibleProvider
|
|
691
|
+
userId={user.id}
|
|
692
|
+
jwt={getToken}
|
|
693
|
+
baseUrl={process.env.DISMISSIBLE_API_URL}
|
|
694
|
+
>
|
|
579
695
|
{children}
|
|
580
696
|
</DismissibleProvider>
|
|
581
697
|
);
|
|
582
698
|
};
|
|
583
699
|
```
|
|
584
700
|
|
|
701
|
+
## Self-Hosting
|
|
702
|
+
|
|
703
|
+
Dismissible is designed to be self-hosted. You have full control over your data.
|
|
704
|
+
|
|
705
|
+
### Option 1: Docker (Recommended)
|
|
706
|
+
|
|
707
|
+
The fastest way to get started:
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
docker run -p 3001:3001 dismissibleio/dismissible-api:latest
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
|
|
714
|
+
|
|
715
|
+
### Option 2: NestJS Module
|
|
716
|
+
|
|
717
|
+
Integrate directly into your existing NestJS application:
|
|
718
|
+
|
|
719
|
+
```bash
|
|
720
|
+
npm install @dismissible/nestjs-api
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
|
|
724
|
+
|
|
585
725
|
## Support
|
|
586
726
|
|
|
587
|
-
- 📖 [Documentation](https://
|
|
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)
|
|
730
|
+
|
|
731
|
+
## License
|
|
732
|
+
|
|
733
|
+
MIT © [Dismissible](https://dismissible.io)
|
|
588
734
|
|
|
589
735
|
## Changelog
|
|
590
736
|
|
|
591
|
-
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|
|
737
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|