mobileapp / src /hooks /useConversations.ts
Antaram Dev Bot
feat: complete ANTARAM.ORG ride-sharing app frontend
5c876be
import { useState, useCallback } from 'react';
import { eq, desc, asc, and, or } from 'drizzle-orm';
import { useDatabase } from './useDatabase';
import { conversations, messages } from '../db/schema';
// ─── Types ─────────────────────────────────────────────────────────────────────
interface ConversationData {
id: string;
participant1Id: string;
participant2Id: string;
lastMessage: string | null;
lastMessageAt: string | null;
unreadCount: number | null;
createdAt: string | null;
updatedAt: string | null;
}
interface MessageData {
id: string;
conversationId: string;
senderId: string;
content: string;
type: 'text' | 'image' | 'location' | 'system';
status: 'sending' | 'sent' | 'delivered' | 'read' | 'failed';
createdAt: string | null;
}
interface UseConversationsReturn {
conversations: ConversationData[];
messages: MessageData[];
loadingConversations: boolean;
loadingMessages: boolean;
loadConversations: () => Promise<void>;
loadMessages: (conversationId: string) => Promise<void>;
sendMessage: (
conversationId: string,
senderId: string,
content: string,
type?: MessageData['type'],
) => Promise<MessageData | null>;
markAsRead: (conversationId: string) => Promise<void>;
getUnreadCount: () => Promise<number>;
}
// ─── Hook ──────────────────────────────────────────────────────────────────────
export function useConversations(userId?: string): UseConversationsReturn {
const { db } = useDatabase();
const [conversationsList, setConversationsList] = useState<ConversationData[]>(
[],
);
const [messagesList, setMessagesList] = useState<MessageData[]>([]);
const [loadingConversations, setLoadingConversations] = useState(false);
const [loadingMessages, setLoadingMessages] = useState(false);
// ── Load conversations ───────────────────────────────────────────────────────
const loadConversations = useCallback(async () => {
try {
setLoadingConversations(true);
const results = await db
.select()
.from(conversations)
.orderBy(desc(conversations.lastMessageAt))
.all();
// Filter to only conversations involving the current user
let filtered = results as ConversationData[];
if (userId) {
filtered = results.filter(
(c) => c.participant1Id === userId || c.participant2Id === userId,
);
}
setConversationsList(filtered);
} catch (error) {
console.error('[useConversations] Failed to load conversations:', error);
setConversationsList([]);
} finally {
setLoadingConversations(false);
}
}, [db, userId]);
// ── Load messages for a specific conversation ────────────────────────────────
const loadMessages = useCallback(
async (conversationId: string) => {
try {
setLoadingMessages(true);
const results = await db
.select()
.from(messages)
.where(eq(messages.conversationId, conversationId))
.orderBy(asc(messages.createdAt))
.all();
setMessagesList(results as MessageData[]);
} catch (error) {
console.error('[useConversations] Failed to load messages:', error);
setMessagesList([]);
} finally {
setLoadingMessages(false);
}
},
[db],
);
// ── Send a message ───────────────────────────────────────────────────────────
const sendMessage = useCallback(
async (
conversationId: string,
senderId: string,
content: string,
type: MessageData['type'] = 'text',
): Promise<MessageData | null> => {
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
try {
// Insert message
await db.insert(messages).values({
id,
conversationId,
senderId,
content,
type,
status: 'sent',
}).run();
// Update conversation's last message and timestamp
await db
.update(conversations)
.set({
lastMessage: content,
lastMessageAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
.where(eq(conversations.id, conversationId))
.run();
// Reload both messages and conversations
await loadMessages(conversationId);
await loadConversations();
return {
id,
conversationId,
senderId,
content,
type,
status: 'sent',
createdAt: new Date().toISOString(),
};
} catch (error) {
console.error('[useConversations] Failed to send message:', error);
return null;
}
},
[db, loadMessages, loadConversations],
);
// ── Mark conversation as read ────────────────────────────────────────────────
const markAsRead = useCallback(
async (conversationId: string) => {
try {
await db
.update(conversations)
.set({ unreadCount: 0, updatedAt: new Date().toISOString() })
.where(eq(conversations.id, conversationId))
.run();
await loadConversations();
} catch (error) {
console.error('[useConversations] Failed to mark as read:', error);
}
},
[db, loadConversations],
);
// ── Get total unread count across all conversations ──────────────────────────
const getUnreadCount = useCallback(async (): Promise<number> => {
try {
const results = await db.select().from(conversations).all();
let filtered = results as ConversationData[];
if (userId) {
filtered = results.filter(
(c) => c.participant1Id === userId || c.participant2Id === userId,
);
}
return filtered.reduce(
(sum, c) => sum + (c.unreadCount ?? 0),
0,
);
} catch (error) {
console.error('[useConversations] Failed to get unread count:', error);
return 0;
}
}, [db, userId]);
return {
conversations: conversationsList,
messages: messagesList,
loadingConversations,
loadingMessages,
loadConversations,
loadMessages,
sendMessage,
markAsRead,
getUnreadCount,
};
}