- Chat list: auth-gated, shows all conversations for current user with other participant's avatar/name, last message preview, and relative time in Macedonian (пред X мин/ч/д) - Chat conversation: FlatList with inverted scroll, message bubbles aligned left (other) and right (self), colored backgrounds, timestamps, KeyboardAvoidingView send bar - Chat mutations: getOrCreate finds or creates a 1:1 chat, send creates message and updates lastMessageAt - Message queries: listByChat with participant auth check - Wired ad detail 'Започни разговор' button to create/open chat - ChatListItem component with avatar, preview, time, link to detail All UI text in Macedonian. TypeScript clean, Convex functions deployed.
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import { query, mutation } from "./_generated/server";
|
||
import { v } from "convex/values";
|
||
|
||
export const listByChat = query({
|
||
args: { token: v.string(), chatId: v.id("chats") },
|
||
handler: async (ctx, args) => {
|
||
const session = await ctx.db
|
||
.query("sessions")
|
||
.withIndex("by_token", (q) => q.eq("token", args.token))
|
||
.first();
|
||
if (!session) throw new Error("Неавторизиран");
|
||
|
||
const chat = await ctx.db.get(args.chatId);
|
||
if (!chat) throw new Error("Разговорот не е пронајден");
|
||
if (!chat.participantIds.includes(session.userId)) {
|
||
throw new Error("Немате пристап до овој разговор");
|
||
}
|
||
|
||
return await ctx.db
|
||
.query("messages")
|
||
.withIndex("by_chat", (q) => q.eq("chatId", args.chatId))
|
||
.order("desc")
|
||
.collect();
|
||
},
|
||
});
|
||
|
||
export const send = mutation({
|
||
args: {
|
||
token: v.string(),
|
||
chatId: v.id("chats"),
|
||
content: v.string(),
|
||
imageId: v.optional(v.string()),
|
||
},
|
||
handler: async (ctx, args) => {
|
||
const session = await ctx.db
|
||
.query("sessions")
|
||
.withIndex("by_token", (q) => q.eq("token", args.token))
|
||
.first();
|
||
if (!session) throw new Error("Неавторизиран");
|
||
|
||
const chat = await ctx.db.get(args.chatId);
|
||
if (!chat) throw new Error("Разговорот не е пронајден");
|
||
if (!chat.participantIds.includes(session.userId)) {
|
||
throw new Error("Немате пристап до овој разговор");
|
||
}
|
||
|
||
const now = Date.now();
|
||
const messageId = await ctx.db.insert("messages", {
|
||
chatId: args.chatId,
|
||
senderId: session.userId,
|
||
content: args.content,
|
||
...(args.imageId ? { imageId: args.imageId } : {}),
|
||
createdAt: now,
|
||
});
|
||
|
||
await ctx.db.patch(args.chatId, { lastMessageAt: now });
|
||
|
||
return messageId;
|
||
},
|
||
}); |