@dismissible/react-client 0.3.2-canary.2.38782c4 → 0.3.2-canary.4.578bcba
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 +397 -170
- package/dist/components/Dismissible.d.ts +7 -4
- package/dist/dismissible-client.es.js +376 -342
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/hooks/useDismissibleItem.d.ts +8 -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
|
+
|
|
88
|
+
return (
|
|
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() {
|
|
44
102
|
return (
|
|
45
|
-
<Dismissible
|
|
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,49 @@ 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 |
|
|
206
|
+
| `metadata` | `string[]` | ❌ | Optional metadata as key:value pairs (can be repeated) |
|
|
128
207
|
|
|
129
208
|
#### Example
|
|
130
209
|
|
|
131
210
|
```tsx
|
|
132
211
|
<Dismissible
|
|
133
|
-
|
|
212
|
+
itemId="promo-banner"
|
|
213
|
+
onDismiss={() => console.log('Banner dismissed')}
|
|
214
|
+
>
|
|
215
|
+
<div className="promo">
|
|
216
|
+
<h3>Special Offer!</h3>
|
|
217
|
+
<p>Get 50% off your first order</p>
|
|
218
|
+
</div>
|
|
219
|
+
</Dismissible>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### Example with Metadata
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
<Dismissible
|
|
226
|
+
itemId="promo-banner"
|
|
227
|
+
metadata={{
|
|
228
|
+
version: 2,
|
|
229
|
+
category: "promotional",
|
|
230
|
+
campaign: "summer-sale"
|
|
231
|
+
}}
|
|
134
232
|
onDismiss={() => console.log('Banner dismissed')}
|
|
135
|
-
onRestore={() => console.log('Banner restored')}
|
|
136
233
|
>
|
|
137
234
|
<div className="promo">
|
|
138
235
|
<h3>Special Offer!</h3>
|
|
@@ -149,7 +246,18 @@ For custom implementations and advanced use cases.
|
|
|
149
246
|
|
|
150
247
|
| Parameter | Type | Required | Description |
|
|
151
248
|
|-----------|------|----------|-------------|
|
|
152
|
-
| `
|
|
249
|
+
| `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
|
|
250
|
+
| `options` | `object` | ❌ | Configuration options |
|
|
251
|
+
|
|
252
|
+
#### Options
|
|
253
|
+
|
|
254
|
+
| Option | Type | Required | Description |
|
|
255
|
+
|--------|------|----------|-------------|
|
|
256
|
+
| `enableCache` | `boolean` | ❌ | Enable localStorage caching (default: true) |
|
|
257
|
+
| `cachePrefix` | `string` | ❌ | Cache key prefix (default: 'dismissible') |
|
|
258
|
+
| `cacheExpiration` | `number` | ❌ | Cache expiration time in milliseconds |
|
|
259
|
+
| `metadata` | `IMetadata` | ❌ | Optional metadata object (`{ [key: string]: string \| number }`) |
|
|
260
|
+
| `initialData` | `IDismissibleItem` | ❌ | Initial data for the dismissible item |
|
|
153
261
|
|
|
154
262
|
#### Returns
|
|
155
263
|
|
|
@@ -157,20 +265,18 @@ For custom implementations and advanced use cases.
|
|
|
157
265
|
|----------|------|-------------|
|
|
158
266
|
| `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
|
|
159
267
|
| `dismiss` | `() => Promise<void>` | Function to dismiss the item |
|
|
268
|
+
| `restore` | `() => Promise<void>` | Function to restore a dismissed item |
|
|
160
269
|
| `isLoading` | `boolean` | Loading state indicator |
|
|
161
270
|
| `error` | `Error \| null` | Error state, if any |
|
|
271
|
+
| `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
|
|
162
272
|
|
|
163
273
|
#### Example
|
|
164
274
|
|
|
165
275
|
```tsx
|
|
166
276
|
import { useDismissibleItem } from '@dismissible/react-client';
|
|
167
277
|
|
|
168
|
-
function CustomDismissible({
|
|
169
|
-
const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(
|
|
170
|
-
|
|
171
|
-
if (dismissedOn) {
|
|
172
|
-
return null; // Item is dismissed
|
|
173
|
-
}
|
|
278
|
+
function CustomDismissible({ itemId, children }) {
|
|
279
|
+
const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
|
|
174
280
|
|
|
175
281
|
if (isLoading) {
|
|
176
282
|
return <div>Loading...</div>;
|
|
@@ -180,6 +286,15 @@ function CustomDismissible({ id, children }) {
|
|
|
180
286
|
return <div>Error: {error.message}</div>;
|
|
181
287
|
}
|
|
182
288
|
|
|
289
|
+
if (dismissedOn) {
|
|
290
|
+
return (
|
|
291
|
+
<div>
|
|
292
|
+
<p>This item was dismissed.</p>
|
|
293
|
+
<button onClick={restore}>Restore</button>
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
183
298
|
return (
|
|
184
299
|
<div>
|
|
185
300
|
{children}
|
|
@@ -193,21 +308,16 @@ function CustomDismissible({ id, children }) {
|
|
|
193
308
|
|
|
194
309
|
## Usage Examples
|
|
195
310
|
|
|
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`:
|
|
311
|
+
### Basic Dismissible Banner
|
|
201
312
|
|
|
202
313
|
```tsx
|
|
203
314
|
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
204
315
|
|
|
205
316
|
function App() {
|
|
206
|
-
|
|
207
|
-
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
|
|
317
|
+
const userId = getCurrentUserId();
|
|
208
318
|
|
|
209
319
|
return (
|
|
210
|
-
<DismissibleProvider
|
|
320
|
+
<DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
|
|
211
321
|
<Dashboard />
|
|
212
322
|
</DismissibleProvider>
|
|
213
323
|
);
|
|
@@ -215,61 +325,48 @@ function App() {
|
|
|
215
325
|
|
|
216
326
|
function Dashboard() {
|
|
217
327
|
return (
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</div>
|
|
225
|
-
</Dismissible>
|
|
226
|
-
</div>
|
|
328
|
+
<Dismissible itemId="welcome-banner">
|
|
329
|
+
<div className="alert alert-info">
|
|
330
|
+
<h4>Welcome!</h4>
|
|
331
|
+
<p>Thanks for joining our platform. Here are some quick tips to get started.</p>
|
|
332
|
+
</div>
|
|
333
|
+
</Dismissible>
|
|
227
334
|
);
|
|
228
335
|
}
|
|
229
336
|
```
|
|
230
337
|
|
|
231
|
-
###
|
|
338
|
+
### JWT Authentication Setup
|
|
339
|
+
|
|
340
|
+
For secure environments, configure JWT authentication:
|
|
232
341
|
|
|
233
342
|
```tsx
|
|
234
|
-
import { DismissibleProvider } from '@dismissible/react-client';
|
|
235
|
-
import { useAuth } from './auth'; // Your auth context
|
|
343
|
+
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
236
344
|
|
|
237
|
-
// Synchronous JWT function
|
|
238
345
|
function App() {
|
|
239
|
-
const { getAccessToken } = useAuth();
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<DismissibleProvider jwt={() => getAccessToken()}>
|
|
243
|
-
<YourApp />
|
|
244
|
-
</DismissibleProvider>
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Asynchronous JWT function
|
|
249
|
-
function AppWithAsyncAuth() {
|
|
250
|
-
const { refreshAndGetToken } = useAuth();
|
|
346
|
+
const { user, getAccessToken } = useAuth();
|
|
251
347
|
|
|
252
348
|
return (
|
|
253
|
-
<DismissibleProvider
|
|
254
|
-
|
|
349
|
+
<DismissibleProvider
|
|
350
|
+
userId={user.id}
|
|
351
|
+
jwt={() => getAccessToken()}
|
|
352
|
+
baseUrl="https://api.yourapp.com"
|
|
353
|
+
>
|
|
354
|
+
<Dashboard />
|
|
255
355
|
</DismissibleProvider>
|
|
256
356
|
);
|
|
257
357
|
}
|
|
258
|
-
```
|
|
259
358
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
264
|
-
|
|
265
|
-
function WelcomeBanner() {
|
|
359
|
+
function Dashboard() {
|
|
266
360
|
return (
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
361
|
+
<div>
|
|
362
|
+
{/* Dismissible state is tracked per user */}
|
|
363
|
+
<Dismissible itemId="user-welcome-banner">
|
|
364
|
+
<div className="alert alert-info">
|
|
365
|
+
<h4>Welcome back!</h4>
|
|
366
|
+
<p>You have 3 new notifications.</p>
|
|
367
|
+
</div>
|
|
368
|
+
</Dismissible>
|
|
369
|
+
</div>
|
|
273
370
|
);
|
|
274
371
|
}
|
|
275
372
|
```
|
|
@@ -292,7 +389,7 @@ const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
|
|
|
292
389
|
function CustomBanner() {
|
|
293
390
|
return (
|
|
294
391
|
<Dismissible
|
|
295
|
-
|
|
392
|
+
itemId="custom-banner"
|
|
296
393
|
DismissButtonComponent={CustomDismissButton}
|
|
297
394
|
>
|
|
298
395
|
<div className="banner">
|
|
@@ -308,7 +405,7 @@ function CustomBanner() {
|
|
|
308
405
|
```tsx
|
|
309
406
|
import { Dismissible } from '@dismissible/react-client';
|
|
310
407
|
|
|
311
|
-
const CustomLoader = ({
|
|
408
|
+
const CustomLoader = ({ itemId }) => (
|
|
312
409
|
<div className="spinner">
|
|
313
410
|
<div className="bounce1"></div>
|
|
314
411
|
<div className="bounce2"></div>
|
|
@@ -329,7 +426,7 @@ const CustomError = ({ error }) => (
|
|
|
329
426
|
function AdvancedBanner() {
|
|
330
427
|
return (
|
|
331
428
|
<Dismissible
|
|
332
|
-
|
|
429
|
+
itemId="advanced-banner"
|
|
333
430
|
LoadingComponent={CustomLoader}
|
|
334
431
|
ErrorComponent={CustomError}
|
|
335
432
|
>
|
|
@@ -349,19 +446,19 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
349
446
|
function Dashboard() {
|
|
350
447
|
return (
|
|
351
448
|
<div>
|
|
352
|
-
<Dismissible
|
|
449
|
+
<Dismissible itemId="feature-announcement">
|
|
353
450
|
<div className="alert alert-success">
|
|
354
451
|
🎉 New feature: Dark mode is now available!
|
|
355
452
|
</div>
|
|
356
453
|
</Dismissible>
|
|
357
454
|
|
|
358
|
-
<Dismissible
|
|
455
|
+
<Dismissible itemId="maintenance-notice">
|
|
359
456
|
<div className="alert alert-warning">
|
|
360
457
|
⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
|
|
361
458
|
</div>
|
|
362
459
|
</Dismissible>
|
|
363
460
|
|
|
364
|
-
<Dismissible
|
|
461
|
+
<Dismissible itemId="survey-request">
|
|
365
462
|
<div className="alert alert-info">
|
|
366
463
|
📝 Help us improve! Take our 2-minute survey.
|
|
367
464
|
</div>
|
|
@@ -371,35 +468,6 @@ function Dashboard() {
|
|
|
371
468
|
}
|
|
372
469
|
```
|
|
373
470
|
|
|
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
471
|
### Error Handling with ignoreErrors
|
|
404
472
|
|
|
405
473
|
```tsx
|
|
@@ -409,7 +477,7 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
409
477
|
function RobustBanner() {
|
|
410
478
|
return (
|
|
411
479
|
<Dismissible
|
|
412
|
-
|
|
480
|
+
itemId="important-announcement"
|
|
413
481
|
ignoreErrors={true}
|
|
414
482
|
>
|
|
415
483
|
<div className="important-banner">
|
|
@@ -421,57 +489,68 @@ function RobustBanner() {
|
|
|
421
489
|
}
|
|
422
490
|
```
|
|
423
491
|
|
|
424
|
-
###
|
|
492
|
+
### Integration with Auth Providers
|
|
425
493
|
|
|
426
494
|
```tsx
|
|
427
495
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
428
496
|
|
|
429
|
-
// With
|
|
430
|
-
function
|
|
497
|
+
// With Firebase Auth
|
|
498
|
+
function AppWithFirebase() {
|
|
499
|
+
const user = firebase.auth().currentUser;
|
|
500
|
+
|
|
431
501
|
return (
|
|
432
502
|
<DismissibleProvider
|
|
503
|
+
userId={user.uid}
|
|
433
504
|
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;
|
|
505
|
+
if (user) {
|
|
506
|
+
return await user.getIdToken();
|
|
444
507
|
}
|
|
508
|
+
throw new Error('User not authenticated');
|
|
445
509
|
}}
|
|
510
|
+
baseUrl="https://api.yourapp.com"
|
|
446
511
|
>
|
|
447
512
|
<YourApp />
|
|
448
513
|
</DismissibleProvider>
|
|
449
514
|
);
|
|
450
515
|
}
|
|
451
516
|
|
|
452
|
-
// With
|
|
453
|
-
function
|
|
517
|
+
// With Auth0
|
|
518
|
+
function AppWithAuth0() {
|
|
519
|
+
const { user, getAccessTokenSilently } = useAuth0();
|
|
520
|
+
|
|
454
521
|
return (
|
|
455
522
|
<DismissibleProvider
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return await user.getIdToken();
|
|
460
|
-
}
|
|
461
|
-
throw new Error('User not authenticated');
|
|
462
|
-
}}
|
|
523
|
+
userId={user.sub}
|
|
524
|
+
jwt={async () => await getAccessTokenSilently()}
|
|
525
|
+
baseUrl="https://api.yourapp.com"
|
|
463
526
|
>
|
|
464
527
|
<YourApp />
|
|
465
528
|
</DismissibleProvider>
|
|
466
529
|
);
|
|
467
530
|
}
|
|
468
531
|
|
|
469
|
-
// With
|
|
470
|
-
function
|
|
471
|
-
const {
|
|
532
|
+
// With token refresh logic
|
|
533
|
+
function AppWithTokenRefresh() {
|
|
534
|
+
const { user } = useAuth();
|
|
472
535
|
|
|
473
536
|
return (
|
|
474
|
-
<DismissibleProvider
|
|
537
|
+
<DismissibleProvider
|
|
538
|
+
userId={user.id}
|
|
539
|
+
jwt={async () => {
|
|
540
|
+
try {
|
|
541
|
+
const response = await fetch('/api/auth/refresh', {
|
|
542
|
+
method: 'POST',
|
|
543
|
+
credentials: 'include'
|
|
544
|
+
});
|
|
545
|
+
const { accessToken } = await response.json();
|
|
546
|
+
return accessToken;
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error('Failed to refresh token:', error);
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
}}
|
|
552
|
+
baseUrl="https://api.yourapp.com"
|
|
553
|
+
>
|
|
475
554
|
<YourApp />
|
|
476
555
|
</DismissibleProvider>
|
|
477
556
|
);
|
|
@@ -481,11 +560,11 @@ function AppWithAuth0() {
|
|
|
481
560
|
### Using the Hook for Complex Logic
|
|
482
561
|
|
|
483
562
|
```tsx
|
|
484
|
-
import { useDismissibleItem
|
|
563
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
485
564
|
import { useState, useEffect } from 'react';
|
|
486
565
|
|
|
487
|
-
function SmartNotification({
|
|
488
|
-
const { dismissedOn, dismiss, isLoading } = useDismissibleItem(
|
|
566
|
+
function SmartNotification({ itemId, message, type = 'info' }) {
|
|
567
|
+
const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId);
|
|
489
568
|
const [autoHide, setAutoHide] = useState(false);
|
|
490
569
|
|
|
491
570
|
// Auto-hide after 10 seconds for info messages
|
|
@@ -517,17 +596,132 @@ function SmartNotification({ id, message, type = 'info' }) {
|
|
|
517
596
|
</div>
|
|
518
597
|
);
|
|
519
598
|
}
|
|
599
|
+
```
|
|
520
600
|
|
|
521
|
-
|
|
522
|
-
|
|
601
|
+
### Using Metadata with Dismissible Items
|
|
602
|
+
|
|
603
|
+
Metadata allows you to attach additional information to dismissible items, which can be useful for analytics, filtering, or conditional logic:
|
|
604
|
+
|
|
605
|
+
```tsx
|
|
606
|
+
import { Dismissible, useDismissibleItem } from '@dismissible/react-client';
|
|
607
|
+
|
|
608
|
+
// Using metadata with the Dismissible component (object format)
|
|
609
|
+
function PromotionalBanner({ campaignId, version }) {
|
|
523
610
|
return (
|
|
524
|
-
<
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
611
|
+
<Dismissible
|
|
612
|
+
itemId={`promo-${campaignId}`}
|
|
613
|
+
metadata={{
|
|
614
|
+
version: version,
|
|
615
|
+
category: "promotional",
|
|
616
|
+
campaign: campaignId,
|
|
617
|
+
}}
|
|
618
|
+
>
|
|
619
|
+
<div className="promo-banner">
|
|
620
|
+
<h3>Special Offer!</h3>
|
|
621
|
+
<p>Limited time promotion</p>
|
|
622
|
+
</div>
|
|
623
|
+
</Dismissible>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Using metadata with the hook (same object format)
|
|
628
|
+
function CustomNotification({ itemId, category, priority }) {
|
|
629
|
+
const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId, {
|
|
630
|
+
metadata: {
|
|
631
|
+
category,
|
|
632
|
+
priority,
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
if (dismissedOn) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return (
|
|
641
|
+
<div className={`notification notification-${category}`}>
|
|
642
|
+
<span>Notification content</span>
|
|
643
|
+
<button onClick={dismiss} disabled={isLoading}>
|
|
644
|
+
Dismiss
|
|
645
|
+
</button>
|
|
646
|
+
</div>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Restoring Dismissed Items
|
|
652
|
+
|
|
653
|
+
Use the `restore` function to bring back previously dismissed content:
|
|
654
|
+
|
|
655
|
+
```tsx
|
|
656
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
657
|
+
|
|
658
|
+
function RestorableBanner({ itemId }) {
|
|
659
|
+
const { dismissedOn, dismiss, restore, isLoading } = useDismissibleItem(itemId);
|
|
660
|
+
|
|
661
|
+
if (dismissedOn) {
|
|
662
|
+
return (
|
|
663
|
+
<div className="dismissed-placeholder">
|
|
664
|
+
<p>Banner was dismissed on {new Date(dismissedOn).toLocaleDateString()}</p>
|
|
665
|
+
<button onClick={restore} disabled={isLoading}>
|
|
666
|
+
{isLoading ? 'Restoring...' : 'Show Banner Again'}
|
|
667
|
+
</button>
|
|
668
|
+
</div>
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return (
|
|
673
|
+
<div className="banner">
|
|
674
|
+
<h3>Welcome!</h3>
|
|
675
|
+
<p>This is a restorable banner.</p>
|
|
676
|
+
<button onClick={dismiss} disabled={isLoading}>
|
|
677
|
+
Dismiss
|
|
678
|
+
</button>
|
|
679
|
+
</div>
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Admin Panel with Restore Capability
|
|
685
|
+
|
|
686
|
+
```tsx
|
|
687
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
688
|
+
|
|
689
|
+
function AdminNotificationManager({ notificationId }) {
|
|
690
|
+
const { dismissedOn, dismiss, restore, item, isLoading, error } =
|
|
691
|
+
useDismissibleItem(notificationId);
|
|
692
|
+
|
|
693
|
+
if (error) {
|
|
694
|
+
return <div className="error">Error: {error.message}</div>;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return (
|
|
698
|
+
<div className="admin-panel">
|
|
699
|
+
<h4>Notification: {notificationId}</h4>
|
|
700
|
+
<p>Status: {dismissedOn ? 'Dismissed' : 'Active'}</p>
|
|
701
|
+
{dismissedOn && (
|
|
702
|
+
<p>Dismissed at: {new Date(dismissedOn).toLocaleString()}</p>
|
|
703
|
+
)}
|
|
704
|
+
|
|
705
|
+
<div className="actions">
|
|
706
|
+
{dismissedOn ? (
|
|
707
|
+
<button
|
|
708
|
+
onClick={restore}
|
|
709
|
+
disabled={isLoading}
|
|
710
|
+
className="btn-restore"
|
|
711
|
+
>
|
|
712
|
+
Restore
|
|
713
|
+
</button>
|
|
714
|
+
) : (
|
|
715
|
+
<button
|
|
716
|
+
onClick={dismiss}
|
|
717
|
+
disabled={isLoading}
|
|
718
|
+
className="btn-dismiss"
|
|
719
|
+
>
|
|
720
|
+
Dismiss
|
|
721
|
+
</button>
|
|
722
|
+
)}
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
531
725
|
);
|
|
532
726
|
}
|
|
533
727
|
```
|
|
@@ -560,32 +754,65 @@ The library includes minimal default styles. You can override them or provide yo
|
|
|
560
754
|
The library is written in TypeScript and exports all type definitions:
|
|
561
755
|
|
|
562
756
|
```tsx
|
|
563
|
-
import type {
|
|
757
|
+
import type {
|
|
564
758
|
DismissibleProps,
|
|
565
759
|
DismissibleProviderProps,
|
|
566
760
|
JwtToken,
|
|
567
|
-
|
|
761
|
+
IMetadata,
|
|
568
762
|
} from '@dismissible/react-client';
|
|
569
763
|
|
|
570
|
-
//
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const AuthProvider: React.FC<DismissibleProviderProps> = ({ children, jwt }) => {
|
|
764
|
+
// Custom provider wrapper
|
|
765
|
+
const AuthenticatedDismissibleProvider: React.FC<{
|
|
766
|
+
children: React.ReactNode;
|
|
767
|
+
}> = ({ children }) => {
|
|
768
|
+
const { user, getToken } = useAuth();
|
|
769
|
+
|
|
577
770
|
return (
|
|
578
|
-
<DismissibleProvider
|
|
771
|
+
<DismissibleProvider
|
|
772
|
+
userId={user.id}
|
|
773
|
+
jwt={getToken}
|
|
774
|
+
baseUrl={process.env.DISMISSIBLE_API_URL}
|
|
775
|
+
>
|
|
579
776
|
{children}
|
|
580
777
|
</DismissibleProvider>
|
|
581
778
|
);
|
|
582
779
|
};
|
|
583
780
|
```
|
|
584
781
|
|
|
782
|
+
## Self-Hosting
|
|
783
|
+
|
|
784
|
+
Dismissible is designed to be self-hosted. You have full control over your data.
|
|
785
|
+
|
|
786
|
+
### Option 1: Docker (Recommended)
|
|
787
|
+
|
|
788
|
+
The fastest way to get started:
|
|
789
|
+
|
|
790
|
+
```bash
|
|
791
|
+
docker run -p 3001:3001 dismissibleio/dismissible-api:latest
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
|
|
795
|
+
|
|
796
|
+
### Option 2: NestJS Module
|
|
797
|
+
|
|
798
|
+
Integrate directly into your existing NestJS application:
|
|
799
|
+
|
|
800
|
+
```bash
|
|
801
|
+
npm install @dismissible/nestjs-api
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
|
|
805
|
+
|
|
585
806
|
## Support
|
|
586
807
|
|
|
587
|
-
- 📖 [Documentation](https://
|
|
808
|
+
- 📖 [Documentation](https://dismissible.io/docs)
|
|
809
|
+
- 🐙 [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
|
|
810
|
+
- 🐙 [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
|
|
811
|
+
|
|
812
|
+
## License
|
|
813
|
+
|
|
814
|
+
MIT © [Dismissible](https://dismissible.io)
|
|
588
815
|
|
|
589
816
|
## Changelog
|
|
590
817
|
|
|
591
|
-
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|
|
818
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|