hazo_chat 2.0.16 → 2.1.1
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 +60 -8
- package/SETUP_CHECKLIST.md +266 -10
- package/dist/api/index.d.ts +17 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +16 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/messages.d.ts +52 -1
- package/dist/api/messages.d.ts.map +1 -1
- package/dist/api/messages.js +380 -22
- package/dist/api/messages.js.map +1 -1
- package/dist/api/types.d.ts +25 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat.js +18 -10
- package/dist/components/hazo_chat/hazo_chat.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_header.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_header.js +22 -1
- package/dist/components/hazo_chat/hazo_chat_header.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.d.ts +5 -4
- package/dist/components/hazo_chat/hazo_chat_messages.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.js +153 -7
- package/dist/components/hazo_chat/hazo_chat_messages.js.map +1 -1
- package/dist/components/ui/chat_bubble.d.ts.map +1 -1
- package/dist/components/ui/chat_bubble.js +28 -5
- package/dist/components/ui/chat_bubble.js.map +1 -1
- package/dist/hooks/use_chat_messages.d.ts +3 -3
- package/dist/hooks/use_chat_messages.d.ts.map +1 -1
- package/dist/hooks/use_chat_messages.js +259 -136
- package/dist/hooks/use_chat_messages.js.map +1 -1
- package/dist/types/index.d.ts +108 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A full-featured React chat component library for 1-1 communication with document
|
|
|
12
12
|
- 📄 **Document Viewer** - Built-in PDF and image viewer with expand/collapse toggle, download, and open in new tab actions
|
|
13
13
|
- 👤 **User Profiles** - Avatar display and user information
|
|
14
14
|
- 🔄 **Infinite Scroll** - Cursor-based pagination for message history
|
|
15
|
-
- ✅ **Read Receipts** -
|
|
15
|
+
- ✅ **Read Receipts** - Automatic mark-as-read when messages become visible using Intersection Observer
|
|
16
16
|
- 🗑️ **Soft Delete** - Delete messages with undo capability
|
|
17
17
|
- 🎨 **Customizable** - TailwindCSS-based theming
|
|
18
18
|
- 🚀 **API-First** - No server-side dependencies in client components
|
|
@@ -242,19 +242,28 @@ This section defines the visual design standards and component behavior specific
|
|
|
242
242
|
|
|
243
243
|
**Message Timestamp Display:**
|
|
244
244
|
- Location: Chat bubble footer (`ChatBubble`)
|
|
245
|
-
- Only timestamp is displayed (no status icons for sent/unread messages)
|
|
246
245
|
- Font size: `text-xs`
|
|
247
246
|
- Color: `text-muted-foreground`
|
|
248
|
-
-
|
|
247
|
+
- Time format: 24-hour format (e.g., "10:37", "15:51")
|
|
248
|
+
- Date prefix: Messages before today show date in `dd/MMM` format (e.g., "02/Dec 10:37")
|
|
249
249
|
- Timezone: Respects `timezone` prop (default: "GMT+10")
|
|
250
250
|
|
|
251
|
-
**
|
|
251
|
+
**Message Status Indicators (Sender's Messages Only):**
|
|
252
252
|
- Location: Chat bubble footer, after timestamp
|
|
253
|
-
- Icon: `IoCheckmarkDoneSharp` (from `react-icons/io5`)
|
|
254
|
-
- Size: `h-4 w-4` (16px × 16px)
|
|
255
|
-
- Color: `text-green-500`
|
|
256
|
-
- Display condition: Only shown when `read_at` is not null AND message is from sender
|
|
257
253
|
- Position: After timestamp with `gap-1` spacing
|
|
254
|
+
- Size: `h-4 w-4` (16px × 16px)
|
|
255
|
+
|
|
256
|
+
**Sent Indicator (Grey Single Check):**
|
|
257
|
+
- Icon: `IoCheckmark` (from `react-icons/io5`)
|
|
258
|
+
- Color: `text-muted-foreground` (grey)
|
|
259
|
+
- Display condition: Shown when message is sent but `read_at` is null
|
|
260
|
+
- Meaning: Message delivered but not yet read by recipient
|
|
261
|
+
|
|
262
|
+
**Read Receipt (Green Double Check):**
|
|
263
|
+
- Icon: `IoCheckmarkDoneSharp` (from `react-icons/io5`)
|
|
264
|
+
- Color: `text-green-500` (green)
|
|
265
|
+
- Display condition: Only shown when `read_at` is not null
|
|
266
|
+
- Meaning: Message has been read by recipient
|
|
258
267
|
|
|
259
268
|
#### Component Behavior
|
|
260
269
|
|
|
@@ -293,6 +302,20 @@ This section defines the visual design standards and component behavior specific
|
|
|
293
302
|
- Indicator: Chevron icon (`IoChevronDown` when collapsed, `IoChevronUp` when expanded)
|
|
294
303
|
- Default state: Collapsed when no references, expanded when references exist
|
|
295
304
|
|
|
305
|
+
**Automatic Mark-as-Read:**
|
|
306
|
+
- Detection: Uses Intersection Observer API to detect when messages become visible
|
|
307
|
+
- Trigger threshold: Messages marked as read when 50% visible in the ScrollArea viewport
|
|
308
|
+
- Scope: Only marks messages where the current user is the receiver (not the sender)
|
|
309
|
+
- State tracking: Prevents duplicate marking using in-memory Set
|
|
310
|
+
- API endpoint: Requires `PATCH /api/hazo_chat/messages/[id]/read` route
|
|
311
|
+
- Visual indicator: Green double-checkmark (IoCheckmarkDoneSharp) appears after timestamp
|
|
312
|
+
- Automatic: No user action required - messages are marked as read automatically when scrolled into view
|
|
313
|
+
|
|
314
|
+
**Note:** The mark-as-read functionality requires:
|
|
315
|
+
1. The `HazoChat` component (includes all hooks and logic)
|
|
316
|
+
2. The API route for marking messages as read (see API Routes Setup below)
|
|
317
|
+
3. Messages must be received by the current user (messages sent by current user are not marked)
|
|
318
|
+
|
|
296
319
|
#### Layout Standards
|
|
297
320
|
|
|
298
321
|
**Chat Input Area:**
|
|
@@ -476,6 +499,7 @@ hazo_chat requires these API endpoints:
|
|
|
476
499
|
|----------|--------|-------------|
|
|
477
500
|
| `/api/hazo_chat/messages` | GET | Fetch chat messages |
|
|
478
501
|
| `/api/hazo_chat/messages` | POST | Send a new message |
|
|
502
|
+
| `/api/hazo_chat/messages/[id]/read` | PATCH | Mark a message as read (automatic) |
|
|
479
503
|
| `/api/hazo_chat/unread_count` | GET | Get unread message counts by reference_id (optional) |
|
|
480
504
|
| `/api/hazo_auth/me` | GET | Get current authenticated user |
|
|
481
505
|
| `/api/hazo_auth/profiles` | POST | Fetch user profiles by IDs |
|
|
@@ -501,6 +525,34 @@ const { GET, POST } = createMessagesHandler({
|
|
|
501
525
|
export { GET, POST };
|
|
502
526
|
```
|
|
503
527
|
|
|
528
|
+
```typescript
|
|
529
|
+
// app/api/hazo_chat/messages/[id]/read/route.ts
|
|
530
|
+
import { NextRequest } from 'next/server';
|
|
531
|
+
import { createMarkAsReadHandler } from 'hazo_chat/api';
|
|
532
|
+
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
533
|
+
|
|
534
|
+
export const dynamic = 'force-dynamic';
|
|
535
|
+
|
|
536
|
+
const { PATCH } = createMarkAsReadHandler({
|
|
537
|
+
getHazoConnect: () => getHazoConnectSingleton(),
|
|
538
|
+
// Optional: custom authentication
|
|
539
|
+
getUserIdFromRequest: async (request) => {
|
|
540
|
+
// Return user ID from your auth system
|
|
541
|
+
return request.cookies.get('user_id')?.value || null;
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Wrapper to handle Next.js App Router params
|
|
546
|
+
async function handlePATCH(
|
|
547
|
+
request: NextRequest,
|
|
548
|
+
context: { params: { id: string } | Promise<{ id: string }> }
|
|
549
|
+
) {
|
|
550
|
+
return PATCH(request, context);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export { handlePATCH as PATCH };
|
|
554
|
+
```
|
|
555
|
+
|
|
504
556
|
### Custom Implementation
|
|
505
557
|
|
|
506
558
|
If you need more control, implement the endpoints manually. See [SETUP_CHECKLIST.md](./SETUP_CHECKLIST.md) for detailed examples.
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -56,12 +56,141 @@ npm install react react-dom next
|
|
|
56
56
|
|
|
57
57
|
## 3. Database Setup
|
|
58
58
|
|
|
59
|
-
### Step 3.1:
|
|
59
|
+
### Step 3.1: PostgreSQL Setup (Recommended)
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
For PostgreSQL databases, follow these steps to create the schema with UUID types, enums, and proper defaults.
|
|
62
|
+
|
|
63
|
+
#### Step 3.1.1: Enable UUID Extension
|
|
64
|
+
|
|
65
|
+
First, enable the UUID extension for generating UUIDs:
|
|
66
|
+
|
|
67
|
+
```sql
|
|
68
|
+
-- Enable UUID extension (required for gen_random_uuid())
|
|
69
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
70
|
+
-- OR for PostgreSQL 13+, use pgcrypto extension
|
|
71
|
+
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Step 3.1.2: Create Enum Types (Optional but Recommended)
|
|
75
|
+
|
|
76
|
+
Create enum types for reference types to ensure data integrity:
|
|
77
|
+
|
|
78
|
+
```sql
|
|
79
|
+
-- Create enum for reference types
|
|
80
|
+
CREATE TYPE hazo_enum_chat_type AS ENUM ('chat', 'field', 'project', 'support', 'general');
|
|
81
|
+
|
|
82
|
+
-- Note: If you need to add more enum values later, use:
|
|
83
|
+
-- ALTER TYPE hazo_chat_reference_type ADD VALUE 'new_type';
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Step 3.1.3: Create hazo_chat Table
|
|
87
|
+
|
|
88
|
+
Create the chat messages table with UUID types and proper defaults:
|
|
89
|
+
|
|
90
|
+
```sql
|
|
91
|
+
-- hazo_chat table for storing chat messages (PostgreSQL)
|
|
92
|
+
CREATE TABLE IF NOT EXISTS hazo_chat (
|
|
93
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
94
|
+
reference_id UUID NOT NULL,
|
|
95
|
+
reference_type hazo_enum_chat_type DEFAULT 'chat' NOT NULL,
|
|
96
|
+
sender_user_id UUID NOT NULL,
|
|
97
|
+
receiver_user_id UUID NOT NULL,
|
|
98
|
+
message_text TEXT,
|
|
99
|
+
reference_list JSONB,
|
|
100
|
+
read_at TIMESTAMPTZ,
|
|
101
|
+
deleted_at TIMESTAMPTZ,
|
|
102
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
103
|
+
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
-- Performance indexes
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver ON hazo_chat(receiver_user_id);
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_read_at ON hazo_chat(read_at) WHERE read_at IS NOT NULL;
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_deleted_at ON hazo_chat(deleted_at) WHERE deleted_at IS NOT NULL;
|
|
114
|
+
|
|
115
|
+
-- Composite index for common query pattern
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver_reference ON hazo_chat(receiver_user_id, reference_id);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Alternative: Without Enum Type (More Flexible)**
|
|
120
|
+
|
|
121
|
+
If you prefer flexibility over strict enum validation:
|
|
62
122
|
|
|
63
123
|
```sql
|
|
64
|
-
-- hazo_chat table
|
|
124
|
+
-- hazo_chat table with TEXT reference_type (more flexible)
|
|
125
|
+
CREATE TABLE IF NOT EXISTS hazo_chat (
|
|
126
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
127
|
+
reference_id UUID NOT NULL,
|
|
128
|
+
reference_type TEXT DEFAULT 'chat' NOT NULL,
|
|
129
|
+
sender_user_id UUID NOT NULL,
|
|
130
|
+
receiver_user_id UUID NOT NULL,
|
|
131
|
+
message_text TEXT,
|
|
132
|
+
reference_list JSONB,
|
|
133
|
+
read_at TIMESTAMPTZ,
|
|
134
|
+
deleted_at TIMESTAMPTZ,
|
|
135
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
136
|
+
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
-- Performance indexes (same as above)
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver ON hazo_chat(receiver_user_id);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_read_at ON hazo_chat(read_at) WHERE read_at IS NOT NULL;
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_deleted_at ON hazo_chat(deleted_at) WHERE deleted_at IS NOT NULL;
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver_reference ON hazo_chat(receiver_user_id, reference_id);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Step 3.1.4: Ensure Users Table Exists (PostgreSQL)
|
|
151
|
+
|
|
152
|
+
You need a users table with at least these fields:
|
|
153
|
+
|
|
154
|
+
```sql
|
|
155
|
+
-- Users table for PostgreSQL
|
|
156
|
+
CREATE TABLE IF NOT EXISTS hazo_users (
|
|
157
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
158
|
+
email_address VARCHAR(255) UNIQUE NOT NULL,
|
|
159
|
+
name VARCHAR(255),
|
|
160
|
+
profile_picture_url TEXT,
|
|
161
|
+
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
|
162
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
163
|
+
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
-- Indexes for users table
|
|
167
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_users_email ON hazo_users(email_address);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_users_active ON hazo_users(is_active) WHERE is_active = TRUE;
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Step 3.1.5: Add Foreign Key Constraints (Optional)
|
|
172
|
+
|
|
173
|
+
Add foreign key constraints for referential integrity:
|
|
174
|
+
|
|
175
|
+
```sql
|
|
176
|
+
-- Add foreign key constraints (optional but recommended)
|
|
177
|
+
ALTER TABLE hazo_chat
|
|
178
|
+
ADD CONSTRAINT fk_hazo_chat_sender
|
|
179
|
+
FOREIGN KEY (sender_user_id)
|
|
180
|
+
REFERENCES hazo_users(id)
|
|
181
|
+
ON DELETE RESTRICT,
|
|
182
|
+
ADD CONSTRAINT fk_hazo_chat_receiver
|
|
183
|
+
FOREIGN KEY (receiver_user_id)
|
|
184
|
+
REFERENCES hazo_users(id)
|
|
185
|
+
ON DELETE RESTRICT;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Step 3.2: SQLite Setup (Alternative)
|
|
189
|
+
|
|
190
|
+
For SQLite databases (development/testing), use this simplified schema:
|
|
191
|
+
|
|
192
|
+
```sql
|
|
193
|
+
-- hazo_chat table for storing chat messages (SQLite)
|
|
65
194
|
CREATE TABLE IF NOT EXISTS hazo_chat (
|
|
66
195
|
id TEXT PRIMARY KEY,
|
|
67
196
|
reference_id TEXT NOT NULL,
|
|
@@ -72,8 +201,8 @@ CREATE TABLE IF NOT EXISTS hazo_chat (
|
|
|
72
201
|
reference_list TEXT,
|
|
73
202
|
read_at TEXT,
|
|
74
203
|
deleted_at TEXT,
|
|
75
|
-
created_at TEXT NOT NULL,
|
|
76
|
-
changed_at TEXT NOT NULL
|
|
204
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
205
|
+
changed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
77
206
|
);
|
|
78
207
|
|
|
79
208
|
-- Performance indexes
|
|
@@ -83,9 +212,7 @@ CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver ON hazo_chat(receiver_user_id)
|
|
|
83
212
|
CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
|
|
84
213
|
```
|
|
85
214
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
You need a users table with at least these fields:
|
|
215
|
+
**SQLite Users Table:**
|
|
89
216
|
|
|
90
217
|
```sql
|
|
91
218
|
CREATE TABLE IF NOT EXISTS hazo_users (
|
|
@@ -94,12 +221,91 @@ CREATE TABLE IF NOT EXISTS hazo_users (
|
|
|
94
221
|
name TEXT,
|
|
95
222
|
profile_picture_url TEXT,
|
|
96
223
|
is_active INTEGER DEFAULT 1,
|
|
97
|
-
created_at TEXT NOT NULL,
|
|
98
|
-
changed_at TEXT NOT NULL
|
|
224
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
225
|
+
changed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
99
226
|
);
|
|
100
227
|
```
|
|
101
228
|
|
|
229
|
+
### Step 3.3: Key Differences Between PostgreSQL and SQLite
|
|
230
|
+
|
|
231
|
+
| Feature | PostgreSQL | SQLite |
|
|
232
|
+
|---------|-----------|--------|
|
|
233
|
+
| **ID Type** | `UUID` with `gen_random_uuid()` default | `TEXT` (manual UUID generation) |
|
|
234
|
+
| **Boolean** | `BOOLEAN` type with `TRUE`/`FALSE` | `INTEGER` with `0`/`1` |
|
|
235
|
+
| **Timestamp** | `TIMESTAMPTZ` with `NOW()` default | `TEXT` with `datetime('now')` |
|
|
236
|
+
| **JSON** | `JSONB` type (binary JSON) | `TEXT` (JSON string) |
|
|
237
|
+
| **Enum** | Native `ENUM` type available | Not supported (use TEXT) |
|
|
238
|
+
| **Extensions** | Requires UUID extension | No extensions needed |
|
|
239
|
+
|
|
240
|
+
#### Step 3.3.1: Verify PostgreSQL Setup
|
|
241
|
+
|
|
242
|
+
Run these queries to verify your PostgreSQL setup:
|
|
243
|
+
|
|
244
|
+
```sql
|
|
245
|
+
-- Verify UUID extension is enabled
|
|
246
|
+
SELECT extname, extversion FROM pg_extension WHERE extname IN ('uuid-ossp', 'pgcrypto');
|
|
247
|
+
|
|
248
|
+
-- Verify enum type exists (if using enum)
|
|
249
|
+
SELECT typname FROM pg_type WHERE typname = 'hazo_chat_reference_type';
|
|
250
|
+
|
|
251
|
+
-- Verify table structure
|
|
252
|
+
SELECT
|
|
253
|
+
column_name,
|
|
254
|
+
data_type,
|
|
255
|
+
column_default,
|
|
256
|
+
is_nullable
|
|
257
|
+
FROM information_schema.columns
|
|
258
|
+
WHERE table_name = 'hazo_chat'
|
|
259
|
+
ORDER BY ordinal_position;
|
|
260
|
+
|
|
261
|
+
-- Verify indexes
|
|
262
|
+
SELECT indexname, indexdef
|
|
263
|
+
FROM pg_indexes
|
|
264
|
+
WHERE tablename = 'hazo_chat';
|
|
265
|
+
|
|
266
|
+
-- Test UUID generation
|
|
267
|
+
SELECT gen_random_uuid() AS test_uuid;
|
|
268
|
+
|
|
269
|
+
-- Test table insert with defaults (replace UUIDs with valid user IDs)
|
|
270
|
+
INSERT INTO hazo_chat (
|
|
271
|
+
reference_id,
|
|
272
|
+
sender_user_id,
|
|
273
|
+
receiver_user_id,
|
|
274
|
+
message_text
|
|
275
|
+
) VALUES (
|
|
276
|
+
gen_random_uuid(),
|
|
277
|
+
(SELECT id FROM hazo_users LIMIT 1), -- Use existing user ID
|
|
278
|
+
(SELECT id FROM hazo_users LIMIT 1), -- Use existing user ID
|
|
279
|
+
'Test message'
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
-- Verify auto-generated values
|
|
283
|
+
SELECT
|
|
284
|
+
id,
|
|
285
|
+
created_at,
|
|
286
|
+
changed_at,
|
|
287
|
+
reference_type
|
|
288
|
+
FROM hazo_chat
|
|
289
|
+
WHERE message_text = 'Test message'
|
|
290
|
+
LIMIT 1;
|
|
291
|
+
|
|
292
|
+
-- Clean up test data
|
|
293
|
+
DELETE FROM hazo_chat WHERE message_text = 'Test message';
|
|
294
|
+
```
|
|
295
|
+
|
|
102
296
|
### Verification
|
|
297
|
+
|
|
298
|
+
**PostgreSQL:**
|
|
299
|
+
- [ ] UUID extension enabled (`uuid-ossp` or `pgcrypto`)
|
|
300
|
+
- [ ] Enum type created (if using enum approach)
|
|
301
|
+
- [ ] `hazo_chat` table exists with UUID columns
|
|
302
|
+
- [ ] All indexes created successfully
|
|
303
|
+
- [ ] Users table exists with UUID primary key
|
|
304
|
+
- [ ] Can query: `SELECT * FROM hazo_chat LIMIT 1`
|
|
305
|
+
- [ ] UUID default generation works: `INSERT INTO hazo_chat (...) VALUES (...)` generates UUID automatically
|
|
306
|
+
- [ ] Timestamp defaults work: `created_at` and `changed_at` auto-populate
|
|
307
|
+
|
|
308
|
+
**SQLite:**
|
|
103
309
|
- [ ] `hazo_chat` table exists in database
|
|
104
310
|
- [ ] Users table exists with at least one user
|
|
105
311
|
- [ ] Can query: `SELECT * FROM hazo_chat LIMIT 1`
|
|
@@ -132,6 +338,41 @@ const { GET, POST } = createMessagesHandler({
|
|
|
132
338
|
export { GET, POST };
|
|
133
339
|
```
|
|
134
340
|
|
|
341
|
+
### Step 4.1.5: Mark as Read API (Automatic Read Receipts)
|
|
342
|
+
|
|
343
|
+
**File: `src/app/api/hazo_chat/messages/[id]/read/route.ts`**
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
/**
|
|
347
|
+
* API route to mark a chat message as read
|
|
348
|
+
* Uses the exportable handler from hazo_chat package
|
|
349
|
+
* Supports PATCH for marking messages as read
|
|
350
|
+
*/
|
|
351
|
+
|
|
352
|
+
export const dynamic = 'force-dynamic';
|
|
353
|
+
|
|
354
|
+
import { NextRequest } from 'next/server';
|
|
355
|
+
import { createMarkAsReadHandler } from 'hazo_chat/api';
|
|
356
|
+
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
357
|
+
|
|
358
|
+
// Create handler using the exportable factory from hazo_chat
|
|
359
|
+
const { PATCH: patchHandler } = createMarkAsReadHandler({
|
|
360
|
+
getHazoConnect: () => getHazoConnectSingleton()
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Wrapper to handle Next.js App Router params
|
|
364
|
+
async function PATCH(
|
|
365
|
+
request: NextRequest,
|
|
366
|
+
context: { params: { id: string } | Promise<{ id: string }> }
|
|
367
|
+
) {
|
|
368
|
+
return patchHandler(request, context);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export { PATCH };
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Note:** This endpoint is called automatically by the `HazoChat` component when messages become visible in the viewport. It uses the Intersection Observer API to detect visibility and marks messages as read when they are at least 50% visible.
|
|
375
|
+
|
|
135
376
|
### Step 4.2: Auth Me API
|
|
136
377
|
|
|
137
378
|
**File: `src/app/api/hazo_auth/me/route.ts`**
|
|
@@ -327,6 +568,7 @@ export async function GET(request: NextRequest) {
|
|
|
327
568
|
| Endpoint | Method | File | Purpose |
|
|
328
569
|
|----------|--------|------|---------|
|
|
329
570
|
| `/api/hazo_chat/messages` | GET, POST | `api/hazo_chat/messages/route.ts` | Message CRUD |
|
|
571
|
+
| `/api/hazo_chat/messages/[id]/read` | PATCH | `api/hazo_chat/messages/[id]/read/route.ts` | Mark message as read (automatic) |
|
|
330
572
|
| `/api/hazo_chat/unread_count` | GET | `api/hazo_chat/unread_count/route.ts` | Get unread counts (optional) |
|
|
331
573
|
| `/api/hazo_auth/me` | GET | `api/hazo_auth/me/route.ts` | Get current user |
|
|
332
574
|
| `/api/hazo_auth/profiles` | POST | `api/hazo_auth/profiles/route.ts` | Get user profiles |
|
|
@@ -336,6 +578,7 @@ export async function GET(request: NextRequest) {
|
|
|
336
578
|
- [ ] `GET /api/hazo_auth/me` returns user data when logged in
|
|
337
579
|
- [ ] `POST /api/hazo_auth/profiles` returns profiles for given IDs
|
|
338
580
|
- [ ] `GET /api/hazo_chat/messages?receiver_user_id=xxx` works
|
|
581
|
+
- [ ] `PATCH /api/hazo_chat/messages/[message-id]/read` marks message as read
|
|
339
582
|
- [ ] `GET /api/hazo_chat/unread_count?receiver_user_id=xxx` returns unread counts (if implemented)
|
|
340
583
|
|
|
341
584
|
---
|
|
@@ -880,6 +1123,19 @@ For detailed specifications, see the [UI Design Standards](#ui-design-standards)
|
|
|
880
1123
|
- [ ] No TypeScript errors
|
|
881
1124
|
|
|
882
1125
|
### Database Verification
|
|
1126
|
+
|
|
1127
|
+
**PostgreSQL:**
|
|
1128
|
+
- [ ] UUID extension enabled (check with: `SELECT * FROM pg_extension WHERE extname = 'uuid-ossp' OR extname = 'pgcrypto';`)
|
|
1129
|
+
- [ ] Enum type created (if using): `SELECT typname FROM pg_type WHERE typname = 'hazo_chat_reference_type';`
|
|
1130
|
+
- [ ] `hazo_chat` table exists with correct column types
|
|
1131
|
+
- [ ] All indexes created (check with: `SELECT indexname FROM pg_indexes WHERE tablename = 'hazo_chat';`)
|
|
1132
|
+
- [ ] UUID default generation works: Test insert without providing ID
|
|
1133
|
+
- [ ] Timestamp defaults work: Test insert without providing timestamps
|
|
1134
|
+
- [ ] Boolean columns accept TRUE/FALSE values
|
|
1135
|
+
- [ ] Can insert and query messages
|
|
1136
|
+
- [ ] Foreign key constraints work (if enabled)
|
|
1137
|
+
|
|
1138
|
+
**SQLite:**
|
|
883
1139
|
- [ ] `hazo_chat` table exists
|
|
884
1140
|
- [ ] Users table exists with test users
|
|
885
1141
|
- [ ] Can insert and query messages
|
package/dist/api/index.d.ts
CHANGED
|
@@ -18,9 +18,24 @@
|
|
|
18
18
|
*
|
|
19
19
|
* export { GET, POST };
|
|
20
20
|
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // app/api/hazo_chat/messages/[id]/route.ts
|
|
25
|
+
* import { createDeleteHandler } from 'hazo_chat/api';
|
|
26
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
27
|
+
*
|
|
28
|
+
* export const dynamic = 'force-dynamic';
|
|
29
|
+
*
|
|
30
|
+
* const { DELETE } = createDeleteHandler({
|
|
31
|
+
* getHazoConnect: () => getHazoConnectSingleton()
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* export { DELETE };
|
|
35
|
+
* ```
|
|
21
36
|
*/
|
|
22
|
-
export { createMessagesHandler } from './messages.js';
|
|
37
|
+
export { createMessagesHandler, createMarkAsReadHandler, createDeleteHandler, } from './messages.js';
|
|
23
38
|
export { createUnreadCountFunction } from './unread_count.js';
|
|
24
|
-
export type { MessagesHandlerOptions, ChatMessageInput, ChatMessageRecord } from './types.js';
|
|
39
|
+
export type { MessagesHandlerOptions, ChatMessageInput, ChatMessageRecord, ApiErrorResponse, ApiSuccessResponse, PaginationMeta, } from './types.js';
|
|
25
40
|
export type { UnreadCountFunctionOptions, UnreadCountResult } from './unread_count.js';
|
|
26
41
|
//# 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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAG9D,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/api/index.js
CHANGED
|
@@ -18,8 +18,23 @@
|
|
|
18
18
|
*
|
|
19
19
|
* export { GET, POST };
|
|
20
20
|
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // app/api/hazo_chat/messages/[id]/route.ts
|
|
25
|
+
* import { createDeleteHandler } from 'hazo_chat/api';
|
|
26
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
27
|
+
*
|
|
28
|
+
* export const dynamic = 'force-dynamic';
|
|
29
|
+
*
|
|
30
|
+
* const { DELETE } = createDeleteHandler({
|
|
31
|
+
* getHazoConnect: () => getHazoConnectSingleton()
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* export { DELETE };
|
|
35
|
+
* ```
|
|
21
36
|
*/
|
|
22
37
|
// Export handler factories
|
|
23
|
-
export { createMessagesHandler } from './messages.js';
|
|
38
|
+
export { createMessagesHandler, createMarkAsReadHandler, createDeleteHandler, } from './messages.js';
|
|
24
39
|
export { createUnreadCountFunction } from './unread_count.js';
|
|
25
40
|
//# sourceMappingURL=index.js.map
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,2BAA2B;AAC3B,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/api/messages.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Messages API Handler Factory
|
|
3
3
|
*
|
|
4
|
-
* Creates GET and
|
|
4
|
+
* Creates GET, POST, and DELETE handlers for the /api/hazo_chat/messages endpoint.
|
|
5
5
|
* These handlers should be used in a Next.js API route.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
@@ -31,4 +31,55 @@ export declare function createMessagesHandler(options: MessagesHandlerOptions):
|
|
|
31
31
|
GET: (request: NextRequest) => Promise<NextResponse>;
|
|
32
32
|
POST: (request: NextRequest) => Promise<NextResponse>;
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Creates a PATCH handler for marking a message as read
|
|
36
|
+
*
|
|
37
|
+
* This handler should be used in a Next.js API route like:
|
|
38
|
+
* /api/hazo_chat/messages/[id]/read/route.ts
|
|
39
|
+
*
|
|
40
|
+
* @param options - Configuration options
|
|
41
|
+
* @returns PATCH handler function
|
|
42
|
+
*/
|
|
43
|
+
export declare function createMarkAsReadHandler(options: MessagesHandlerOptions): {
|
|
44
|
+
PATCH: (request: NextRequest, context: {
|
|
45
|
+
params: {
|
|
46
|
+
id: string;
|
|
47
|
+
} | Promise<{
|
|
48
|
+
id: string;
|
|
49
|
+
}>;
|
|
50
|
+
}) => Promise<NextResponse>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Creates a DELETE handler for soft-deleting a message
|
|
54
|
+
*
|
|
55
|
+
* This handler should be used in a Next.js API route like:
|
|
56
|
+
* /api/hazo_chat/messages/[id]/route.ts
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // app/api/hazo_chat/messages/[id]/route.ts
|
|
61
|
+
* import { createDeleteHandler } from 'hazo_chat/api';
|
|
62
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
63
|
+
*
|
|
64
|
+
* export const dynamic = 'force-dynamic';
|
|
65
|
+
*
|
|
66
|
+
* const { DELETE } = createDeleteHandler({
|
|
67
|
+
* getHazoConnect: () => getHazoConnectSingleton()
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* export { DELETE };
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @param options - Configuration options
|
|
74
|
+
* @returns DELETE handler function
|
|
75
|
+
*/
|
|
76
|
+
export declare function createDeleteHandler(options: MessagesHandlerOptions): {
|
|
77
|
+
DELETE: (request: NextRequest, context: {
|
|
78
|
+
params: {
|
|
79
|
+
id: string;
|
|
80
|
+
} | Promise<{
|
|
81
|
+
id: string;
|
|
82
|
+
}>;
|
|
83
|
+
}) => Promise<NextResponse>;
|
|
84
|
+
};
|
|
34
85
|
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/api/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,OAAO,KAAK,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/api/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,OAAO,KAAK,EAAE,sBAAsB,EAA6E,MAAM,YAAY,CAAC;AAkEpI;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB;mBAcvC,WAAW,KAAG,OAAO,CAAC,YAAY,CAAC;oBAoKlC,WAAW,KAAG,OAAO,CAAC,YAAY,CAAC;EAqIjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,sBAAsB;qBAY1D,WAAW,WACX;QAAE,MAAM,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,KAC5D,OAAO,CAAC,YAAY,CAAC;EA8HzB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,sBAAsB;sBAatD,WAAW,WACX;QAAE,MAAM,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,KAC5D,OAAO,CAAC,YAAY,CAAC;EAwHzB"}
|