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