@dismissible/react-client 0.3.0 → 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 +342 -188
- 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,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A React component library for creating dismissible UI elements with persistent state management.
|
|
4
4
|
|
|
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)**
|
|
8
|
+
|
|
5
9
|
[](https://badge.fury.io/js/@dismissible%2Freact-client)
|
|
6
10
|
[](https://opensource.org/licenses/MIT)
|
|
7
11
|
|
|
@@ -9,12 +13,14 @@ A React component library for creating dismissible UI elements with persistent s
|
|
|
9
13
|
|
|
10
14
|
- 🎯 **Easy to use** - Simple component API for dismissible content
|
|
11
15
|
- 💾 **Persistent state** - Dismissal state is saved and restored across sessions
|
|
12
|
-
-
|
|
16
|
+
- 🔄 **Restore support** - Restore previously dismissed items programmatically
|
|
17
|
+
- 🔐 **JWT Authentication** - Built-in support for secure JWT-based authentication
|
|
13
18
|
- 🎨 **Customizable** - Custom loading, error, and dismiss button components
|
|
14
19
|
- ♿ **Accessible** - Built with accessibility best practices
|
|
15
20
|
- 🪝 **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
|
|
16
21
|
- 📦 **Lightweight** - Minimal bundle size with tree-shaking support
|
|
17
22
|
- 🔧 **TypeScript** - Full TypeScript support with complete type definitions
|
|
23
|
+
- 🐳 **Self-hosted** - Works with your own Dismissible API server
|
|
18
24
|
|
|
19
25
|
## Installation
|
|
20
26
|
|
|
@@ -32,13 +38,69 @@ npm install react react-dom
|
|
|
32
38
|
|
|
33
39
|
## Quick Start
|
|
34
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
|
+
|
|
35
82
|
```tsx
|
|
36
|
-
import
|
|
37
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
83
|
+
import { DismissibleProvider } from '@dismissible/react-client';
|
|
38
84
|
|
|
39
85
|
function App() {
|
|
86
|
+
const userId = getCurrentUserId(); // Get from your auth system
|
|
87
|
+
|
|
40
88
|
return (
|
|
41
|
-
<
|
|
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">
|
|
42
104
|
<div className="banner">
|
|
43
105
|
<h2>Welcome to our app!</h2>
|
|
44
106
|
<p>This banner can be dismissed and won't show again.</p>
|
|
@@ -52,14 +114,15 @@ function App() {
|
|
|
52
114
|
|
|
53
115
|
### `<DismissibleProvider>` Component
|
|
54
116
|
|
|
55
|
-
Context provider
|
|
117
|
+
Context provider that configures authentication and API settings. **Required** - all `<Dismissible>` components must be wrapped in a provider.
|
|
56
118
|
|
|
57
119
|
#### Props
|
|
58
120
|
|
|
59
121
|
| Prop | Type | Required | Description |
|
|
60
122
|
|------|------|----------|-------------|
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
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) |
|
|
63
126
|
| `children` | `ReactNode` | ✅ | Components that will use the dismissible functionality |
|
|
64
127
|
|
|
65
128
|
#### Example
|
|
@@ -67,10 +130,23 @@ Context provider for JWT authentication and configuration. Wrap your app or comp
|
|
|
67
130
|
```tsx
|
|
68
131
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
69
132
|
|
|
70
|
-
//
|
|
133
|
+
// Basic setup with userId
|
|
71
134
|
function App() {
|
|
72
135
|
return (
|
|
73
|
-
<DismissibleProvider
|
|
136
|
+
<DismissibleProvider userId="user-123" baseUrl="http://localhost:3001">
|
|
137
|
+
<YourApp />
|
|
138
|
+
</DismissibleProvider>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// With static JWT
|
|
143
|
+
function AppWithJWT() {
|
|
144
|
+
return (
|
|
145
|
+
<DismissibleProvider
|
|
146
|
+
userId="user-123"
|
|
147
|
+
jwt="eyJhbGciOiJIUzI1NiIs..."
|
|
148
|
+
baseUrl="https://api.yourapp.com"
|
|
149
|
+
>
|
|
74
150
|
<YourApp />
|
|
75
151
|
</DismissibleProvider>
|
|
76
152
|
);
|
|
@@ -78,17 +154,29 @@ function App() {
|
|
|
78
154
|
|
|
79
155
|
// With dynamic JWT function
|
|
80
156
|
function AppWithDynamicAuth() {
|
|
157
|
+
const { user, getAccessToken } = useAuth();
|
|
158
|
+
|
|
81
159
|
return (
|
|
82
|
-
<DismissibleProvider
|
|
160
|
+
<DismissibleProvider
|
|
161
|
+
userId={user.id}
|
|
162
|
+
jwt={() => getAccessToken()}
|
|
163
|
+
baseUrl="https://api.yourapp.com"
|
|
164
|
+
>
|
|
83
165
|
<YourApp />
|
|
84
166
|
</DismissibleProvider>
|
|
85
167
|
);
|
|
86
168
|
}
|
|
87
169
|
|
|
88
|
-
//
|
|
89
|
-
function
|
|
170
|
+
// With async JWT function
|
|
171
|
+
function AppWithAsyncAuth() {
|
|
172
|
+
const { user, refreshAndGetToken } = useAuth();
|
|
173
|
+
|
|
90
174
|
return (
|
|
91
|
-
<DismissibleProvider
|
|
175
|
+
<DismissibleProvider
|
|
176
|
+
userId={user.id}
|
|
177
|
+
jwt={async () => await refreshAndGetToken()}
|
|
178
|
+
baseUrl="https://api.yourapp.com"
|
|
179
|
+
>
|
|
92
180
|
<YourApp />
|
|
93
181
|
</DismissibleProvider>
|
|
94
182
|
);
|
|
@@ -99,24 +187,29 @@ function AppWithoutAuth() {
|
|
|
99
187
|
|
|
100
188
|
The main component for creating dismissible content.
|
|
101
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
|
+
|
|
102
192
|
#### Props
|
|
103
193
|
|
|
104
194
|
| Prop | Type | Required | Description |
|
|
105
195
|
|------|------|----------|-------------|
|
|
106
|
-
| `
|
|
196
|
+
| `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
|
|
107
197
|
| `children` | `ReactNode` | ✅ | Content to render when not dismissed |
|
|
108
198
|
| `onDismiss` | `() => void` | ❌ | Callback fired when item is dismissed |
|
|
109
|
-
| `LoadingComponent` | `ComponentType<{
|
|
110
|
-
| `ErrorComponent` | `ComponentType<{
|
|
111
|
-
| `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 |
|
|
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 |
|
|
112
206
|
|
|
113
207
|
#### Example
|
|
114
208
|
|
|
115
209
|
```tsx
|
|
116
210
|
<Dismissible
|
|
117
|
-
|
|
211
|
+
itemId="promo-banner"
|
|
118
212
|
onDismiss={() => console.log('Banner dismissed')}
|
|
119
|
-
onRestore={() => console.log('Banner restored')}
|
|
120
213
|
>
|
|
121
214
|
<div className="promo">
|
|
122
215
|
<h3>Special Offer!</h3>
|
|
@@ -133,7 +226,17 @@ For custom implementations and advanced use cases.
|
|
|
133
226
|
|
|
134
227
|
| Parameter | Type | Required | Description |
|
|
135
228
|
|-----------|------|----------|-------------|
|
|
136
|
-
| `
|
|
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 |
|
|
137
240
|
|
|
138
241
|
#### Returns
|
|
139
242
|
|
|
@@ -141,20 +244,18 @@ For custom implementations and advanced use cases.
|
|
|
141
244
|
|----------|------|-------------|
|
|
142
245
|
| `dismissedOn` | `string \| null` | ISO date string when item was dismissed, or null |
|
|
143
246
|
| `dismiss` | `() => Promise<void>` | Function to dismiss the item |
|
|
247
|
+
| `restore` | `() => Promise<void>` | Function to restore a dismissed item |
|
|
144
248
|
| `isLoading` | `boolean` | Loading state indicator |
|
|
145
249
|
| `error` | `Error \| null` | Error state, if any |
|
|
250
|
+
| `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
|
|
146
251
|
|
|
147
252
|
#### Example
|
|
148
253
|
|
|
149
254
|
```tsx
|
|
150
255
|
import { useDismissibleItem } from '@dismissible/react-client';
|
|
151
256
|
|
|
152
|
-
function CustomDismissible({
|
|
153
|
-
const { dismissedOn, dismiss, isLoading, error } = useDismissibleItem(
|
|
154
|
-
|
|
155
|
-
if (dismissedOn) {
|
|
156
|
-
return null; // Item is dismissed
|
|
157
|
-
}
|
|
257
|
+
function CustomDismissible({ itemId, children }) {
|
|
258
|
+
const { dismissedOn, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
|
|
158
259
|
|
|
159
260
|
if (isLoading) {
|
|
160
261
|
return <div>Loading...</div>;
|
|
@@ -164,6 +265,15 @@ function CustomDismissible({ id, children }) {
|
|
|
164
265
|
return <div>Error: {error.message}</div>;
|
|
165
266
|
}
|
|
166
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
|
+
|
|
167
277
|
return (
|
|
168
278
|
<div>
|
|
169
279
|
{children}
|
|
@@ -177,19 +287,16 @@ function CustomDismissible({ id, children }) {
|
|
|
177
287
|
|
|
178
288
|
## Usage Examples
|
|
179
289
|
|
|
180
|
-
###
|
|
181
|
-
|
|
182
|
-
For enterprise accounts that require user-specific dismissible state, wrap your app with the `DismissibleProvider`:
|
|
290
|
+
### Basic Dismissible Banner
|
|
183
291
|
|
|
184
292
|
```tsx
|
|
185
293
|
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
186
294
|
|
|
187
295
|
function App() {
|
|
188
|
-
|
|
189
|
-
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
|
|
296
|
+
const userId = getCurrentUserId();
|
|
190
297
|
|
|
191
298
|
return (
|
|
192
|
-
<DismissibleProvider
|
|
299
|
+
<DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
|
|
193
300
|
<Dashboard />
|
|
194
301
|
</DismissibleProvider>
|
|
195
302
|
);
|
|
@@ -197,49 +304,48 @@ function App() {
|
|
|
197
304
|
|
|
198
305
|
function Dashboard() {
|
|
199
306
|
return (
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
</div>
|
|
207
|
-
</Dismissible>
|
|
208
|
-
</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>
|
|
209
313
|
);
|
|
210
314
|
}
|
|
211
315
|
```
|
|
212
316
|
|
|
213
|
-
###
|
|
317
|
+
### JWT Authentication Setup
|
|
318
|
+
|
|
319
|
+
For secure environments, configure JWT authentication:
|
|
214
320
|
|
|
215
321
|
```tsx
|
|
216
|
-
import { DismissibleProvider } from '@dismissible/react-client';
|
|
217
|
-
import { useAuth } from './auth'; // Your auth context
|
|
322
|
+
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
218
323
|
|
|
219
324
|
function App() {
|
|
220
|
-
const { getAccessToken } = useAuth();
|
|
325
|
+
const { user, getAccessToken } = useAuth();
|
|
221
326
|
|
|
222
327
|
return (
|
|
223
|
-
<DismissibleProvider
|
|
224
|
-
|
|
328
|
+
<DismissibleProvider
|
|
329
|
+
userId={user.id}
|
|
330
|
+
jwt={() => getAccessToken()}
|
|
331
|
+
baseUrl="https://api.yourapp.com"
|
|
332
|
+
>
|
|
333
|
+
<Dashboard />
|
|
225
334
|
</DismissibleProvider>
|
|
226
335
|
);
|
|
227
336
|
}
|
|
228
|
-
```
|
|
229
337
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
```tsx
|
|
233
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
234
|
-
|
|
235
|
-
function WelcomeBanner() {
|
|
338
|
+
function Dashboard() {
|
|
236
339
|
return (
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
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>
|
|
243
349
|
);
|
|
244
350
|
}
|
|
245
351
|
```
|
|
@@ -262,7 +368,7 @@ const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
|
|
|
262
368
|
function CustomBanner() {
|
|
263
369
|
return (
|
|
264
370
|
<Dismissible
|
|
265
|
-
|
|
371
|
+
itemId="custom-banner"
|
|
266
372
|
DismissButtonComponent={CustomDismissButton}
|
|
267
373
|
>
|
|
268
374
|
<div className="banner">
|
|
@@ -278,7 +384,7 @@ function CustomBanner() {
|
|
|
278
384
|
```tsx
|
|
279
385
|
import { Dismissible } from '@dismissible/react-client';
|
|
280
386
|
|
|
281
|
-
const CustomLoader = ({
|
|
387
|
+
const CustomLoader = ({ itemId }) => (
|
|
282
388
|
<div className="spinner">
|
|
283
389
|
<div className="bounce1"></div>
|
|
284
390
|
<div className="bounce2"></div>
|
|
@@ -299,7 +405,7 @@ const CustomError = ({ error }) => (
|
|
|
299
405
|
function AdvancedBanner() {
|
|
300
406
|
return (
|
|
301
407
|
<Dismissible
|
|
302
|
-
|
|
408
|
+
itemId="advanced-banner"
|
|
303
409
|
LoadingComponent={CustomLoader}
|
|
304
410
|
ErrorComponent={CustomError}
|
|
305
411
|
>
|
|
@@ -319,19 +425,19 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
319
425
|
function Dashboard() {
|
|
320
426
|
return (
|
|
321
427
|
<div>
|
|
322
|
-
<Dismissible
|
|
428
|
+
<Dismissible itemId="feature-announcement">
|
|
323
429
|
<div className="alert alert-success">
|
|
324
430
|
🎉 New feature: Dark mode is now available!
|
|
325
431
|
</div>
|
|
326
432
|
</Dismissible>
|
|
327
433
|
|
|
328
|
-
<Dismissible
|
|
434
|
+
<Dismissible itemId="maintenance-notice">
|
|
329
435
|
<div className="alert alert-warning">
|
|
330
436
|
⚠️ Scheduled maintenance: Sunday 2AM-4AM EST
|
|
331
437
|
</div>
|
|
332
438
|
</Dismissible>
|
|
333
439
|
|
|
334
|
-
<Dismissible
|
|
440
|
+
<Dismissible itemId="survey-request">
|
|
335
441
|
<div className="alert alert-info">
|
|
336
442
|
📝 Help us improve! Take our 2-minute survey.
|
|
337
443
|
</div>
|
|
@@ -341,67 +447,91 @@ function Dashboard() {
|
|
|
341
447
|
}
|
|
342
448
|
```
|
|
343
449
|
|
|
344
|
-
###
|
|
450
|
+
### Error Handling with ignoreErrors
|
|
345
451
|
|
|
346
452
|
```tsx
|
|
347
453
|
import { Dismissible } from '@dismissible/react-client';
|
|
348
454
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (user.isReturning) {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
|
|
455
|
+
// Show content even if API fails
|
|
456
|
+
function RobustBanner() {
|
|
355
457
|
return (
|
|
356
|
-
<Dismissible
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
</ul>
|
|
458
|
+
<Dismissible
|
|
459
|
+
itemId="important-announcement"
|
|
460
|
+
ignoreErrors={true}
|
|
461
|
+
>
|
|
462
|
+
<div className="important-banner">
|
|
463
|
+
<h3>Critical System Update</h3>
|
|
464
|
+
<p>System maintenance scheduled for tonight. Please save your work.</p>
|
|
364
465
|
</div>
|
|
365
466
|
</Dismissible>
|
|
366
467
|
);
|
|
367
468
|
}
|
|
368
469
|
```
|
|
369
470
|
|
|
370
|
-
###
|
|
371
|
-
|
|
372
|
-
The behavior changes based on whether JWT authentication is configured:
|
|
471
|
+
### Integration with Auth Providers
|
|
373
472
|
|
|
374
473
|
```tsx
|
|
375
|
-
import { DismissibleProvider
|
|
474
|
+
import { DismissibleProvider } from '@dismissible/react-client';
|
|
376
475
|
|
|
377
|
-
// With
|
|
378
|
-
function
|
|
476
|
+
// With Firebase Auth
|
|
477
|
+
function AppWithFirebase() {
|
|
478
|
+
const user = firebase.auth().currentUser;
|
|
479
|
+
|
|
379
480
|
return (
|
|
380
|
-
<DismissibleProvider
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
</div>
|
|
481
|
+
<DismissibleProvider
|
|
482
|
+
userId={user.uid}
|
|
483
|
+
jwt={async () => {
|
|
484
|
+
if (user) {
|
|
485
|
+
return await user.getIdToken();
|
|
486
|
+
}
|
|
487
|
+
throw new Error('User not authenticated');
|
|
488
|
+
}}
|
|
489
|
+
baseUrl="https://api.yourapp.com"
|
|
490
|
+
>
|
|
491
|
+
<YourApp />
|
|
392
492
|
</DismissibleProvider>
|
|
393
493
|
);
|
|
394
494
|
}
|
|
395
495
|
|
|
396
|
-
//
|
|
397
|
-
function
|
|
496
|
+
// With Auth0
|
|
497
|
+
function AppWithAuth0() {
|
|
498
|
+
const { user, getAccessTokenSilently } = useAuth0();
|
|
499
|
+
|
|
398
500
|
return (
|
|
399
|
-
<
|
|
400
|
-
{
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
501
|
+
<DismissibleProvider
|
|
502
|
+
userId={user.sub}
|
|
503
|
+
jwt={async () => await getAccessTokenSilently()}
|
|
504
|
+
baseUrl="https://api.yourapp.com"
|
|
505
|
+
>
|
|
506
|
+
<YourApp />
|
|
507
|
+
</DismissibleProvider>
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// With token refresh logic
|
|
512
|
+
function AppWithTokenRefresh() {
|
|
513
|
+
const { user } = useAuth();
|
|
514
|
+
|
|
515
|
+
return (
|
|
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
|
+
>
|
|
533
|
+
<YourApp />
|
|
534
|
+
</DismissibleProvider>
|
|
405
535
|
);
|
|
406
536
|
}
|
|
407
537
|
```
|
|
@@ -409,11 +539,11 @@ function AnonymousApp() {
|
|
|
409
539
|
### Using the Hook for Complex Logic
|
|
410
540
|
|
|
411
541
|
```tsx
|
|
412
|
-
import { useDismissibleItem
|
|
542
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
413
543
|
import { useState, useEffect } from 'react';
|
|
414
544
|
|
|
415
|
-
function SmartNotification({
|
|
416
|
-
const { dismissedOn, dismiss, isLoading } = useDismissibleItem(
|
|
545
|
+
function SmartNotification({ itemId, message, type = 'info' }) {
|
|
546
|
+
const { dismissedOn, dismiss, isLoading } = useDismissibleItem(itemId);
|
|
417
547
|
const [autoHide, setAutoHide] = useState(false);
|
|
418
548
|
|
|
419
549
|
// Auto-hide after 10 seconds for info messages
|
|
@@ -445,17 +575,82 @@ function SmartNotification({ id, message, type = 'info' }) {
|
|
|
445
575
|
</div>
|
|
446
576
|
);
|
|
447
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
|
+
}
|
|
448
600
|
|
|
449
|
-
// Usage with authentication
|
|
450
|
-
function App() {
|
|
451
601
|
return (
|
|
452
|
-
<
|
|
453
|
-
<
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
</
|
|
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>
|
|
459
654
|
);
|
|
460
655
|
}
|
|
461
656
|
```
|
|
@@ -488,105 +683,64 @@ The library includes minimal default styles. You can override them or provide yo
|
|
|
488
683
|
The library is written in TypeScript and exports all type definitions:
|
|
489
684
|
|
|
490
685
|
```tsx
|
|
491
|
-
import type {
|
|
686
|
+
import type {
|
|
492
687
|
DismissibleProps,
|
|
493
688
|
DismissibleProviderProps,
|
|
494
689
|
JwtToken,
|
|
495
|
-
IDismissibleItem
|
|
496
690
|
} from '@dismissible/react-client';
|
|
497
691
|
|
|
498
|
-
//
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
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
|
+
|
|
505
698
|
return (
|
|
506
|
-
<DismissibleProvider
|
|
699
|
+
<DismissibleProvider
|
|
700
|
+
userId={user.id}
|
|
701
|
+
jwt={getToken}
|
|
702
|
+
baseUrl={process.env.DISMISSIBLE_API_URL}
|
|
703
|
+
>
|
|
507
704
|
{children}
|
|
508
705
|
</DismissibleProvider>
|
|
509
706
|
);
|
|
510
707
|
};
|
|
511
708
|
```
|
|
512
709
|
|
|
513
|
-
##
|
|
710
|
+
## Self-Hosting
|
|
514
711
|
|
|
515
|
-
|
|
712
|
+
Dismissible is designed to be self-hosted. You have full control over your data.
|
|
516
713
|
|
|
517
|
-
|
|
518
|
-
- npm or yarn
|
|
714
|
+
### Option 1: Docker (Recommended)
|
|
519
715
|
|
|
520
|
-
|
|
716
|
+
The fastest way to get started:
|
|
521
717
|
|
|
522
718
|
```bash
|
|
523
|
-
|
|
524
|
-
git clone https://github.com/your-org/dismissible.git
|
|
525
|
-
cd dismissible/react-client
|
|
526
|
-
|
|
527
|
-
# Install dependencies
|
|
528
|
-
npm install
|
|
529
|
-
|
|
530
|
-
# Start development server
|
|
531
|
-
npm run dev
|
|
719
|
+
docker run -p 3001:3001 dismissibleio/dismissible-api:latest
|
|
532
720
|
```
|
|
533
721
|
|
|
534
|
-
|
|
722
|
+
See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
|
|
535
723
|
|
|
536
|
-
|
|
537
|
-
- `npm run build` - Build the library for production
|
|
538
|
-
- `npm run test` - Run tests with Vitest
|
|
539
|
-
- `npm run test:watch` - Run tests in watch mode
|
|
540
|
-
- `npm run lint` - Run ESLint
|
|
541
|
-
- `npm run format` - Format code with Prettier
|
|
542
|
-
- `npm run storybook` - Start Storybook development server
|
|
543
|
-
- `npm run build-storybook` - Build Storybook for production
|
|
724
|
+
### Option 2: NestJS Module
|
|
544
725
|
|
|
545
|
-
|
|
726
|
+
Integrate directly into your existing NestJS application:
|
|
546
727
|
|
|
547
728
|
```bash
|
|
548
|
-
|
|
549
|
-
npm run test
|
|
550
|
-
|
|
551
|
-
# Run tests in watch mode
|
|
552
|
-
npm run test:watch
|
|
553
|
-
|
|
554
|
-
# Run tests with coverage
|
|
555
|
-
npm run test -- --coverage
|
|
729
|
+
npm install @dismissible/nestjs-api
|
|
556
730
|
```
|
|
557
731
|
|
|
558
|
-
|
|
732
|
+
See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
|
|
559
733
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
```bash
|
|
563
|
-
npm run storybook
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
## Contributing
|
|
567
|
-
|
|
568
|
-
We welcome contributions! Please see our [Contributing Guide](../CONTRIBUTING.md) for details.
|
|
569
|
-
|
|
570
|
-
### Development Workflow
|
|
734
|
+
## Support
|
|
571
735
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
4. Add tests for new functionality
|
|
576
|
-
5. Run tests: `npm run test`
|
|
577
|
-
6. Run linting: `npm run lint`
|
|
578
|
-
7. Commit your changes: `git commit -am 'Add new feature'`
|
|
579
|
-
8. Push to the branch: `git push origin feature/my-new-feature`
|
|
580
|
-
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)
|
|
581
739
|
|
|
582
740
|
## License
|
|
583
741
|
|
|
584
|
-
MIT © [Dismissible](https://
|
|
585
|
-
|
|
586
|
-
## Support
|
|
587
|
-
|
|
588
|
-
- 📖 [Documentation](https://docs.dismissible.io)
|
|
742
|
+
MIT © [Dismissible](https://dismissible.io)
|
|
589
743
|
|
|
590
744
|
## Changelog
|
|
591
745
|
|
|
592
|
-
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|
|
746
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|