link,[object Object]
Skip to content

Listings Management ​

Overview ​

The listings system is the core of AcqMarketplace, enabling sellers to create comprehensive business profiles and buyers to discover opportunities. This document covers the complete listing lifecycle, admin moderation, AI-powered creation assistance, and integration points.

Listing Lifecycle ​

mermaid
graph LR
    A[Draft] --> B[Pending Review]
    B --> C{Admin Review}
    C -->|Approved| D[Active]
    C -->|Rejected| E[Needs Changes]
    E --> B
    D --> F[Under Offer]
    F --> G[Sold]
    D --> H[Archived]

Search & Filters ​

  • Filter model (public Explore):
    • Keys: searchTerm, category, techStack[], businessAge, priceRange, profitRange, revenueRange, clientsRange, sortBy.
    • Source: src/hooks/useExploreFilters.ts:4 and :16-27.
  • Query building and filter application:
    • Active only: .eq('status','active') β€” src/hooks/useEnhancedExploreQuery.ts:29.
    • Text search (title/description ilike): :32-34.
    • Category: :36-38.
    • Tech stack (array contains): :40-48 with tech name mapping via src/hooks/useTechStack.ts:17-21.
    • Business age (date range over established_date): :50-70.
    • Price range: :73-78.
    • Revenue range: :81-85.
    • Profit range: :87-92.
    • Customers range: :94-99.

Sorting ​

  • Sponsored first: order('is_sponsored', { ascending: false }) β€” src/hooks/useEnhancedExploreQuery.ts:101-103.
  • Then by selected sort:
    • price-low β†’ price asc β€” :104-106
    • price-high β†’ price desc β€” :106-108
    • monthly_revenue β†’ desc β€” :108-110
    • monthly_profit β†’ desc β€” :110-112
    • default created_at desc β€” :112-114
  • UI sort labels/toggles: src/components/explore/ExploreFilters.tsx:61-71, 139-178.

Pagination modes ​

  • Pagination (9 items/page): ITEMS_PER_PAGE=9 β€” src/hooks/useEnhancedExploreQuery.ts:8, :164-195.
  • Infinite scroll (Load More): useInfiniteQuery β€” :141-159.
  • Mode selection: Explore/ExploreInfinite β†’ ExploreCore β†’ ExploreMain β€” src/pages/Explore.tsx:5-7, src/pages/ExploreInfinite.tsx:5-7, src/components/explore/ExploreMain.tsx:13-16, 61-82.

Status Definitions ​

StatusDescriptionUser ActionsAdmin Actions
DraftBeing created by sellerEdit, Submit for ReviewView Only
PendingAwaiting admin approvalView OnlyApprove, Reject, Comment
ActiveLive on marketplaceEdit (limited), Manage OffersArchive, Feature
Under OfferDeal in progressView Only, Manage Current DealMonitor, Mediate
SoldSuccessfully transferredView OnlyArchive
ArchivedRemoved from marketplaceReactivate RequestRestore

Core Components ​

Listing Creation Flow ​

File: src/pages/CreateListing.tsxFeatures:

  • Multi-step form with validation
  • Image upload and management
  • AI-Powered Project Analysis: Auto-populate fields from project URLs
    • Mistral AI integration for intelligent data extraction
    • Robust error handling and JSON parsing recovery
    • Financial defaults generation for missing data
    • Category normalization and structured description mapping
  • Rich text descriptions with i18n
  • Technology stack selection
  • Financial metrics input

Create Listing Fields (Required vs Optional) ​

Grouped by form sections. Fields marked Required reflect the UI (RequiredFieldIndicator) and must be provided to submit.

  • Basic Information (required)

    • title / title_ro / title_en β€” Required
    • category β€” Required
    • website_url β€” Required
    • screenshot_url β€” Required (general listing screenshot)
    • images[] β€” Required (at least 1)
  • Overview (required)

    • business_model / business_model_ro / business_model_en β€” Required
    • established_date β€” Required
    • number_of_customers β€” Required
  • Financial Data (required)

    • monthly_revenue β€” Required
    • monthly_profit β€” Required
    • price β€” Required
    • allow_negotiation β€” Optional (boolean)
    • price_reasoning / price_reasoning_ro / price_reasoning_en β€” Optional
    • revenue_metrics_screenshot β€” Optional
  • Traffic & Audience

    • monthly_traffic β€” Required
    • target_audience / target_audience_ro / target_audience_en β€” Optional
    • traffic_metrics_screenshot β€” Optional
  • Business Details

    • competitors / competitors_ro / competitors_en β€” Optional
    • growth_opportunities / growth_opportunities_ro / growth_opportunities_en β€” Optional
    • reason_for_selling / reason_for_selling_ro / reason_for_selling_en β€” Required
  • Resources & Technologies

    • technologies[] β€” Optional
    • included_assets[] β€” Optional
  • Additional Terms

    • terms_conditions / terms_conditions_ro / terms_conditions_en β€” Optional
  • Administrator (evidence & docs)

    • files (financial documents) β€” Admin-only UI

Notes:

  • Many text fields are multilingual; when present, use the localized variant (e.g., *_ro, *_en).
  • Sensitive fields may be blurred for certain users; see Data Visibility below.

Field Validation Matrix (key rules) ​

FieldTypeRules
titletext3–120 chars
categorytextmust match listing_categories.slug
website_urlURLvalid http(s) URL
imagesarray[text]min length 1, max 10
screenshot_urltextoptional if imagesβ‰₯1, else required
business_modeltext50–1500 chars
established_datedateYYYY-MM-DD, <= today
number_of_customersinteger>= 0
monthly_revenuenumeric>= 0
monthly_profitnumericcan be negative, recommended β‰₯ 0
pricenumeric> 0
monthly_trafficinteger>= 0
reason_for_sellingtext50–1500 chars
price_reasoningtextoptional
target_audiencetextoptional
competitorstextoptional
growth_opportunitiestextoptional
terms_conditionstextoptional

Listing Detail View ​

File: src/pages/ListingDetail.tsxFeatures:

  • Comprehensive business information
  • Image gallery with lightbox
  • Financial metrics (subscription-gated)
  • Contact seller functionality
  • Offer submission

Files: src/pages/Explore.tsx, src/pages/ExploreInfinite.tsx, src/pages/ExploreCore.tsxFeatures:

  • Advanced filtering (price, category, metrics)
  • Sorting options (newest, price, revenue)
  • Pagination and infinite scroll
  • Saved searches and watchlist
  • Featured listings priority

Data Model ​

Core Table: listings ​

sql
-- Key columns from migration analysis
id uuid PRIMARY KEY
seller_id uuid REFERENCES profiles(id)
title text NOT NULL
price numeric NOT NULL
status listing_status DEFAULT 'draft'
monthly_revenue numeric
monthly_profit numeric
monthly_traffic integer
category text NOT NULL
verification_status text DEFAULT 'pending'
featured boolean DEFAULT false
created_at timestamptz DEFAULT now()
  • listing_views: Track visitor engagement
  • admin_listing_comments: Admin feedback system
  • offers: Purchase offers from buyers
  • saved_searches: User search preferences
  • favorites / watchlist: User watchlist items

Categories & Q&A ​

  • listing_categories: Used for category selection in the create-listing form (public SELECT; admin-managed)
  • listing_questions (Q&A): Buyers ask questions; sellers/admin answer; visible on approved listings

Favorites & Watchlist ​

  • Hook: src/hooks/useUserFavorites.ts:12-22 reads favorites.listing_id for the current user, exposed in Explore β€” src/components/explore/ExploreMain.tsx:23-24, 38-71.

Admin Listings Management ​

  • Filters and status/category selectors: src/components/admin/listings/ListingFilters.tsx:33-59, 61-71.
  • Sorting tabs with counts: src/components/admin/listings/ListingSortTabs.tsx:28-39, 44-77.
  • Admin pages: src/pages/admin/AdminListings.tsx and src/pages/admin/AdminListingsDetail.tsx.

Admin Moderation System ​

Complete Listing Approval Flow ​

The listing approval process is a critical part of maintaining platform quality and ensuring all listings meet our standards before becoming visible to users.

Flow Overview ​

mermaid
graph TD
    A[Seller Submits Listing] --> B[Status: Pending]
    B --> C[Admin Reviews Listing]
    C --> D{Review Decision}
    D -->|Approve| E[Check Seller Verification]
    E --> F{Seller Verified?}
    F -->|Yes| G[Update Both Status & Verification]
    F -->|No| H[Update Only Status]
    G --> I[Listing Visible to All Users]
    H --> J[Listing Visible to Verified Users Only]
    D -->|Reject| K[Add Admin Comments]
    K --> L[Notify Seller]
    L --> M[Seller Makes Changes]
    M --> B
    D -->|Request Changes| N[Add Field-Specific Comments]
    N --> L

Detailed Approval Process ​

1. Initial Review Phase

  • Location: /admin/listings (AdminListings.tsx)
  • Actions Available:
    • View complete listing details
    • Add field-specific comments
    • Approve listing
    • Reject listing
    • Request specific changes

2. Approval Logic (Updated) When admin clicks "Approve":

typescript
// New approval logic in AdminListings.tsx
const approveListingMutation = useMutation({
  mutationFn: async (listingId: string) => {
    // 1. Check if seller is verified
    const { data: listing } = await supabase
      .from('listings')
      .select(`
        *,
        seller:profiles!listings_seller_id_fkey (
          seller_verification_status,
          seller_verified
        )
      `)
      .eq('id', listingId)
      .single();

    // 2. Determine verification status
    const isSellerVerified = listing.seller?.seller_verified === true || 
                            listing.seller?.seller_verification_status === 'approved';

    // 3. Update both status and verification_status if seller is verified
    const updateData = { status: 'active' };
    if (isSellerVerified) {
      updateData.verification_status = 'approved';
    }

    // 4. Apply updates
    const { data, error } = await supabase
      .from('listings')
      .update(updateData)
      .eq('id', listingId)
      .select()
      .single();

    return data;
  }
});

3. Visibility Rules After Approval

Seller StatusListing StatusVerification StatusVisibility
VerifiedActiveApprovedβœ… All Users (Public)
Not VerifiedActivePending❌ Only Owner & Admin

4. RLS Policy Requirements

sql
-- Listings are visible to public only if:
-- 1. status = 'active' AND
-- 2. verification_status = 'approved' OR 'verified'
CREATE POLICY "listings_consolidated" ON listings
FOR ALL TO public
USING (
  (seller_id = auth.uid()) OR 
  is_admin_user() OR 
  ((status = 'active') AND (verification_status = ANY (ARRAY['approved', 'verified']))) OR
  (auth.role() = 'service_role')
);

Review Process Steps ​

1. Initial Review

  • Verify business information completeness
  • Check financial document authenticity
  • Validate contact information
  • Review for policy compliance

2. Feedback System

  • Field-specific comments using admin_listing_comments
  • Resolution tracking per comment
  • Email notifications to sellers

3. Approval Criteria

  • Complete business description
  • Valid financial metrics
  • Appropriate categorization
  • No policy violations

Admin Actions ​

Approve Listing

typescript
// Automatic dual-update for verified sellers
const approveListing = async (listingId: string) => {
  // Updates both status='active' AND verification_status='approved'
  // Only if seller is already verified
}

Reject Listing

typescript
const rejectListing = async (listingId: string, reason: string) => {
  // Update status to 'rejected'
  // Add rejection reason
  // Notify seller with feedback
}

Request Changes

typescript
const requestChanges = async (listingId: string, comments: AdminComment[]) => {
  // Add field-specific comments
  // Keep status as 'pending'
  // Notify seller of required changes
}

Common Issues & Solutions ​

Issue: Listing approved but not visible to users Cause: Seller not verified, so only status was updated Solution: Verify seller first, then approve listing

Issue: Admin approval not working Cause: Missing seller verification check in approval logic Solution: Use updated approval mutation that checks seller verification

Issue: Listing visible to owner but not public Cause: verification_status still 'pending' Solution: Check seller verification status and update accordingly

Testing the Approval Flow ​

  1. Create Test Listing:

    sql
    INSERT INTO listings (title, status, verification_status, seller_id)
    VALUES ('Test Listing', 'pending', 'pending', 'seller-uuid');
  2. Test Approval as Admin:

    • Go to /admin/listings
    • Find pending listing
    • Click "Approve"
    • Verify both status and verification_status updated
  3. Verify Visibility:

    • Check as unauthenticated user on /explore
    • Should be visible if seller is verified
    • Should be hidden if seller not verified

Email Notifications ​

Approval Notification:

  • Sent to seller when listing approved
  • Includes listing title and link
  • Confirms visibility status

Rejection Notification:

  • Sent to seller when listing rejected
  • Includes admin comments and feedback
  • Explains next steps for resubmission

Change Request Notification:

  • Sent to seller when changes requested
  • Includes field-specific comments
  • Links to edit listing page

Subscription-Based Access Control ​

Data Visibility (Blur Policy) ​

Implemented via: blurred_fields (config), can_view_field() (verification), listing_view_secure (server-side masking projection)

  • Full documentation and real field matrix: see docs/architecture/blur-policy.md.

Implementation Pattern ​

typescript
// Check field visibility based on user subscription
const canViewField = await supabase.rpc('can_view_field', {
  p_listing: listingId,
  p_field_key: 'monthly_profit',
  p_user: userId
})

if (canViewField) {
  // Show actual value
} else {
  // Show blurred placeholder
}

Search and Filtering ​

See β€œSearch & Filters” above for the authoritative list and implementation references.

Promotion Features ​

  • Priority Placement: Top of search results
  • Enhanced Visibility: Special styling and badges
  • Analytics Tracking: Views, clicks, conversion rates
  • Scheduling: Start/end date management

Implementation ​

sql
-- Featured listing promotion
UPDATE listings 
SET 
  featured = true,
  sponsored_start_date = NOW(),
  sponsored_end_date = NOW() + INTERVAL '30 days'
WHERE id = $1

Image Management ​

Upload System ​

Storage: Supabase Storage with CDN Allowed Formats: JPG, PNG, WebP Size Limits: 5MB per image, 10 images per listing

Security & Access

  • Private bucket with signed URLs (see Security & Privacy β†’ Storage & Media Security)
  • Store only keys in DB; resolve signed URLs at render-time
typescript
// Image upload implementation
const uploadListingImage = async (file: File, listingId: string) => {
  const fileExt = file.name.split('.').pop()
  const filePath = `listings/${listingId}/${Date.now()}.${fileExt}`
  
  const { data, error } = await supabase.storage
    .from('listing-images')
    .upload(filePath, file)
    
  return data?.path
}

Internationalization ​

Multi-language Support ​

All listing content supports Romanian and English:

  • title / title_ro / title_en
  • description / description_ro / description_en
  • business_model / business_model_ro / business_model_en

Content Management ​

typescript
// Language-aware content retrieval
const getLocalizedContent = (listing: Listing, field: string, locale: string) => {
  const localizedField = `${field}_${locale}`
  return listing[localizedField] || listing[field] || ''
}

Performance Optimizations ​

Database Indexing ​

sql
-- Key indexes for listing queries
CREATE INDEX idx_listings_status ON listings(status);
CREATE INDEX idx_listings_category ON listings(category);
CREATE INDEX idx_listings_price ON listings(price);
CREATE INDEX idx_listings_featured ON listings(featured, created_at);

Caching Strategy ​

  • Listing Cards: React Query with 5-minute cache
  • Listing Details: Fresh data on page load
  • Search Results: 2-minute cache with invalidation
  • Images: CDN caching with 1-year expiry

Integration Points ​

With Offers System ​

  • Automatic offer notifications
  • Seller dashboard integration
  • Deal creation workflow

With Messaging System ​

  • Contact seller functionality
  • Conversation initiation
  • Notification delivery

With Analytics ​

  • View tracking and analytics
  • Conversion funnel analysis
  • Performance metrics

API Endpoints ​

Core Listing Operations ​

typescript
// Fetch public listings
GET /rest/v1/listings?status=eq.active

// Create new listing (authenticated)
POST /rest/v1/listings

// Update listing (owner only)
PATCH /rest/v1/listings?id=eq.{listing_id}

// Admin moderation
POST /rest/v1/rpc/admin_moderate_listing

REST API Contracts (examples) ​

http
POST /rest/v1/listings
Content-Type: application/json

{
  "title": "AI SEO SaaS",
  "category": "saas",
  "website_url": "https://example.com",
  "price": 9900,
  "monthly_revenue": 800,
  "monthly_profit": 500,
  "monthly_traffic": 12000,
  "business_model": "SaaS subscription",
  "established_date": "2023-04-01",
  "number_of_customers": 42,
  "images": ["listings/{id}/main.webp"]
}

PATCH /rest/v1/listings?id=eq.{listing_id}
Content-Type: application/json

{
  "price": 8900,
  "price_reasoning": "Promo price for quick sale"
}

GET /rest/v1/listing_view_secure?id=eq.{listing_id}

JSON Schema: Create Listing Payload ​

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://acqm/docs/schemas/create-listing.schema.json",
  "title": "CreateListingPayload",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "title": { "type": "string", "minLength": 3, "maxLength": 120 },
    "category": { "type": "string", "minLength": 1 },
    "website_url": { "type": "string", "format": "uri" },
    "price": { "type": "number", "exclusiveMinimum": 0 },
    "monthly_revenue": { "type": "number", "minimum": 0 },
    "monthly_profit": { "type": "number" },
    "monthly_traffic": { "type": "integer", "minimum": 0 },
    "business_model": { "type": "string", "minLength": 50, "maxLength": 1500 },
    "established_date": { "type": "string", "format": "date" },
    "number_of_customers": { "type": "integer", "minimum": 0 },
    "images": {
      "type": "array",
      "items": { "type": "string" },
      "minItems": 1,
      "maxItems": 10
    },
    "screenshot_url": { "type": "string" },
    "allow_negotiation": { "type": "boolean" },
    "price_reasoning": { "type": "string" },
    "revenue_metrics_screenshot": { "type": "string" },
    "target_audience": { "type": "string" },
    "traffic_metrics_screenshot": { "type": "string" },
    "competitors": { "type": "string" },
    "growth_opportunities": { "type": "string" },
    "reason_for_selling": { "type": "string", "minLength": 50, "maxLength": 1500 }
  },
  "required": [
    "title",
    "category",
    "website_url",
    "price",
    "monthly_revenue",
    "monthly_profit",
    "monthly_traffic",
    "business_model",
    "established_date",
    "number_of_customers",
    "images",
    "screenshot_url",
    "reason_for_selling"
  ]
}

listing_view_secure β€” Example Responses ​

Blurred (public/free)

json
{
  "id": "c1b6c5f4-1a2b-4b2e-9a0a-111111111111",
  "title": "AI SEO SaaS",
  "category": "saas",
  "price": 9900,
  "monthly_revenue": null,
  "monthly_profit": null,
  "monthly_traffic": null,
  "number_of_customers": null,
  "website_url": null,
  "screenshot_url": "listings/..../cover.webp",
  "traffic_metrics_screenshot": null,
  "revenue_metrics_screenshot": null,
  "main_image": "listings/..../main.webp",
  "featured": false,
  "verification_status": "approved",
  "created_at": "2025-09-01T12:00:00.000Z"
}

Visible (starter/pro/owner/admin)

json
{
  "id": "c1b6c5f4-1a2b-4b2e-9a0a-111111111111",
  "title": "AI SEO SaaS",
  "category": "saas",
  "price": 9900,
  "monthly_revenue": 800,
  "monthly_profit": 500,
  "monthly_traffic": 12000,
  "number_of_customers": 42,
  "website_url": "https://example.com",
  "screenshot_url": "listings/..../cover.webp",
  "traffic_metrics_screenshot": "listings/..../traffic.webp",
  "revenue_metrics_screenshot": "listings/..../revenue.webp",
  "main_image": "listings/..../main.webp",
  "featured": false,
  "verification_status": "approved",
  "created_at": "2025-09-01T12:00:00.000Z"
}

Notes:

  • Null values indicate masked (blurred) fields by the secure view.
  • Owners and admins always receive fully visible values.

Custom RPC Functions ​

  • get_admin_listing_comments(listing_id): Retrieve admin feedback
  • send_admin_comments_to_user(listing_id): Notify seller of feedback
  • can_view_field(listing_id, field_key, user_id): Check field visibility

Security Considerations ​

Row Level Security (RLS) ​

sql
-- Listings visibility policy
CREATE POLICY "Public can view active listings" 
ON listings FOR SELECT 
TO public 
USING (status = 'active' AND verification_status = 'approved');

-- Sellers can manage own listings
CREATE POLICY "Sellers manage own listings" 
ON listings FOR ALL 
TO authenticated 
USING (seller_id = auth.uid());

Data Validation ​

  • Server-side validation for all financial data
  • Image content scanning for inappropriate material
  • Text content moderation for spam/scams
  • Business verification document validation

Testing Strategy ​

Unit Tests ​

  • Listing creation workflow
  • Filter and search logic
  • Data validation functions
  • Permission checking

Integration Tests ​

  • Admin moderation flow
  • Subscription access control
  • Image upload and processing
  • Email notification delivery

E2E Tests ​

  • Complete listing creation
  • Buyer browsing experience
  • Admin review process
  • Mobile responsiveness

Troubleshooting ​

Common Issues ​

Listing Not Appearing in Search

  • Check listing status (must be 'active')
  • Verify verification_status is 'approved'
  • Confirm category assignment
  • Review RLS policies

Image Upload Failures

  • Verify file size (<5MB)
  • Check file format (JPG/PNG/WebP)
  • Confirm Supabase Storage permissions
  • Review bucket policy configuration

Admin Comments Not Saving

  • Verify admin role permissions
  • Check admin_listing_comments table access
  • Confirm notification triggers are working
  • Review RPC function permissions

Next Steps ​

  1. Enhanced Search: Implement Elasticsearch for advanced search
  2. AI Features: Auto-categorization and valuation assistance
  3. Video Content: Support for business demo videos
  4. Verification: Enhanced business verification process
  5. Analytics: Advanced listing performance metrics

Related Documentation: