@dismissible/react-client 0.3.2 → 1.0.0-canary.4.a9fb4a5
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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +247 -232
- package/dist/components/Dismissible.d.ts +3 -4
- package/dist/config/api.config.d.ts +0 -14
- package/dist/contexts/DismissibleContext.d.ts +3 -2
- package/dist/contexts/DismissibleProvider.d.ts +3 -1
- package/dist/dismissible-client.es.js +388 -345
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/hooks/useDismissibleItem.d.ts +17 -16
- package/dist/mockServiceWorker.js +17 -12
- package/dist/{style.css → react-client.css} +1 -1
- package/dist/root.d.ts +0 -6
- package/dist/types/dismissible.types.d.ts +7 -3
- package/dist/utils/auth.utils.d.ts +0 -6
- package/package.json +68 -48
package/README.md
CHANGED
|
@@ -1,24 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://dismissible.io" target="_blank"><img src="https://raw.githubusercontent.com/DismissibleIo/dismissible-api/main/docs/images/dismissible_logo.png" width="240" alt="Dismissible" /></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">Never Show The Same Thing Twice!</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/@dismissible/react-client" target="_blank"><img src="https://img.shields.io/npm/v/@dismissible/react-client.svg" alt="NPM Version" /></a>
|
|
8
|
+
<a href="https://github.com/dismissibleio/dismissible-react-client/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/npm/l/@dismissible/react-client.svg" alt="Package License" /></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@dismissible/react-client" target="_blank"><img src="https://img.shields.io/npm/dm/@dismissible/react-client.svg" alt="NPM Downloads" /></a>
|
|
10
|
+
<a href="https://github.com/dismissibleio/dismissible-react-client" target="_blank"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/dismissibleio/dismissible-react-client/publish.yml" /></a>
|
|
11
|
+
<a href="https://paypal.me/joshstuartx" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" /></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
Dismissible manages the state of your UI elements across sessions, so your users see what matters, once! No more onboarding messages reappearing on every tab, no more notifications haunting users across devices. Dismissible syncs dismissal state everywhere, so every message is intentional, never repetitive.
|
|
2
15
|
|
|
3
|
-
|
|
16
|
+
# @dismissible/react-client
|
|
4
17
|
|
|
5
|
-
|
|
18
|
+
This is the React component library for creating dismissible UI elements with persistent state management.
|
|
6
19
|
|
|
7
|
-
|
|
20
|
+
This component is used with the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api), which you can self-host with Docker or integrate into your NestJS application.
|
|
8
21
|
|
|
9
|
-
[
|
|
10
|
-
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
**[dismissible.io](https://dismissible.io)** | **[Documentation](https://dismissible.io/docs)** | **[API Server](https://github.com/DismissibleIo/dismissible-api)**
|
|
11
23
|
|
|
12
24
|
## Features
|
|
13
25
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
26
|
+
- **Easy to use** - Simple component API for dismissible content
|
|
27
|
+
- **Persistent state** - Dismissal state is saved and restored across sessions when using the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api)
|
|
28
|
+
- **Restore support** - Restore previously dismissed items programmatically
|
|
29
|
+
- **JWT Authentication** - Built-in support for secure JWT-based authentication
|
|
30
|
+
- **Customizable** - Custom loading, error, and dismiss button components
|
|
31
|
+
- **Accessible** - Built with accessibility best practices
|
|
32
|
+
- **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
|
|
33
|
+
- **Lightweight** - Minimal bundle size with tree-shaking support
|
|
34
|
+
- **TypeScript** - Full TypeScript support with complete type definitions
|
|
22
35
|
|
|
23
36
|
## Installation
|
|
24
37
|
|
|
@@ -36,13 +49,61 @@ npm install react react-dom
|
|
|
36
49
|
|
|
37
50
|
## Quick Start
|
|
38
51
|
|
|
52
|
+
### 1. Set up the Dismissible API Server
|
|
53
|
+
|
|
54
|
+
First, you need a [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api). The easiest way is with Docker:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
# docker-compose.yml
|
|
58
|
+
version: '3.8'
|
|
59
|
+
services:
|
|
60
|
+
api:
|
|
61
|
+
image: dismissibleio/dismissible-api:latest
|
|
62
|
+
ports:
|
|
63
|
+
- '3001:3001'
|
|
64
|
+
environment:
|
|
65
|
+
DISMISSIBLE_PORT: 3001
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
docker-compose up -d
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
OR
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
docker run -p 3001:3001 -e DISMISSIBLE_PORT=3001 dismissibleio/dismissible-api:latest
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
See the [API Server documentation](https://github.com/DismissibleIo/dismissible-api) for more deployment options including NestJS integration, public Docker image and more.
|
|
79
|
+
|
|
80
|
+
### 2. Configure the Provider
|
|
81
|
+
|
|
82
|
+
Wrap your app with `DismissibleProvider`. The `userId` prop is **required** to track all your dismissals per user:
|
|
83
|
+
|
|
39
84
|
```tsx
|
|
40
|
-
import
|
|
41
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
85
|
+
import { DismissibleProvider } from '@dismissible/react-client';
|
|
42
86
|
|
|
43
87
|
function App() {
|
|
88
|
+
const userId = getCurrentUserId();
|
|
89
|
+
|
|
44
90
|
return (
|
|
45
|
-
<
|
|
91
|
+
<DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
|
|
92
|
+
<YourApp />
|
|
93
|
+
</DismissibleProvider>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Use Dismissible Components
|
|
99
|
+
Now wrap any component you want to be dismissible with the `<Dismissible>` component, and the `itemId`, along with the `userId`, will become the unique key that is tracked across sessions and devices.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { Dismissible } from '@dismissible/react-client';
|
|
103
|
+
|
|
104
|
+
function WelcomeBanner() {
|
|
105
|
+
return (
|
|
106
|
+
<Dismissible itemId="welcome-banner">
|
|
46
107
|
<div className="banner">
|
|
47
108
|
<h2>Welcome to our app!</h2>
|
|
48
109
|
<p>This banner can be dismissed and won't show again.</p>
|
|
@@ -56,16 +117,15 @@ function App() {
|
|
|
56
117
|
|
|
57
118
|
### `<DismissibleProvider>` Component
|
|
58
119
|
|
|
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)
|
|
120
|
+
Context provider that configures authentication and API settings. **Required** - all `<Dismissible>` components must be wrapped in a provider.
|
|
62
121
|
|
|
63
122
|
#### Props
|
|
64
123
|
|
|
65
124
|
| Prop | Type | Required | Description |
|
|
66
125
|
|------|------|----------|-------------|
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
126
|
+
| `userId` | `string` | ✅ | User ID for tracking dismissals per user |
|
|
127
|
+
| `jwt` | `string \| (() => string) \| (() => Promise<string>)` | ❌ | JWT token for secure authentication |
|
|
128
|
+
| `baseUrl` | `string` | ❌ | API base URL (defaults to your self-hosted server) |
|
|
69
129
|
| `children` | `ReactNode` | ✅ | Components that will use the dismissible functionality |
|
|
70
130
|
|
|
71
131
|
#### Example
|
|
@@ -73,37 +133,53 @@ Context provider for JWT authentication and configuration. Wrap your app or comp
|
|
|
73
133
|
```tsx
|
|
74
134
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
75
135
|
|
|
76
|
-
//
|
|
136
|
+
// Basic setup with userId
|
|
77
137
|
function App() {
|
|
78
138
|
return (
|
|
79
|
-
<DismissibleProvider
|
|
139
|
+
<DismissibleProvider userId="user-123" baseUrl="http://localhost:3001">
|
|
80
140
|
<YourApp />
|
|
81
141
|
</DismissibleProvider>
|
|
82
142
|
);
|
|
83
143
|
}
|
|
84
144
|
|
|
85
|
-
// With
|
|
86
|
-
function
|
|
145
|
+
// With static JWT
|
|
146
|
+
function AppWithJWT() {
|
|
87
147
|
return (
|
|
88
|
-
<DismissibleProvider
|
|
148
|
+
<DismissibleProvider
|
|
149
|
+
userId="user-123"
|
|
150
|
+
jwt="eyJhbGciOiJIUzI1NiIs..."
|
|
151
|
+
baseUrl="https://api.yourapp.com"
|
|
152
|
+
>
|
|
89
153
|
<YourApp />
|
|
90
154
|
</DismissibleProvider>
|
|
91
155
|
);
|
|
92
156
|
}
|
|
93
157
|
|
|
94
|
-
// With
|
|
95
|
-
function
|
|
158
|
+
// With dynamic JWT function
|
|
159
|
+
function AppWithDynamicAuth() {
|
|
160
|
+
const { user, getAccessToken } = useAuth();
|
|
161
|
+
|
|
96
162
|
return (
|
|
97
|
-
<DismissibleProvider
|
|
163
|
+
<DismissibleProvider
|
|
164
|
+
userId={user.id}
|
|
165
|
+
jwt={() => getAccessToken()}
|
|
166
|
+
baseUrl="https://api.yourapp.com"
|
|
167
|
+
>
|
|
98
168
|
<YourApp />
|
|
99
169
|
</DismissibleProvider>
|
|
100
170
|
);
|
|
101
171
|
}
|
|
102
172
|
|
|
103
|
-
//
|
|
104
|
-
function
|
|
173
|
+
// With async JWT function
|
|
174
|
+
function AppWithAsyncAuth() {
|
|
175
|
+
const { user, refreshAndGetToken } = useAuth();
|
|
176
|
+
|
|
105
177
|
return (
|
|
106
|
-
<DismissibleProvider
|
|
178
|
+
<DismissibleProvider
|
|
179
|
+
userId={user.id}
|
|
180
|
+
jwt={async () => await refreshAndGetToken()}
|
|
181
|
+
baseUrl="https://api.yourapp.com"
|
|
182
|
+
>
|
|
107
183
|
<YourApp />
|
|
108
184
|
</DismissibleProvider>
|
|
109
185
|
);
|
|
@@ -114,25 +190,29 @@ function AppWithoutAuth() {
|
|
|
114
190
|
|
|
115
191
|
The main component for creating dismissible content.
|
|
116
192
|
|
|
193
|
+
> **Note:** The `<Dismissible>` component renders `null` when an item is dismissed. For restore functionality, use the `useDismissibleItem` hook directly in custom implementations.
|
|
194
|
+
|
|
117
195
|
#### Props
|
|
118
196
|
|
|
119
197
|
| Prop | Type | Required | Description |
|
|
120
198
|
|------|------|----------|-------------|
|
|
121
|
-
| `
|
|
199
|
+
| `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
|
|
122
200
|
| `children` | `ReactNode` | ✅ | Content to render when not dismissed |
|
|
123
201
|
| `onDismiss` | `() => void` | ❌ | Callback fired when item is dismissed |
|
|
124
|
-
| `LoadingComponent` | `ComponentType<{
|
|
125
|
-
| `ErrorComponent` | `ComponentType<{
|
|
126
|
-
| `DismissButtonComponent` | `ComponentType<{
|
|
202
|
+
| `LoadingComponent` | `ComponentType<{itemId: string}>` | ❌ | Custom loading component |
|
|
203
|
+
| `ErrorComponent` | `ComponentType<{itemId: string, error: Error}>` | ❌ | Custom error component |
|
|
204
|
+
| `DismissButtonComponent` | `ComponentType<{onDismiss: () => Promise<void>, ariaLabel: string}>` | ❌ | Custom dismiss button |
|
|
127
205
|
| `ignoreErrors` | `boolean` | ❌ | Ignore errors and display component anyway (default: false) |
|
|
206
|
+
| `enableCache` | `boolean` | ❌ | Enable localStorage caching (default: true) |
|
|
207
|
+
| `cachePrefix` | `string` | ❌ | Cache key prefix (default: 'dismissible') |
|
|
208
|
+
| `cacheExpiration` | `number` | ❌ | Cache expiration time in milliseconds |
|
|
128
209
|
|
|
129
210
|
#### Example
|
|
130
211
|
|
|
131
212
|
```tsx
|
|
132
213
|
<Dismissible
|
|
133
|
-
|
|
214
|
+
itemId="promo-banner"
|
|
134
215
|
onDismiss={() => console.log('Banner dismissed')}
|
|
135
|
-
onRestore={() => console.log('Banner restored')}
|
|
136
216
|
>
|
|
137
217
|
<div className="promo">
|
|
138
218
|
<h3>Special Offer!</h3>
|
|
@@ -149,28 +229,36 @@ For custom implementations and advanced use cases.
|
|
|
149
229
|
|
|
150
230
|
| Parameter | Type | Required | Description |
|
|
151
231
|
|-----------|------|----------|-------------|
|
|
152
|
-
| `
|
|
232
|
+
| `itemId` | `string` | ✅ | Unique identifier for the dismissible item |
|
|
233
|
+
| `options` | `object` | ❌ | Configuration options |
|
|
234
|
+
|
|
235
|
+
#### Options
|
|
236
|
+
|
|
237
|
+
| Option | Type | Required | Description |
|
|
238
|
+
|--------|------|----------|-------------|
|
|
239
|
+
| `enableCache` | `boolean` | ❌ | Enable localStorage caching (default: true) |
|
|
240
|
+
| `cachePrefix` | `string` | ❌ | Cache key prefix (default: 'dismissible') |
|
|
241
|
+
| `cacheExpiration` | `number` | ❌ | Cache expiration time in milliseconds |
|
|
242
|
+
| `initialData` | `IDismissibleItem` | ❌ | Initial data for the dismissible item |
|
|
153
243
|
|
|
154
244
|
#### Returns
|
|
155
245
|
|
|
156
246
|
| Property | Type | Description |
|
|
157
247
|
|----------|------|-------------|
|
|
158
|
-
| `
|
|
248
|
+
| `dismissedAt` | `string \| undefined` | ISO date string when item was dismissed, or undefined |
|
|
159
249
|
| `dismiss` | `() => Promise<void>` | Function to dismiss the item |
|
|
250
|
+
| `restore` | `() => Promise<void>` | Function to restore a dismissed item |
|
|
160
251
|
| `isLoading` | `boolean` | Loading state indicator |
|
|
161
|
-
| `error` | `Error \|
|
|
252
|
+
| `error` | `Error \| undefined` | Error state, if any |
|
|
253
|
+
| `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
|
|
162
254
|
|
|
163
255
|
#### Example
|
|
164
256
|
|
|
165
257
|
```tsx
|
|
166
258
|
import { useDismissibleItem } from '@dismissible/react-client';
|
|
167
259
|
|
|
168
|
-
function CustomDismissible({
|
|
169
|
-
const {
|
|
170
|
-
|
|
171
|
-
if (dismissedOn) {
|
|
172
|
-
return null; // Item is dismissed
|
|
173
|
-
}
|
|
260
|
+
function CustomDismissible({ itemId, children }) {
|
|
261
|
+
const { dismissedAt, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
|
|
174
262
|
|
|
175
263
|
if (isLoading) {
|
|
176
264
|
return <div>Loading...</div>;
|
|
@@ -180,6 +268,15 @@ function CustomDismissible({ id, children }) {
|
|
|
180
268
|
return <div>Error: {error.message}</div>;
|
|
181
269
|
}
|
|
182
270
|
|
|
271
|
+
if (dismissedAt) {
|
|
272
|
+
return (
|
|
273
|
+
<div>
|
|
274
|
+
<p>This item was dismissed.</p>
|
|
275
|
+
<button onClick={restore}>Restore</button>
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
183
280
|
return (
|
|
184
281
|
<div>
|
|
185
282
|
{children}
|
|
@@ -193,21 +290,16 @@ function CustomDismissible({ id, children }) {
|
|
|
193
290
|
|
|
194
291
|
## Usage Examples
|
|
195
292
|
|
|
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`:
|
|
293
|
+
### Basic Dismissible Banner
|
|
201
294
|
|
|
202
295
|
```tsx
|
|
203
296
|
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
204
297
|
|
|
205
298
|
function App() {
|
|
206
|
-
|
|
207
|
-
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
|
|
299
|
+
const userId = getCurrentUserId();
|
|
208
300
|
|
|
209
301
|
return (
|
|
210
|
-
<DismissibleProvider
|
|
302
|
+
<DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
|
|
211
303
|
<Dashboard />
|
|
212
304
|
</DismissibleProvider>
|
|
213
305
|
);
|
|
@@ -215,61 +307,47 @@ function App() {
|
|
|
215
307
|
|
|
216
308
|
function Dashboard() {
|
|
217
309
|
return (
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</div>
|
|
225
|
-
</Dismissible>
|
|
226
|
-
</div>
|
|
310
|
+
<Dismissible itemId="welcome-banner">
|
|
311
|
+
<div className="alert alert-info">
|
|
312
|
+
<h4>Welcome!</h4>
|
|
313
|
+
<p>Thanks for joining our platform. Here are some quick tips to get started.</p>
|
|
314
|
+
</div>
|
|
315
|
+
</Dismissible>
|
|
227
316
|
);
|
|
228
317
|
}
|
|
229
318
|
```
|
|
230
319
|
|
|
231
|
-
###
|
|
320
|
+
### JWT Authentication Setup
|
|
321
|
+
|
|
322
|
+
For secure environments, configure JWT authentication:
|
|
232
323
|
|
|
233
324
|
```tsx
|
|
234
|
-
import { DismissibleProvider } from '@dismissible/react-client';
|
|
235
|
-
import { useAuth } from './auth'; // Your auth context
|
|
325
|
+
import { DismissibleProvider, Dismissible } from '@dismissible/react-client';
|
|
236
326
|
|
|
237
|
-
// Synchronous JWT function
|
|
238
327
|
function App() {
|
|
239
|
-
const { getAccessToken } = useAuth();
|
|
328
|
+
const { user, getAccessToken } = useAuth();
|
|
240
329
|
|
|
241
330
|
return (
|
|
242
|
-
<DismissibleProvider
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// Asynchronous JWT function
|
|
249
|
-
function AppWithAsyncAuth() {
|
|
250
|
-
const { refreshAndGetToken } = useAuth();
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<DismissibleProvider jwt={async () => await refreshAndGetToken()}>
|
|
254
|
-
<YourApp />
|
|
331
|
+
<DismissibleProvider
|
|
332
|
+
userId={user.id}
|
|
333
|
+
jwt={() => getAccessToken()}
|
|
334
|
+
baseUrl="https://api.yourapp.com"
|
|
335
|
+
>
|
|
336
|
+
<Dashboard />
|
|
255
337
|
</DismissibleProvider>
|
|
256
338
|
);
|
|
257
339
|
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Basic Dismissible Banner
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
import { Dismissible } from '@dismissible/react-client';
|
|
264
340
|
|
|
265
|
-
function
|
|
341
|
+
function Dashboard() {
|
|
266
342
|
return (
|
|
267
|
-
<
|
|
268
|
-
<
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
343
|
+
<div>
|
|
344
|
+
<Dismissible itemId="user-welcome-banner">
|
|
345
|
+
<div className="alert alert-info">
|
|
346
|
+
<h4>Welcome back!</h4>
|
|
347
|
+
<p>You have 3 new notifications.</p>
|
|
348
|
+
</div>
|
|
349
|
+
</Dismissible>
|
|
350
|
+
</div>
|
|
273
351
|
);
|
|
274
352
|
}
|
|
275
353
|
```
|
|
@@ -292,7 +370,7 @@ const CustomDismissButton = ({ onDismiss, ariaLabel }) => (
|
|
|
292
370
|
function CustomBanner() {
|
|
293
371
|
return (
|
|
294
372
|
<Dismissible
|
|
295
|
-
|
|
373
|
+
itemId="custom-banner"
|
|
296
374
|
DismissButtonComponent={CustomDismissButton}
|
|
297
375
|
>
|
|
298
376
|
<div className="banner">
|
|
@@ -308,7 +386,7 @@ function CustomBanner() {
|
|
|
308
386
|
```tsx
|
|
309
387
|
import { Dismissible } from '@dismissible/react-client';
|
|
310
388
|
|
|
311
|
-
const CustomLoader = ({
|
|
389
|
+
const CustomLoader = ({ itemId }) => (
|
|
312
390
|
<div className="spinner">
|
|
313
391
|
<div className="bounce1"></div>
|
|
314
392
|
<div className="bounce2"></div>
|
|
@@ -329,7 +407,7 @@ const CustomError = ({ error }) => (
|
|
|
329
407
|
function AdvancedBanner() {
|
|
330
408
|
return (
|
|
331
409
|
<Dismissible
|
|
332
|
-
|
|
410
|
+
itemId="advanced-banner"
|
|
333
411
|
LoadingComponent={CustomLoader}
|
|
334
412
|
ErrorComponent={CustomError}
|
|
335
413
|
>
|
|
@@ -349,21 +427,21 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
349
427
|
function Dashboard() {
|
|
350
428
|
return (
|
|
351
429
|
<div>
|
|
352
|
-
<Dismissible
|
|
430
|
+
<Dismissible itemId="feature-announcement">
|
|
353
431
|
<div className="alert alert-success">
|
|
354
|
-
|
|
432
|
+
New feature: Dark mode is now available!
|
|
355
433
|
</div>
|
|
356
434
|
</Dismissible>
|
|
357
435
|
|
|
358
|
-
<Dismissible
|
|
436
|
+
<Dismissible itemId="maintenance-notice">
|
|
359
437
|
<div className="alert alert-warning">
|
|
360
|
-
|
|
438
|
+
Scheduled maintenance: Sunday 2AM-4AM EST
|
|
361
439
|
</div>
|
|
362
440
|
</Dismissible>
|
|
363
441
|
|
|
364
|
-
<Dismissible
|
|
442
|
+
<Dismissible itemId="survey-request">
|
|
365
443
|
<div className="alert alert-info">
|
|
366
|
-
|
|
444
|
+
Help us improve! Take our 2-minute survey.
|
|
367
445
|
</div>
|
|
368
446
|
</Dismissible>
|
|
369
447
|
</div>
|
|
@@ -371,35 +449,6 @@ function Dashboard() {
|
|
|
371
449
|
}
|
|
372
450
|
```
|
|
373
451
|
|
|
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
452
|
### Error Handling with ignoreErrors
|
|
404
453
|
|
|
405
454
|
```tsx
|
|
@@ -409,7 +458,7 @@ import { Dismissible } from '@dismissible/react-client';
|
|
|
409
458
|
function RobustBanner() {
|
|
410
459
|
return (
|
|
411
460
|
<Dismissible
|
|
412
|
-
|
|
461
|
+
itemId="important-announcement"
|
|
413
462
|
ignoreErrors={true}
|
|
414
463
|
>
|
|
415
464
|
<div className="important-banner">
|
|
@@ -421,76 +470,19 @@ function RobustBanner() {
|
|
|
421
470
|
}
|
|
422
471
|
```
|
|
423
472
|
|
|
424
|
-
### Async JWT Authentication Examples
|
|
425
|
-
|
|
426
|
-
```tsx
|
|
427
|
-
import { DismissibleProvider } from '@dismissible/react-client';
|
|
428
|
-
|
|
429
|
-
// With token refresh logic
|
|
430
|
-
function AppWithTokenRefresh() {
|
|
431
|
-
return (
|
|
432
|
-
<DismissibleProvider
|
|
433
|
-
jwt={async () => {
|
|
434
|
-
try {
|
|
435
|
-
const token = await fetch('/api/auth/refresh', {
|
|
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;
|
|
444
|
-
}
|
|
445
|
-
}}
|
|
446
|
-
>
|
|
447
|
-
<YourApp />
|
|
448
|
-
</DismissibleProvider>
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// With Firebase Auth
|
|
453
|
-
function AppWithFirebase() {
|
|
454
|
-
return (
|
|
455
|
-
<DismissibleProvider
|
|
456
|
-
jwt={async () => {
|
|
457
|
-
const user = firebase.auth().currentUser;
|
|
458
|
-
if (user) {
|
|
459
|
-
return await user.getIdToken();
|
|
460
|
-
}
|
|
461
|
-
throw new Error('User not authenticated');
|
|
462
|
-
}}
|
|
463
|
-
>
|
|
464
|
-
<YourApp />
|
|
465
|
-
</DismissibleProvider>
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// With Auth0
|
|
470
|
-
function AppWithAuth0() {
|
|
471
|
-
const { getAccessTokenSilently } = useAuth0();
|
|
472
|
-
|
|
473
|
-
return (
|
|
474
|
-
<DismissibleProvider jwt={async () => await getAccessTokenSilently()}>
|
|
475
|
-
<YourApp />
|
|
476
|
-
</DismissibleProvider>
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
```
|
|
480
|
-
|
|
481
473
|
### Using the Hook for Complex Logic
|
|
482
474
|
|
|
483
475
|
```tsx
|
|
484
|
-
import { useDismissibleItem
|
|
476
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
485
477
|
import { useState, useEffect } from 'react';
|
|
486
478
|
|
|
487
|
-
function SmartNotification({
|
|
488
|
-
const {
|
|
479
|
+
function SmartNotification({ itemId, message, type = 'info' }) {
|
|
480
|
+
const { dismissedAt, dismiss, isLoading } = useDismissibleItem(itemId);
|
|
489
481
|
const [autoHide, setAutoHide] = useState(false);
|
|
490
482
|
|
|
491
483
|
// Auto-hide after 10 seconds for info messages
|
|
492
484
|
useEffect(() => {
|
|
493
|
-
if (type === 'info' && !
|
|
485
|
+
if (type === 'info' && !dismissedAt) {
|
|
494
486
|
const timer = setTimeout(() => {
|
|
495
487
|
setAutoHide(true);
|
|
496
488
|
dismiss();
|
|
@@ -498,9 +490,9 @@ function SmartNotification({ id, message, type = 'info' }) {
|
|
|
498
490
|
|
|
499
491
|
return () => clearTimeout(timer);
|
|
500
492
|
}
|
|
501
|
-
}, [type,
|
|
493
|
+
}, [type, dismissedAt, dismiss]);
|
|
502
494
|
|
|
503
|
-
if (
|
|
495
|
+
if (dismissedAt || autoHide) {
|
|
504
496
|
return null;
|
|
505
497
|
}
|
|
506
498
|
|
|
@@ -517,17 +509,37 @@ function SmartNotification({ id, message, type = 'info' }) {
|
|
|
517
509
|
</div>
|
|
518
510
|
);
|
|
519
511
|
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Restoring Dismissed Items
|
|
515
|
+
|
|
516
|
+
Use the `restore` function to bring back previously dismissed content:
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
import { useDismissibleItem } from '@dismissible/react-client';
|
|
520
|
+
|
|
521
|
+
function RestorableBanner({ itemId }) {
|
|
522
|
+
const { dismissedAt, dismiss, restore, isLoading } = useDismissibleItem(itemId);
|
|
523
|
+
|
|
524
|
+
if (dismissedAt) {
|
|
525
|
+
return (
|
|
526
|
+
<div className="dismissed-placeholder">
|
|
527
|
+
<p>Banner was dismissed on {new Date(dismissedAt).toLocaleDateString()}</p>
|
|
528
|
+
<button onClick={restore} disabled={isLoading}>
|
|
529
|
+
{isLoading ? 'Restoring...' : 'Show Banner Again'}
|
|
530
|
+
</button>
|
|
531
|
+
</div>
|
|
532
|
+
);
|
|
533
|
+
}
|
|
520
534
|
|
|
521
|
-
// Usage with async authentication
|
|
522
|
-
function App() {
|
|
523
535
|
return (
|
|
524
|
-
<
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
</
|
|
536
|
+
<div className="banner">
|
|
537
|
+
<h3>Welcome!</h3>
|
|
538
|
+
<p>This is a restorable banner.</p>
|
|
539
|
+
<button onClick={dismiss} disabled={isLoading}>
|
|
540
|
+
Dismiss
|
|
541
|
+
</button>
|
|
542
|
+
</div>
|
|
531
543
|
);
|
|
532
544
|
}
|
|
533
545
|
```
|
|
@@ -555,37 +567,40 @@ The library includes minimal default styles. You can override them or provide yo
|
|
|
555
567
|
}
|
|
556
568
|
```
|
|
557
569
|
|
|
558
|
-
##
|
|
570
|
+
## Self-Hosting
|
|
559
571
|
|
|
560
|
-
|
|
572
|
+
Dismissible is designed to be self-hosted. You have full control over your data.
|
|
561
573
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
} from '@dismissible/react-client';
|
|
569
|
-
|
|
570
|
-
// Dismissible component with custom props
|
|
571
|
-
const MyComponent: React.FC<DismissibleProps> = (props) => {
|
|
572
|
-
// Your component implementation
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
// Provider with JWT authentication
|
|
576
|
-
const AuthProvider: React.FC<DismissibleProviderProps> = ({ children, jwt }) => {
|
|
577
|
-
return (
|
|
578
|
-
<DismissibleProvider jwt={jwt}>
|
|
579
|
-
{children}
|
|
580
|
-
</DismissibleProvider>
|
|
581
|
-
);
|
|
582
|
-
};
|
|
574
|
+
### Option 1: Docker (Recommended)
|
|
575
|
+
|
|
576
|
+
The fastest way to get started:
|
|
577
|
+
|
|
578
|
+
```bash
|
|
579
|
+
docker run -p 3001:3001 dismissibleio/dismissible-api:latest
|
|
583
580
|
```
|
|
584
581
|
|
|
582
|
+
See the [Docker documentation](https://dismissible.io/docs/docker) for production configuration.
|
|
583
|
+
|
|
584
|
+
### Option 2: NestJS Module
|
|
585
|
+
|
|
586
|
+
Integrate directly into your existing NestJS application:
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
npm install @dismissible/nestjs-api
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup instructions.
|
|
593
|
+
|
|
585
594
|
## Support
|
|
586
595
|
|
|
587
|
-
-
|
|
596
|
+
- [Documentation](https://dismissible.io/docs)
|
|
597
|
+
- [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
|
|
598
|
+
- [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
|
|
599
|
+
|
|
600
|
+
## License
|
|
601
|
+
|
|
602
|
+
MIT © [Dismissible](https://dismissible.io)
|
|
588
603
|
|
|
589
604
|
## Changelog
|
|
590
605
|
|
|
591
|
-
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|
|
606
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes.
|