Seller tools
Bulk uploads, inventory management, and pricing suggestions
Overview
Current state
One-by-one listing creation, no inventory management.
Target state
- Bulk upload via CSV/spreadsheet
- Inventory dashboard
- Auto-relist sold items
- Pricing suggestions
- Listing templates
Features
Bulk upload
- CSV import for multiple items
- Photo batch upload
- Field mapping UI
- Validation and error handling
Inventory dashboard
- All listings in one view
- Filter by status (active, sold, draft)
- Bulk actions (edit, delete, relist)
- Sales analytics per item
Pricing tools
- Market price comparison
- Price drop suggestions
- "Sell fast" pricing
- Competitor analysis
Templates
- Save listing templates
- Quick duplicate listings
- Category-specific defaults
- Auto-fill common fields
Architecture
CSV import
async function importListingsFromCSV(
sellerId: string,
file: File
): Promise<ImportResult> {
const rows = await parseCSV(file);
const results: ImportResult = { success: [], errors: [] };
for (const [index, row] of rows.entries()) {
try {
const validated = listingSchema.parse({
title: row.title,
description: row.description,
price: parseFloat(row.price),
categoryId: await getCategoryId(row.category),
brandId: await getBrandId(row.brand),
conditionId: await getConditionId(row.condition),
photos: row.photos?.split(',').map(url => url.trim()),
});
const item = await createItem(sellerId, validated);
results.success.push({ row: index + 1, itemId: item.id });
} catch (error) {
results.errors.push({ row: index + 1, error: error.message });
}
}
return results;
}Bulk actions
async function bulkUpdateItems(
sellerId: string,
itemIds: string[],
updates: Partial<Item>
) {
// Verify ownership
const items = await db.query.items.findMany({
where: and(
inArray(items.id, itemIds),
eq(items.sellerId, sellerId),
),
});
if (items.length !== itemIds.length) {
throw new Error('Some items not found or not owned');
}
await db.update(items)
.set(updates)
.where(inArray(items.id, itemIds));
}
// Bulk delete
async function bulkDeleteItems(sellerId: string, itemIds: string[]) {
await db.delete(items)
.where(and(
inArray(items.id, itemIds),
eq(items.sellerId, sellerId),
));
}Price suggestions
async function getPriceSuggestion(item: {
title: string;
categoryId: string;
brandId?: string;
conditionId: string;
}): Promise<PriceSuggestion> {
const similarItems = await db.query.items.findMany({
where: and(
eq(items.categoryId, item.categoryId),
item.brandId ? eq(items.brandId, item.brandId) : undefined,
eq(items.status, 'sold'),
),
orderBy: desc(items.soldAt),
limit: 20,
});
if (similarItems.length < 3) {
return { suggestion: null, confidence: 'low' };
}
const prices = similarItems.map(i => i.price);
const avg = prices.reduce((a, b) => a + b, 0) / prices.length;
const median = prices.sort()[Math.floor(prices.length / 2)];
return {
suggestion: Math.round(median),
sellFast: Math.round(median * 0.85),
maximize: Math.round(median * 1.15),
marketRange: { min: Math.min(...prices), max: Math.max(...prices) },
confidence: similarItems.length > 10 ? 'high' : 'medium',
};
}Implementation
Inventory dashboard
Build all listings view with status filters and basic bulk actions.
Bulk upload
Create CSV import UI with field mapping and error handling.
Pricing tools
Implement price suggestion API, market comparison, and price drop alerts.
Templates
Add save/load templates, quick duplicate, and auto-fill fields.
Checklist
Dashboard
- Listings table view
- Status filters
- Search and sort
- Bulk select UI
Bulk actions
- Bulk edit modal
- Bulk delete with confirmation
- Bulk relist
Import/Export
- CSV template download
- CSV upload and parse
- Field mapping UI
- Import progress and results
Pricing
- Price suggestion API
- Suggestion display on listing form
- Market comparison view