- 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.
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import { View, Text, Pressable } from "react-native";
|
|
import { Link } from "expo-router";
|
|
import { useTheme } from "./theme";
|
|
import { Badge } from "./ui/badge";
|
|
import { Rating } from "./ui/rating";
|
|
import { getCategoryName } from "../lib/constants";
|
|
|
|
interface AdCardProps {
|
|
ad: {
|
|
_id: string;
|
|
title: string;
|
|
category: string;
|
|
location: string;
|
|
priceRange?: string;
|
|
ratingAvg?: number;
|
|
reviewCount: number;
|
|
};
|
|
}
|
|
|
|
export function AdCard({ ad }: AdCardProps) {
|
|
const theme = useTheme();
|
|
const categoryName = getCategoryName(ad.category);
|
|
|
|
return (
|
|
<Link href={`/ad/${ad._id}` as any} asChild>
|
|
<Pressable
|
|
style={{
|
|
backgroundColor: theme.colors.card,
|
|
borderRadius: theme.radius.lg,
|
|
padding: theme.spacing.md,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
gap: theme.spacing.sm,
|
|
}}
|
|
>
|
|
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "flex-start" }}>
|
|
<View style={{ flex: 1, gap: 2 }}>
|
|
<Text style={{ fontSize: 16, fontWeight: "600", color: theme.colors.text }}>
|
|
{ad.title}
|
|
</Text>
|
|
{ad.ratingAvg != null && ad.ratingAvg > 0 && (
|
|
<Rating value={ad.ratingAvg} count={ad.reviewCount} size={14} />
|
|
)}
|
|
</View>
|
|
{ad.priceRange && (
|
|
<Text style={{ fontSize: 14, fontWeight: "600", color: theme.colors.primary, marginLeft: 8 }}>
|
|
{ad.priceRange}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
|
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
|
|
<Badge label={categoryName} variant="primary" />
|
|
<Text style={{ fontSize: 13, color: theme.colors.textTertiary }}>📍 {ad.location}</Text>
|
|
</View>
|
|
</Pressable>
|
|
</Link>
|
|
);
|
|
} |