Full flow for contractors to create, manage, and analyze marketplace listings, and for posters to browse, view details, and send inquiries.
ListingCard + EmptyState components
useQuery(api.marketplace.queries.getMyListings)
Create form with category chips, pricing modes
useMutation(api.marketplace.mutations.create)
ListingDetailScreen with analytics overlay
useQuery(api.marketplace.queries.get) + analytics
ListingCard + ListingFilterBar + ActiveFilters
useQuery(api.marketplace.queries.list) with filter args
ListingDetailScreen + ListingTypeBadge
useQuery(api.marketplace.queries.get) + save/unsave mutations
Inquiry form with listing reference card
useMutation(api.marketplace.mutations.sendInquiry)
InquiriesScreen with tab filtering + unread badge
useQuery(api.marketplace.queries.listMyInquiries) + markInquiryRead
InquiryDetailModal with conversation thread
respondToInquiry mutation + role-conditional CTAs
marketplaceListings
_id: Id<"marketplaceListings">
ownerId: Id<"contractorUsers">
listingType: "job_posting" | "business_for_sale" | "service_offered"
title: string
description: string
category: string
city?: string
state: string
status: "active" | "paused" | "draft" | "expired"
priceType: "fixed" | "range" | "hourly" | "negotiable" | "contact"
priceMin?: number
priceMax?: number
photos?: string[]
viewCount: number
inquiryCount: number
saveCount: number
isFeatured?: boolean
marketplaceInquiries
_id: Id<"marketplaceInquiries">
listingId: Id<"marketplaceListings">
senderId: Id<"posterUsers">
senderName?: string
senderEmail?: string
message: string
response?: string
status: "pending" | "read" | "responded"
respondedAt?: number
api.marketplace.queries.list
{ listingType?, category?, state?, city?, priceMin?, priceMax?, limit }
api.marketplace.queries.get
{ listingId: Id<"marketplaceListings"> }
api.marketplace.queries.getMyListings
{ status: "active" | "paused" | "draft" | "expired", limit }
api.marketplace.queries.getCategories
{}
api.marketplace.queries.listMyInquiries
{ limit }
api.marketplace.queries.getUnreadInquiryCount
{}
api.marketplace.mutations.create
{ listingType, title, description, category, city?, state, priceType?, priceMin?, priceMax?, employmentType?, experienceLevel?, isRemote? }
api.marketplace.mutations.toggleActive
{ listingId }
api.marketplace.mutations.deleteListing
{ listingId }
api.marketplace.mutations.saveListing
{ listingId }
api.marketplace.mutations.unsaveListing
{ listingId }
api.marketplace.mutations.toggleSave
{ listingId }
api.marketplace.mutations.sendInquiry
{ listingId, message }
api.marketplace.mutations.respondToInquiry
{ inquiryId, response }
api.marketplace.mutations.markInquiryRead
{ inquiryId }
Hooks
useMarketplace() — combined listing + filter state
useListings(filters) — paginated listing query
useInquiries() — inquiry list + unread count
Shared Components
<ListingCard> — listing preview card
<ListingFilterBar> — type/category tabs
<ListingFiltersModal> — full filter sheet
<ActiveFilters> — removable filter chips
<ListingTypeBadge> — type indicator
<ListingStatusBadge> — status indicator
Screens
(contractor)/marketplace/my-listings.tsx
(contractor)/marketplace/create.tsx
(contractor)/marketplace/[id].tsx
(contractor)/marketplace/inquiries.tsx
(poster)/marketplace/index.tsx
(poster)/marketplace/[id].tsx
(poster)/marketplace/inquiries.tsx