- 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.
70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
import { View, Text } from "react-native";
|
|
import { useTheme } from "./theme";
|
|
import { Avatar } from "./ui/avatar";
|
|
import { Rating } from "./ui/rating";
|
|
|
|
function formatTimeAgo(timestamp: number): string {
|
|
const now = Date.now();
|
|
const diff = now - timestamp;
|
|
|
|
const seconds = Math.floor(diff / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const hours = Math.floor(minutes / 60);
|
|
const days = Math.floor(hours / 24);
|
|
const months = Math.floor(days / 30);
|
|
const years = Math.floor(days / 365);
|
|
|
|
if (years > 0) return `пред ${years} ${years === 1 ? "година" : "години"}`;
|
|
if (months > 0) return `пред ${months} ${months === 1 ? "месец" : "месеци"}`;
|
|
if (days > 0) return `пред ${days} ${days === 1 ? "ден" : "денови"}`;
|
|
if (hours > 0) return `пред ${hours} ${hours === 1 ? "час" : "часа"}`;
|
|
if (minutes > 0) return `пред ${minutes} ${minutes === 1 ? "минута" : "минути"}`;
|
|
return "пред малку";
|
|
}
|
|
|
|
interface ReviewCardProps {
|
|
review: {
|
|
_id: string;
|
|
rating: number;
|
|
comment?: string;
|
|
createdAt: number;
|
|
};
|
|
customerName: string;
|
|
customerAvatarId?: string;
|
|
}
|
|
|
|
export function ReviewCard({ review, customerName, customerAvatarId }: ReviewCardProps) {
|
|
const theme = useTheme();
|
|
|
|
return (
|
|
<View
|
|
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", alignItems: "center", gap: theme.spacing.sm }}>
|
|
<Avatar uri={customerAvatarId ?? null} name={customerName} size={36} />
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={{ fontSize: 14, fontWeight: "600", color: theme.colors.text }}>
|
|
{customerName}
|
|
</Text>
|
|
<Text style={{ fontSize: 12, color: theme.colors.textTertiary }}>
|
|
{formatTimeAgo(review.createdAt)}
|
|
</Text>
|
|
</View>
|
|
<Rating value={review.rating} size={16} />
|
|
</View>
|
|
|
|
{review.comment ? (
|
|
<Text style={{ fontSize: 14, color: theme.colors.textSecondary, lineHeight: 20 }}>
|
|
{review.comment}
|
|
</Text>
|
|
) : null}
|
|
</View>
|
|
);
|
|
} |