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.
- package/README.md +508 -6
- package/SETUP_CHECKLIST.md +96 -1
- 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 +12 -9
- 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 +30 -7
- 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 +3 -3
- 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
|
-
|
|
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).
|
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
|
|
|
@@ -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 [
|
|
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
|
|
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"}
|