mojmajstor/app/(tabs)/(explore)/index.tsx
2026-06-06 03:54:22 +02:00

238 lines
9.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 { View, Text, Pressable, ScrollView, RefreshControl } from "react-native";
import { useLocalSearchParams, Link } from "expo-router";
import { useState, useCallback } from "react";
import { Ionicons } from "@expo/vector-icons";
import { useTheme } from "../../../components/theme";
import { usePaginatedQuery, useQuery } from "convex/react";
import { api } from "../../../convex/_generated/api";
import { CategoryGrid } from "../../../components/category-grid";
import { AdCard } from "../../../components/ad-card";
import { Loading } from "../../../components/ui/loading";
import { CATEGORIES, CATEGORY_EMOJI, getCategoryName } from "../../../lib/constants";
export default function ExploreScreen() {
const theme = useTheme();
const { category } = useLocalSearchParams<{ category?: string }>();
const [refreshing, setRefreshing] = useState(false);
const categories = useQuery(api.categories.list);
const categoryAdsResult = usePaginatedQuery(
api.ads.getByCategoryPaginated,
category ? { category } : "skip",
{ initialNumItems: 20 }
);
const allAdsResult = usePaginatedQuery(api.ads.listPaginated, {}, { initialNumItems: 20 });
const onRefresh = useCallback(() => {
setRefreshing(true);
setTimeout(() => setRefreshing(false), 800);
}, []);
const displayCategories = categories ?? CATEGORIES;
if (category) {
const catName = getCategoryName(category);
const catEmoji = CATEGORY_EMOJI[category] ?? "📋";
return (
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
<View
style={{
backgroundColor: theme.colors.surface,
paddingTop: 60,
paddingBottom: theme.spacing.md,
paddingHorizontal: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.divider,
...theme.shadows.xs,
}}
>
<View style={{ flexDirection: "row", alignItems: "center", gap: theme.spacing.md }}>
<Link href="/(tabs)/(explore)" asChild>
<Pressable
accessible
accessibilityLabel="Назад"
accessibilityRole="link"
style={{
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: theme.colors.surfaceAlt,
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name="arrow-back" size={20} color={theme.colors.text} />
</Pressable>
</Link>
<View
style={{
width: 44,
height: 44,
borderRadius: theme.radius.md,
backgroundColor: theme.colors.primaryLight,
alignItems: "center",
justifyContent: "center",
}}
>
<Text style={{ fontSize: 22 }}>{catEmoji}</Text>
</View>
<Text selectable style={{ ...theme.typography.h3, color: theme.colors.text }}>
{catName}
</Text>
</View>
</View>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.colors.primary} />
}
contentContainerStyle={{ padding: theme.spacing.md, gap: theme.spacing.sm, paddingBottom: 100 }}
>
{categoryAdsResult.status === "LoadingFirstPage" ? (
<Loading skeleton />
) : categoryAdsResult.results.length === 0 ? (
<View style={{ alignItems: "center", paddingVertical: theme.spacing.xxl }}>
<View
style={{
width: 72,
height: 72,
borderRadius: 36,
backgroundColor: theme.colors.primaryLight,
alignItems: "center",
justifyContent: "center",
marginBottom: theme.spacing.md,
...theme.shadows.xs,
}}
>
<Ionicons name="folder-open" size={30} color={theme.colors.primary} />
</View>
<Text style={{ ...theme.typography.bodyBold, color: theme.colors.text, marginBottom: theme.spacing.xs }}>
Нема огласи
</Text>
<Text style={{ ...theme.typography.caption, color: theme.colors.textSecondary, textAlign: "center" }}>
Нема огласи во оваа категорија
</Text>
</View>
) : (
categoryAdsResult.results.map((ad) => (
<AdCard key={ad._id} ad={ad} />
))
)}
{categoryAdsResult.status === "CanLoadMore" && (
<Pressable
accessible
accessibilityLabel="Вчитај повеќе огласи"
accessibilityRole="button"
onPress={() => categoryAdsResult.loadMore(20)}
style={{
backgroundColor: theme.colors.surface,
borderRadius: theme.radius.lg,
paddingVertical: 14,
alignItems: "center",
borderWidth: 1,
borderColor: theme.colors.border,
marginTop: theme.spacing.sm,
...theme.shadows.xs,
}}
>
<Text style={{ color: theme.colors.primary, fontWeight: "600", fontSize: 14 }}>
Вчитај повеќе
</Text>
</Pressable>
)}
</ScrollView>
</View>
);
}
return (
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
<View style={{ paddingHorizontal: theme.spacing.md, paddingTop: 60, paddingBottom: theme.spacing.md, backgroundColor: theme.colors.surface, ...theme.shadows.xs }}>
<Text style={{ ...theme.typography.h2, color: theme.colors.text }}>
Категории
</Text>
<Text style={{ ...theme.typography.caption, color: theme.colors.textSecondary, marginTop: 4 }}>
Изберете категорија за да пребарувате
</Text>
</View>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.colors.primary} />
}
contentContainerStyle={{ padding: theme.spacing.md, gap: theme.spacing.md, paddingBottom: 100 }}
>
<CategoryGrid categories={displayCategories} />
<View style={{ marginTop: theme.spacing.lg }}>
<View style={{ flexDirection: "row", alignItems: "center", gap: theme.spacing.sm, marginBottom: theme.spacing.md }}>
<Text style={{ ...theme.typography.h4, color: theme.colors.text }}>
Сите огласи
</Text>
<View style={{ backgroundColor: theme.colors.surfaceAlt, paddingHorizontal: 10, paddingVertical: 3, borderRadius: theme.radius.full }}>
<Text style={{ ...theme.typography.label, color: theme.colors.textSecondary }}>
{allAdsResult.results.length}+
</Text>
</View>
</View>
{allAdsResult.status === "LoadingFirstPage" ? (
<Loading skeleton />
) : allAdsResult.results.length === 0 ? (
<View style={{ alignItems: "center", paddingVertical: theme.spacing.xxl }}>
<View
style={{
width: 72,
height: 72,
borderRadius: 36,
backgroundColor: theme.colors.surfaceAlt,
alignItems: "center",
justifyContent: "center",
marginBottom: theme.spacing.md,
...theme.shadows.xs,
}}
>
<Ionicons name="cube" size={30} color={theme.colors.textTertiary} />
</View>
<Text style={{ ...theme.typography.caption, color: theme.colors.textSecondary }}>
Нема огласи
</Text>
</View>
) : (
allAdsResult.results.map((ad) => (
<AdCard key={ad._id} ad={ad} />
))
)}
{allAdsResult.status === "CanLoadMore" && (
<Pressable
accessible
accessibilityLabel="Вчитај повеќе огласи"
accessibilityRole="button"
onPress={() => allAdsResult.loadMore(20)}
style={{
backgroundColor: theme.colors.surface,
borderRadius: theme.radius.lg,
paddingVertical: 14,
alignItems: "center",
borderWidth: 1,
borderColor: theme.colors.border,
marginTop: theme.spacing.sm,
...theme.shadows.xs,
}}
>
<Text style={{ color: theme.colors.primary, fontWeight: "600", fontSize: 14 }}>
Вчитај повеќе
</Text>
</Pressable>
)}
</View>
</ScrollView>
</View>
);
}