@codingfactory/notify-kit-client 0.1.0 → 0.2.0

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 ADDED
@@ -0,0 +1,500 @@
1
+ # @coding-factory/notify-kit-client
2
+
3
+ Headless Vue 3/Nuxt client for [Notify Kit](../notify-kit/) notifications.
4
+
5
+ ## Features
6
+
7
+ - **TypeScript-first** - Strict types matching the backend payload contract
8
+ - **HTTP Client** - Full API coverage with type-safe methods
9
+ - **Vue 3 Composables** - Headless, reactive state management
10
+ - **Nuxt Module** - SSR-safe plugin with auto-imports
11
+ - **Real-time Integration** - Laravel Echo subscription handling
12
+ - **Modal Queue Management** - Built-in critical notification modals
13
+ - **Toast Policy Helpers** - Decide what toasts and plays sounds
14
+
15
+ ## Ownership
16
+
17
+ `@coding-factory/notify-kit-client` is the package-owned home for generic frontend notification behavior.
18
+
19
+ - Put typed notification API calls, unread-count refresh logic, inbox pagination, realtime merge rules, modal queue helpers, and preference-settings access here.
20
+ - Keep consumer apps thin: they should mostly provide rendering, routing, branded presentation, and app-specific side effects.
21
+ - Do not add a second app-local notification store or duplicate realtime subscription pipeline when the behavior can live in this package.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @coding-factory/notify-kit-client
27
+ ```
28
+
29
+ For Nuxt applications, no additional setup is needed for the module.
30
+
31
+ ## Vue 3 Setup
32
+
33
+ ### 1. Create and Provide the Client
34
+
35
+ ```typescript
36
+ // main.ts
37
+ import { createApp, provide } from 'vue';
38
+ import {
39
+ createNotifyKitClient,
40
+ NOTIFY_KIT_CLIENT_KEY,
41
+ } from '@coding-factory/notify-kit-client';
42
+ import App from './App.vue';
43
+
44
+ const client = createNotifyKitClient({
45
+ baseUrl: '/api/notify-kit',
46
+ // Optional: provide auth headers
47
+ getAuthHeaders: () => ({
48
+ Authorization: `Bearer ${getToken()}`,
49
+ }),
50
+ });
51
+
52
+ const app = createApp(App);
53
+ app.provide(NOTIFY_KIT_CLIENT_KEY, client);
54
+ app.mount('#app');
55
+ ```
56
+
57
+ ### 2. Use Composables in Components
58
+
59
+ ```vue
60
+ <script setup lang="ts">
61
+ import {
62
+ useNotifyKitClient,
63
+ useNotifyKitStore,
64
+ useNotifyKitModals,
65
+ } from '@coding-factory/notify-kit-client';
66
+
67
+ // Access the client for API calls
68
+ const { client } = useNotifyKitClient();
69
+
70
+ // Access reactive store state
71
+ const store = useNotifyKitStore();
72
+ const unreadCount = computed(() => store.state.unreadCount);
73
+
74
+ // Access modal queue
75
+ const { currentModal, hasModals, ack, snooze } = useNotifyKitModals();
76
+ </script>
77
+ ```
78
+
79
+ ### 3. Set Up Real-time with Echo
80
+
81
+ ```typescript
82
+ // composables/useNotifications.ts
83
+ import Echo from 'laravel-echo';
84
+ import Pusher from 'pusher-js';
85
+ import {
86
+ useNotifyKitClient,
87
+ useNotifyKitRealtime,
88
+ useNotifyKitStore,
89
+ defaultToastPolicy,
90
+ } from '@coding-factory/notify-kit-client';
91
+
92
+ // Configure Echo (one-time setup)
93
+ window.Pusher = Pusher;
94
+ const echo = new Echo({
95
+ broadcaster: 'reverb',
96
+ key: import.meta.env.VITE_REVERB_APP_KEY,
97
+ wsHost: import.meta.env.VITE_REVERB_HOST,
98
+ wsPort: import.meta.env.VITE_REVERB_PORT,
99
+ forceTLS: false,
100
+ enabledTransports: ['ws'],
101
+ });
102
+
103
+ export function useNotifications() {
104
+ const { client } = useNotifyKitClient();
105
+ const store = useNotifyKitStore();
106
+
107
+ const { connect, disconnect, isConnected } = useNotifyKitRealtime({
108
+ echo: () => echo,
109
+ autoConnect: true,
110
+ onNotification: (notification) => {
111
+ // Handle new notification
112
+ if (defaultToastPolicy.shouldToast(notification)) {
113
+ showToast(notification);
114
+ }
115
+ },
116
+ });
117
+
118
+ return {
119
+ connect,
120
+ disconnect,
121
+ isConnected,
122
+ unreadCount: computed(() => store.state.unreadCount),
123
+ };
124
+ }
125
+ ```
126
+
127
+ ## Nuxt 3 Setup
128
+
129
+ ### 1. Add the Module
130
+
131
+ ```typescript
132
+ // nuxt.config.ts
133
+ export default defineNuxtConfig({
134
+ modules: ['@coding-factory/notify-kit-client/nuxt'],
135
+
136
+ notifyKit: {
137
+ baseUrl: '/api/notify-kit',
138
+ autoConnect: true,
139
+ },
140
+
141
+ // Proxy API to Laravel backend
142
+ routeRules: {
143
+ '/api/**': { proxy: 'http://localhost:8000/api/**' },
144
+ },
145
+ });
146
+ ```
147
+
148
+ ### 2. Use the Auto-imported Composable
149
+
150
+ ```vue
151
+ <script setup lang="ts">
152
+ // useNotifyKitNuxt is auto-imported
153
+ const { client, state, fetchUnreadCount } = useNotifyKitNuxt();
154
+
155
+ onMounted(async () => {
156
+ await fetchUnreadCount();
157
+ });
158
+ </script>
159
+
160
+ <template>
161
+ <div>
162
+ <span>{{ state.unreadCount }} unread</span>
163
+ </div>
164
+ </template>
165
+ ```
166
+
167
+ ### SSR Considerations
168
+
169
+ The Nuxt plugin handles SSR safety automatically. For components that use browser APIs (like Echo), wrap them in `<ClientOnly>`:
170
+
171
+ ```vue
172
+ <template>
173
+ <ClientOnly>
174
+ <ModalManager />
175
+ <ToastContainer />
176
+ </ClientOnly>
177
+ </template>
178
+ ```
179
+
180
+ ## Composables Reference
181
+
182
+ ### useNotifyKitClient
183
+
184
+ Access the HTTP client for API calls.
185
+
186
+ ```typescript
187
+ const { client } = useNotifyKitClient();
188
+
189
+ // API methods
190
+ await client.listNotifications({ unread: true, per_page: 10 });
191
+ await client.getUnreadCount();
192
+ await client.markRead(notificationId);
193
+ await client.markAllRead();
194
+ await client.deleteNotification(notificationId);
195
+ await client.listModals();
196
+ await client.ackModal(modalId);
197
+ await client.snoozeModal(modalId, 15); // minutes
198
+ await client.getPreferences();
199
+ await client.updatePreferences({ categories: { ... } });
200
+ ```
201
+
202
+ ### useNotifyKitStore
203
+
204
+ Centralized reactive state for notifications.
205
+
206
+ ```typescript
207
+ const store = useNotifyKitStore();
208
+
209
+ // Reactive state
210
+ store.state.notifications; // NotifyKitNotification[]
211
+ store.state.unreadCount; // number
212
+ store.state.modalQueue; // NotifyKitNotification[]
213
+ store.state.connectionState; // 'disconnected' | 'connecting' | 'connected' | 'error'
214
+ store.state.broadcastChannel; // string | null
215
+
216
+ // Computed helpers
217
+ store.hasUnread; // ComputedRef<boolean>
218
+ store.currentModal; // ComputedRef<NotifyKitNotification | null>
219
+ store.hasModals; // ComputedRef<boolean>
220
+
221
+ // Actions
222
+ store.addNotification(notification);
223
+ store.markNotificationRead(id);
224
+ store.removeNotification(id);
225
+ store.incrementUnreadCount();
226
+ store.decrementUnreadCount();
227
+ store.enqueueModal(notification);
228
+ store.dequeueModal(id);
229
+ store.setConnectionState(state);
230
+ store.setBroadcastChannel(channel);
231
+ store.reset();
232
+ ```
233
+
234
+ ### useNotifyKitRealtime
235
+
236
+ Real-time subscription management via Laravel Echo.
237
+
238
+ ```typescript
239
+ const {
240
+ isConnected,
241
+ connectionState,
242
+ connect,
243
+ disconnect,
244
+ reconnect,
245
+ } = useNotifyKitRealtime({
246
+ echo: () => echoInstance,
247
+ autoConnect: true,
248
+ onNotification: (notification) => {
249
+ // Called when notification received
250
+ },
251
+ onConnectionChange: (state) => {
252
+ // Called when connection state changes
253
+ },
254
+ });
255
+ ```
256
+
257
+ ### useNotifyKitModals
258
+
259
+ Modal queue management for critical notifications.
260
+
261
+ ```typescript
262
+ const {
263
+ currentModal, // Current modal to display (first in queue)
264
+ modalQueue, // All queued modals
265
+ hasModals, // Are there modals to show?
266
+ isLoading, // Loading outstanding modals?
267
+ loadOutstandingModals, // Fetch from API on login/refresh
268
+ ack, // Acknowledge a modal
269
+ snooze, // Snooze a modal
270
+ dismissCurrent, // Ack current modal
271
+ snoozeCurrent, // Snooze current modal
272
+ getSnoozeOptions, // Get allowed snooze durations
273
+ } = useNotifyKitModals();
274
+
275
+ // Acknowledge
276
+ await ack(notificationId);
277
+
278
+ // Snooze for 60 minutes
279
+ await snooze(notificationId, 60);
280
+
281
+ // Get snooze options from modal spec
282
+ const options = getSnoozeOptions(notification);
283
+ // [15, 60, 240, 1440]
284
+ ```
285
+
286
+ ## Toast Policy Helpers
287
+
288
+ Decide which notifications should display as toasts and play sounds.
289
+
290
+ ```typescript
291
+ import {
292
+ defaultToastPolicy,
293
+ createToastPolicy,
294
+ } from '@coding-factory/notify-kit-client';
295
+
296
+ // Default policy
297
+ // - Critical modals (requires_ack): no toast (modal is the UX)
298
+ // - Social category: no toast (too frequent)
299
+ // - Security category: always toast + play sound
300
+ // - Danger level: play sound
301
+ // - Everything else: toast, no sound
302
+
303
+ if (defaultToastPolicy.shouldToast(notification)) {
304
+ showToast(notification);
305
+ }
306
+
307
+ if (defaultToastPolicy.shouldPlaySound(notification)) {
308
+ playSound();
309
+ }
310
+
311
+ // Custom policy
312
+ const customPolicy = createToastPolicy({
313
+ // Enable toasts for social
314
+ toastCategories: {
315
+ social: true,
316
+ },
317
+ // Play sound for warnings too
318
+ soundLevels: ['warning', 'danger'],
319
+ // Play sound for orders
320
+ soundCategories: ['security', 'orders'],
321
+ });
322
+ ```
323
+
324
+ ## TypeScript Types
325
+
326
+ All types are exported for use in your application.
327
+
328
+ ```typescript
329
+ import type {
330
+ // Core notification types
331
+ NotifyKitNotification,
332
+ NotificationCategory,
333
+ NotificationLevel,
334
+ NotifyKitModalSpec,
335
+ NotifyKitModalState,
336
+
337
+ // API response types
338
+ NotifyKitMeResponse,
339
+ NotifyKitListResponse,
340
+ NotifyKitGroupedEntry,
341
+ NotifyKitUnreadCountResponse,
342
+ NotifyKitPreferences,
343
+ PaginationMeta,
344
+
345
+ // Client configuration
346
+ NotifyKitClientConfig,
347
+ } from '@coding-factory/notify-kit-client';
348
+ ```
349
+
350
+ ### Type Guards
351
+
352
+ Validate data at runtime:
353
+
354
+ ```typescript
355
+ import {
356
+ isNotifyKitNotification,
357
+ isModalEnabled,
358
+ isValidCategory,
359
+ isValidLevel,
360
+ isPaginationMeta,
361
+ isMeResponse,
362
+ isUnreadCountResponse,
363
+ isModalSpec,
364
+ isChannelPreferences,
365
+ } from '@coding-factory/notify-kit-client';
366
+
367
+ // Validate incoming notification from Echo
368
+ if (isNotifyKitNotification(data)) {
369
+ store.addNotification(data);
370
+ }
371
+
372
+ // Check if notification has active modal
373
+ if (isModalEnabled(notification)) {
374
+ store.enqueueModal(notification);
375
+ }
376
+ ```
377
+
378
+ ## Recommended UX Patterns
379
+
380
+ ### Notification Bell
381
+
382
+ ```vue
383
+ <script setup lang="ts">
384
+ const { client, state } = useNotifyKitNuxt();
385
+
386
+ async function loadNotifications() {
387
+ const response = await client.listNotifications({ per_page: 10 });
388
+ state.notifications = response.data;
389
+ }
390
+
391
+ async function markAsRead(id: string) {
392
+ await client.markRead(id);
393
+ // Optimistically update UI
394
+ const notification = state.notifications.find(n => n.id === id);
395
+ if (notification) notification.read_at = new Date().toISOString();
396
+ state.unreadCount--;
397
+ }
398
+ </script>
399
+
400
+ <template>
401
+ <div class="bell">
402
+ <button @click="toggle">
403
+ 🔔
404
+ <span v-if="state.unreadCount > 0" class="badge">
405
+ {{ state.unreadCount }}
406
+ </span>
407
+ </button>
408
+ <!-- Dropdown with notification list -->
409
+ </div>
410
+ </template>
411
+ ```
412
+
413
+ ### Modal Manager
414
+
415
+ ```vue
416
+ <script setup lang="ts">
417
+ const { currentModal, hasModals, ack, snooze, getSnoozeOptions } = useNotifyKitModals();
418
+
419
+ async function handleAck() {
420
+ if (currentModal.value) {
421
+ await ack(currentModal.value.id);
422
+ }
423
+ }
424
+
425
+ async function handleSnooze(minutes: number) {
426
+ if (currentModal.value) {
427
+ await snooze(currentModal.value.id, minutes);
428
+ }
429
+ }
430
+ </script>
431
+
432
+ <template>
433
+ <Teleport to="body">
434
+ <div v-if="hasModals && currentModal" class="modal-overlay">
435
+ <div class="modal">
436
+ <h2>{{ currentModal.title }}</h2>
437
+ <p>{{ currentModal.body }}</p>
438
+
439
+ <div class="actions">
440
+ <select @change="handleSnooze(Number($event.target.value))">
441
+ <option disabled selected>Remind me later</option>
442
+ <option v-for="min in getSnoozeOptions(currentModal)" :value="min">
443
+ {{ formatMinutes(min) }}
444
+ </option>
445
+ </select>
446
+
447
+ <button @click="handleAck">Acknowledge</button>
448
+ </div>
449
+ </div>
450
+ </div>
451
+ </Teleport>
452
+ </template>
453
+ ```
454
+
455
+ ## Examples
456
+
457
+ See the [examples directory](./examples/) for complete working examples:
458
+
459
+ - **Vue 3 App** - `examples/vue-app/`
460
+ - **Nuxt 3 App** - `examples/nuxt-app/`
461
+
462
+ ## API Reference
463
+
464
+ ### Client Configuration
465
+
466
+ ```typescript
467
+ interface NotifyKitClientConfig {
468
+ /** Base URL for the API (e.g., '/api/notify-kit') */
469
+ baseUrl: string;
470
+
471
+ /** Optional function to provide auth headers */
472
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
473
+
474
+ /** Optional custom fetch function */
475
+ fetch?: typeof fetch;
476
+ }
477
+ ```
478
+
479
+ ### Module Options (Nuxt)
480
+
481
+ ```typescript
482
+ interface NotifyKitModuleOptions {
483
+ /** Base URL for the API (default: '/api/notify-kit') */
484
+ baseUrl?: string;
485
+
486
+ /** Auto-connect to real-time on client (default: true) */
487
+ autoConnect?: boolean;
488
+ }
489
+ ```
490
+
491
+ ## Requirements
492
+
493
+ - Vue 3.4+
494
+ - TypeScript 5.0+
495
+ - For real-time: Laravel Echo + Pusher.js (or Reverb)
496
+ - Backend: [notify-kit](../notify-kit/) Laravel package
497
+
498
+ ## License
499
+
500
+ MIT