@okyrychenko-dev/react-action-guard 0.2.1 → 0.2.3
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 +463 -4
- package/dist/index.cjs +151 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -38
- package/dist/index.d.ts +38 -38
- package/dist/index.js +152 -106
- package/dist/index.js.map +1 -1
- package/package.json +14 -2
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @okyrychenko-dev/react-action-guard
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@okyrychenko-dev/react-action-guard)
|
|
4
|
+
[](https://www.npmjs.com/package/@okyrychenko-dev/react-action-guard)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
3
7
|
> Elegant UI blocking management for React applications with priorities, scopes, and automatic cleanup
|
|
4
8
|
|
|
5
9
|
## Features
|
|
@@ -7,8 +11,15 @@
|
|
|
7
11
|
- Priority-based blocking system
|
|
8
12
|
- Scoped blocking (global, specific areas, or multiple scopes)
|
|
9
13
|
- Automatic cleanup on unmount
|
|
10
|
-
-
|
|
14
|
+
- Advanced hooks for different use cases:
|
|
15
|
+
- Confirmable blockers with custom dialogs
|
|
16
|
+
- Scheduled blocking for maintenance windows
|
|
17
|
+
- Conditional blocking based on application state
|
|
18
|
+
- Async action wrapping
|
|
19
|
+
- Advanced middleware system for analytics, logging, and performance monitoring
|
|
20
|
+
- TypeScript support with full type safety
|
|
11
21
|
- Built on Zustand for efficient state management
|
|
22
|
+
- Tree-shakeable - import only what you need
|
|
12
23
|
- Hooks-based API
|
|
13
24
|
|
|
14
25
|
## Installation
|
|
@@ -29,6 +40,16 @@ function MyComponent() {
|
|
|
29
40
|
}
|
|
30
41
|
```
|
|
31
42
|
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
📚 **[Interactive Storybook Documentation](https://your-storybook-url.com)** - Explore live examples and detailed guides for all hooks
|
|
46
|
+
|
|
47
|
+
To run Storybook locally:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run storybook
|
|
51
|
+
```
|
|
52
|
+
|
|
32
53
|
## API Reference
|
|
33
54
|
|
|
34
55
|
### Hooks
|
|
@@ -107,6 +128,138 @@ function MyComponent() {
|
|
|
107
128
|
}
|
|
108
129
|
```
|
|
109
130
|
|
|
131
|
+
#### `useConfirmableBlocker(blockerId, config, options)`
|
|
132
|
+
|
|
133
|
+
Creates a blocker that requires user confirmation before being removed.
|
|
134
|
+
|
|
135
|
+
**Parameters:**
|
|
136
|
+
|
|
137
|
+
- `blockerId: string` - Unique identifier for the blocker
|
|
138
|
+
- `config: BlockerConfig` - Configuration object
|
|
139
|
+
- `options: ConfirmableBlockerOptions`
|
|
140
|
+
- `enabled?: boolean` - Whether the blocker is active (default: true)
|
|
141
|
+
- `confirmMessage?: string` - Message to show in confirmation dialog
|
|
142
|
+
- `onConfirm?: () => void | Promise<void>` - Callback when user confirms
|
|
143
|
+
|
|
144
|
+
**Returns:**
|
|
145
|
+
|
|
146
|
+
- `isBlocking: boolean` - Whether the blocker is currently active
|
|
147
|
+
- `cancel: () => void` - Manually remove the blocker without confirmation
|
|
148
|
+
- `requestRemoval: () => Promise<boolean>` - Request removal with confirmation
|
|
149
|
+
|
|
150
|
+
**Example:**
|
|
151
|
+
|
|
152
|
+
```jsx
|
|
153
|
+
function UnsavedChangesGuard() {
|
|
154
|
+
const { isBlocking, requestRemoval } = useConfirmableBlocker(
|
|
155
|
+
"unsaved-changes",
|
|
156
|
+
{
|
|
157
|
+
scope: "navigation",
|
|
158
|
+
reason: "Unsaved changes",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
enabled: hasUnsavedChanges,
|
|
162
|
+
confirmMessage: "You have unsaved changes. Are you sure you want to leave?",
|
|
163
|
+
onConfirm: async () => {
|
|
164
|
+
await discardChanges();
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div>
|
|
171
|
+
{isBlocking && <UnsavedIndicator />}
|
|
172
|
+
<button onClick={requestRemoval}>Leave</button>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### `useScheduledBlocker(blockerId, config)`
|
|
179
|
+
|
|
180
|
+
Blocks UI during a scheduled time period or maintenance window.
|
|
181
|
+
|
|
182
|
+
**Parameters:**
|
|
183
|
+
|
|
184
|
+
- `blockerId: string` - Unique identifier for the blocker
|
|
185
|
+
- `config: ScheduledBlockerConfig`
|
|
186
|
+
- `schedule: BlockingSchedule`
|
|
187
|
+
- `start: string | Date | number` - Start time (ISO string, Date, or timestamp)
|
|
188
|
+
- `end?: string | Date | number` - End time (optional)
|
|
189
|
+
- `duration?: number` - Duration in milliseconds (takes precedence over end)
|
|
190
|
+
- `onScheduleStart?: () => void` - Callback when blocking starts
|
|
191
|
+
- `onScheduleEnd?: () => void` - Callback when blocking ends
|
|
192
|
+
- Plus all `BlockerConfig` properties (scope, reason, priority)
|
|
193
|
+
|
|
194
|
+
**Example:**
|
|
195
|
+
|
|
196
|
+
```jsx
|
|
197
|
+
function MaintenanceWindow() {
|
|
198
|
+
useScheduledBlocker("maintenance", {
|
|
199
|
+
scope: "global",
|
|
200
|
+
reason: "Scheduled maintenance",
|
|
201
|
+
priority: 1000,
|
|
202
|
+
schedule: {
|
|
203
|
+
start: "2024-01-15T02:00:00Z",
|
|
204
|
+
duration: 3600000, // 1 hour in milliseconds
|
|
205
|
+
},
|
|
206
|
+
onScheduleStart: () => {
|
|
207
|
+
console.log("Maintenance started");
|
|
208
|
+
},
|
|
209
|
+
onScheduleEnd: () => {
|
|
210
|
+
console.log("Maintenance completed");
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return <div>App content</div>;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### `useConditionalBlocker(blockerId, config)`
|
|
219
|
+
|
|
220
|
+
Periodically checks a condition and blocks/unblocks based on the result.
|
|
221
|
+
|
|
222
|
+
**Parameters:**
|
|
223
|
+
|
|
224
|
+
- `blockerId: string` - Unique identifier for the blocker
|
|
225
|
+
- `config: ConditionalBlockerConfig<TState>`
|
|
226
|
+
- `scope: string | string[]` - Required scope(s) to block
|
|
227
|
+
- `condition: (state?: TState) => boolean` - Function that determines if blocking should be active
|
|
228
|
+
- `checkInterval?: number` - How often to check the condition in ms (default: 1000)
|
|
229
|
+
- `state?: TState` - Optional state to pass to the condition function
|
|
230
|
+
- Plus all other `BlockerConfig` properties (reason, priority)
|
|
231
|
+
|
|
232
|
+
**Example:**
|
|
233
|
+
|
|
234
|
+
```jsx
|
|
235
|
+
function NetworkStatusBlocker() {
|
|
236
|
+
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
const handleOnline = () => setIsOnline(true);
|
|
240
|
+
const handleOffline = () => setIsOnline(false);
|
|
241
|
+
|
|
242
|
+
window.addEventListener("online", handleOnline);
|
|
243
|
+
window.addEventListener("offline", handleOffline);
|
|
244
|
+
|
|
245
|
+
return () => {
|
|
246
|
+
window.removeEventListener("online", handleOnline);
|
|
247
|
+
window.removeEventListener("offline", handleOffline);
|
|
248
|
+
};
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
useConditionalBlocker("network-check", {
|
|
252
|
+
scope: ["form", "navigation"],
|
|
253
|
+
reason: "No network connection",
|
|
254
|
+
priority: 100,
|
|
255
|
+
condition: () => !isOnline,
|
|
256
|
+
checkInterval: 2000,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return <div>App content</div>;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
110
263
|
### Store
|
|
111
264
|
|
|
112
265
|
#### `useUIBlockingStore`
|
|
@@ -117,6 +270,7 @@ Direct access to the Zustand store for advanced use cases.
|
|
|
117
270
|
|
|
118
271
|
- `addBlocker(id, config)` - Manually add a blocker
|
|
119
272
|
- `removeBlocker(id)` - Manually remove a blocker
|
|
273
|
+
- `updateBlocker(id, config)` - Update an existing blocker
|
|
120
274
|
- `isBlocked(scope)` - Check if scope is blocked
|
|
121
275
|
- `getBlockingInfo(scope)` - Get detailed blocking information
|
|
122
276
|
- `clearAllBlockers()` - Remove all blockers
|
|
@@ -128,7 +282,7 @@ Direct access to the Zustand store for advanced use cases.
|
|
|
128
282
|
import { useUIBlockingStore } from "@okyrychenko-dev/react-action-guard";
|
|
129
283
|
|
|
130
284
|
function AdvancedComponent() {
|
|
131
|
-
const { addBlocker, removeBlocker } = useUIBlockingStore();
|
|
285
|
+
const { addBlocker, removeBlocker, updateBlocker } = useUIBlockingStore();
|
|
132
286
|
|
|
133
287
|
const startBlocking = () => {
|
|
134
288
|
addBlocker("custom-blocker", {
|
|
@@ -138,6 +292,12 @@ function AdvancedComponent() {
|
|
|
138
292
|
});
|
|
139
293
|
};
|
|
140
294
|
|
|
295
|
+
const updatePriority = () => {
|
|
296
|
+
updateBlocker("custom-blocker", {
|
|
297
|
+
priority: 200,
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
|
|
141
301
|
const stopBlocking = () => {
|
|
142
302
|
removeBlocker("custom-blocker");
|
|
143
303
|
};
|
|
@@ -145,18 +305,159 @@ function AdvancedComponent() {
|
|
|
145
305
|
return (
|
|
146
306
|
<div>
|
|
147
307
|
<button onClick={startBlocking}>Start</button>
|
|
308
|
+
<button onClick={updatePriority}>Increase Priority</button>
|
|
148
309
|
<button onClick={stopBlocking}>Stop</button>
|
|
149
310
|
</div>
|
|
150
311
|
);
|
|
151
312
|
}
|
|
152
313
|
```
|
|
153
314
|
|
|
315
|
+
## Middleware System
|
|
316
|
+
|
|
317
|
+
The library includes a powerful middleware system that allows you to hook into blocker lifecycle events for analytics, logging, and performance monitoring.
|
|
318
|
+
|
|
319
|
+
### Built-in Middleware
|
|
320
|
+
|
|
321
|
+
#### Analytics Middleware
|
|
322
|
+
|
|
323
|
+
Track blocker events with your analytics provider (Google Analytics, Mixpanel, Amplitude, or custom).
|
|
324
|
+
|
|
325
|
+
```jsx
|
|
326
|
+
import { configureMiddleware, analyticsMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
327
|
+
|
|
328
|
+
// Google Analytics
|
|
329
|
+
configureMiddleware([analyticsMiddleware({ provider: "ga" })]);
|
|
330
|
+
|
|
331
|
+
// Mixpanel
|
|
332
|
+
configureMiddleware([analyticsMiddleware({ provider: "mixpanel" })]);
|
|
333
|
+
|
|
334
|
+
// Amplitude
|
|
335
|
+
configureMiddleware([analyticsMiddleware({ provider: "amplitude" })]);
|
|
336
|
+
|
|
337
|
+
// Custom analytics
|
|
338
|
+
configureMiddleware([
|
|
339
|
+
analyticsMiddleware({
|
|
340
|
+
track: (event, data) => {
|
|
341
|
+
myAnalytics.track(event, data);
|
|
342
|
+
},
|
|
343
|
+
}),
|
|
344
|
+
]);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### Logger Middleware
|
|
348
|
+
|
|
349
|
+
Log blocker lifecycle events to the console for debugging.
|
|
350
|
+
|
|
351
|
+
```jsx
|
|
352
|
+
import { configureMiddleware, loggerMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
353
|
+
|
|
354
|
+
configureMiddleware([loggerMiddleware]);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Performance Middleware
|
|
358
|
+
|
|
359
|
+
Monitor blocker performance and detect slow operations.
|
|
360
|
+
|
|
361
|
+
```jsx
|
|
362
|
+
import { configureMiddleware, performanceMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
363
|
+
|
|
364
|
+
configureMiddleware([
|
|
365
|
+
performanceMiddleware({
|
|
366
|
+
onSlowBlock: (blockerId, duration) => {
|
|
367
|
+
console.warn(`Blocker ${blockerId} was active for ${duration}ms`);
|
|
368
|
+
},
|
|
369
|
+
slowBlockThreshold: 5000, // 5 seconds
|
|
370
|
+
}),
|
|
371
|
+
]);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Custom Middleware
|
|
375
|
+
|
|
376
|
+
Create your own middleware to handle blocker events:
|
|
377
|
+
|
|
378
|
+
```jsx
|
|
379
|
+
import { configureMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
380
|
+
|
|
381
|
+
const myCustomMiddleware = (context) => {
|
|
382
|
+
const { action, blockerId, config, timestamp } = context;
|
|
383
|
+
|
|
384
|
+
if (action === "add") {
|
|
385
|
+
console.log(`Blocker added: ${blockerId}`, config);
|
|
386
|
+
} else if (action === "remove") {
|
|
387
|
+
console.log(`Blocker removed: ${blockerId}`);
|
|
388
|
+
} else if (action === "update") {
|
|
389
|
+
console.log(`Blocker updated: ${blockerId}`, config);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
configureMiddleware([myCustomMiddleware]);
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Combining Middleware
|
|
397
|
+
|
|
398
|
+
You can combine multiple middleware for comprehensive monitoring:
|
|
399
|
+
|
|
400
|
+
```jsx
|
|
401
|
+
import {
|
|
402
|
+
configureMiddleware,
|
|
403
|
+
analyticsMiddleware,
|
|
404
|
+
loggerMiddleware,
|
|
405
|
+
performanceMiddleware,
|
|
406
|
+
} from "@okyrychenko-dev/react-action-guard";
|
|
407
|
+
|
|
408
|
+
configureMiddleware([
|
|
409
|
+
loggerMiddleware,
|
|
410
|
+
analyticsMiddleware({ provider: "ga" }),
|
|
411
|
+
performanceMiddleware({
|
|
412
|
+
slowBlockThreshold: 3000,
|
|
413
|
+
onSlowBlock: (blockerId, duration) => {
|
|
414
|
+
// Send to error tracking service
|
|
415
|
+
errorTracker.captureMessage(`Slow blocker: ${blockerId}`, {
|
|
416
|
+
duration,
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
}),
|
|
420
|
+
]);
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Tree Shaking
|
|
424
|
+
|
|
425
|
+
The library is fully tree-shakeable. Import only the features you need to keep your bundle size small:
|
|
426
|
+
|
|
427
|
+
```jsx
|
|
428
|
+
// Only imports the hook you need
|
|
429
|
+
import { useBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
430
|
+
|
|
431
|
+
// Middleware is not included unless you import it
|
|
432
|
+
import { configureMiddleware, analyticsMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
The package is configured with `"sideEffects": false`, allowing modern bundlers (Webpack, Rollup, Vite) to eliminate unused code automatically.
|
|
436
|
+
|
|
154
437
|
## TypeScript
|
|
155
438
|
|
|
156
439
|
The package is written in TypeScript and includes full type definitions.
|
|
157
440
|
|
|
158
441
|
```typescript
|
|
159
|
-
import type {
|
|
442
|
+
import type {
|
|
443
|
+
// Core types
|
|
444
|
+
BlockerConfig,
|
|
445
|
+
BlockerInfo,
|
|
446
|
+
UIBlockingState,
|
|
447
|
+
|
|
448
|
+
// Hook types
|
|
449
|
+
ConfirmableBlockerOptions,
|
|
450
|
+
ScheduledBlockerConfig,
|
|
451
|
+
ConditionalBlockerConfig,
|
|
452
|
+
BlockingSchedule,
|
|
453
|
+
|
|
454
|
+
// Middleware types
|
|
455
|
+
Middleware,
|
|
456
|
+
MiddlewareContext,
|
|
457
|
+
AnalyticsConfig,
|
|
458
|
+
AnalyticsProvider,
|
|
459
|
+
PerformanceMiddlewareConfig,
|
|
460
|
+
} from "@okyrychenko-dev/react-action-guard";
|
|
160
461
|
```
|
|
161
462
|
|
|
162
463
|
## Use Cases
|
|
@@ -180,9 +481,11 @@ function DataLoader() {
|
|
|
180
481
|
}
|
|
181
482
|
```
|
|
182
483
|
|
|
183
|
-
### Form Submission
|
|
484
|
+
### Form Submission with Analytics
|
|
184
485
|
|
|
185
486
|
```jsx
|
|
487
|
+
import { useAsyncAction, useIsBlocked } from "@okyrychenko-dev/react-action-guard";
|
|
488
|
+
|
|
186
489
|
function UserForm() {
|
|
187
490
|
const executeWithBlocking = useAsyncAction("submit-form", "form");
|
|
188
491
|
const isBlocked = useIsBlocked("form");
|
|
@@ -202,6 +505,46 @@ function UserForm() {
|
|
|
202
505
|
}
|
|
203
506
|
```
|
|
204
507
|
|
|
508
|
+
### Unsaved Changes Protection
|
|
509
|
+
|
|
510
|
+
```jsx
|
|
511
|
+
import { useConfirmableBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
512
|
+
|
|
513
|
+
function FormWithUnsavedWarning() {
|
|
514
|
+
const [formData, setFormData] = useState({});
|
|
515
|
+
const [hasChanges, setHasChanges] = useState(false);
|
|
516
|
+
|
|
517
|
+
const { requestRemoval } = useConfirmableBlocker(
|
|
518
|
+
"unsaved-form",
|
|
519
|
+
{
|
|
520
|
+
scope: "navigation",
|
|
521
|
+
reason: "Unsaved form data",
|
|
522
|
+
priority: 100,
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
enabled: hasChanges,
|
|
526
|
+
confirmMessage: "You have unsaved changes. Discard them?",
|
|
527
|
+
onConfirm: () => {
|
|
528
|
+
setFormData({});
|
|
529
|
+
setHasChanges(false);
|
|
530
|
+
},
|
|
531
|
+
}
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
return (
|
|
535
|
+
<form>
|
|
536
|
+
<input
|
|
537
|
+
onChange={(e) => {
|
|
538
|
+
setFormData({ ...formData, name: e.target.value });
|
|
539
|
+
setHasChanges(true);
|
|
540
|
+
}}
|
|
541
|
+
/>
|
|
542
|
+
<button onClick={requestRemoval}>Cancel</button>
|
|
543
|
+
</form>
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
205
548
|
### Global Loading Overlay
|
|
206
549
|
|
|
207
550
|
```jsx
|
|
@@ -217,22 +560,138 @@ function App() {
|
|
|
217
560
|
}
|
|
218
561
|
```
|
|
219
562
|
|
|
563
|
+
### Multi-Step Process with Priority
|
|
564
|
+
|
|
565
|
+
```jsx
|
|
566
|
+
function MultiStepWizard() {
|
|
567
|
+
const [step, setStep] = useState(1);
|
|
568
|
+
|
|
569
|
+
// Higher priority for payment step
|
|
570
|
+
useBlocker(
|
|
571
|
+
"payment-step",
|
|
572
|
+
{
|
|
573
|
+
scope: ["navigation", "form"],
|
|
574
|
+
reason: "Processing payment",
|
|
575
|
+
priority: 100,
|
|
576
|
+
},
|
|
577
|
+
step === 3
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
// Lower priority for other steps
|
|
581
|
+
useBlocker(
|
|
582
|
+
"wizard-step",
|
|
583
|
+
{
|
|
584
|
+
scope: "navigation",
|
|
585
|
+
reason: "Wizard in progress",
|
|
586
|
+
priority: 50,
|
|
587
|
+
},
|
|
588
|
+
step < 3
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
return <div>Step {step}</div>;
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Scheduled Maintenance Window
|
|
596
|
+
|
|
597
|
+
```jsx
|
|
598
|
+
import { useScheduledBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
599
|
+
|
|
600
|
+
function App() {
|
|
601
|
+
useScheduledBlocker("weekly-maintenance", {
|
|
602
|
+
scope: "global",
|
|
603
|
+
reason: "Weekly system maintenance",
|
|
604
|
+
priority: 500,
|
|
605
|
+
schedule: {
|
|
606
|
+
start: new Date("2024-01-21T03:00:00Z"),
|
|
607
|
+
duration: 1800000, // 30 minutes
|
|
608
|
+
},
|
|
609
|
+
onScheduleStart: () => {
|
|
610
|
+
showNotification("System maintenance in progress");
|
|
611
|
+
},
|
|
612
|
+
onScheduleEnd: () => {
|
|
613
|
+
showNotification("Maintenance completed");
|
|
614
|
+
window.location.reload();
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
return <YourApp />;
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Dynamic Blocking Based on State
|
|
623
|
+
|
|
624
|
+
```jsx
|
|
625
|
+
import { useConditionalBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
626
|
+
|
|
627
|
+
function StorageQuotaGuard() {
|
|
628
|
+
const [storageUsed, setStorageUsed] = useState(0);
|
|
629
|
+
const STORAGE_LIMIT = 1000000; // 1MB
|
|
630
|
+
|
|
631
|
+
useConditionalBlocker("storage-limit", {
|
|
632
|
+
scope: ["upload", "save"],
|
|
633
|
+
reason: "Storage quota exceeded",
|
|
634
|
+
priority: 200,
|
|
635
|
+
condition: () => storageUsed > STORAGE_LIMIT,
|
|
636
|
+
state: storageUsed,
|
|
637
|
+
checkInterval: 5000, // Check every 5 seconds
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
return (
|
|
641
|
+
<div>
|
|
642
|
+
<p>
|
|
643
|
+
Storage used: {storageUsed} / {STORAGE_LIMIT} bytes
|
|
644
|
+
</p>
|
|
645
|
+
<UploadButton />
|
|
646
|
+
</div>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
220
651
|
## Development
|
|
221
652
|
|
|
222
653
|
```bash
|
|
223
654
|
# Install dependencies
|
|
224
655
|
npm install
|
|
225
656
|
|
|
657
|
+
# Run tests
|
|
658
|
+
npm run test
|
|
659
|
+
|
|
660
|
+
# Run tests with coverage
|
|
661
|
+
npm run test:coverage
|
|
662
|
+
|
|
226
663
|
# Build the package
|
|
227
664
|
npm run build
|
|
228
665
|
|
|
229
666
|
# Type checking
|
|
230
667
|
npm run typecheck
|
|
231
668
|
|
|
669
|
+
# Lint code
|
|
670
|
+
npm run lint
|
|
671
|
+
|
|
672
|
+
# Fix lint errors
|
|
673
|
+
npm run lint:fix
|
|
674
|
+
|
|
675
|
+
# Format code
|
|
676
|
+
npm run format
|
|
677
|
+
|
|
232
678
|
# Watch mode for development
|
|
233
679
|
npm run dev
|
|
234
680
|
```
|
|
235
681
|
|
|
682
|
+
## Contributing
|
|
683
|
+
|
|
684
|
+
Contributions are welcome! Please ensure:
|
|
685
|
+
|
|
686
|
+
1. All tests pass (`npm run test`)
|
|
687
|
+
2. Code is properly typed (`npm run typecheck`)
|
|
688
|
+
3. Linting passes (`npm run lint`)
|
|
689
|
+
4. Code is formatted (`npm run format`)
|
|
690
|
+
|
|
691
|
+
## Changelog
|
|
692
|
+
|
|
693
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes in each version.
|
|
694
|
+
|
|
236
695
|
## License
|
|
237
696
|
|
|
238
697
|
MIT © Olexii Kyrychenko
|