From 0780976661980855d896b61817af4325ddb42996 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 24 Nov 2025 16:15:00 +0100 Subject: [PATCH] Enhance TradeCreator with mobile navigation and gift mode functionality --- src/components/TradeCreator.tsx | 672 +++++++++++++++++++++++--------- 1 file changed, 477 insertions(+), 195 deletions(-) diff --git a/src/components/TradeCreator.tsx b/src/components/TradeCreator.tsx index 5fa20e8..ef1e3a9 100644 --- a/src/components/TradeCreator.tsx +++ b/src/components/TradeCreator.tsx @@ -1,5 +1,5 @@ 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 { useToast } from '../contexts/ToastContext'; import { getUserCollection, getCardsByIds } from '../services/api'; @@ -25,6 +25,8 @@ interface SelectedCard { maxQuantity: number; } +type MobileStep = 'want' | 'give' | 'review'; + export default function TradeCreator({ receiverId, receiverUsername, @@ -39,6 +41,10 @@ export default function TradeCreator({ const [submitting, setSubmitting] = useState(false); const [message, setMessage] = useState(''); + // Mobile step state + const [isGiftMode, setIsGiftMode] = useState(false); + const [mobileStep, setMobileStep] = useState('want'); + // Cards I'm offering (from my collection) const [myOfferedCards, setMyOfferedCards] = useState>(new Map()); // Cards I want (from their collection) @@ -48,6 +54,16 @@ export default function TradeCreator({ loadMyCollection(); }, [user]); + // When gift mode is toggled, adjust mobile step + useEffect(() => { + if (isGiftMode) { + setWantedCards(new Map()); + setMobileStep('give'); + } else { + setMobileStep('want'); + } + }, [isGiftMode]); + const loadMyCollection = async () => { if (!user) return; setLoading(true); @@ -133,7 +149,6 @@ export default function TradeCreator({ const handleSubmit = async () => { if (!user) return; - // At least one side should have cards (allowing gifts) if (myOfferedCards.size === 0 && wantedCards.size === 0) { toast.warning('Please select at least one card to trade or gift'); return; @@ -171,209 +186,476 @@ export default function TradeCreator({ const isGift = 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; + onAdd: (card: Card, maxQty: number) => void; + onRemove: (cardId: string) => void; + emptyMessage: string; + selectionColor: 'green' | 'blue'; + }) => { + if (items.length === 0) { + return

{emptyMessage}

; + } + + const ringColor = selectionColor === 'green' ? 'ring-green-500' : 'ring-blue-500'; + const badgeColor = selectionColor === 'green' ? 'bg-green-600' : 'bg-blue-500'; + + return ( +
+ {items.map(({ card, quantity }) => { + const selected = selectedCards.get(card.id); + const remainingQty = quantity - (selected?.quantity || 0); + return ( +
remainingQty > 0 && onAdd(card, quantity)} + > + {card.name} +
+ {remainingQty}/{quantity} +
+ {selected && ( + + )} +
+ ); + })} +
+ ); + }; + + // Selected cards summary component + const SelectedCardsSummary = ({ + cards, + onRemove, + label, + emptyLabel, + color, + }: { + cards: Map; + 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 ( +
+

{label}:

+ {cards.size === 0 ? ( +

{emptyLabel}

+ ) : ( +
+ {Array.from(cards.values()).map((item) => ( +
+ {item.card.name} + x{item.quantity} + +
+ ))} +
+ )} +
+ ); + }; + + // Loading state + if (loading) { + return ( +
+ +
+ ); + } + return ( -
-
- {/* Header */} -
-
- -

Trade with {receiverUsername}

-
- -
+
+
- {/* Content */} -
- {/* My Collection (Left) */} -
-

- My Collection (I give) -

- {loading ? ( -
-
-
- ) : myCollection.length === 0 ? ( -

Your collection is empty

- ) : ( -
- {myCollection.map(({ card, quantity }) => { - const offered = myOfferedCards.get(card.id); - const remainingQty = quantity - (offered?.quantity || 0); - return ( -
remainingQty > 0 && addToOffer(card, quantity)} - > - {card.name} -
- {remainingQty}/{quantity} -
- {offered && ( -
- +{offered.quantity} -
- )} -
- ); - })} -
- )} -
- - {/* Their Collection (Right) */} -
-

- {receiverUsername}'s Collection (I want) -

- {receiverCollection.length === 0 ? ( -

Their collection is empty

- ) : ( -
- {receiverCollection.map(({ card, quantity }) => { - const wanted = wantedCards.get(card.id); - const remainingQty = quantity - (wanted?.quantity || 0); - return ( -
remainingQty > 0 && addToWanted(card, quantity)} - > - {card.name} -
- {remainingQty}/{quantity} -
- {wanted && ( -
- +{wanted.quantity} -
- )} -
- ); - })} -
- )} -
-
- - {/* Trade Summary */} -
-
- {/* I Give */} -
-

I Give:

- {myOfferedCards.size === 0 ? ( -

Nothing selected (gift request)

- ) : ( -
- {Array.from(myOfferedCards.values()).map((item) => ( -
- {item.card.name} - x{item.quantity} - -
- ))} -
- )} -
- - {/* I Want */} -
-

I Want:

- {wantedCards.size === 0 ? ( -

Nothing selected (gift)

- ) : ( -
- {Array.from(wantedCards.values()).map((item) => ( -
- {item.card.name} - x{item.quantity} - -
- ))} -
- )} + {/* ============ MOBILE VIEW ============ */} +
+ {/* Mobile Header */} +
+
+ +

Trade with {receiverUsername}

+
- {/* Message */} -
- setMessage(e.target.value)} - 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" + {/* Gift Toggle */} +
+