Enhance TradeCreator with mobile navigation and gift mode functionality
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { X, ArrowLeftRight, Plus, Minus, Send, Gift, Loader2 } from 'lucide-react';
|
import { X, ArrowLeftRight, ArrowRight, ArrowLeft, Minus, Send, Gift, Loader2, Check } from 'lucide-react';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { useToast } from '../contexts/ToastContext';
|
import { useToast } from '../contexts/ToastContext';
|
||||||
import { getUserCollection, getCardsByIds } from '../services/api';
|
import { getUserCollection, getCardsByIds } from '../services/api';
|
||||||
@@ -25,6 +25,8 @@ interface SelectedCard {
|
|||||||
maxQuantity: number;
|
maxQuantity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MobileStep = 'want' | 'give' | 'review';
|
||||||
|
|
||||||
export default function TradeCreator({
|
export default function TradeCreator({
|
||||||
receiverId,
|
receiverId,
|
||||||
receiverUsername,
|
receiverUsername,
|
||||||
@@ -39,6 +41,10 @@ export default function TradeCreator({
|
|||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
// Mobile step state
|
||||||
|
const [isGiftMode, setIsGiftMode] = useState(false);
|
||||||
|
const [mobileStep, setMobileStep] = useState<MobileStep>('want');
|
||||||
|
|
||||||
// Cards I'm offering (from my collection)
|
// Cards I'm offering (from my collection)
|
||||||
const [myOfferedCards, setMyOfferedCards] = useState<Map<string, SelectedCard>>(new Map());
|
const [myOfferedCards, setMyOfferedCards] = useState<Map<string, SelectedCard>>(new Map());
|
||||||
// Cards I want (from their collection)
|
// Cards I want (from their collection)
|
||||||
@@ -48,6 +54,16 @@ export default function TradeCreator({
|
|||||||
loadMyCollection();
|
loadMyCollection();
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
// When gift mode is toggled, adjust mobile step
|
||||||
|
useEffect(() => {
|
||||||
|
if (isGiftMode) {
|
||||||
|
setWantedCards(new Map());
|
||||||
|
setMobileStep('give');
|
||||||
|
} else {
|
||||||
|
setMobileStep('want');
|
||||||
|
}
|
||||||
|
}, [isGiftMode]);
|
||||||
|
|
||||||
const loadMyCollection = async () => {
|
const loadMyCollection = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -133,7 +149,6 @@ export default function TradeCreator({
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
// At least one side should have cards (allowing gifts)
|
|
||||||
if (myOfferedCards.size === 0 && wantedCards.size === 0) {
|
if (myOfferedCards.size === 0 && wantedCards.size === 0) {
|
||||||
toast.warning('Please select at least one card to trade or gift');
|
toast.warning('Please select at least one card to trade or gift');
|
||||||
return;
|
return;
|
||||||
@@ -171,179 +186,445 @@ export default function TradeCreator({
|
|||||||
const isGift = myOfferedCards.size > 0 && wantedCards.size === 0;
|
const isGift = myOfferedCards.size > 0 && wantedCards.size === 0;
|
||||||
const isRequest = myOfferedCards.size === 0 && wantedCards.size > 0;
|
const isRequest = myOfferedCards.size === 0 && wantedCards.size > 0;
|
||||||
|
|
||||||
|
// Mobile navigation
|
||||||
|
const goToNextStep = () => {
|
||||||
|
if (mobileStep === 'want') setMobileStep('give');
|
||||||
|
else if (mobileStep === 'give') setMobileStep('review');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToPrevStep = () => {
|
||||||
|
if (mobileStep === 'review') setMobileStep('give');
|
||||||
|
else if (mobileStep === 'give' && !isGiftMode) setMobileStep('want');
|
||||||
|
};
|
||||||
|
|
||||||
|
const canGoNext = () => {
|
||||||
|
if (mobileStep === 'want') return true; // Can skip wanting cards (request nothing)
|
||||||
|
if (mobileStep === 'give') return true; // Can skip giving cards (gift request)
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const canSubmit = myOfferedCards.size > 0 || wantedCards.size > 0;
|
||||||
|
|
||||||
|
// Collection grid component
|
||||||
|
const CollectionGrid = ({
|
||||||
|
items,
|
||||||
|
selectedCards,
|
||||||
|
onAdd,
|
||||||
|
onRemove,
|
||||||
|
emptyMessage,
|
||||||
|
selectionColor,
|
||||||
|
}: {
|
||||||
|
items: CollectionItem[];
|
||||||
|
selectedCards: Map<string, SelectedCard>;
|
||||||
|
onAdd: (card: Card, maxQty: number) => void;
|
||||||
|
onRemove: (cardId: string) => void;
|
||||||
|
emptyMessage: string;
|
||||||
|
selectionColor: 'green' | 'blue';
|
||||||
|
}) => {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return <p className="text-gray-400 text-center py-8">{emptyMessage}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ringColor = selectionColor === 'green' ? 'ring-green-500' : 'ring-blue-500';
|
||||||
|
const badgeColor = selectionColor === 'green' ? 'bg-green-600' : 'bg-blue-500';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4">
|
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2">
|
||||||
<div className="bg-gray-800 rounded-lg w-full max-w-6xl max-h-[90vh] overflow-hidden flex flex-col">
|
{items.map(({ card, quantity }) => {
|
||||||
{/* Header */}
|
const selected = selectedCards.get(card.id);
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
const remainingQty = quantity - (selected?.quantity || 0);
|
||||||
<div className="flex items-center gap-2">
|
return (
|
||||||
<ArrowLeftRight size={24} className="text-blue-400" />
|
<div
|
||||||
<h2 className="text-xl font-bold">Trade with {receiverUsername}</h2>
|
key={card.id}
|
||||||
|
className={`relative cursor-pointer rounded-lg overflow-hidden transition active:scale-95 ${
|
||||||
|
selected ? `ring-2 ${ringColor}` : 'active:ring-2 active:ring-gray-500'
|
||||||
|
}`}
|
||||||
|
onClick={() => remainingQty > 0 && onAdd(card, quantity)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={card.image_uris?.small || card.image_uris?.normal}
|
||||||
|
alt={card.name}
|
||||||
|
className={`w-full h-auto ${remainingQty === 0 ? 'opacity-50' : ''}`}
|
||||||
|
/>
|
||||||
|
<div className="absolute top-1 right-1 bg-gray-900/80 text-white text-[10px] px-1 py-0.5 rounded">
|
||||||
|
{remainingQty}/{quantity}
|
||||||
</div>
|
</div>
|
||||||
|
{selected && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRemove(card.id);
|
||||||
|
}}
|
||||||
|
className={`absolute bottom-1 left-1 ${badgeColor} text-white text-[10px] px-1.5 py-0.5 rounded flex items-center gap-0.5`}
|
||||||
|
>
|
||||||
|
+{selected.quantity}
|
||||||
|
<Minus size={10} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Selected cards summary component
|
||||||
|
const SelectedCardsSummary = ({
|
||||||
|
cards,
|
||||||
|
onRemove,
|
||||||
|
label,
|
||||||
|
emptyLabel,
|
||||||
|
color,
|
||||||
|
}: {
|
||||||
|
cards: Map<string, SelectedCard>;
|
||||||
|
onRemove: (cardId: string) => void;
|
||||||
|
label: string;
|
||||||
|
emptyLabel: string;
|
||||||
|
color: 'green' | 'blue';
|
||||||
|
}) => {
|
||||||
|
const bgColor = color === 'green' ? 'bg-green-900/50' : 'bg-blue-900/50';
|
||||||
|
const textColor = color === 'green' ? 'text-green-400' : 'text-blue-400';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4 className={`text-xs font-semibold ${textColor} mb-1`}>{label}:</h4>
|
||||||
|
{cards.size === 0 ? (
|
||||||
|
<p className="text-gray-500 text-xs">{emptyLabel}</p>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{Array.from(cards.values()).map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.card.id}
|
||||||
|
className={`flex items-center gap-1 ${bgColor} px-1.5 py-0.5 rounded text-xs`}
|
||||||
|
>
|
||||||
|
<span className="truncate max-w-[80px]">{item.card.name}</span>
|
||||||
|
<span className={textColor}>x{item.quantity}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => onRemove(item.card.id)}
|
||||||
|
className="text-red-400 active:text-red-300"
|
||||||
|
>
|
||||||
|
<Minus size={12} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loading state
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black/80 z-50 flex items-center justify-center">
|
||||||
|
<Loader2 className="animate-spin text-blue-500" size={48} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-0 md:p-4">
|
||||||
|
<div className="bg-gray-800 w-full h-full md:rounded-lg md:w-full md:max-w-6xl md:max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
|
|
||||||
|
{/* ============ MOBILE VIEW ============ */}
|
||||||
|
<div className="flex flex-col h-full md:hidden">
|
||||||
|
{/* Mobile Header */}
|
||||||
|
<div className="flex items-center justify-between p-3 border-b border-gray-700">
|
||||||
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
|
<ArrowLeftRight size={20} className="text-blue-400 flex-shrink-0" />
|
||||||
|
<h2 className="font-bold truncate">Trade with {receiverUsername}</h2>
|
||||||
|
</div>
|
||||||
|
<button onClick={onClose} className="p-2 -mr-2 active:bg-gray-700 rounded-lg">
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Gift Toggle */}
|
||||||
|
<div className="p-3 border-b border-gray-700">
|
||||||
|
<label className="flex items-center gap-3 cursor-pointer">
|
||||||
|
<div
|
||||||
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
||||||
|
isGiftMode ? 'bg-purple-600' : 'bg-gray-600'
|
||||||
|
}`}
|
||||||
|
onClick={() => setIsGiftMode(!isGiftMode)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${
|
||||||
|
isGiftMode ? 'translate-x-7' : 'translate-x-1'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Gift size={18} className={isGiftMode ? 'text-purple-400' : 'text-gray-400'} />
|
||||||
|
<span className={isGiftMode ? 'text-purple-400' : 'text-gray-400'}>
|
||||||
|
This is a gift (I don't want anything back)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step Indicator */}
|
||||||
|
<div className="flex items-center justify-center gap-2 p-2 bg-gray-900/50">
|
||||||
|
{!isGiftMode && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`w-2 h-2 rounded-full ${mobileStep === 'want' ? 'bg-blue-500' : 'bg-gray-600'}`}
|
||||||
|
/>
|
||||||
|
<span className={`text-xs ${mobileStep === 'want' ? 'text-blue-400' : 'text-gray-500'}`}>
|
||||||
|
I Want
|
||||||
|
</span>
|
||||||
|
<ArrowRight size={14} className="text-gray-500" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`w-2 h-2 rounded-full ${mobileStep === 'give' ? 'bg-green-500' : 'bg-gray-600'}`}
|
||||||
|
/>
|
||||||
|
<span className={`text-xs ${mobileStep === 'give' ? 'text-green-400' : 'text-gray-500'}`}>
|
||||||
|
I Give
|
||||||
|
</span>
|
||||||
|
<ArrowRight size={14} className="text-gray-500" />
|
||||||
|
<div
|
||||||
|
className={`w-2 h-2 rounded-full ${mobileStep === 'review' ? 'bg-purple-500' : 'bg-gray-600'}`}
|
||||||
|
/>
|
||||||
|
<span className={`text-xs ${mobileStep === 'review' ? 'text-purple-400' : 'text-gray-500'}`}>
|
||||||
|
Review
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Content */}
|
||||||
|
<div className="flex-1 overflow-y-auto p-3">
|
||||||
|
{/* Step: Want (their collection) */}
|
||||||
|
{mobileStep === 'want' && !isGiftMode && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-blue-400 mb-3">
|
||||||
|
Select cards from {receiverUsername}'s collection
|
||||||
|
</h3>
|
||||||
|
<CollectionGrid
|
||||||
|
items={receiverCollection}
|
||||||
|
selectedCards={wantedCards}
|
||||||
|
onAdd={addToWanted}
|
||||||
|
onRemove={removeFromWanted}
|
||||||
|
emptyMessage="Their collection is empty"
|
||||||
|
selectionColor="blue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step: Give (my collection) */}
|
||||||
|
{mobileStep === 'give' && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-green-400 mb-3">
|
||||||
|
Select cards to {isGiftMode ? 'gift' : 'offer'}
|
||||||
|
</h3>
|
||||||
|
{myCollection.length === 0 ? (
|
||||||
|
<p className="text-gray-400 text-center py-8">Your collection is empty</p>
|
||||||
|
) : (
|
||||||
|
<CollectionGrid
|
||||||
|
items={myCollection}
|
||||||
|
selectedCards={myOfferedCards}
|
||||||
|
onAdd={addToOffer}
|
||||||
|
onRemove={removeFromOffer}
|
||||||
|
emptyMessage="Your collection is empty"
|
||||||
|
selectionColor="green"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step: Review */}
|
||||||
|
{mobileStep === 'review' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-sm font-semibold text-purple-400">Review Trade</h3>
|
||||||
|
|
||||||
|
{/* Summary */}
|
||||||
|
<div className="bg-gray-900/50 rounded-lg p-3 space-y-3">
|
||||||
|
<SelectedCardsSummary
|
||||||
|
cards={myOfferedCards}
|
||||||
|
onRemove={removeFromOffer}
|
||||||
|
label="I Give"
|
||||||
|
emptyLabel="Nothing (requesting cards)"
|
||||||
|
color="green"
|
||||||
|
/>
|
||||||
|
{!isGiftMode && (
|
||||||
|
<SelectedCardsSummary
|
||||||
|
cards={wantedCards}
|
||||||
|
onRemove={removeFromWanted}
|
||||||
|
label="I Want"
|
||||||
|
emptyLabel="Nothing (sending gift)"
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Message */}
|
||||||
|
<div>
|
||||||
|
<label className="text-xs text-gray-400 mb-1 block">Message (optional)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
placeholder="Add a message..."
|
||||||
|
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Footer */}
|
||||||
|
<div className="border-t border-gray-700 p-3 flex gap-2">
|
||||||
|
{/* Back button */}
|
||||||
|
{(mobileStep !== 'want' && !isGiftMode) || (mobileStep !== 'give' && isGiftMode) ? (
|
||||||
|
<button
|
||||||
|
onClick={goToPrevStep}
|
||||||
|
disabled={mobileStep === 'give' && isGiftMode}
|
||||||
|
className="flex items-center justify-center gap-1 px-4 py-2.5 bg-gray-700 active:bg-gray-600 disabled:opacity-50 rounded-lg flex-1"
|
||||||
|
>
|
||||||
|
<ArrowLeft size={18} />
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-2 hover:bg-gray-700 rounded-lg transition"
|
className="flex items-center justify-center gap-1 px-4 py-2.5 bg-gray-700 active:bg-gray-600 rounded-lg flex-1"
|
||||||
>
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Next/Submit button */}
|
||||||
|
{mobileStep === 'review' ? (
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={submitting || !canSubmit}
|
||||||
|
className="flex items-center justify-center gap-2 px-4 py-2.5 bg-blue-600 active:bg-blue-700 disabled:bg-gray-600 rounded-lg flex-1"
|
||||||
|
>
|
||||||
|
{submitting ? (
|
||||||
|
<Loader2 className="animate-spin" size={18} />
|
||||||
|
) : isGift ? (
|
||||||
|
<>
|
||||||
|
<Gift size={18} />
|
||||||
|
Send Gift
|
||||||
|
</>
|
||||||
|
) : isRequest ? (
|
||||||
|
<>
|
||||||
|
<Send size={18} />
|
||||||
|
Request
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Send size={18} />
|
||||||
|
Send Trade
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={goToNextStep}
|
||||||
|
className="flex items-center justify-center gap-1 px-4 py-2.5 bg-blue-600 active:bg-blue-700 rounded-lg flex-1"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
<ArrowRight size={18} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ============ DESKTOP VIEW ============ */}
|
||||||
|
<div className="hidden md:flex md:flex-col h-full">
|
||||||
|
{/* Desktop Header */}
|
||||||
|
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ArrowLeftRight size={24} className="text-blue-400" />
|
||||||
|
<h2 className="text-xl font-bold">Trade with {receiverUsername}</h2>
|
||||||
|
<label className="flex items-center gap-2 ml-4 cursor-pointer">
|
||||||
|
<div
|
||||||
|
className={`relative w-10 h-5 rounded-full transition-colors ${
|
||||||
|
isGiftMode ? 'bg-purple-600' : 'bg-gray-600'
|
||||||
|
}`}
|
||||||
|
onClick={() => setIsGiftMode(!isGiftMode)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`absolute top-0.5 w-4 h-4 bg-white rounded-full transition-transform ${
|
||||||
|
isGiftMode ? 'translate-x-5' : 'translate-x-0.5'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Gift size={16} className={isGiftMode ? 'text-purple-400' : 'text-gray-400'} />
|
||||||
|
<span className={`text-sm ${isGiftMode ? 'text-purple-400' : 'text-gray-400'}`}>
|
||||||
|
Gift mode
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button onClick={onClose} className="p-2 hover:bg-gray-700 rounded-lg transition">
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Desktop Content */}
|
||||||
<div className="flex-1 overflow-hidden flex flex-col md:flex-row">
|
<div className="flex-1 overflow-hidden flex">
|
||||||
{/* My Collection (Left) */}
|
{/* My Collection (Left) */}
|
||||||
<div className="flex-1 p-4 border-b md:border-b-0 md:border-r border-gray-700 overflow-y-auto">
|
<div className="flex-1 p-4 border-r border-gray-700 overflow-y-auto">
|
||||||
<h3 className="text-lg font-semibold mb-3 text-green-400">
|
<h3 className="text-lg font-semibold mb-3 text-green-400">
|
||||||
My Collection (I give)
|
My Collection (I give)
|
||||||
</h3>
|
</h3>
|
||||||
{loading ? (
|
<CollectionGrid
|
||||||
<div className="flex justify-center py-8">
|
items={myCollection}
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
|
selectedCards={myOfferedCards}
|
||||||
</div>
|
onAdd={addToOffer}
|
||||||
) : myCollection.length === 0 ? (
|
onRemove={removeFromOffer}
|
||||||
<p className="text-gray-400 text-center py-4">Your collection is empty</p>
|
emptyMessage="Your collection is empty"
|
||||||
) : (
|
selectionColor="green"
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 gap-2">
|
|
||||||
{myCollection.map(({ card, quantity }) => {
|
|
||||||
const offered = myOfferedCards.get(card.id);
|
|
||||||
const remainingQty = quantity - (offered?.quantity || 0);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={card.id}
|
|
||||||
className={`relative cursor-pointer rounded-lg overflow-hidden transition ${
|
|
||||||
offered ? 'ring-2 ring-green-500' : 'hover:ring-2 hover:ring-gray-500'
|
|
||||||
}`}
|
|
||||||
onClick={() => remainingQty > 0 && addToOffer(card, quantity)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={card.image_uris?.small}
|
|
||||||
alt={card.name}
|
|
||||||
className={`w-full h-auto ${remainingQty === 0 ? 'opacity-50' : ''}`}
|
|
||||||
/>
|
/>
|
||||||
<div className="absolute top-1 right-1 bg-blue-600 text-white text-xs px-1.5 py-0.5 rounded">
|
|
||||||
{remainingQty}/{quantity}
|
|
||||||
</div>
|
|
||||||
{offered && (
|
|
||||||
<div className="absolute bottom-1 left-1 bg-green-600 text-white text-xs px-1.5 py-0.5 rounded">
|
|
||||||
+{offered.quantity}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Their Collection (Right) */}
|
{/* Their Collection (Right) */}
|
||||||
|
{!isGiftMode && (
|
||||||
<div className="flex-1 p-4 overflow-y-auto">
|
<div className="flex-1 p-4 overflow-y-auto">
|
||||||
<h3 className="text-lg font-semibold mb-3 text-blue-400">
|
<h3 className="text-lg font-semibold mb-3 text-blue-400">
|
||||||
{receiverUsername}'s Collection (I want)
|
{receiverUsername}'s Collection (I want)
|
||||||
</h3>
|
</h3>
|
||||||
{receiverCollection.length === 0 ? (
|
<CollectionGrid
|
||||||
<p className="text-gray-400 text-center py-4">Their collection is empty</p>
|
items={receiverCollection}
|
||||||
) : (
|
selectedCards={wantedCards}
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 gap-2">
|
onAdd={addToWanted}
|
||||||
{receiverCollection.map(({ card, quantity }) => {
|
onRemove={removeFromWanted}
|
||||||
const wanted = wantedCards.get(card.id);
|
emptyMessage="Their collection is empty"
|
||||||
const remainingQty = quantity - (wanted?.quantity || 0);
|
selectionColor="blue"
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={card.id}
|
|
||||||
className={`relative cursor-pointer rounded-lg overflow-hidden transition ${
|
|
||||||
wanted ? 'ring-2 ring-blue-500' : 'hover:ring-2 hover:ring-gray-500'
|
|
||||||
}`}
|
|
||||||
onClick={() => remainingQty > 0 && addToWanted(card, quantity)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={card.image_uris?.small}
|
|
||||||
alt={card.name}
|
|
||||||
className={`w-full h-auto ${remainingQty === 0 ? 'opacity-50' : ''}`}
|
|
||||||
/>
|
/>
|
||||||
<div className="absolute top-1 right-1 bg-blue-600 text-white text-xs px-1.5 py-0.5 rounded">
|
|
||||||
{remainingQty}/{quantity}
|
|
||||||
</div>
|
|
||||||
{wanted && (
|
|
||||||
<div className="absolute bottom-1 left-1 bg-blue-500 text-white text-xs px-1.5 py-0.5 rounded">
|
|
||||||
+{wanted.quantity}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Trade Summary */}
|
{/* Desktop Footer */}
|
||||||
<div className="border-t border-gray-700 p-4">
|
<div className="border-t border-gray-700 p-4">
|
||||||
<div className="flex flex-col md:flex-row gap-4 mb-4">
|
<div className="flex gap-6 mb-4">
|
||||||
{/* I Give */}
|
<SelectedCardsSummary
|
||||||
<div className="flex-1">
|
cards={myOfferedCards}
|
||||||
<h4 className="text-sm font-semibold text-green-400 mb-2">I Give:</h4>
|
onRemove={removeFromOffer}
|
||||||
{myOfferedCards.size === 0 ? (
|
label="I Give"
|
||||||
<p className="text-gray-500 text-sm">Nothing selected (gift request)</p>
|
emptyLabel="Nothing selected (gift request)"
|
||||||
) : (
|
color="green"
|
||||||
<div className="flex flex-wrap gap-2">
|
/>
|
||||||
{Array.from(myOfferedCards.values()).map((item) => (
|
{!isGiftMode && (
|
||||||
<div
|
<SelectedCardsSummary
|
||||||
key={item.card.id}
|
cards={wantedCards}
|
||||||
className="flex items-center gap-1 bg-green-900/50 px-2 py-1 rounded text-sm"
|
onRemove={removeFromWanted}
|
||||||
>
|
label="I Want"
|
||||||
<span>{item.card.name}</span>
|
emptyLabel="Nothing selected (gift)"
|
||||||
<span className="text-green-400">x{item.quantity}</span>
|
color="blue"
|
||||||
<button
|
/>
|
||||||
onClick={() => removeFromOffer(item.card.id)}
|
|
||||||
className="ml-1 text-red-400 hover:text-red-300"
|
|
||||||
>
|
|
||||||
<Minus size={14} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* I Want */}
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex-1">
|
|
||||||
<h4 className="text-sm font-semibold text-blue-400 mb-2">I Want:</h4>
|
|
||||||
{wantedCards.size === 0 ? (
|
|
||||||
<p className="text-gray-500 text-sm">Nothing selected (gift)</p>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{Array.from(wantedCards.values()).map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.card.id}
|
|
||||||
className="flex items-center gap-1 bg-blue-900/50 px-2 py-1 rounded text-sm"
|
|
||||||
>
|
|
||||||
<span>{item.card.name}</span>
|
|
||||||
<span className="text-blue-400">x{item.quantity}</span>
|
|
||||||
<button
|
|
||||||
onClick={() => removeFromWanted(item.card.id)}
|
|
||||||
className="ml-1 text-red-400 hover:text-red-300"
|
|
||||||
>
|
|
||||||
<Minus size={14} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Message */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
placeholder="Add a message (optional)"
|
placeholder="Add a message (optional)"
|
||||||
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="flex-1 px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Submit */}
|
|
||||||
<div className="flex justify-end gap-3">
|
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition"
|
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition"
|
||||||
@@ -352,11 +633,11 @@ export default function TradeCreator({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={submitting || (myOfferedCards.size === 0 && wantedCards.size === 0)}
|
disabled={submitting || !canSubmit}
|
||||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 rounded-lg transition"
|
className="flex items-center gap-2 px-6 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 rounded-lg transition"
|
||||||
>
|
>
|
||||||
{submitting ? (
|
{submitting ? (
|
||||||
<div className="animate-spin rounded-full h-5 w-5 border-t-2 border-b-2 border-white"></div>
|
<Loader2 className="animate-spin" size={20} />
|
||||||
) : isGift ? (
|
) : isGift ? (
|
||||||
<>
|
<>
|
||||||
<Gift size={20} />
|
<Gift size={20} />
|
||||||
@@ -378,5 +659,6 @@ export default function TradeCreator({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user