mojmajstor/components/review-card.tsx
echo 3e5b85a08e feat: Phase 7 - polish, error handling, pagination, accessibility
- Error boundary with Macedonian fallback screen and retry button
  wrapping all navigation
- Pull-to-refresh on all list/detail screens (home, explore, posts,
  chat, profile, ad detail, post detail)
- Accessibility: labels on all interactive elements, roles on
  Pressable/Button, selectable text for user data
- Deep linking: mojmajstor:// scheme configured in app.json,
  ad/[id] and chat/[id] routes work via Expo Router auto-routing
- Pagination: cursor-based paginated queries for ads, posts,
  messages, chats, reviews with Load More buttons on frontend
- Profile: shows customer posts with PostCard, loading/error states,
  create-post navigation
- Auth error messages in Macedonian (Невалидни акредитиви, итн.)

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

72 lines
2.4 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
accessible
accessibilityLabel={`Оценка ${review.rating} од 5 од ${customerName}${review.comment ? `, ${review.comment}` : ""}`}
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 selectable 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 selectable style={{ fontSize: 14, color: theme.colors.textSecondary, lineHeight: 20 }}>
{review.comment}
</Text>
) : null}
</View>
);
}