hazo_chat 2.0.9 → 2.0.11

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.
Files changed (38) hide show
  1. package/README.md +508 -6
  2. package/SETUP_CHECKLIST.md +96 -1
  3. package/dist/api/index.d.ts +2 -0
  4. package/dist/api/index.d.ts.map +1 -1
  5. package/dist/api/index.js +1 -0
  6. package/dist/api/index.js.map +1 -1
  7. package/dist/api/unread_count.d.ts +57 -0
  8. package/dist/api/unread_count.d.ts.map +1 -0
  9. package/dist/api/unread_count.js +86 -0
  10. package/dist/api/unread_count.js.map +1 -0
  11. package/dist/components/hazo_chat/hazo_chat.d.ts.map +1 -1
  12. package/dist/components/hazo_chat/hazo_chat.js +12 -9
  13. package/dist/components/hazo_chat/hazo_chat.js.map +1 -1
  14. package/dist/components/hazo_chat/hazo_chat_header.d.ts +1 -1
  15. package/dist/components/hazo_chat/hazo_chat_header.d.ts.map +1 -1
  16. package/dist/components/hazo_chat/hazo_chat_header.js +2 -2
  17. package/dist/components/hazo_chat/hazo_chat_header.js.map +1 -1
  18. package/dist/components/hazo_chat/hazo_chat_messages.d.ts +1 -1
  19. package/dist/components/hazo_chat/hazo_chat_messages.d.ts.map +1 -1
  20. package/dist/components/hazo_chat/hazo_chat_messages.js +2 -2
  21. package/dist/components/hazo_chat/hazo_chat_messages.js.map +1 -1
  22. package/dist/components/ui/button.d.ts +1 -1
  23. package/dist/components/ui/chat_bubble.d.ts +2 -2
  24. package/dist/components/ui/chat_bubble.d.ts.map +1 -1
  25. package/dist/components/ui/chat_bubble.js +30 -7
  26. package/dist/components/ui/chat_bubble.js.map +1 -1
  27. package/dist/components/ui/hover-card.d.ts +13 -0
  28. package/dist/components/ui/hover-card.d.ts.map +1 -0
  29. package/dist/components/ui/hover-card.js +17 -0
  30. package/dist/components/ui/hover-card.js.map +1 -0
  31. package/dist/components/ui/index.d.ts +1 -0
  32. package/dist/components/ui/index.d.ts.map +1 -1
  33. package/dist/components/ui/index.js +1 -0
  34. package/dist/components/ui/index.js.map +1 -1
  35. package/dist/types/index.d.ts +16 -0
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/package.json +3 -3
  38. package/UI_DESIGN_STANDARDS.md +0 -315
package/README.md CHANGED
@@ -119,7 +119,178 @@ See `test-app/src/app/globals.css` for complete variable definitions with exampl
119
119
 
120
120
  ### UI Design Standards
121
121
 
122
- For detailed visual design specifications, component behavior, and layout standards, see [UI_DESIGN_STANDARDS.md](./UI_DESIGN_STANDARDS.md).
122
+ This section defines the visual design standards and component behavior specifications for hazo_chat. All consuming projects should follow these standards to ensure consistent UI appearance and behavior.
123
+
124
+ #### Visual Design Elements
125
+
126
+ **Document Preview Icon:**
127
+ - Location: Empty state in document viewer (`HazoChatDocumentViewer`)
128
+ - Icon: `IoDocumentOutline` (from `react-icons/io5`)
129
+ - Size: `w-12 h-12` (48px × 48px)
130
+ - Opacity: `opacity-50`
131
+ - Text: "Select a document to preview" in `text-sm`
132
+
133
+ **REFERENCES Section Font:**
134
+ - Location: References section header
135
+ - Font size: `text-[9px]` (9px)
136
+ - Font weight: `font-medium`
137
+ - Text transform: `uppercase`
138
+ - Letter spacing: `tracking-wider`
139
+ - Color: `text-muted-foreground`
140
+
141
+ **Input Area Padding:**
142
+ - Location: Chat input container (`HazoChatInput`)
143
+ - Padding: `p-4` (16px on all sides)
144
+ - Border: `border-t` (top border only)
145
+ - Background: `bg-background`
146
+
147
+ **Message Timestamp Display:**
148
+ - Location: Chat bubble footer (`ChatBubble`)
149
+ - Only timestamp is displayed (no status icons for sent/unread messages)
150
+ - Font size: `text-xs`
151
+ - Color: `text-muted-foreground`
152
+ - Format: 24-hour format (e.g., "10:37 AM", "15:51")
153
+ - Timezone: Respects `timezone` prop (default: "GMT+10")
154
+
155
+ **Read Receipt Indicator:**
156
+ - Location: Chat bubble footer, after timestamp
157
+ - Icon: `IoCheckmarkDoneSharp` (from `react-icons/io5`)
158
+ - Size: `h-4 w-4` (16px × 16px)
159
+ - Color: `text-green-500`
160
+ - Display condition: Only shown when `read_at` is not null AND message is from sender
161
+ - Position: After timestamp with `gap-1` spacing
162
+
163
+ #### Component Behavior
164
+
165
+ **Close Button:**
166
+ - Visibility: Always visible when `on_close` prop is provided to `HazoChat` component
167
+ - Icon: `IoClose` (from `react-icons/io5`)
168
+ - Size: `h-8 w-8` (32px × 32px)
169
+ - Variant: `ghost`
170
+ - Hover state: `hover:bg-destructive/10 hover:text-destructive`
171
+ - Position: Top-right of header, after refresh button
172
+
173
+ **Hamburger Menu Button:**
174
+ - Desktop behavior: Hidden (`md:hidden` class)
175
+ - Mobile behavior: Visible on screens < 768px
176
+ - Purpose: Toggle document viewer sidebar on mobile
177
+ - Icon: `IoMenuOutline` (from `react-icons/io5`)
178
+ - Size: `h-8 w-8` (32px × 32px)
179
+ - Position: Top-left of header, before title
180
+
181
+ **Important:** If hamburger button appears on desktop, check Tailwind CSS configuration and ensure `md:` breakpoint utilities are working correctly.
182
+
183
+ **Document Viewer Toggle Button:**
184
+ - Icon: Chevron (`IoChevronBack` when expanded, `IoChevronForward` when collapsed)
185
+ - Size: `h-8 w-6` (32px height × 24px width)
186
+ - Position: Absolute, vertically centered between columns
187
+ - Variant: `outline`
188
+ - Border: `rounded-r-md rounded-l-none border-l-0`
189
+ - Behavior: Smooth transitions with `transition-all duration-300`
190
+ - Desktop: Visible when document viewer column is present
191
+ - Mobile: Hidden when sidebar is closed
192
+
193
+ **References Section Collapse/Expand:**
194
+ - Collapsed height: `max-h-8`
195
+ - Expanded height: `max-h-96`
196
+ - Transition: `transition-all duration-300 ease-in-out`
197
+ - Indicator: Chevron icon (`IoChevronDown` when collapsed, `IoChevronUp` when expanded)
198
+ - Default state: Collapsed when no references, expanded when references exist
199
+
200
+ #### Layout Standards
201
+
202
+ **Chat Input Area:**
203
+ - Layout: Flex container with `flex items-center gap-2`
204
+ - Components: Single `Input` field and Send button (`Button` with `IoSend` icon)
205
+ - No attachment buttons: Attachment/image buttons removed for simplified design
206
+ - Input padding: `p-4` on container
207
+ - Button alignment: Aligned with input height using flex
208
+
209
+ **Button Alignment and Sizing:**
210
+ - Send button: Standard button size, aligned with input
211
+ - All interactive buttons: Consistent sizing for visual harmony
212
+ - Icon sizes within buttons: `w-4 h-4` (16px × 16px)
213
+
214
+ **Responsive Breakpoints:**
215
+ - Mobile: < 768px (default)
216
+ - Desktop: >= 768px (`md:` prefix)
217
+ - Standard Tailwind breakpoints used throughout
218
+
219
+ #### Container Requirements
220
+
221
+ **Important:** When wrapping HazoChat in containers (e.g., Card components):
222
+
223
+ 1. **Avoid nested `overflow-hidden`**: Nested overflow-hidden containers can clip rounded corners. Use overflow-hidden only on the HazoChat component itself.
224
+
225
+ 2. **Padding**: If wrapping in a Card, ensure proper padding is maintained. Avoid `p-0` on CardContent as it may affect internal spacing.
226
+
227
+ 3. **Tailwind Configuration**: Ensure `./node_modules/hazo_chat/dist/**/*.{js,ts,jsx,tsx}` is included in your Tailwind `content` array so all utility classes (including `rounded-*` classes) are compiled.
228
+
229
+ #### Typography
230
+
231
+ **Font Families:**
232
+ - Primary: System font stack or custom font via `--font-sans` CSS variable
233
+ - Monospace: System monospace or custom font via `--font-mono` CSS variable
234
+
235
+ **Font Sizes:**
236
+ - Header title: `text-sm` (14px)
237
+ - Header subtitle: `text-xs` (12px)
238
+ - Message text: `text-sm` (14px)
239
+ - Timestamp: `text-xs` (12px)
240
+ - References header: `text-[9px]` (9px)
241
+ - Empty state text: `text-sm` (14px)
242
+
243
+ #### Spacing and Padding
244
+
245
+ **Standard Padding Values:**
246
+ - Container padding: `p-4` (16px)
247
+ - Header padding: `px-4` (16px horizontal)
248
+ - Message bubble padding: `px-4 py-2` (16px horizontal, 8px vertical)
249
+ - Gap between elements: `gap-2` (8px) or `gap-1` (4px) for tight spacing
250
+
251
+ #### Animation and Transitions
252
+
253
+ **Standard Transitions:**
254
+ - Component state changes: `transition-all duration-300`
255
+ - Hover states: `transition-colors`
256
+ - Smooth animations for expand/collapse: `ease-in-out`
257
+
258
+ **Loading States:**
259
+ - Skeleton loaders for initial message load
260
+ - Spinner animation for refresh button when loading
261
+
262
+ #### Accessibility
263
+
264
+ **ARIA Labels:**
265
+ All interactive elements must have appropriate ARIA labels:
266
+ - Input fields: `aria-label="Message input"`
267
+ - Buttons: `aria-label` describing action (e.g., "Send message", "Refresh chat history")
268
+ - Close button: `aria-label="Close chat"`
269
+ - Toggle buttons: `aria-label` and `aria-expanded` attributes
270
+
271
+ **Keyboard Navigation:**
272
+ - Send message: Enter key
273
+ - Close dialogs: Escape key (handled by Alert Dialog component)
274
+
275
+ #### Component Dependencies
276
+
277
+ **Required External Components:**
278
+ The following components must be available in consuming projects:
279
+ 1. **AlertDialog** (shadcn/ui style) - Used for user acknowledgment dialogs, not included in hazo_chat package
280
+ 2. **Toaster** (from `sonner` package) - Used for toast notifications, must be added to root layout with `position="top-right"` and `richColors` prop
281
+
282
+ **Included Components:**
283
+ The following UI components are included in hazo_chat package:
284
+ - Button, Input, Textarea, Avatar, ScrollArea, Tooltip, Separator, Badge
285
+ - ChatBubble (chat-specific), LoadingSkeleton (chat-specific)
286
+
287
+ **Example References:**
288
+ For complete implementation examples, see:
289
+ - `test-app/src/app/page.tsx` - Usage example
290
+ - `test-app/src/app/layout.tsx` - Toaster setup
291
+ - `test-app/src/components/ui/alert-dialog.tsx` - Alert Dialog implementation
292
+ - `test-app/src/app/globals.css` - CSS variables example
293
+ - `test-app/tailwind.config.ts` - Tailwind configuration example
123
294
 
124
295
  ## Quick Start
125
296
 
@@ -173,6 +344,7 @@ hazo_chat requires these API endpoints:
173
344
  |----------|--------|-------------|
174
345
  | `/api/hazo_chat/messages` | GET | Fetch chat messages |
175
346
  | `/api/hazo_chat/messages` | POST | Send a new message |
347
+ | `/api/hazo_chat/unread_count` | GET | Get unread message counts by reference_id (optional) |
176
348
  | `/api/hazo_auth/me` | GET | Get current authenticated user |
177
349
  | `/api/hazo_auth/profiles` | POST | Fetch user profiles by IDs |
178
350
 
@@ -201,6 +373,157 @@ export { GET, POST };
201
373
 
202
374
  If you need more control, implement the endpoints manually. See [SETUP_CHECKLIST.md](./SETUP_CHECKLIST.md) for detailed examples.
203
375
 
376
+ ## Library Functions
377
+
378
+ hazo_chat provides server-side library functions that can be used in API routes, server components, or server actions.
379
+
380
+ ### hazo_chat_get_unread_count
381
+
382
+ Get unread message counts grouped by reference_id for a receiver user.
383
+
384
+ **Purpose:** Returns an array of reference IDs with the count of unread messages (where `read_at` is `null`) for a given receiver user ID. Useful for displaying unread message badges or notifications.
385
+
386
+ **Usage:**
387
+
388
+ ```typescript
389
+ import { createUnreadCountFunction } from 'hazo_chat/api';
390
+ import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
391
+
392
+ // Create the function using the factory
393
+ const hazo_chat_get_unread_count = createUnreadCountFunction({
394
+ getHazoConnect: () => getHazoConnectSingleton()
395
+ });
396
+
397
+ // Use the function
398
+ const unreadCounts = await hazo_chat_get_unread_count('receiver-user-id-123');
399
+ // Returns: [
400
+ // { reference_id: 'ref-1', count: 5 },
401
+ // { reference_id: 'ref-2', count: 3 },
402
+ // { reference_id: '', count: 1 } // Empty reference_id for general messages
403
+ // ]
404
+ ```
405
+
406
+ **Return Type:**
407
+
408
+ ```typescript
409
+ interface UnreadCountResult {
410
+ reference_id: string; // The reference ID (empty string for messages without reference)
411
+ count: number; // Number of unread messages for this reference
412
+ }
413
+ ```
414
+
415
+ **Function Behavior:**
416
+ - Only counts messages where `read_at` is `null` and `deleted_at` is `null`
417
+ - Groups results by `reference_id`
418
+ - Sorts results by count (descending - most unread first)
419
+ - Returns empty array if no unread messages found
420
+ - Returns empty array on errors (doesn't throw)
421
+
422
+ **Example: API Route Implementation**
423
+
424
+ ```typescript
425
+ // app/api/hazo_chat/unread_count/route.ts
426
+ import { createUnreadCountFunction } from 'hazo_chat/api';
427
+ import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
428
+ import { NextRequest, NextResponse } from 'next/server';
429
+
430
+ export const dynamic = 'force-dynamic';
431
+
432
+ const hazo_chat_get_unread_count = createUnreadCountFunction({
433
+ getHazoConnect: () => getHazoConnectSingleton()
434
+ });
435
+
436
+ export async function GET(request: NextRequest) {
437
+ try {
438
+ const { searchParams } = new URL(request.url);
439
+ const receiver_user_id = searchParams.get('receiver_user_id');
440
+
441
+ if (!receiver_user_id) {
442
+ return NextResponse.json(
443
+ {
444
+ success: false,
445
+ error: 'receiver_user_id is required',
446
+ unread_counts: []
447
+ },
448
+ { status: 400 }
449
+ );
450
+ }
451
+
452
+ const unread_counts = await hazo_chat_get_unread_count(receiver_user_id);
453
+
454
+ return NextResponse.json({
455
+ success: true,
456
+ receiver_user_id,
457
+ unread_counts,
458
+ total_references: unread_counts.length,
459
+ total_unread: unread_counts.reduce((sum, item) => sum + item.count, 0)
460
+ });
461
+ } catch (error) {
462
+ const error_message = error instanceof Error ? error.message : 'Unknown error';
463
+ return NextResponse.json(
464
+ {
465
+ success: false,
466
+ error: error_message,
467
+ unread_counts: []
468
+ },
469
+ { status: 500 }
470
+ );
471
+ }
472
+ }
473
+ ```
474
+
475
+ **Example: Server Component Usage**
476
+
477
+ ```typescript
478
+ // app/chat/unread-badge.tsx
479
+ import { createUnreadCountFunction } from 'hazo_chat/api';
480
+ import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
481
+
482
+ const hazo_chat_get_unread_count = createUnreadCountFunction({
483
+ getHazoConnect: () => getHazoConnectSingleton()
484
+ });
485
+
486
+ export default async function UnreadBadge({
487
+ receiver_user_id
488
+ }: {
489
+ receiver_user_id: string
490
+ }) {
491
+ const unread_counts = await hazo_chat_get_unread_count(receiver_user_id);
492
+ const total_unread = unread_counts.reduce((sum, item) => sum + item.count, 0);
493
+
494
+ if (total_unread === 0) return null;
495
+
496
+ return (
497
+ <div className="flex gap-2">
498
+ {unread_counts.map((item) => (
499
+ <div key={item.reference_id || 'general'}>
500
+ <span>{item.reference_id || 'General'}: {item.count}</span>
501
+ </div>
502
+ ))}
503
+ <span>Total: {total_unread}</span>
504
+ </div>
505
+ );
506
+ }
507
+ ```
508
+
509
+ **Example: Server Action Usage**
510
+
511
+ ```typescript
512
+ // app/actions/chat.ts
513
+ 'use server';
514
+
515
+ import { createUnreadCountFunction } from 'hazo_chat/api';
516
+ import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
517
+
518
+ const hazo_chat_get_unread_count = createUnreadCountFunction({
519
+ getHazoConnect: () => getHazoConnectSingleton()
520
+ });
521
+
522
+ export async function getUnreadCounts(receiver_user_id: string) {
523
+ return await hazo_chat_get_unread_count(receiver_user_id);
524
+ }
525
+ ```
526
+
204
527
  ## Props Reference
205
528
 
206
529
  ### HazoChatProps
@@ -219,6 +542,9 @@ If you need more control, implement the endpoints manually. See [SETUP_CHECKLIST
219
542
  | `title` | `string` | ❌ | - | Chat header title |
220
543
  | `subtitle` | `string` | ❌ | - | Chat header subtitle |
221
544
  | `on_close` | `() => void` | ❌ | - | Close button callback |
545
+ | `show_sidebar_toggle` | `boolean` | ❌ | `false` | Show sidebar toggle button (hamburger menu) |
546
+ | `show_delete_button` | `boolean` | ❌ | `true` | Show delete button on chat bubbles |
547
+ | `bubble_radius` | `'default' \| 'full'` | ❌ | `'default'` | Bubble border radius style: `'default'` (rounded with tail) or `'full'` (fully round) |
222
548
  | `className` | `string` | ❌ | - | Additional CSS classes |
223
549
 
224
550
  ### Example with All Props
@@ -242,6 +568,186 @@ If you need more control, implement the endpoints manually. See [SETUP_CHECKLIST
242
568
  />
243
569
  ```
244
570
 
571
+ ## Customization
572
+
573
+ hazo_chat provides props to customize component appearance and behavior without needing CSS overrides. This eliminates the need for extensive CSS customizations in consuming projects.
574
+
575
+ ### Common Customizations
576
+
577
+ #### Hide Sidebar Toggle Button (Hamburger Menu)
578
+
579
+ By default, the sidebar toggle button is hidden (`show_sidebar_toggle={false}`). To show it:
580
+
581
+ ```tsx
582
+ <HazoChat
583
+ receiver_user_id="user-123"
584
+ show_sidebar_toggle={true} // Show hamburger menu button
585
+ />
586
+ ```
587
+
588
+ #### Hide Delete Button on Chat Bubbles
589
+
590
+ To hide the delete button on chat bubbles:
591
+
592
+ ```tsx
593
+ <HazoChat
594
+ receiver_user_id="user-123"
595
+ show_delete_button={false} // Hide delete button
596
+ />
597
+ ```
598
+
599
+ #### Make Chat Bubbles Fully Round
600
+
601
+ To make all chat bubbles fully round (instead of the default style with a tail):
602
+
603
+ ```tsx
604
+ <HazoChat
605
+ receiver_user_id="user-123"
606
+ bubble_radius="full" // Fully round all corners
607
+ />
608
+ ```
609
+
610
+ ### Customization Props Summary
611
+
612
+ | Prop | Default | Options | Description |
613
+ |------|--------|---------|------------|
614
+ | `show_sidebar_toggle` | `false` | `boolean` | Show/hide the hamburger menu button |
615
+ | `show_delete_button` | `true` | `boolean` | Show/hide delete button on chat bubbles |
616
+ | `bubble_radius` | `'default'` | `'default' \| 'full'` | Bubble border radius style |
617
+
618
+ ### Example: Full Customization
619
+
620
+ ```tsx
621
+ <HazoChat
622
+ receiver_user_id="user-123"
623
+ reference_id="project-456"
624
+ show_sidebar_toggle={false} // Hide hamburger menu
625
+ show_delete_button={false} // Hide delete buttons
626
+ bubble_radius="full" // Fully round bubbles
627
+ title="Project Chat"
628
+ className="h-[600px]"
629
+ />
630
+ ```
631
+
632
+ ### Why Use Props Instead of CSS?
633
+
634
+ Using props instead of CSS overrides provides:
635
+ - **Type safety**: TypeScript will catch invalid values
636
+ - **Consistency**: Ensures all instances use the same styling
637
+ - **Maintainability**: Easier to update across the codebase
638
+ - **No CSS conflicts**: Avoids specificity issues with Tailwind utilities
639
+
640
+ If you need more advanced styling that isn't covered by props, you can still use CSS overrides, but props should cover most common customization needs.
641
+
642
+ ## Checking for Messages
643
+
644
+ If you need to check whether messages exist for a given reference (without loading all messages), you can call the messages API endpoint directly. This is useful for showing indicators or badges.
645
+
646
+ **Important:** The API requires `receiver_user_id` as a query parameter and returns an object response, not a direct array.
647
+
648
+ ### Correct API Usage Pattern
649
+
650
+ ```typescript
651
+ // ✅ CORRECT: Include receiver_user_id and handle object response
652
+ async function checkMessagesExist(
653
+ recipient_user_id: string,
654
+ reference_id: string,
655
+ reference_type?: string
656
+ ): Promise<boolean> {
657
+ const params = new URLSearchParams({
658
+ receiver_user_id: recipient_user_id, // ✅ Required parameter
659
+ reference_id,
660
+ ...(reference_type && { reference_type }),
661
+ });
662
+
663
+ const response = await fetch(`/api/hazo_chat/messages?${params.toString()}`, {
664
+ credentials: 'include'
665
+ });
666
+
667
+ if (!response.ok) {
668
+ return false;
669
+ }
670
+
671
+ const data = await response.json();
672
+
673
+ // ✅ Handle object response format: { success: true, messages: [], current_user_id }
674
+ const messages = data.messages || data; // Handle both object and array responses
675
+ const has_messages_result = Array.isArray(messages) && messages.length > 0;
676
+
677
+ return has_messages_result;
678
+ }
679
+ ```
680
+
681
+ ### Common Mistakes to Avoid
682
+
683
+ ```typescript
684
+ // ❌ WRONG: Missing receiver_user_id parameter
685
+ const params = new URLSearchParams({
686
+ reference_id,
687
+ ...(reference_type && { reference_type }),
688
+ });
689
+ // This will return a 400 error: "receiver_user_id is required"
690
+
691
+ // ❌ WRONG: Expecting direct array response
692
+ const data = await response.json();
693
+ const has_messages_result = Array.isArray(data) && data.length > 0;
694
+ // This fails because API returns: { success: true, messages: [], current_user_id }
695
+ ```
696
+
697
+ ### Example: Custom Hook Implementation
698
+
699
+ ```typescript
700
+ 'use client';
701
+
702
+ import { useState, useEffect } from 'react';
703
+
704
+ function useChatMessagesCheck(
705
+ recipient_user_id: string,
706
+ reference_id: string,
707
+ reference_type?: string
708
+ ) {
709
+ const [has_messages, set_has_messages] = useState(false);
710
+ const [is_checking, set_is_checking] = useState(true);
711
+
712
+ useEffect(() => {
713
+ async function check() {
714
+ if (!recipient_user_id || !reference_id) {
715
+ set_is_checking(false);
716
+ return;
717
+ }
718
+
719
+ try {
720
+ const params = new URLSearchParams({
721
+ receiver_user_id: recipient_user_id, // ✅ Required
722
+ reference_id,
723
+ ...(reference_type && { reference_type }),
724
+ });
725
+
726
+ const response = await fetch(
727
+ `/api/hazo_chat/messages?${params.toString()}`,
728
+ { credentials: 'include' }
729
+ );
730
+
731
+ if (response.ok) {
732
+ const data = await response.json();
733
+ const messages = data.messages || data; // ✅ Handle object response
734
+ set_has_messages(Array.isArray(messages) && messages.length > 0);
735
+ }
736
+ } catch (error) {
737
+ console.error('[useChatMessagesCheck] Error:', error);
738
+ set_has_messages(false);
739
+ } finally {
740
+ set_is_checking(false);
741
+ }
742
+ }
743
+
744
+ check();
745
+ }, [recipient_user_id, reference_id, reference_type]);
746
+
747
+ return { has_messages, is_checking };
748
+ }
749
+ ```
750
+
245
751
  ## Hooks
246
752
 
247
753
  ### useChatMessages
@@ -536,7 +1042,7 @@ npm run build:test-app
536
1042
  import { HazoChat, useChatMessages, useChatReferences, useFileUpload } from 'hazo_chat';
537
1043
 
538
1044
  // API handlers (for server-side routes)
539
- import { createMessagesHandler } from 'hazo_chat/api';
1045
+ import { createMessagesHandler, createUnreadCountFunction } from 'hazo_chat/api';
540
1046
 
541
1047
  // Components only
542
1048
  import { HazoChat, ChatBubble } from 'hazo_chat/components';
@@ -593,7 +1099,3 @@ MIT © Pubs Abayasiri
593
1099
  ---
594
1100
 
595
1101
  For detailed setup instructions, see [SETUP_CHECKLIST.md](./SETUP_CHECKLIST.md).
596
-
597
- For UI design standards and visual specifications, see [UI_DESIGN_STANDARDS.md](./UI_DESIGN_STANDARDS.md).
598
-
599
- For UI design standards and specifications, see [UI_DESIGN_STANDARDS.md](./UI_DESIGN_STANDARDS.md).
@@ -233,11 +233,101 @@ export async function POST(request: NextRequest) {
233
233
  }
234
234
  ```
235
235
 
236
+ ### Step 4.4: Unread Count API (Optional - For Unread Badges)
237
+
238
+ **File: `src/app/api/hazo_chat/unread_count/route.ts`**
239
+
240
+ This endpoint is optional but useful for displaying unread message counts or badges in your UI.
241
+
242
+ ```typescript
243
+ /**
244
+ * API route to get unread message counts grouped by reference_id
245
+ * Uses the exportable library function from hazo_chat
246
+ */
247
+
248
+ export const dynamic = 'force-dynamic';
249
+
250
+ import { createUnreadCountFunction } from 'hazo_chat/api';
251
+ import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
252
+ import { NextRequest, NextResponse } from 'next/server';
253
+
254
+ // Create the unread count function using the factory
255
+ const hazo_chat_get_unread_count = createUnreadCountFunction({
256
+ getHazoConnect: () => getHazoConnectSingleton()
257
+ });
258
+
259
+ export async function GET(request: NextRequest) {
260
+ try {
261
+ const { searchParams } = new URL(request.url);
262
+ const receiver_user_id = searchParams.get('receiver_user_id');
263
+
264
+ if (!receiver_user_id) {
265
+ return NextResponse.json(
266
+ {
267
+ success: false,
268
+ error: 'receiver_user_id is required',
269
+ unread_counts: []
270
+ },
271
+ { status: 400 }
272
+ );
273
+ }
274
+
275
+ // Call the library function
276
+ const unread_counts = await hazo_chat_get_unread_count(receiver_user_id);
277
+
278
+ return NextResponse.json({
279
+ success: true,
280
+ receiver_user_id,
281
+ unread_counts,
282
+ total_references: unread_counts.length,
283
+ total_unread: unread_counts.reduce((sum, item) => sum + item.count, 0)
284
+ });
285
+ } catch (error) {
286
+ const error_message = error instanceof Error ? error.message : 'Unknown error';
287
+ console.error('[hazo_chat/unread_count] Error:', error_message, error);
288
+
289
+ return NextResponse.json(
290
+ {
291
+ success: false,
292
+ error: error_message,
293
+ unread_counts: []
294
+ },
295
+ { status: 500 }
296
+ );
297
+ }
298
+ }
299
+ ```
300
+
301
+ **What this endpoint does:**
302
+ - Takes `receiver_user_id` as a query parameter
303
+ - Returns an array of objects with `reference_id` and `count` of unread messages
304
+ - Only counts messages where `read_at` is `null` and `deleted_at` is `null`
305
+ - Groups results by `reference_id`
306
+ - Sorts by count (descending - most unread first)
307
+
308
+ **Response format:**
309
+ ```json
310
+ {
311
+ "success": true,
312
+ "receiver_user_id": "user-123",
313
+ "unread_counts": [
314
+ { "reference_id": "ref-1", "count": 5 },
315
+ { "reference_id": "ref-2", "count": 3 },
316
+ { "reference_id": "", "count": 1 }
317
+ ],
318
+ "total_references": 3,
319
+ "total_unread": 9
320
+ }
321
+ ```
322
+
323
+ **Note:** This endpoint is optional. Only create it if you need to display unread message counts in your UI.
324
+
236
325
  ### API Routes Summary
237
326
 
238
327
  | Endpoint | Method | File | Purpose |
239
328
  |----------|--------|------|---------|
240
329
  | `/api/hazo_chat/messages` | GET, POST | `api/hazo_chat/messages/route.ts` | Message CRUD |
330
+ | `/api/hazo_chat/unread_count` | GET | `api/hazo_chat/unread_count/route.ts` | Get unread counts (optional) |
241
331
  | `/api/hazo_auth/me` | GET | `api/hazo_auth/me/route.ts` | Get current user |
242
332
  | `/api/hazo_auth/profiles` | POST | `api/hazo_auth/profiles/route.ts` | Get user profiles |
243
333
 
@@ -246,6 +336,7 @@ export async function POST(request: NextRequest) {
246
336
  - [ ] `GET /api/hazo_auth/me` returns user data when logged in
247
337
  - [ ] `POST /api/hazo_auth/profiles` returns profiles for given IDs
248
338
  - [ ] `GET /api/hazo_chat/messages?receiver_user_id=xxx` works
339
+ - [ ] `GET /api/hazo_chat/unread_count?receiver_user_id=xxx` returns unread counts (if implemented)
249
340
 
250
341
  ---
251
342
 
@@ -704,7 +795,7 @@ module.exports = nextConfig;
704
795
  - [ ] Smooth expand/collapse transitions
705
796
  - [ ] Button position adjusts correctly
706
797
 
707
- For detailed specifications, see [UI_DESIGN_STANDARDS.md](./UI_DESIGN_STANDARDS.md).
798
+ For detailed specifications, see the [UI Design Standards](#ui-design-standards) section in README.md.
708
799
 
709
800
  ---
710
801
 
@@ -735,6 +826,9 @@ curl -X POST http://localhost:3000/api/hazo_auth/profiles \
735
826
 
736
827
  # Test messages endpoint
737
828
  curl "http://localhost:3000/api/hazo_chat/messages?receiver_user_id=user-id"
829
+
830
+ # Test unread count endpoint (if implemented)
831
+ curl "http://localhost:3000/api/hazo_chat/unread_count?receiver_user_id=user-id"
738
832
  ```
739
833
 
740
834
  ### UI Verification & Responsive Behavior
@@ -912,6 +1006,7 @@ npm install hazo_chat hazo_connect
912
1006
 
913
1007
  # 2. Create API routes
914
1008
  mkdir -p src/app/api/hazo_chat/messages
1009
+ mkdir -p src/app/api/hazo_chat/unread_count # Optional: for unread counts
915
1010
  mkdir -p src/app/api/hazo_auth/me
916
1011
  mkdir -p src/app/api/hazo_auth/profiles
917
1012
 
@@ -20,5 +20,7 @@
20
20
  * ```
21
21
  */
22
22
  export { createMessagesHandler } from './messages.js';
23
+ export { createUnreadCountFunction } from './unread_count.js';
23
24
  export type { MessagesHandlerOptions, ChatMessageInput, ChatMessageRecord } from './types.js';
25
+ export type { UnreadCountFunctionOptions, UnreadCountResult } from './unread_count.js';
24
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAG9D,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,0BAA0B,EAC1B,iBAAiB,EAClB,MAAM,mBAAmB,CAAC"}