Add comprehensive plant trading marketplace with: - Prisma schema with marketplace models (Listing, Offer, SellerProfile, WishlistItem) - Service layer for listings, offers, search, and matching - API endpoints for CRUD operations, search, and recommendations - Marketplace pages: home, listing detail, create, my-listings, my-offers - Reusable UI components: ListingCard, ListingGrid, OfferForm, SearchFilters, etc. Features: - Browse and search listings by category, price, tags - Create and manage listings (draft, active, sold, cancelled) - Make and manage offers on listings - Seller and buyer views with statistics - Featured and recommended listings - In-memory store (ready for database migration via Agent 2)
117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
// API: Single Offer Management
|
|
// GET /api/marketplace/offers/[id] - Get an offer
|
|
// PUT /api/marketplace/offers/[id] - Update offer (accept/reject/withdraw)
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { offerService, listingService } from '@/lib/marketplace';
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
const { id: offerId } = req.query;
|
|
|
|
if (!offerId || typeof offerId !== 'string') {
|
|
return res.status(400).json({ error: 'Offer ID is required' });
|
|
}
|
|
|
|
try {
|
|
switch (req.method) {
|
|
case 'GET':
|
|
return handleGet(offerId, req, res);
|
|
case 'PUT':
|
|
return handlePut(offerId, req, res);
|
|
default:
|
|
res.setHeader('Allow', ['GET', 'PUT']);
|
|
return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
|
|
}
|
|
} catch (error) {
|
|
console.error('Offer API error:', error);
|
|
return res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: error instanceof Error ? error.message : 'Unknown error',
|
|
});
|
|
}
|
|
}
|
|
|
|
async function handleGet(offerId: string, req: NextApiRequest, res: NextApiResponse) {
|
|
const userId = req.headers['x-user-id'] as string;
|
|
|
|
const offer = await offerService.getOfferById(offerId);
|
|
|
|
if (!offer) {
|
|
return res.status(404).json({ error: 'Offer not found' });
|
|
}
|
|
|
|
// Get listing to check authorization
|
|
const listing = await listingService.getListingById(offer.listingId);
|
|
|
|
// Only buyer or seller can view the offer
|
|
if (userId !== offer.buyerId && userId !== listing?.sellerId) {
|
|
return res.status(403).json({ error: 'Not authorized to view this offer' });
|
|
}
|
|
|
|
return res.status(200).json({
|
|
...offer,
|
|
listing,
|
|
});
|
|
}
|
|
|
|
async function handlePut(offerId: string, req: NextApiRequest, res: NextApiResponse) {
|
|
const userId = req.headers['x-user-id'] as string;
|
|
|
|
if (!userId) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
const { action, amount } = req.body;
|
|
|
|
if (!action) {
|
|
return res.status(400).json({ error: 'Action is required (accept, reject, withdraw, update)' });
|
|
}
|
|
|
|
try {
|
|
let result;
|
|
|
|
switch (action) {
|
|
case 'accept':
|
|
result = await offerService.acceptOffer(offerId, userId);
|
|
break;
|
|
|
|
case 'reject':
|
|
result = await offerService.rejectOffer(offerId, userId);
|
|
break;
|
|
|
|
case 'withdraw':
|
|
result = await offerService.withdrawOffer(offerId, userId);
|
|
break;
|
|
|
|
case 'update':
|
|
if (!amount || typeof amount !== 'number') {
|
|
return res.status(400).json({ error: 'New amount is required for update action' });
|
|
}
|
|
result = await offerService.updateOfferAmount(offerId, userId, amount);
|
|
break;
|
|
|
|
default:
|
|
return res.status(400).json({
|
|
error: 'Invalid action',
|
|
validActions: ['accept', 'reject', 'withdraw', 'update'],
|
|
});
|
|
}
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
action,
|
|
offer: result,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('Unauthorized') || error.message.includes('not your')) {
|
|
return res.status(403).json({ error: error.message });
|
|
}
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
throw error;
|
|
}
|
|
}
|