mojmajstor/convex/reviews.ts
echo c700d139f7 feat: Phase 6 - reviews and ratings with computed fields
- Review create mutation with auth, customer-only role check,
  self-review prevention, and ad ratingAvg/reviewCount recomputation
- ReviewCard component: reviewer avatar, name, relative time in
  Macedonian, star rating, comment
- Create review screen: interactive 5-star selector (Многу лошо–Отлично),
  comment field, auth gate, submit and navigate back
- Ad detail: review section listing all reviews with reviewer names,
  overall rating summary with count, 'Напиши оценка' button
- AdCard: rating stars shown next to title when ad has reviews

All UI text in Macedonian. TypeScript clean, Convex functions deployed.
2026-05-29 18:41:07 +02:00

79 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
export const getByAd = query({
args: { adId: v.id("ads") },
handler: async (ctx, args) => {
return await ctx.db
.query("reviews")
.withIndex("by_ad", (q) => q.eq("adId", args.adId))
.order("desc")
.collect();
},
});
export const getByHandyman = query({
args: { handymanId: v.id("users") },
handler: async (ctx, args) => {
return await ctx.db
.query("reviews")
.withIndex("by_handyman", (q) => q.eq("handymanId", args.handymanId))
.order("desc")
.collect();
},
});
export const create = mutation({
args: {
token: v.string(),
adId: v.id("ads"),
rating: v.number(),
comment: v.optional(v.string()),
},
handler: async (ctx, args) => {
if (args.rating < 1 || args.rating > 5) {
throw new Error("Оцената мора да биде помеѓу 1 и 5");
}
const session = await ctx.db
.query("sessions")
.withIndex("by_token", (q) => q.eq("token", args.token))
.first();
if (!session) throw new Error("Неавторизиран");
const user = await ctx.db.get(session.userId);
if (!user) throw new Error("Корисникот не е пронајден");
if (user.role !== "customer") throw new Error("Само клиенти можат да пишуваат оцени");
const ad = await ctx.db.get(args.adId);
if (!ad) throw new Error("Огласот не е пронајден");
if (ad.handymanId === session.userId) {
throw new Error("Не можете да оцените свој оглас");
}
const reviewId = await ctx.db.insert("reviews", {
adId: args.adId,
customerId: session.userId,
handymanId: ad.handymanId,
rating: args.rating,
comment: args.comment,
createdAt: Date.now(),
});
const allReviews = await ctx.db
.query("reviews")
.withIndex("by_ad", (q) => q.eq("adId", args.adId))
.collect();
const totalRating = allReviews.reduce((sum, r) => sum + r.rating, 0);
const ratingAvg = totalRating / allReviews.length;
await ctx.db.patch(args.adId, {
ratingAvg,
reviewCount: allReviews.length,
});
return reviewId;
},
});