Initial marketplace implementation
Full-stack marketplace for buying/selling second-hand items. React 19 + TypeScript + Tailwind CSS v4 frontend with 17 screens, Express + Prisma + Socket.io backend, Stripe payments, JWT auth. Deployed at https://marketplace.173.212.212.157.sslip.io/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
21
client/src/utils/constants.ts
Normal file
21
client/src/utils/constants.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export const CATEGORIES: { value: string; label: string; icon: string }[] = [
|
||||
{ value: 'ELECTRONICS', label: 'Electronics', icon: 'Smartphone' },
|
||||
{ value: 'FURNITURE', label: 'Furniture', icon: 'Armchair' },
|
||||
{ value: 'CLOTHING', label: 'Clothing & Shoes', icon: 'Shirt' },
|
||||
{ value: 'HOME_GARDEN', label: 'Home & Garden', icon: 'Home' },
|
||||
{ value: 'SPORTS', label: 'Sports & Outdoors', icon: 'Dumbbell' },
|
||||
{ value: 'BOOKS', label: 'Books', icon: 'BookOpen' },
|
||||
{ value: 'GAMES', label: 'Games', icon: 'Gamepad2' },
|
||||
{ value: 'VEHICLES', label: 'Vehicles', icon: 'Car' },
|
||||
{ value: 'OTHER', label: 'Other', icon: 'Package' },
|
||||
];
|
||||
|
||||
export const CONDITIONS: { value: string; label: string }[] = [
|
||||
{ value: 'NEW', label: 'New' },
|
||||
{ value: 'LIKE_NEW', label: 'Like New' },
|
||||
{ value: 'GENTLY_USED', label: 'Gently Used' },
|
||||
{ value: 'USED', label: 'Used' },
|
||||
{ value: 'FAIR', label: 'Fair' },
|
||||
];
|
||||
|
||||
export const LISTING_FEE = 5;
|
||||
31
client/src/utils/format.ts
Normal file
31
client/src/utils/format.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export function formatCurrency(amount: number): string {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
export function formatDate(date: string): string {
|
||||
const d = new Date(date);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - d.getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
|
||||
if (minutes < 1) return 'Just now';
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
if (days < 7) return `${days}d ago`;
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
export function formatDateFull(date: string): string {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
368
client/src/utils/mockData.ts
Normal file
368
client/src/utils/mockData.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
import type { User, Listing, Offer, Conversation, Message, Notification } from '../types';
|
||||
|
||||
export const mockUsers: User[] = [
|
||||
{
|
||||
id: '1',
|
||||
email: 'john@email.com',
|
||||
fullName: 'John Smith',
|
||||
nickname: 'JohnS',
|
||||
avatar: undefined,
|
||||
phone: '(555) 123-4567',
|
||||
location: 'Houston, TX',
|
||||
bio: 'Avid collector and seller of quality pre-owned items.',
|
||||
rating: 4.8,
|
||||
createdAt: '2024-01-15T10:00:00Z',
|
||||
showEmail: false,
|
||||
showPhone: true,
|
||||
showLocation: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
email: 'mike@email.com',
|
||||
fullName: 'Mike Brown',
|
||||
nickname: 'MikeB',
|
||||
avatar: undefined,
|
||||
phone: '(555) 987-6543',
|
||||
location: 'Austin, TX',
|
||||
bio: 'Looking for great deals on furniture and electronics.',
|
||||
rating: 4.5,
|
||||
createdAt: '2024-02-20T10:00:00Z',
|
||||
showEmail: true,
|
||||
showPhone: true,
|
||||
showLocation: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
email: 'sarah@email.com',
|
||||
fullName: 'Sarah Williams',
|
||||
nickname: 'SarahW',
|
||||
avatar: undefined,
|
||||
phone: '(555) 456-7890',
|
||||
location: 'Dallas, TX',
|
||||
bio: 'Fashion enthusiast selling vintage finds.',
|
||||
rating: 4.9,
|
||||
createdAt: '2024-03-10T10:00:00Z',
|
||||
showEmail: false,
|
||||
showPhone: false,
|
||||
showLocation: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCurrentUser = mockUsers[0]!;
|
||||
|
||||
export const mockListings: Listing[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Leather Armchair',
|
||||
description: 'Comfortable leather armchair in great condition! Has minimal wear. Perfect for any living room. Rich brown color with brass studs. Selling because I\'m redecorating.',
|
||||
price: 120,
|
||||
obo: true,
|
||||
category: 'FURNITURE',
|
||||
condition: 'GENTLY_USED',
|
||||
status: 'ACTIVE',
|
||||
location: 'Houston, TX',
|
||||
viewCount: 245,
|
||||
images: [
|
||||
{ id: '1', url: '/placeholder-armchair.jpg', order: 0 },
|
||||
{ id: '2', url: '/placeholder-armchair-2.jpg', order: 1 },
|
||||
],
|
||||
seller: mockUsers[0]!,
|
||||
sellerId: '1',
|
||||
isFavorited: false,
|
||||
createdAt: '2024-06-01T10:00:00Z',
|
||||
updatedAt: '2024-06-01T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Wireless Headphones',
|
||||
description: 'Premium wireless headphones with noise cancellation. Battery lasts 30+ hours. Comes with original box and charging cable.',
|
||||
price: 65,
|
||||
obo: true,
|
||||
category: 'ELECTRONICS',
|
||||
condition: 'LIKE_NEW',
|
||||
status: 'ACTIVE',
|
||||
location: 'Austin, TX',
|
||||
viewCount: 189,
|
||||
images: [{ id: '3', url: '/placeholder-headphones.jpg', order: 0 }],
|
||||
seller: mockUsers[1]!,
|
||||
sellerId: '2',
|
||||
isFavorited: true,
|
||||
createdAt: '2024-06-05T10:00:00Z',
|
||||
updatedAt: '2024-06-05T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Denim Jacket',
|
||||
description: 'Classic denim jacket, size M. Barely worn, like new condition. Great for layering in spring and fall.',
|
||||
price: 32,
|
||||
obo: false,
|
||||
category: 'CLOTHING',
|
||||
condition: 'LIKE_NEW',
|
||||
status: 'ACTIVE',
|
||||
location: 'Dallas, TX',
|
||||
viewCount: 78,
|
||||
images: [{ id: '4', url: '/placeholder-jacket.jpg', order: 0 }],
|
||||
seller: mockUsers[2]!,
|
||||
sellerId: '3',
|
||||
isFavorited: false,
|
||||
createdAt: '2024-06-10T10:00:00Z',
|
||||
updatedAt: '2024-06-10T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'Coffee Maker',
|
||||
description: 'Programmable 12-cup coffee maker. Works perfectly. Includes reusable filter and carafe.',
|
||||
price: 45,
|
||||
obo: true,
|
||||
category: 'HOME_GARDEN',
|
||||
condition: 'USED',
|
||||
status: 'ACTIVE',
|
||||
location: 'Houston, TX',
|
||||
viewCount: 134,
|
||||
images: [{ id: '5', url: '/placeholder-coffee.jpg', order: 0 }],
|
||||
seller: mockUsers[0]!,
|
||||
sellerId: '1',
|
||||
isFavorited: false,
|
||||
createdAt: '2024-06-15T10:00:00Z',
|
||||
updatedAt: '2024-06-15T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'Smartwatch',
|
||||
description: 'Feature-rich smartwatch with heart rate monitor, GPS, and sleep tracking. Compatible with iOS and Android.',
|
||||
price: 85,
|
||||
obo: true,
|
||||
category: 'ELECTRONICS',
|
||||
condition: 'GENTLY_USED',
|
||||
status: 'ACTIVE',
|
||||
location: 'Austin, TX',
|
||||
viewCount: 312,
|
||||
images: [{ id: '6', url: '/placeholder-watch.jpg', order: 0 }],
|
||||
seller: mockUsers[1]!,
|
||||
sellerId: '2',
|
||||
isFavorited: true,
|
||||
createdAt: '2024-06-18T10:00:00Z',
|
||||
updatedAt: '2024-06-18T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'Designer Handbag',
|
||||
description: 'Authentic designer handbag in excellent condition. Comes with dust bag and authenticity card.',
|
||||
price: 220,
|
||||
obo: true,
|
||||
category: 'CLOTHING',
|
||||
condition: 'GENTLY_USED',
|
||||
status: 'ACTIVE',
|
||||
location: 'Dallas, TX',
|
||||
viewCount: 456,
|
||||
images: [{ id: '7', url: '/placeholder-handbag.jpg', order: 0 }],
|
||||
seller: mockUsers[2]!,
|
||||
sellerId: '3',
|
||||
isFavorited: false,
|
||||
createdAt: '2024-06-20T10:00:00Z',
|
||||
updatedAt: '2024-06-20T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
title: 'Gaming Laptop',
|
||||
description: 'High-performance gaming laptop with RTX 3070, 16GB RAM, 512GB SSD. Runs all modern games at high settings.',
|
||||
price: 1200,
|
||||
obo: true,
|
||||
category: 'ELECTRONICS',
|
||||
condition: 'GENTLY_USED',
|
||||
status: 'ACTIVE',
|
||||
location: 'Houston, TX',
|
||||
viewCount: 567,
|
||||
images: [{ id: '8', url: '/placeholder-laptop.jpg', order: 0 }],
|
||||
seller: mockUsers[0]!,
|
||||
sellerId: '1',
|
||||
isFavorited: false,
|
||||
createdAt: '2024-06-22T10:00:00Z',
|
||||
updatedAt: '2024-06-22T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
title: 'Mountain Bike',
|
||||
description: '21-speed mountain bike, aluminum frame. Recently serviced with new tires and brakes.',
|
||||
price: 300,
|
||||
obo: true,
|
||||
category: 'SPORTS',
|
||||
condition: 'USED',
|
||||
status: 'ACTIVE',
|
||||
location: 'Austin, TX',
|
||||
viewCount: 223,
|
||||
images: [{ id: '9', url: '/placeholder-bike.jpg', order: 0 }],
|
||||
seller: mockUsers[1]!,
|
||||
sellerId: '2',
|
||||
isFavorited: false,
|
||||
createdAt: '2024-06-25T10:00:00Z',
|
||||
updatedAt: '2024-06-25T10:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockOffers: Offer[] = [
|
||||
{
|
||||
id: '1',
|
||||
amount: 98,
|
||||
message: 'Would you accept $98 for the armchair?',
|
||||
status: 'PENDING',
|
||||
buyer: mockUsers[1]!,
|
||||
buyerId: '2',
|
||||
seller: mockUsers[0]!,
|
||||
sellerId: '1',
|
||||
listing: mockListings[0]!,
|
||||
listingId: '1',
|
||||
createdAt: '2024-06-20T14:00:00Z',
|
||||
updatedAt: '2024-06-20T14:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
amount: 50,
|
||||
message: 'Interested in the headphones. Can you do $50?',
|
||||
status: 'PENDING',
|
||||
buyer: mockUsers[0]!,
|
||||
buyerId: '1',
|
||||
seller: mockUsers[1]!,
|
||||
sellerId: '2',
|
||||
listing: mockListings[1]!,
|
||||
listingId: '2',
|
||||
createdAt: '2024-06-21T10:00:00Z',
|
||||
updatedAt: '2024-06-21T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
amount: 180,
|
||||
message: 'I can pick up today if you accept $180.',
|
||||
status: 'ACCEPTED',
|
||||
buyer: mockUsers[1]!,
|
||||
buyerId: '2',
|
||||
seller: mockUsers[0]!,
|
||||
sellerId: '1',
|
||||
listing: mockListings[6]!,
|
||||
listingId: '7',
|
||||
createdAt: '2024-06-22T09:00:00Z',
|
||||
updatedAt: '2024-06-22T12:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
amount: 35,
|
||||
message: 'Is the coffee maker still available?',
|
||||
status: 'DECLINED',
|
||||
buyer: mockUsers[2]!,
|
||||
buyerId: '3',
|
||||
seller: mockUsers[0]!,
|
||||
sellerId: '1',
|
||||
listing: mockListings[3]!,
|
||||
listingId: '4',
|
||||
createdAt: '2024-06-23T16:00:00Z',
|
||||
updatedAt: '2024-06-23T18:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockConversations: Conversation[] = [
|
||||
{
|
||||
id: '1',
|
||||
user1: mockUsers[0]!,
|
||||
user2: mockUsers[1]!,
|
||||
listing: mockListings[0],
|
||||
lastMessage: {
|
||||
id: 'm1',
|
||||
content: 'Sounds good! I can do $110. 😊',
|
||||
senderId: '1',
|
||||
conversationId: '1',
|
||||
isRead: true,
|
||||
createdAt: '2024-06-20T15:30:00Z',
|
||||
},
|
||||
unreadCount: 0,
|
||||
updatedAt: '2024-06-20T15:30:00Z',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
user1: mockUsers[0]!,
|
||||
user2: mockUsers[2]!,
|
||||
listing: mockListings[3],
|
||||
lastMessage: {
|
||||
id: 'm2',
|
||||
content: 'Is the coffee maker still available?',
|
||||
senderId: '3',
|
||||
conversationId: '2',
|
||||
isRead: false,
|
||||
createdAt: '2024-06-21T09:00:00Z',
|
||||
},
|
||||
unreadCount: 1,
|
||||
updatedAt: '2024-06-21T09:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockMessages: Message[] = [
|
||||
{
|
||||
id: 'm1',
|
||||
content: 'Hi! Would you take $110 for the Leather Armchair? I\'m interested.',
|
||||
senderId: '2',
|
||||
conversationId: '1',
|
||||
isRead: true,
|
||||
createdAt: '2024-06-20T14:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'm2',
|
||||
content: 'Sounds good! I can do $110. 😊',
|
||||
senderId: '1',
|
||||
conversationId: '1',
|
||||
isRead: true,
|
||||
createdAt: '2024-06-20T15:30:00Z',
|
||||
},
|
||||
{
|
||||
id: 'm3',
|
||||
content: 'Is the coffee maker still available?',
|
||||
senderId: '3',
|
||||
conversationId: '2',
|
||||
isRead: false,
|
||||
createdAt: '2024-06-21T09:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockNotifications: Notification[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'NEW_OFFER',
|
||||
title: 'New Offer',
|
||||
body: 'JakoTV123 just made an offer for your Nintendo Switch',
|
||||
data: { listingId: '1', offerId: '1', amount: 180 },
|
||||
isRead: false,
|
||||
createdAt: new Date(Date.now() - 3600000).toISOString(),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'ITEM_SOLD',
|
||||
title: 'Item Sold',
|
||||
body: 'You sold your Coffee Maker for $45',
|
||||
data: { listingId: '4' },
|
||||
isRead: false,
|
||||
createdAt: new Date(Date.now() - 7200000).toISOString(),
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'ITEM_FAVORITED',
|
||||
title: 'Item Favorited',
|
||||
body: 'Ethan123 loved your Reebok Sneakers',
|
||||
data: { listingId: '3' },
|
||||
isRead: true,
|
||||
createdAt: new Date(Date.now() - 10800000).toISOString(),
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'NEW_OFFER',
|
||||
title: 'New Offer',
|
||||
body: 'AlessaJd1 just made an offer for your Mountain Bike',
|
||||
data: { listingId: '8', offerId: '5', amount: 270 },
|
||||
isRead: true,
|
||||
createdAt: new Date(Date.now() - 14400000).toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
export const mockSoldItems = [
|
||||
{ listing: mockListings[0]!, salePrice: 180, buyer: mockUsers[1]!, date: '2024-06-20T12:00:00Z' },
|
||||
{ listing: mockListings[1]!, salePrice: 70, buyer: mockUsers[2]!, date: '2024-04-02T10:00:00Z' },
|
||||
{ listing: mockListings[2]!, salePrice: 30, buyer: mockUsers[1]!, date: '2024-03-27T10:00:00Z' },
|
||||
{ listing: mockListings[3]!, salePrice: 40, buyer: mockUsers[1]!, date: '2024-03-27T10:00:00Z' },
|
||||
];
|
||||
Reference in New Issue
Block a user