hazo_chat 2.0.10 → 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.
- package/README.md +336 -1
- package/SETUP_CHECKLIST.md +95 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/unread_count.d.ts +57 -0
- package/dist/api/unread_count.d.ts.map +1 -0
- package/dist/api/unread_count.js +86 -0
- package/dist/api/unread_count.js.map +1 -0
- package/dist/components/hazo_chat/hazo_chat.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat.js +11 -10
- package/dist/components/hazo_chat/hazo_chat.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_header.d.ts +1 -1
- package/dist/components/hazo_chat/hazo_chat_header.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_header.js +2 -2
- package/dist/components/hazo_chat/hazo_chat_header.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.d.ts +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.js +2 -2
- package/dist/components/hazo_chat/hazo_chat_messages.js.map +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/chat_bubble.d.ts +2 -2
- package/dist/components/ui/chat_bubble.d.ts.map +1 -1
- package/dist/components/ui/chat_bubble.js +29 -10
- package/dist/components/ui/chat_bubble.js.map +1 -1
- package/dist/components/ui/hover-card.d.ts +13 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +17 -0
- package/dist/components/ui/hover-card.js.map +1 -0
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/types/index.d.ts +16 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -344,6 +344,7 @@ hazo_chat requires these API endpoints:
|
|
|
344
344
|
|----------|--------|-------------|
|
|
345
345
|
| `/api/hazo_chat/messages` | GET | Fetch chat messages |
|
|
346
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) |
|
|
347
348
|
| `/api/hazo_auth/me` | GET | Get current authenticated user |
|
|
348
349
|
| `/api/hazo_auth/profiles` | POST | Fetch user profiles by IDs |
|
|
349
350
|
|
|
@@ -372,6 +373,157 @@ export { GET, POST };
|
|
|
372
373
|
|
|
373
374
|
If you need more control, implement the endpoints manually. See [SETUP_CHECKLIST.md](./SETUP_CHECKLIST.md) for detailed examples.
|
|
374
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
|
+
|
|
375
527
|
## Props Reference
|
|
376
528
|
|
|
377
529
|
### HazoChatProps
|
|
@@ -390,6 +542,9 @@ If you need more control, implement the endpoints manually. See [SETUP_CHECKLIST
|
|
|
390
542
|
| `title` | `string` | ❌ | - | Chat header title |
|
|
391
543
|
| `subtitle` | `string` | ❌ | - | Chat header subtitle |
|
|
392
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) |
|
|
393
548
|
| `className` | `string` | ❌ | - | Additional CSS classes |
|
|
394
549
|
|
|
395
550
|
### Example with All Props
|
|
@@ -413,6 +568,186 @@ If you need more control, implement the endpoints manually. See [SETUP_CHECKLIST
|
|
|
413
568
|
/>
|
|
414
569
|
```
|
|
415
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
|
+
|
|
416
751
|
## Hooks
|
|
417
752
|
|
|
418
753
|
### useChatMessages
|
|
@@ -707,7 +1042,7 @@ npm run build:test-app
|
|
|
707
1042
|
import { HazoChat, useChatMessages, useChatReferences, useFileUpload } from 'hazo_chat';
|
|
708
1043
|
|
|
709
1044
|
// API handlers (for server-side routes)
|
|
710
|
-
import { createMessagesHandler } from 'hazo_chat/api';
|
|
1045
|
+
import { createMessagesHandler, createUnreadCountFunction } from 'hazo_chat/api';
|
|
711
1046
|
|
|
712
1047
|
// Components only
|
|
713
1048
|
import { HazoChat, ChatBubble } from 'hazo_chat/components';
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -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
|
|
|
@@ -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
|
|
package/dist/api/index.d.ts
CHANGED
|
@@ -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
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/api/index.js
CHANGED
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,2BAA2B;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,2BAA2B;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unread Count Library Function Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a function to get unread message counts grouped by reference_id
|
|
5
|
+
* for a given receiver user ID.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // In your server-side code
|
|
10
|
+
* import { createUnreadCountFunction } from 'hazo_chat/api';
|
|
11
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
12
|
+
*
|
|
13
|
+
* const getUnreadCount = createUnreadCountFunction({
|
|
14
|
+
* getHazoConnect: () => getHazoConnectSingleton()
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Use the function
|
|
18
|
+
* const unreadCounts = await getUnreadCount('receiver-user-id-123');
|
|
19
|
+
* // Returns: [{ reference_id: 'ref-1', count: 5 }, { reference_id: 'ref-2', count: 3 }]
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Options for creating the unread count function
|
|
24
|
+
*/
|
|
25
|
+
export interface UnreadCountFunctionOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Function to get the hazo_connect adapter instance.
|
|
28
|
+
* Called each time the function is invoked to get a fresh connection.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
33
|
+
*
|
|
34
|
+
* const options = {
|
|
35
|
+
* getHazoConnect: () => getHazoConnectSingleton()
|
|
36
|
+
* };
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
getHazoConnect: () => unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Result type for unread count function
|
|
43
|
+
*/
|
|
44
|
+
export interface UnreadCountResult {
|
|
45
|
+
/** The reference ID */
|
|
46
|
+
reference_id: string;
|
|
47
|
+
/** Number of unread messages for this reference */
|
|
48
|
+
count: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a function to get unread message counts by reference_id
|
|
52
|
+
*
|
|
53
|
+
* @param options - Configuration options
|
|
54
|
+
* @returns Function that takes receiver_user_id and returns unread counts
|
|
55
|
+
*/
|
|
56
|
+
export declare function createUnreadCountFunction(options: UnreadCountFunctionOptions): (receiver_user_id: string) => Promise<UnreadCountResult[]>;
|
|
57
|
+
//# sourceMappingURL=unread_count.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unread_count.d.ts","sourceRoot":"","sources":["../../src/api/unread_count.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;;;;;OAYG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,uBAAuB;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,IAUzE,kBAAkB,MAAM,KACvB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA2DhC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unread Count Library Function Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a function to get unread message counts grouped by reference_id
|
|
5
|
+
* for a given receiver user ID.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // In your server-side code
|
|
10
|
+
* import { createUnreadCountFunction } from 'hazo_chat/api';
|
|
11
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
12
|
+
*
|
|
13
|
+
* const getUnreadCount = createUnreadCountFunction({
|
|
14
|
+
* getHazoConnect: () => getHazoConnectSingleton()
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Use the function
|
|
18
|
+
* const unreadCounts = await getUnreadCount('receiver-user-id-123');
|
|
19
|
+
* // Returns: [{ reference_id: 'ref-1', count: 5 }, { reference_id: 'ref-2', count: 3 }]
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { createCrudService } from 'hazo_connect/server';
|
|
23
|
+
/**
|
|
24
|
+
* Creates a function to get unread message counts by reference_id
|
|
25
|
+
*
|
|
26
|
+
* @param options - Configuration options
|
|
27
|
+
* @returns Function that takes receiver_user_id and returns unread counts
|
|
28
|
+
*/
|
|
29
|
+
export function createUnreadCountFunction(options) {
|
|
30
|
+
const { getHazoConnect } = options;
|
|
31
|
+
/**
|
|
32
|
+
* Get unread message counts grouped by reference_id for a receiver user
|
|
33
|
+
*
|
|
34
|
+
* @param receiver_user_id - The user ID to get unread counts for
|
|
35
|
+
* @returns Array of objects with reference_id and count of unread messages
|
|
36
|
+
*/
|
|
37
|
+
return async function hazo_chat_get_unread_count(receiver_user_id) {
|
|
38
|
+
try {
|
|
39
|
+
if (!receiver_user_id || receiver_user_id.trim() === '') {
|
|
40
|
+
console.error('[hazo_chat_get_unread_count] Missing receiver_user_id');
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
console.log('[hazo_chat_get_unread_count] Fetching unread counts for:', {
|
|
44
|
+
receiver_user_id,
|
|
45
|
+
});
|
|
46
|
+
// Get hazo_connect instance and create CRUD service
|
|
47
|
+
const hazoConnect = getHazoConnect();
|
|
48
|
+
const chatService = createCrudService(hazoConnect, 'hazo_chat');
|
|
49
|
+
// Fetch all unread messages for this receiver
|
|
50
|
+
const unread_messages = await chatService.list((qb) => {
|
|
51
|
+
return qb
|
|
52
|
+
.select('*')
|
|
53
|
+
.where('receiver_user_id', 'eq', receiver_user_id)
|
|
54
|
+
.where('read_at', 'is', null)
|
|
55
|
+
.where('deleted_at', 'is', null);
|
|
56
|
+
});
|
|
57
|
+
// Group by reference_id and count
|
|
58
|
+
const count_map = new Map();
|
|
59
|
+
for (const message of unread_messages) {
|
|
60
|
+
const ref_id = message.reference_id || '';
|
|
61
|
+
const current_count = count_map.get(ref_id) || 0;
|
|
62
|
+
count_map.set(ref_id, current_count + 1);
|
|
63
|
+
}
|
|
64
|
+
// Convert map to array of results
|
|
65
|
+
const results = Array.from(count_map.entries()).map(([reference_id, count]) => ({
|
|
66
|
+
reference_id,
|
|
67
|
+
count,
|
|
68
|
+
}));
|
|
69
|
+
// Sort by count descending (most unread first)
|
|
70
|
+
results.sort((a, b) => b.count - a.count);
|
|
71
|
+
console.log('[hazo_chat_get_unread_count] Found unread counts:', {
|
|
72
|
+
receiver_user_id,
|
|
73
|
+
total_references: results.length,
|
|
74
|
+
total_unread: unread_messages.length,
|
|
75
|
+
});
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const error_message = error instanceof Error ? error.message : 'Unknown error';
|
|
80
|
+
console.error('[hazo_chat_get_unread_count] Error:', error_message, error);
|
|
81
|
+
// Return empty array on error rather than throwing
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=unread_count.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unread_count.js","sourceRoot":"","sources":["../../src/api/unread_count.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAkCxD;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAmC;IAC3E,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IAEnC;;;;;OAKG;IACH,OAAO,KAAK,UAAU,0BAA0B,CAC9C,gBAAwB;QAExB,IAAI,CAAC;YACH,IAAI,CAAC,gBAAgB,IAAI,gBAAgB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACxD,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;gBACvE,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,0DAA0D,EAAE;gBACtE,gBAAgB;aACjB,CAAC,CAAC;YAEH,oDAAoD;YACpD,MAAM,WAAW,GAAG,cAAc,EAAwB,CAAC;YAC3D,MAAM,WAAW,GAAG,iBAAiB,CAAoB,WAAW,EAAE,WAAW,CAAC,CAAC;YAEnF,8CAA8C;YAC9C,MAAM,eAAe,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;gBACpD,OAAO,EAAE;qBACN,MAAM,CAAC,GAAG,CAAC;qBACX,KAAK,CAAC,kBAAkB,EAAE,IAAI,EAAE,gBAAgB,CAAC;qBACjD,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC;qBAC5B,KAAK,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC,CAAC,CAAC;YAEH,kCAAkC;YAClC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;YAE5C,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;gBACtC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;gBAC1C,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACjD,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,CAAC,CAAC;YAC3C,CAAC;YAED,kCAAkC;YAClC,MAAM,OAAO,GAAwB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CACtE,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1B,YAAY;gBACZ,KAAK;aACN,CAAC,CACH,CAAC;YAEF,+CAA+C;YAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;YAE1C,OAAO,CAAC,GAAG,CAAC,mDAAmD,EAAE;gBAC/D,gBAAgB;gBAChB,gBAAgB,EAAE,OAAO,CAAC,MAAM;gBAChC,YAAY,EAAE,eAAe,CAAC,MAAM;aACrC,CAAC,CAAC;YAEH,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,aAAa,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YAC/E,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;YAE3E,mDAAmD;YACnD,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hazo_chat.d.ts","sourceRoot":"","sources":["../../../src/components/hazo_chat/hazo_chat.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EACV,aAAa,EAId,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"hazo_chat.d.ts","sourceRoot":"","sources":["../../../src/components/hazo_chat/hazo_chat.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EACV,aAAa,EAId,MAAM,sBAAsB,CAAC;AAyZ9B;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CAyD5C;yBAzDe,QAAQ"}
|