mojmajstor/app/(tabs)/(posts)/index.tsx
echo c85ea09d4b feat: Phase 4 - customer posts feed, create, detail, respond
- Add create/close post mutations with auth & ownership verification
- Add getOrCreate chat mutation for handyman response flow
- PostCard component: title, description preview, category badge,
  location, budget, status badge (open=green, closed=red)
- Post detail screen: full description, category, location, budget,
  status, time-ago. Owner: close button. Handyman: respond button
  creates/opens chat with customer
- Create post screen: title, description, category picker, location,
  budget, auth-gated with redirect
- Posts feed: open/all filter tabs, new-post button for customers

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

110 lines
3.7 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, ScrollView, Pressable } from "react-native";
import { useRouter } from "expo-router";
import { useState } from "react";
import { useQuery } from "convex/react";
import { api } from "../../../convex/_generated/api";
import { useAuth } from "../../_layout";
import { useTheme } from "../../../components/theme";
import { PostCard } from "../../../components/post-card";
import { Loading } from "../../../components/ui/loading";
const TABS = [
{ key: "open", label: "Отворени" },
{ key: "all", label: "Сите" },
] as const;
type TabKey = (typeof TABS)[number]["key"];
export default function PostsScreen() {
const router = useRouter();
const theme = useTheme();
const { token, isAuthenticated } = useAuth();
const currentUser = useQuery(
api.users.getCurrentUser,
isAuthenticated && token ? { token } : "skip"
);
const [activeTab, setActiveTab] = useState<TabKey>("open");
const posts = useQuery(
api.posts.list,
activeTab === "open" ? { status: "open" } : {}
);
const isCustomer = currentUser?.role !== "handyman";
return (
<ScrollView
contentInsetAdjustmentBehavior="automatic"
contentContainerStyle={{ padding: theme.spacing.lg, gap: theme.spacing.md, paddingBottom: 80 }}
>
{isAuthenticated && isCustomer && (
<Pressable onPress={() => router.push("/post/create" as any)}>
<View
style={{
backgroundColor: theme.colors.primary,
borderRadius: theme.radius.md,
paddingVertical: 12,
paddingHorizontal: theme.spacing.lg,
alignItems: "center",
}}
>
<Text style={{ color: "#FFFFFF", fontWeight: "600", fontSize: 16 }}>
+ Ново побарување
</Text>
</View>
</Pressable>
)}
<View style={{ flexDirection: "row", backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding: 4 }}>
{TABS.map((tab) => (
<Pressable
key={tab.key}
onPress={() => setActiveTab(tab.key)}
style={{
flex: 1,
paddingVertical: 10,
borderRadius: theme.radius.sm,
alignItems: "center",
backgroundColor: activeTab === tab.key ? theme.colors.card : "transparent",
...(activeTab === tab.key
? { boxShadow: "0 1px 3px rgba(0,0,0,0.08)" as any }
: {}),
}}
>
<Text
style={{
fontWeight: activeTab === tab.key ? "600" : "400",
fontSize: 14,
color: activeTab === tab.key ? theme.colors.text : theme.colors.textSecondary,
}}
>
{tab.label}
</Text>
</Pressable>
))}
</View>
{posts === undefined ? (
<Loading />
) : posts.length === 0 ? (
<View style={{ alignItems: "center", paddingVertical: 60 }}>
<Text style={{ fontSize: 48, marginBottom: 16 }}>📋</Text>
<Text style={{ fontSize: 18, fontWeight: "600", color: theme.colors.text, marginBottom: 8 }}>
Нема побарувања
</Text>
<Text style={{ color: theme.colors.textSecondary, textAlign: "center" }}>
{activeTab === "open"
? "Нема отворени побарувања во моментот."
: "Сеуште нема побарувања."}
</Text>
</View>
) : (
<View style={{ gap: theme.spacing.md }}>
{posts.map((post) => (
<PostCard key={post._id} post={post} />
))}
</View>
)}
</ScrollView>
);
}