diff --git a/apps/sale/app/community/applications/page.tsx b/apps/sale/app/community/applications/page.tsx new file mode 100644 index 0000000..c1bafc7 --- /dev/null +++ b/apps/sale/app/community/applications/page.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { useCommunityStore } from "../../../features/community/store/community-store"; +import { ApplicationCard } from "../../../features/community/components/ApplicationCard"; +import { EmptyState } from "@workspace/ui/components/ui/empty-state"; +import Link from "next/link"; +import { Button } from "@workspace/ui/components/ui/button"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; +import { Inbox, Briefcase } from "lucide-react"; +import { useToastStore } from "@workspace/ui/components/ui/bee-toast/useToastStore"; + +export default function ApplicationsPage() { + const { posts, currentUser, acceptApplication } = useCommunityStore(); + const { add } = useToastStore(); + const router = useRouter(); + + // If GUEST, redirect back + useEffect(() => { + if (currentUser.role === "GUEST") { + router.push("/community"); + } + }, [currentUser.role, router]); + + if (currentUser.role === "GUEST") { + return null; + } + + const isLearner = currentUser.role === "LEARNER"; + + // Prepare list of { application, post } + const displayApplications: { application: any; post: any }[] = []; + + if (isLearner) { + posts.forEach((post) => { + if (post.author.id === currentUser.id && post.applications.length > 0) { + post.applications.forEach((app) => { + displayApplications.push({ application: app, post }); + }); + } + }); + // Sort applications by newest first (descending) + displayApplications.sort( + (a, b) => + new Date(b.application.createdAt).getTime() - + new Date(a.application.createdAt).getTime(), + ); + } else { + posts.forEach((post) => { + const myApp = post.applications.find( + (app) => app.tutor.id === currentUser.id, + ); + if (myApp) { + displayApplications.push({ application: myApp, post }); + } + }); + // Sort applications by newest first + displayApplications.sort( + (a, b) => + new Date(b.application.createdAt).getTime() - + new Date(a.application.createdAt).getTime(), + ); + } + + const handleAccept = (applicationId: string, postId: string) => { + acceptApplication(postId, applicationId); + add({ + title: "Đã kết nối thành công!", + description: + "Đang chuyển hướng vào Phòng Chat 3 bên (Học viên - Gia sư - Cố vấn)...", + variant: "success", + }); + }; + + return ( +
+
+

+ {isLearner ? ( + <> + + Gia sư yêu cầu kết nối + + ) : ( + <> + + Hồ sơ đã gửi + + )} +

+

+ {isLearner + ? "Danh sách các hồ sơ gia sư đã gửi đến bài đăng của bạn." + : "Theo dõi trạng thái các hồ sơ bạn đã gửi đến học viên."} +

+
+ +
+ {displayApplications.length === 0 ? ( + + + + } + /> + ) : ( + displayApplications.map(({ application, post }) => ( + handleAccept(appId, post.id) : undefined + } + /> + )) + )} +
+
+ ); +} diff --git a/apps/sale/app/community/layout.tsx b/apps/sale/app/community/layout.tsx new file mode 100644 index 0000000..bd93cbc --- /dev/null +++ b/apps/sale/app/community/layout.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from "react"; +import { RoleSwitcherMockBar } from "../../features/community/components/RoleSwitcherMockBar"; +import { CommunityBottomBar } from "../../features/community/components/CommunityBottomBar"; + +export default function CommunityLayout({ children }: { children: ReactNode }) { + return ( +
+ +
+ {children} +
+ +
+ ); +} diff --git a/apps/sale/app/community/my-posts/page.tsx b/apps/sale/app/community/my-posts/page.tsx new file mode 100644 index 0000000..b603422 --- /dev/null +++ b/apps/sale/app/community/my-posts/page.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useCommunityStore } from "../../../features/community/store/community-store"; +import { PostCard } from "../../../features/community/components/PostCard"; +import { EmptyState } from "@workspace/ui/components/ui/empty-state"; +import { PlusCircle } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@workspace/ui/components/ui/button"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; + +export default function MyPostsPage() { + const { posts, currentUser } = useCommunityStore(); + const router = useRouter(); + + // If not a learner, redirect back + useEffect(() => { + if (currentUser.role !== "LEARNER") { + router.push("/community"); + } + }, [currentUser.role, router]); + + const myPosts = posts.filter(post => post.author.id === currentUser.id); + + if (currentUser.role !== "LEARNER") { + return null; + } + + return ( +
+
+

Bài đăng của tôi

+ + + +
+ +
+ {myPosts.length === 0 ? ( + + + + } + /> + ) : ( + myPosts.map(post => ( + + )) + )} +
+
+ ); +} diff --git a/apps/sale/app/community/page.tsx b/apps/sale/app/community/page.tsx new file mode 100644 index 0000000..60b500e --- /dev/null +++ b/apps/sale/app/community/page.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { FilterSidebar } from "../../features/community/components/FilterSidebar"; +import { PostCreateBox } from "../../features/community/components/PostCreateBox"; +import { InfiniteFeedList } from "../../features/community/components/InfiniteFeedList"; +import { useCommunityStore } from "../../features/community/store/community-store"; +import { Award, BookOpen } from "lucide-react"; + +export default function CommunityPage() { + const { currentUser, posts } = useCommunityStore(); + const myPostsCount = posts.filter( + (p) => p.author.id === currentUser.id, + ).length; + + return ( +
+ {/* Tiêu đề trang */} +
+

+ Cộng đồng tìm gia sư +

+

+ Kết nối nhanh chóng với hàng ngàn gia sư chất lượng cao +

+
+ +
+ {/* Cột trái: Bộ lọc (Desktop) */} +
+
+ +
+
+ + {/* Cột giữa: Feed chính */} +
+ + + {/* Bộ lọc trên Mobile (Có thể mở rộng thành Drawer sau, tạm thời hiện inline) */} +
+ +
+ + +
+ + {/* Cột phải: Widget phụ trợ */} +
+
+ {/* Widget: Bài đăng của tôi (Chỉ Learner mới thấy có ý nghĩa) */} + {currentUser.role === "LEARNER" && ( +
+

+ + Quản lý bài đăng +

+
+ Bài đăng của tôi: + + {myPostsCount} bài + +
+ {myPostsCount > 0 && ( + + )} +
+ )} + + {/* Widget: Gia sư tiêu biểu */} +
+

+ + Gia sư tiêu biểu tuần +

+ +
+
+ Tutor +
+
+ Trần Lê Huy +
+
+ IELTS 8.0 • 50+ giờ dạy +
+
+
+
+ Tutor +
+
+ Nguyễn Mai Hoa +
+
+ Toán LTĐH • Đánh giá 5.0 +
+
+
+
+ Tutor +
+
+ Phạm Văn A +
+
+ Lập trình cơ bản • 30+ hs +
+
+
+
+ + +
+
+
+
+
+ ); +} diff --git a/apps/sale/features/community/components/ApplicantQueueDrawer.tsx b/apps/sale/features/community/components/ApplicantQueueDrawer.tsx new file mode 100644 index 0000000..95820ce --- /dev/null +++ b/apps/sale/features/community/components/ApplicantQueueDrawer.tsx @@ -0,0 +1,94 @@ +"use client"; + +import { CommunityPost, TutorApplication } from "../types/community"; +import { useCommunityStore } from "../store/community-store"; +import { useToastStore } from "@workspace/ui/components/ui/bee-toast/useToastStore"; +import { Button } from "@workspace/ui/components/ui/button"; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@workspace/ui/components/ui/avatar"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetDescription, +} from "@workspace/ui/components/ui/sheet"; +import { CheckCircle2, Clock, DollarSign, Quote } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; +import { vi } from "date-fns/locale"; +import { ApplicationCard } from "./ApplicationCard"; + +interface ApplicantQueueDrawerProps { + post: CommunityPost; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function ApplicantQueueDrawer({ + post, + open, + onOpenChange, +}: ApplicantQueueDrawerProps) { + const { acceptApplication } = useCommunityStore(); + const { add } = useToastStore(); + + const handleAccept = (applicationId: string) => { + acceptApplication(post.id, applicationId); + add({ + title: "Đã kết nối thành công!", + description: + "Đang chuyển hướng vào Phòng Chat 3 bên (Học viên - Gia sư - Cố vấn)...", + variant: "success", + }); + onOpenChange(false); + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("vi-VN", { + style: "currency", + currency: "VND", + }).format(amount); + }; + + return ( + + + + Danh sách hồ sơ ({post.applications.length}) + + Xem hồ sơ các gia sư đã ứng tuyển và chọn người phù hợp nhất cho lớp{" "} + {post.subject} + . + + + + {post.applications.length === 0 ? ( +
+
+ +
+

+ Chưa có hồ sơ nào. +
+ Vui lòng chờ thêm một thời gian nữa nhé! +

+
+ ) : ( +
+ {post.applications.map((app: TutorApplication) => ( + + ))} +
+ )} +
+
+ ); +} diff --git a/apps/sale/features/community/components/ApplicationCard.tsx b/apps/sale/features/community/components/ApplicationCard.tsx new file mode 100644 index 0000000..b50e259 --- /dev/null +++ b/apps/sale/features/community/components/ApplicationCard.tsx @@ -0,0 +1,129 @@ +import { CommunityPost, TutorApplication } from "../types/community"; +import { Avatar, AvatarFallback, AvatarImage } from "@workspace/ui/components/ui/avatar"; +import { Clock, DollarSign, Quote, CheckCircle2, BookOpen } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; +import { vi } from "date-fns/locale"; +import { Button } from "@workspace/ui/components/ui/button"; +import Link from "next/link"; + +interface ApplicationCardProps { + application: TutorApplication; + post: CommunityPost; + onAccept?: (applicationId: string) => void; + showPostInfo?: boolean; +} + +export function ApplicationCard({ application: app, post, onAccept, showPostInfo }: ApplicationCardProps) { + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("vi-VN", { + style: "currency", + currency: "VND", + }).format(amount); + }; + + return ( +
+
+ + + + {app.tutor.displayName?.charAt(0) || "U"} + + +
+
+
+
+ {app.tutor.displayName} +
+
+ + {formatDistanceToNow(new Date(app.createdAt), { + addSuffix: true, + locale: vi, + })} +
+
+ {app.status === "ACCEPTED" && ( + + Đã chọn + + )} + {app.status === "PENDING" && ( + + Đang chờ + + )} +
+
+
+ + {showPostInfo && ( +
+ +
+ Ứng tuyển cho bài đăng: {post.subject} +
+
+ )} + +
+ +

+ "{app.pitchNote}" +

+
+ +
+
+
+ +
+
+
+ Thù lao đề xuất +
+
+ {formatCurrency(app.proposedRate)} +
+
+
+
+
+ +
+
+
+ Lịch trống +
+
+ {app.availableSlots.length > 0 + ? app.availableSlots.join(", ") + : "Chưa cập nhật"} +
+
+
+
+ +
+ + + + {onAccept && post.status === "OPEN" && app.status === "PENDING" && ( + + )} +
+
+ ); +} diff --git a/apps/sale/features/community/components/CommunityBottomBar.tsx b/apps/sale/features/community/components/CommunityBottomBar.tsx new file mode 100644 index 0000000..840fc34 --- /dev/null +++ b/apps/sale/features/community/components/CommunityBottomBar.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { motion, AnimatePresence } from "motion/react"; +import { + Home, + ClipboardList, + Briefcase, + ChevronRight, + Menu, +} from "lucide-react"; +import { useCommunityStore } from "../store/community-store"; + +export function CommunityBottomBar() { + const [isExpanded, setIsExpanded] = useState(true); + const pathname = usePathname(); + const { currentUser } = useCommunityStore(); + + const navItems = [ + { + label: "Trang chủ", + href: "/community", + icon: , + show: true, + }, + { + label: "Bài đăng của tôi", + href: "/community/my-posts", + icon: , + show: currentUser.role === "LEARNER", + }, + { + label: "Quản lý kết nối", + href: "/community/applications", + icon: , + show: currentUser.role === "LEARNER" || currentUser.role === "TUTOR", + }, + ].filter((item) => item.show); + + if (currentUser.role === "GUEST") { + return null; // Do not show bottom bar for guests + } + + return ( +
+ + {isExpanded ? ( + + {navItems.map((item) => { + const isActive = pathname === item.href; + return ( + + {item.icon} + {item.label} + + ); + })} + + + + ) : ( + setIsExpanded(true)} + className="bg-white/60 backdrop-blur-xl text-primary border border-white/40 p-4 rounded-full shadow-[0_8px_32px_rgba(0,0,0,0.1)] hover:scale-105 transition-transform" + title="Mở menu cộng đồng" + > + + + )} + +
+ ); +} diff --git a/apps/sale/features/community/components/FilterSidebar.tsx b/apps/sale/features/community/components/FilterSidebar.tsx new file mode 100644 index 0000000..b9935ed --- /dev/null +++ b/apps/sale/features/community/components/FilterSidebar.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { useCommunityStore } from "../store/community-store"; +import { Search, RotateCcw, ShieldAlert } from "lucide-react"; +import { Button } from "@workspace/ui/components/ui/button"; +import { Input } from "@workspace/ui/components/ui/input"; +import { TeachingMode } from "../types/community"; + +export function FilterSidebar() { + const { filters, setFilters } = useCommunityStore(); + + const handleReset = () => { + setFilters({ + searchQuery: "", + subject: "", + gradeLevel: "", + teachingMode: "ALL", + district: "", + minBudget: undefined, + maxBudget: undefined, + }); + }; + + return ( +
+
+

Bộ lọc tìm kiếm

+
+ + setFilters({ searchQuery: e.target.value })} + /> +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + +
+
+
+ +
+
+

Quy tắc cộng đồng

+

+ Vui lòng không chia sẻ SĐT/Zalo trong bài đăng để bảo mật thông tin. Mọi tương tác sẽ được kết nối an toàn qua nền tảng. +

+
+
+
+
+ ); +} diff --git a/apps/sale/features/community/components/InfiniteFeedList.tsx b/apps/sale/features/community/components/InfiniteFeedList.tsx new file mode 100644 index 0000000..fbf9e5a --- /dev/null +++ b/apps/sale/features/community/components/InfiniteFeedList.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useCommunityStore } from "../store/community-store"; +import { PostCard } from "./PostCard"; +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { EmptyState } from "@workspace/ui/components/ui/empty-state"; + +export function InfiniteFeedList() { + const { posts, filters } = useCommunityStore(); + const [isLoading, setIsLoading] = useState(true); + + // Giả lập loading lần đầu + useEffect(() => { + const timer = setTimeout(() => { + setIsLoading(false); + }, 800); + return () => clearTimeout(timer); + }, []); + + const filteredPosts = posts.filter(post => { + // Lọc theo search query + if (filters.searchQuery) { + const q = filters.searchQuery.toLowerCase(); + const matchSearch = + post.content.toLowerCase().includes(q) || + post.subject.toLowerCase().includes(q) || + (post.district && post.district.toLowerCase().includes(q)); + if (!matchSearch) return false; + } + + // Lọc theo môn học + if (filters.subject && post.subject !== filters.subject) return false; + + // Lọc theo hình thức học + if (filters.teachingMode && filters.teachingMode !== "ALL" && post.teachingMode !== filters.teachingMode) return false; + + // Lọc theo ngân sách + if (filters.minBudget && post.budgetPerSession < filters.minBudget) return false; + if (filters.maxBudget && post.budgetPerSession > filters.maxBudget) return false; + + return true; + }); + + if (isLoading) { + return ( +
+ +

Đang tải danh sách bài đăng...

+
+ ); + } + + if (filteredPosts.length === 0) { + return ( + + ); + } + + return ( +
+ {filteredPosts.map(post => ( + + ))} + + {/* Mock Infinite Scroll Loader */} + {filteredPosts.length > 0 && ( +
+ + Đang tải thêm... +
+ )} +
+ ); +} diff --git a/apps/sale/features/community/components/PostCard.tsx b/apps/sale/features/community/components/PostCard.tsx new file mode 100644 index 0000000..7809d86 --- /dev/null +++ b/apps/sale/features/community/components/PostCard.tsx @@ -0,0 +1,293 @@ +"use client"; + +import { useState } from "react"; +import { CommunityPost } from "../types/community"; +import { useCommunityStore } from "../store/community-store"; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@workspace/ui/components/ui/avatar"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@workspace/ui/components/ui/dropdown-menu"; +import { Button } from "@workspace/ui/components/ui/button"; +import { + BookOpen, + GraduationCap, + MapPin, + Video, + Users, + CheckCircle2, + MoreVertical, + Edit, + Trash2, +} from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; +import { vi } from "date-fns/locale"; +import { TutorApplyModal } from "./TutorApplyModal"; +import { ApplicantQueueDrawer } from "./ApplicantQueueDrawer"; +import { PostEditModal } from "./PostEditModal"; +import { useToastStore } from "@workspace/ui/components/ui/bee-toast/useToastStore"; + +interface PostCardProps { + post: CommunityPost; +} + +export function PostCard({ post }: PostCardProps) { + const { currentUser, deletePost } = useCommunityStore(); + const [isApplyModalOpen, setIsApplyModalOpen] = useState(false); + const [isQueueDrawerOpen, setIsQueueDrawerOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const { add } = useToastStore(); + + const isAuthor = currentUser.id === post.author.id; + const isTutor = currentUser.role === "TUTOR"; + const isGuest = currentUser.role === "GUEST"; + + const hasApplied = post.applications.some( + (app) => app.tutor.id === currentUser.id, + ); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("vi-VN", { + style: "currency", + currency: "VND", + }).format(amount); + }; + + const handleActionClick = () => { + if (isGuest) { + add({ + title: "Yêu cầu đăng nhập", + description: "Vui lòng đăng nhập để thực hiện chức năng này.", + variant: "warning", + }); + return; + } + + if (isTutor) { + setIsApplyModalOpen(true); + } else if (isAuthor) { + setIsQueueDrawerOpen(true); + } + }; + + const handleDelete = () => { + if (window.confirm("Bạn có chắc chắn muốn xóa bài đăng này?")) { + deletePost(post.id); + add({ + title: "Đã xóa", + description: "Bài đăng đã được xóa thành công.", + variant: "success", + }); + } + }; + + const renderStatusBadge = () => { + switch (post.status) { + case "OPEN": + return ( + + Đang tìm gia sư + + ); + case "IN_SESSION": + return ( + + Đã kết nối + + ); + case "CLOSED": + return ( + + Đã đóng + + ); + default: + return null; + } + }; + + return ( + <> +
+ {/* Header */} +
+
+ + + + {post.author.displayName?.charAt(0) || "U"} + + +
+
+ {post.author.displayName} +
+
+ + {formatDistanceToNow(new Date(post.createdAt), { + addSuffix: true, + locale: vi, + })} + + {post.city && post.teachingMode !== "ONLINE" && ( + <> + + + + {post.city} + + + )} +
+
+
+
+ {renderStatusBadge()} + {isAuthor && ( + + + + + + setIsEditModalOpen(true)}> + + Sửa bài đăng + + + + Xóa bài đăng + + + + )} +
+
+ + {/* Content */} +
+ {post.content} +
+ + {/* Images if any */} + {post.imageUrls && post.imageUrls.length > 0 && ( +
1 ? "grid-cols-2" : "grid-cols-1"}`} + > + {post.imageUrls.map((url, index) => ( + Post image + ))} +
+ )} + + {/* Tags */} +
+
+ {post.subject} +
+
+ Lớp {post.gradeLevel} +
+
+ {post.teachingMode === "ONLINE" ? ( +
+ {(post.district || post.city) && post.teachingMode !== "ONLINE" && ( +
+ {post.district} + {post.district && post.city ? ", " : ""} + {post.city} +
+ )} +
+ {formatCurrency(post.budgetPerSession)}/buổi +
+
+ + {/* Footer Actions */} +
+
+ + {post.applicationsCount} hồ sơ đã gửi +
+ +
+ {isAuthor && ( + + )} + + {!isAuthor && (isTutor || isGuest) && ( + + )} +
+
+
+ + + + + + ); +} diff --git a/apps/sale/features/community/components/PostCreateBox.tsx b/apps/sale/features/community/components/PostCreateBox.tsx new file mode 100644 index 0000000..888bf09 --- /dev/null +++ b/apps/sale/features/community/components/PostCreateBox.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { useState } from "react"; +import { useCommunityStore } from "../store/community-store"; +import { Avatar, AvatarFallback, AvatarImage } from "@workspace/ui/components/ui/avatar"; +import { AlertCircle } from "lucide-react"; +import { Button } from "@workspace/ui/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@workspace/ui/components/ui/dialog"; +import { PostCreateModal } from "./PostCreateModal"; + +export function PostCreateBox() { + const { currentUser } = useCommunityStore(); + const [showActiveSessionWarning, setShowActiveSessionWarning] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + + if (currentUser.role !== "LEARNER") { + return null; // Only Learner can see this box + } + + const handleFocus = () => { + if (currentUser.hasActiveLearnerClass) { + setShowActiveSessionWarning(true); + } else { + setIsModalOpen(true); + } + }; + + return ( + <> +
+
+ + + {currentUser.displayName?.charAt(0) || "U"} + +
+
+ Bạn cần tìm gia sư môn gì, mục tiêu học tập ra sao? +
+
+
+
+ + + + +
+
+ +
+ Không thể đăng bài mới +
+ + Bạn đang có 1 phiên kết nối học tập chưa hoàn tất. Vui lòng xử lý phiên chat hiện tại hoặc hoàn thành lớp học trước khi tạo bài đăng mới. + +
+ + + +
+
+ + + + ); +} diff --git a/apps/sale/features/community/components/PostCreateModal.tsx b/apps/sale/features/community/components/PostCreateModal.tsx new file mode 100644 index 0000000..b7f6b2a --- /dev/null +++ b/apps/sale/features/community/components/PostCreateModal.tsx @@ -0,0 +1,226 @@ +"use client"; + +import { useState } from "react"; +import { useCommunityStore } from "../store/community-store"; +import { Button } from "@workspace/ui/components/ui/button"; +import { + BookOpen, + GraduationCap, + MapPin, + Video, + DollarSign, + ImagePlus, + Send, + Loader2, +} from "lucide-react"; +import { TeachingMode } from "../types/community"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@workspace/ui/components/ui/dialog"; +import { provinces } from "tinhthanhvn"; +import { useToastStore } from "@workspace/ui/components/ui/bee-toast/useToastStore"; + +interface PostCreateModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function PostCreateModal({ open, onOpenChange }: PostCreateModalProps) { + const { createPost } = useCommunityStore(); + const { add } = useToastStore(); + + const [content, setContent] = useState(""); + const [subject, setSubject] = useState(""); + const [gradeLevel, setGradeLevel] = useState(""); + const [teachingMode, setTeachingMode] = useState("ONLINE"); + const [budgetPerSession, setBudgetPerSession] = useState(""); + const [city, setCity] = useState(""); + + const provinceList = provinces.all(); // usually returns an array of { code, name, ... } + + const handleSubmit = () => { + // Only content and subject are strictly required. Others are optional or have defaults. + if (!content.trim() || !subject) return; + + createPost({ + content, + imageUrls: [], + subject, + gradeLevel: gradeLevel || "Khác", + teachingMode, + budgetPerSession: budgetPerSession ? parseInt(budgetPerSession) : 0, + city: city || undefined, + }); + + add({ + title: "Đăng bài thành công", + description: "Bài đăng của bạn đã được xuất bản.", + variant: "success", + }); + + // Reset + setContent(""); + setSubject(""); + setGradeLevel(""); + setBudgetPerSession(""); + setCity(""); + onOpenChange(false); + }; + + return ( + + + + + Tạo bài đăng tìm gia sư + + + +
+
+