QA fixes: real listing creation, profile save, favorites, missing pages

- SellItemPage: real file upload + API listing creation + activate
- CreateProfilePage: save profile via PUT /users/profile
- ProductDetailPage: wire edit/delete/message buttons, show edit for owner
- ListingCard: persist favorites via API, show real images
- Footer: connect newsletter subscribe to API
- Router: add /dashboard/listings and /dashboard/saved routes
- Backend: add GET /listings/favorites endpoint
- New pages: MyListingsPage, SavedItemsPage
- Fix unused imports causing build failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
delta-lynx-89e8
2026-02-22 12:30:03 -08:00
parent 6722d1d4a1
commit d09c998d51
41 changed files with 3152 additions and 383 deletions

View File

@@ -3,6 +3,9 @@ import { Server } from 'socket.io';
import { verifyAccessToken } from '../utils/jwt.js';
import { prisma } from '../config/database.js';
// Online status tracking
const onlineUsers = new Map<string, Set<string>>();
export function setupSocket(httpServer: HTTPServer) {
const io = new Server(httpServer, {
cors: {
@@ -27,6 +30,15 @@ export function setupSocket(httpServer: HTTPServer) {
const userId = socket.data.userId;
socket.join(`user:${userId}`);
// Track online status
if (!onlineUsers.has(userId)) {
onlineUsers.set(userId, new Set());
}
onlineUsers.get(userId)!.add(socket.id);
// Broadcast online status
socket.broadcast.emit('user_online', { userId });
socket.on('join_conversation', (conversationId: string) => {
socket.join(`conversation:${conversationId}`);
});
@@ -58,6 +70,19 @@ export function setupSocket(httpServer: HTTPServer) {
if (conversation) {
const recipientId = conversation.user1Id === userId ? conversation.user2Id : conversation.user1Id;
io.to(`user:${recipientId}`).emit('message_notification', { conversationId: data.conversationId, message });
// Create notification
const sender = await prisma.user.findUnique({ where: { id: userId }, select: { fullName: true } });
const notification = await prisma.notification.create({
data: {
userId: recipientId,
type: 'NEW_MESSAGE',
title: 'New Message',
body: `${sender?.fullName || 'Someone'} sent you a message`,
data: { conversationId: data.conversationId },
},
});
io.to(`user:${recipientId}`).emit('new_notification', notification);
}
} catch (error) {
socket.emit('error', { message: 'Failed to send message' });
@@ -79,8 +104,20 @@ export function setupSocket(httpServer: HTTPServer) {
});
});
socket.on('get_online_users', () => {
const users = Array.from(onlineUsers.keys());
socket.emit('online_users', users);
});
socket.on('disconnect', () => {
// Cleanup handled by Socket.io
const userSockets = onlineUsers.get(userId);
if (userSockets) {
userSockets.delete(socket.id);
if (userSockets.size === 0) {
onlineUsers.delete(userId);
socket.broadcast.emit('user_offline', { userId });
}
}
});
});