- 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.
156 lines
5.9 KiB
TypeScript
156 lines
5.9 KiB
TypeScript
import { View, Text, ScrollView, Alert } from "react-native";
|
||
import { Stack, useRouter } from "expo-router";
|
||
import { useState } from "react";
|
||
import { useMutation } from "convex/react";
|
||
import { api } from "../../convex/_generated/api";
|
||
import { useAuth } from "../_layout";
|
||
import { useTheme } from "../../components/theme";
|
||
import { Button } from "../../components/ui/button";
|
||
import { Input } from "../../components/ui/input";
|
||
import { Card } from "../../components/ui/card";
|
||
import { CATEGORIES } from "../../lib/constants";
|
||
|
||
export default function CreatePostScreen() {
|
||
const router = useRouter();
|
||
const theme = useTheme();
|
||
const { token, isAuthenticated } = useAuth();
|
||
|
||
const [title, setTitle] = useState("");
|
||
const [description, setDescription] = useState("");
|
||
const [category, setCategory] = useState<string | null>(null);
|
||
const [location, setLocation] = useState("");
|
||
const [budget, setBudget] = useState("");
|
||
const [submitting, setSubmitting] = useState(false);
|
||
|
||
const createPost = useMutation(api.posts.create);
|
||
|
||
if (!isAuthenticated) {
|
||
return (
|
||
<>
|
||
<Stack.Screen options={{ title: "Ново побарување", headerShown: true, headerBackButtonDisplayMode: "minimal" }} />
|
||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: theme.colors.background, padding: theme.spacing.lg }}>
|
||
<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", marginBottom: theme.spacing.lg }}>
|
||
Треба да сте најавени за да креирате побарување.
|
||
</Text>
|
||
<Button onPress={() => router.push("/(auth)/login" as any)}>Најави се</Button>
|
||
</View>
|
||
</>
|
||
);
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
if (!title.trim()) {
|
||
Alert.alert("Грешка", "Внесете наслов");
|
||
return;
|
||
}
|
||
if (!description.trim()) {
|
||
Alert.alert("Грешка", "Внесете опис");
|
||
return;
|
||
}
|
||
|
||
setSubmitting(true);
|
||
try {
|
||
const postId = await createPost({
|
||
token: token!,
|
||
title: title.trim(),
|
||
description: description.trim(),
|
||
category: category ?? undefined,
|
||
location: location.trim() || undefined,
|
||
budget: budget.trim() || undefined,
|
||
});
|
||
router.replace(`/post/${postId}` as any);
|
||
} catch (e: any) {
|
||
Alert.alert("Грешка", e.message || "Неуспешно креирање");
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<Stack.Screen options={{ title: "Ново побарување", headerShown: true, headerBackButtonDisplayMode: "minimal" }} />
|
||
<ScrollView contentInsetAdjustmentBehavior="automatic" contentContainerStyle={{ padding: theme.spacing.lg, gap: theme.spacing.md }}>
|
||
<View style={{ gap: theme.spacing.xs }}>
|
||
<Text style={{ fontSize: 14, fontWeight: "600", color: theme.colors.text }}>Наслов *</Text>
|
||
<Input
|
||
placeholder="Што ви треба?"
|
||
value={title}
|
||
onChangeText={setTitle}
|
||
maxLength={100}
|
||
/>
|
||
</View>
|
||
|
||
<View style={{ gap: theme.spacing.xs }}>
|
||
<Text style={{ fontSize: 14, fontWeight: "600", color: theme.colors.text }}>Опис *</Text>
|
||
<Input
|
||
placeholder="Опишете го проблемот детално..."
|
||
value={description}
|
||
onChangeText={setDescription}
|
||
multiline
|
||
numberOfLines={4}
|
||
style={{ minHeight: 100, textAlignVertical: "top" }}
|
||
maxLength={2000}
|
||
/>
|
||
</View>
|
||
|
||
<View style={{ gap: theme.spacing.xs }}>
|
||
<Text style={{ fontSize: 14, fontWeight: "600", color: theme.colors.text }}>Категорија</Text>
|
||
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: theme.spacing.xs }}>
|
||
{CATEGORIES.map((cat) => (
|
||
<View
|
||
key={cat.slug}
|
||
style={{
|
||
backgroundColor: category === cat.slug ? theme.colors.primary : theme.colors.surface,
|
||
borderRadius: theme.radius.md,
|
||
paddingHorizontal: 12,
|
||
paddingVertical: 8,
|
||
borderWidth: 1,
|
||
borderColor: category === cat.slug ? theme.colors.primary : theme.colors.border,
|
||
}}
|
||
>
|
||
<Text
|
||
style={{
|
||
color: category === cat.slug ? "#FFFFFF" : theme.colors.text,
|
||
fontSize: 13,
|
||
fontWeight: "500",
|
||
}}
|
||
onPress={() => setCategory(category === cat.slug ? null : cat.slug)}
|
||
>
|
||
{cat.name}
|
||
</Text>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
|
||
<View style={{ gap: theme.spacing.xs }}>
|
||
<Text style={{ fontSize: 14, fontWeight: "600", color: theme.colors.text }}>Локација</Text>
|
||
<Input
|
||
placeholder="нр. Скопје, Битола..."
|
||
value={location}
|
||
onChangeText={setLocation}
|
||
maxLength={100}
|
||
/>
|
||
</View>
|
||
|
||
<View style={{ gap: theme.spacing.xs }}>
|
||
<Text style={{ fontSize: 14, fontWeight: "600", color: theme.colors.text }}>Буџет</Text>
|
||
<Input
|
||
placeholder="нр. 5000 - 10000 денари"
|
||
value={budget}
|
||
onChangeText={setBudget}
|
||
maxLength={100}
|
||
/>
|
||
</View>
|
||
|
||
<Button onPress={handleSubmit} loading={submitting} disabled={submitting}>
|
||
Објави побарување
|
||
</Button>
|
||
</ScrollView>
|
||
</>
|
||
);
|
||
} |