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:
@@ -2,6 +2,8 @@ import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Heart, MapPin } from 'lucide-react';
|
||||
import { Badge } from '../ui/Badge';
|
||||
import { api } from '../../api/client';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import type { Listing } from '../../types';
|
||||
import { formatCurrency } from '../../utils/format';
|
||||
|
||||
@@ -10,34 +12,57 @@ interface ListingCardProps {
|
||||
}
|
||||
|
||||
export function ListingCard({ listing }: ListingCardProps) {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const [isFav, setIsFav] = useState(listing.isFavorited ?? false);
|
||||
const [toggling, setToggling] = useState(false);
|
||||
|
||||
const conditionVariant = listing.condition === 'NEW' ? 'success'
|
||||
: listing.condition === 'LIKE_NEW' ? 'info'
|
||||
: 'default';
|
||||
|
||||
const categoryEmoji =
|
||||
listing.category === 'FURNITURE' ? '\uD83E\uDE91' :
|
||||
listing.category === 'ELECTRONICS' ? '\uD83C\uDFA7' :
|
||||
listing.category === 'CLOTHING' ? '\uD83D\uDC55' :
|
||||
listing.category === 'HOME_GARDEN' ? '\u2615' :
|
||||
listing.category === 'SPORTS' ? '\uD83D\uDEB4' :
|
||||
listing.category === 'BOOKS' ? '\uD83D\uDCDA' :
|
||||
listing.category === 'GAMES' ? '\uD83C\uDFAE' : '\uD83D\uDCE6';
|
||||
|
||||
const handleFavorite = async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!isAuthenticated || toggling) return;
|
||||
setToggling(true);
|
||||
try {
|
||||
const res = await api.post<{ isFavorited: boolean }>(`/listings/${listing.id}/favorite`);
|
||||
setIsFav(res.isFavorited);
|
||||
} catch {}
|
||||
setToggling(false);
|
||||
};
|
||||
|
||||
const hasImage = listing.images?.[0]?.url;
|
||||
|
||||
return (
|
||||
<Link to={`/listings/${listing.id}`} className="group block">
|
||||
<div className="bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all duration-200">
|
||||
{/* Image */}
|
||||
<div className="relative aspect-square bg-gray-100">
|
||||
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-primary-50 to-pink-50">
|
||||
<span className="text-4xl">
|
||||
{listing.category === 'FURNITURE' ? '\uD83E\uDE91' :
|
||||
listing.category === 'ELECTRONICS' ? '\uD83C\uDFA7' :
|
||||
listing.category === 'CLOTHING' ? '\uD83D\uDC55' :
|
||||
listing.category === 'HOME_GARDEN' ? '\u2615' :
|
||||
listing.category === 'SPORTS' ? '\uD83D\uDEB4' :
|
||||
listing.category === 'BOOKS' ? '\uD83D\uDCDA' :
|
||||
listing.category === 'GAMES' ? '\uD83C\uDFAE' : '\uD83D\uDCE6'}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => { e.preventDefault(); setIsFav(!isFav); }}
|
||||
className="absolute top-2 right-2 p-1.5 bg-white/80 backdrop-blur rounded-full hover:bg-white transition-colors cursor-pointer"
|
||||
>
|
||||
<Heart className={`w-4 h-4 ${isFav ? 'fill-pink-500 text-pink-500' : 'text-gray-400'}`} />
|
||||
</button>
|
||||
{hasImage ? (
|
||||
<img src={listing.images[0].url} alt={listing.title} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-primary-50 to-pink-50">
|
||||
<span className="text-4xl">{categoryEmoji}</span>
|
||||
</div>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<button
|
||||
onClick={handleFavorite}
|
||||
className="absolute top-2 right-2 p-1.5 bg-white/80 backdrop-blur rounded-full hover:bg-white transition-colors cursor-pointer"
|
||||
>
|
||||
<Heart className={`w-4 h-4 ${isFav ? 'fill-pink-500 text-pink-500' : 'text-gray-400'}`} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
Reference in New Issue
Block a user