@dismissible/react-client 0.3.2 → 1.0.0
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/LICENSE +21 -0
- package/README.md +325 -170
- 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 -359
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/hooks/useDismissibleItem.d.ts +6 -7
- package/dist/mockServiceWorker.js +17 -12
- package/dist/{style.css → react-client.css} +1 -1
- 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 +55 -36
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 itemId="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,25 +187,29 @@ 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 |
|
|
120
195
|
|------|------|----------|-------------|
|
|
121
|
-
| `
|
|
196
|
+
| `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
|
|
122
197
|
| `children` | `ReactNode` | ✅ | Content to render when not dismissed |
|
|
123
198
|
| `onDismiss` | `() => void` | ❌ | Callback fired when item is dismissed |
|
|
124
|
-
| `LoadingComponent` | `ComponentType<{
|
|
125
|
-
| `ErrorComponent` | `ComponentType<{
|
|
126
|
-
| `DismissButtonComponent` | `ComponentType<{
|
|
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 |
|
|
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
|
|
|
131
209
|
```tsx
|
|
132
210
|
<Dismissible
|
|
133
|
-
|
|
211
|
+
itemId="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>
|
|
@@ -149,7 +226,17 @@ For custom implementations and advanced use cases.
|
|
|
149
226
|
|
|
150
227
|
| Parameter | Type | Required | Description |
|
|
151
228
|
|-----------|------|----------|-------------|
|
|
152
|
-
| `
|
|
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 |
|
|
153
240
|
|
|
154
241
|
#### Returns
|
|
155
242
|
|
|
@@ -157,20 +244,18 @@ For custom implementations and advanced use cases.
|
|
|
157
244
|
|----------|------|-------------|
|
|
158
245
|
| `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
|
|
159
246
|
| `dismiss` | `() => Promise<void>` | Function to dismiss the item |
|
|
247
|
+
| `restore` | `() => Promise<void>` | Function to restore a dismissed item |
|
|
160
248
|
| `isLoading` | `boolean` | Loading state indicator |
|
|
161
249
|
| `error` | `Error \| null` | Error state, if any |
|
|
250
|
+
| `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
|
|
162
251
|
|
|
163
252
|
#### Example
|
|
164
253
|
|
|
165
254
|
```tsx
|
|
166
255
|
import { useDismissibleItem } from '@dismissible/react-client';
|
|
167
256
|
|
|
168
|
-
function CustomDismissible({
|
|
169
|
-
const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(
|
|
170
|
-
|
|
171
|
-
if (dismissedOn) {
|
|
172
|
-
return null; // Item is dismissed
|
|
173
|
-
}
|
|
257
|
+
function CustomDismissible({ itemId, children }) {
|
|
258
|
+
const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
|
|
174
259
|
|
|
175
260
|
if (isLoading) {
|
|
176
261
|
return <div>Loading...</div>;
|
|
@@ -180,6 +265,15 @@ function CustomDismissible({ id, children }) {
|
|
|
180
265
|
return <div>Error: {error.message}</div>;
|
|
181
266
|
}
|
|
182
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
|
+
|
|
183
277
|
return (
|
|
184
278
|
<div>
|
|
185
279
|
{children}
|
|
@@ -193,21 +287,16 @@ function CustomDismissible({ id, children }) {
|
|
|
193
287
|
|
|
194
288
|
## Usage Examples
|
|
195
289
|
|
|
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`:
|
|
290
|
+
### Basic Dismissible Banner
|
|
201
291
|
|
|
202
292
|
```tsx
|
|
203
293
|
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
204
294
|
|
|
205
295
|
function App() {
|
|
206
|
-
|
|
207
|
-
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
|
|
296
|
+
const userId = getCurrentUserId();
|
|
208
297
|
|
|
209
298
|
return (
|
|
210
|
-
<DismissibleProvider
|
|
299
|
+
<DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
|
|
211
300
|
<Dashboard />
|
|
212
301
|
</DismissibleProvider>
|
|
213
302
|
);
|
|
@@ -215,61 +304,48 @@ function App() {
|
|
|
215
304
|
|
|
216
305
|
function Dashboard() {
|
|
217
306
|
return (
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</div>
|
|
225
|
-
</Dismissible>
|
|
226
|
-
</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>
|
|
227
313
|
);
|
|
228
314
|
}
|
|
229
315
|
```
|
|
230
316
|
|
|
231
|
-
###
|
|
317
|
+
### JWT Authentication Setup
|
|
318
|
+
|
|
319
|
+
For secure environments, configure JWT authentication:
|
|
232
320
|
|
|
233
321
|
```tsx
|
|
234
|
-
import { DismissibleProvider } from '@dismissible/react-client';
|
|
235
|
-
import { useAuth } from './auth'; // Your auth context
|
|
322
|
+
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
236
323
|
|
|
237
|
-
// Synchronous JWT function
|
|
238
324
|
function App() {
|
|
239
|
-
const { getAccessToken } = useAuth();
|
|
325
|
+
const { user, getAccessToken } = useAuth();
|
|
240
326
|
|
|
241
327
|
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 />
|
|
328
|
+
<DismissibleProvider
|
|
329
|
+
userId={user.id}
|
|
330
|
+
jwt={() => getAccessToken()}
|
|
331
|
+
baseUrl="https://api.yourapp.com"
|
|
332
|
+
>
|
|
333
|
+
<Dashboard />
|
|
255
334
|
</DismissibleProvider>
|
|
256
335
|
);
|
|
257
336
|
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Basic Dismissible Banner
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
264
337
|
|
|
265
|
-
function
|
|
338
|
+
function Dashboard() {
|
|
266
339
|
return (
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
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>
|
|
273
349
|
);
|
|
274
350
|
}
|
|
275
351
|
```
|
|
@@ -292,7 +368,7 @@ const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
|
|
|
292
368
|
function CustomBanner() {
|
|
293
369
|
return (
|
|
294
370
|
<Dismissible
|
|
295
|
-
|
|
371
|
+
itemId="custom-banner"
|
|
296
372
|
DismissButtonComponent={CustomDismissButton}
|
|
297
373
|
>
|
|
298
374
|
<div className="banner">
|
|
@@ -308,7 +384,7 @@ function CustomBanner() {
|
|
|
308
384
|
```tsx
|
|
309
385
|
import { Dismissible } from '@dismissible/react-client';
|
|
310
386
|
|
|
311
|
-
const CustomLoader = ({
|
|
387
|
+
const CustomLoader = ({ itemId }) => (
|
|
312
388
|
<div className="spinner">
|
|
313
389
|
<div className="bounce1"></div>
|
|
314
390
|
<div className="bounce2"></div>
|
|
@@ -329,7 +405,7 @@ const CustomError = ({ error }) => (
|
|
|
329
405
|
function AdvancedBanner() {
|
|
330
406
|
return (
|
|
331
407
|
<Dismissible
|
|
332
|
-
|
|
408
|
+
itemId="advanced-banner"
|
|
333
409
|
LoadingComponent={CustomLoader}
|
|
334
410
|
ErrorComponent={CustomError}
|
|
335
411
|
>
|
|
@@ -349,19 +425,19 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
349
425
|
function Dashboard() {
|
|
350
426
|
return (
|
|
351
427
|
<div>
|
|
352
|
-
<Dismissible
|
|
428
|
+
<Dismissible itemId="feature-announcement">
|
|
353
429
|
<div className="alert alert-success">
|
|
354
430
|
🎉 New feature: Dark mode is now available!
|
|
355
431
|
</div>
|
|
356
432
|
</Dismissible>
|
|
357
433
|
|
|
358
|
-
<Dismissible
|
|
434
|
+
<Dismissible itemId="maintenance-notice">
|
|
359
435
|
<div className="alert alert-warning">
|
|
360
436
|
⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
|
|
361
437
|
</div>
|
|
362
438
|
</Dismissible>
|
|
363
439
|
|
|
364
|
-
<Dismissible
|
|
440
|
+
<Dismissible itemId="survey-request">
|
|
365
441
|
<div className="alert alert-info">
|
|
366
442
|
📝 Help us improve! Take our 2-minute survey.
|
|
367
443
|
</div>
|
|
@@ -371,35 +447,6 @@ function Dashboard() {
|
|
|
371
447
|
}
|
|
372
448
|
```
|
|
373
449
|
|
|
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
450
|
### Error Handling with ignoreErrors
|
|
404
451
|
|
|
405
452
|
```tsx
|
|
@@ -409,7 +456,7 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
409
456
|
function RobustBanner() {
|
|
410
457
|
return (
|
|
411
458
|
<Dismissible
|
|
412
|
-
|
|
459
|
+
itemId="important-announcement"
|
|
413
460
|
ignoreErrors={true}
|
|
414
461
|
>
|
|
415
462
|
<div className="important-banner">
|
|
@@ -421,57 +468,68 @@ function RobustBanner() {
|
|
|
421
468
|
}
|
|
422
469
|
```
|
|
423
470
|
|
|
424
|
-
###
|
|
471
|
+
### Integration with Auth Providers
|
|
425
472
|
|
|
426
473
|
```tsx
|
|
427
474
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
428
475
|
|
|
429
|
-
// With
|
|
430
|
-
function
|
|
476
|
+
// With Firebase Auth
|
|
477
|
+
function AppWithFirebase() {
|
|
478
|
+
const user = firebase.auth().currentUser;
|
|
479
|
+
|
|
431
480
|
return (
|
|
432
481
|
<DismissibleProvider
|
|
482
|
+
userId={user.uid}
|
|
433
483
|
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;
|
|
484
|
+
if (user) {
|
|
485
|
+
return await user.getIdToken();
|
|
444
486
|
}
|
|
487
|
+
throw new Error('User not authenticated');
|
|
445
488
|
}}
|
|
489
|
+
baseUrl="https://api.yourapp.com"
|
|
446
490
|
>
|
|
447
491
|
<YourApp />
|
|
448
492
|
</DismissibleProvider>
|
|
449
493
|
);
|
|
450
494
|
}
|
|
451
495
|
|
|
452
|
-
// With
|
|
453
|
-
function
|
|
496
|
+
// With Auth0
|
|
497
|
+
function AppWithAuth0() {
|
|
498
|
+
const { user, getAccessTokenSilently } = useAuth0();
|
|
499
|
+
|
|
454
500
|
return (
|
|
455
501
|
<DismissibleProvider
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return await user.getIdToken();
|
|
460
|
-
}
|
|
461
|
-
throw new Error('User not authenticated');
|
|
462
|
-
}}
|
|
502
|
+
userId={user.sub}
|
|
503
|
+
jwt={async () => await getAccessTokenSilently()}
|
|
504
|
+
baseUrl="https://api.yourapp.com"
|
|
463
505
|
>
|
|
464
506
|
<YourApp />
|
|
465
507
|
</DismissibleProvider>
|
|
466
508
|
);
|
|
467
509
|
}
|
|
468
510
|
|
|
469
|
-
// With
|
|
470
|
-
function
|
|
471
|
-
const {
|
|
511
|
+
// With token refresh logic
|
|
512
|
+
function AppWithTokenRefresh() {
|
|
513
|
+
const { user } = useAuth();
|
|
472
514
|
|
|
473
515
|
return (
|
|
474
|
-
<DismissibleProvider
|
|
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
|
+
>
|
|
475
533
|
<YourApp />
|
|
476
534
|
</DismissibleProvider>
|
|
477
535
|
);
|
|
@@ -481,11 +539,11 @@ function AppWithAuth0() {
|
|
|
481
539
|
### Using the Hook for Complex Logic
|
|
482
540
|
|
|
483
541
|
```tsx
|
|
484
|
-
import { useDismissibleItem
|
|
542
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
485
543
|
import { useState, useEffect } from 'react';
|
|
486
544
|
|
|
487
|
-
function SmartNotification({
|
|
488
|
-
const { dismissedOn, dismiss, isLoading } = useDismissibleItem(
|
|
545
|
+
function SmartNotification({ itemId, message, type = 'info' }) {
|
|
546
|
+
const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId);
|
|
489
547
|
const [autoHide, setAutoHide] = useState(false);
|
|
490
548
|
|
|
491
549
|
// Auto-hide after 10 seconds for info messages
|
|
@@ -517,17 +575,82 @@ function SmartNotification({ id, message, type = 'info' }) {
|
|
|
517
575
|
</div>
|
|
518
576
|
);
|
|
519
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
|
+
}
|
|
520
600
|
|
|
521
|
-
// Usage with async authentication
|
|
522
|
-
function App() {
|
|
523
601
|
return (
|
|
524
|
-
<
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
</
|
|
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>
|
|
531
654
|
);
|
|
532
655
|
}
|
|
533
656
|
```
|
|
@@ -560,32 +683,64 @@ The library includes minimal default styles. You can override them or provide yo
|
|
|
560
683
|
The library is written in TypeScript and exports all type definitions:
|
|
561
684
|
|
|
562
685
|
```tsx
|
|
563
|
-
import type {
|
|
686
|
+
import type {
|
|
564
687
|
DismissibleProps,
|
|
565
688
|
DismissibleProviderProps,
|
|
566
689
|
JwtToken,
|
|
567
|
-
IDismissibleItem
|
|
568
690
|
} from '@dismissible/react-client';
|
|
569
691
|
|
|
570
|
-
//
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
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
|
+
|
|
577
698
|
return (
|
|
578
|
-
<DismissibleProvider
|
|
699
|
+
<DismissibleProvider
|
|
700
|
+
userId={user.id}
|
|
701
|
+
jwt={getToken}
|
|
702
|
+
baseUrl={process.env.DISMISSIBLE_API_URL}
|
|
703
|
+
>
|
|
579
704
|
{children}
|
|
580
705
|
</DismissibleProvider>
|
|
581
706
|
);
|
|
582
707
|
};
|
|
583
708
|
```
|
|
584
709
|
|
|
710
|
+
## Self-Hosting
|
|
711
|
+
|
|
712
|
+
Dismissible is designed to be self-hosted. You have full control over your data.
|
|
713
|
+
|
|
714
|
+
### Option 1: Docker (Recommended)
|
|
715
|
+
|
|
716
|
+
The fastest way to get started:
|
|
717
|
+
|
|
718
|
+
```bash
|
|
719
|
+
docker run -p 3001:3001 dismissibleio/dismissible-api:latest
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
|
|
723
|
+
|
|
724
|
+
### Option 2: NestJS Module
|
|
725
|
+
|
|
726
|
+
Integrate directly into your existing NestJS application:
|
|
727
|
+
|
|
728
|
+
```bash
|
|
729
|
+
npm install @dismissible/nestjs-api
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
|
|
733
|
+
|
|
585
734
|
## Support
|
|
586
735
|
|
|
587
|
-
- 📖 [Documentation](https://
|
|
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)
|
|
739
|
+
|
|
740
|
+
## License
|
|
741
|
+
|
|
742
|
+
MIT © [Dismissible](https://dismissible.io)
|
|
588
743
|
|
|
589
744
|
## Changelog
|
|
590
745
|
|
|
591
|
-
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|
|
746
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|