update trade system

This commit is contained in:
2025-11-26 19:12:07 +01:00
parent abbe68888d
commit 89fc4a782c
7 changed files with 128 additions and 102 deletions

View File

@@ -1,11 +1,15 @@
{ {
"permissions": {
"allow": [
"mcp__supabase__apply_migration",
"mcp__supabase__list_tables",
"mcp__supabase__execute_sql",
"Bash(npm run build:*)",
"mcp__supabase__get_advisors"
]
},
"enableAllProjectMcpServers": true, "enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [ "enabledMcpjsonServers": [
"supabase" "supabase"
], ]
"permissions": {
"allow": [
"mcp__supabase__apply_migration"
]
}
} }

View File

@@ -82,7 +82,7 @@ define(['./workbox-ca84f546'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "index.html", "url": "index.html",
"revision": "0.ufhads5pjvs" "revision": "0.tucc18p1f38"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View File

@@ -794,8 +794,10 @@ export default function Community() {
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{(tradesSubTab === 'pending' ? pendingTrades : tradeHistory).map((trade) => { {(tradesSubTab === 'pending' ? pendingTrades : tradeHistory).map((trade) => {
const isSender = trade.sender_id === user?.id; const isUser1 = trade.user1_id === user?.id;
const otherUser = isSender ? trade.receiver : trade.sender; const myUserId = user?.id || '';
const otherUserId = isUser1 ? trade.user2_id : trade.user1_id;
const otherUser = isUser1 ? trade.user2 : trade.user1;
const statusColors: Record<string, string> = { const statusColors: Record<string, string> = {
accepted: 'text-green-400', accepted: 'text-green-400',
declined: 'text-red-400', declined: 'text-red-400',
@@ -819,7 +821,7 @@ export default function Community() {
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<ArrowLeftRight size={16} className="text-blue-400 flex-shrink-0" /> <ArrowLeftRight size={16} className="text-blue-400 flex-shrink-0" />
<span className="text-sm truncate"> <span className="text-sm truncate">
{isSender ? 'To' : 'From'}: <strong>{otherUser?.username}</strong> With: <strong>{otherUser?.username}</strong>
</span> </span>
</div> </div>
<span className={`text-xs capitalize ${statusColors[trade.status]}`}> <span className={`text-xs capitalize ${statusColors[trade.status]}`}>
@@ -829,18 +831,18 @@ export default function Community() {
{/* Items */} {/* Items */}
<div className="grid grid-cols-2 gap-2 text-sm"> <div className="grid grid-cols-2 gap-2 text-sm">
{renderTradeItems(trade.items, trade.sender_id, isSender ? 'Give' : 'Receive')} {renderTradeItems(trade.items, myUserId, 'You Give')}
{renderTradeItems(trade.items, trade.receiver_id, isSender ? 'Get' : 'Send')} {renderTradeItems(trade.items, otherUserId, 'You Get')}
</div> </div>
{canViewDetails && ( {canViewDetails && (
<p className="text-xs text-blue-400 text-center pt-1"> <p className="text-xs text-blue-400 text-center pt-1">
{isSender ? 'Tap to view/edit' : 'Tap to view details'} Tap to view details
</p> </p>
)} )}
{/* Actions - Only show quick actions for sender (cancel) */} {/* Actions - Allow any user to cancel pending trade */}
{tradesSubTab === 'pending' && isSender && ( {tradesSubTab === 'pending' && (
<div className="flex gap-2 pt-2 border-t border-gray-700" onClick={(e) => e.stopPropagation()}> <div className="flex gap-2 pt-2 border-t border-gray-700" onClick={(e) => e.stopPropagation()}>
<button <button
onClick={() => handleCancelTrade(trade.id)} onClick={() => handleCancelTrade(trade.id)}

View File

@@ -375,12 +375,12 @@ export default function TradeCreator({
setSubmitting(true); setSubmitting(true);
try { try {
const senderCards = Array.from(myOfferedCards.values()).map((item) => ({ const myCards = Array.from(myOfferedCards.values()).map((item) => ({
cardId: item.card.id, cardId: item.card.id,
quantity: item.quantity, quantity: item.quantity,
})); }));
const receiverCards = Array.from(wantedCards.values()).map((item) => ({ const theirCards = Array.from(wantedCards.values()).map((item) => ({
cardId: item.card.id, cardId: item.card.id,
quantity: item.quantity, quantity: item.quantity,
})); }));
@@ -391,18 +391,18 @@ export default function TradeCreator({
tradeId: existingTradeId, tradeId: existingTradeId,
editorId: user.id, editorId: user.id,
message: message || undefined, message: message || undefined,
senderCards, myCards,
receiverCards, theirCards,
}); });
toast.success('Trade updated!'); toast.success('Trade updated!');
} else { } else {
// Create new trade // Create new trade
await createTrade({ await createTrade({
senderId: user.id, user1Id: user.id,
receiverId, user2Id: receiverId,
message: message || undefined, message: message || undefined,
senderCards, user1Cards: myCards,
receiverCards, user2Cards: theirCards,
}); });
toast.success('Trade offer sent!'); toast.success('Trade offer sent!');
} }

View File

@@ -50,9 +50,11 @@ export default function TradeDetail({
const [showEditMode, setShowEditMode] = useState(false); const [showEditMode, setShowEditMode] = useState(false);
const [editReceiverCollection, setEditReceiverCollection] = useState<CollectionItem[]>([]); const [editReceiverCollection, setEditReceiverCollection] = useState<CollectionItem[]>([]);
const isSender = trade.sender_id === user?.id; const isUser1 = trade.user1_id === user?.id;
const isReceiver = trade.receiver_id === user?.id; const isUser2 = trade.user2_id === user?.id;
const otherUser = isSender ? trade.receiver : trade.sender; const otherUser = isUser1 ? trade.user2 : trade.user1;
const myUserId = user?.id || '';
const otherUserId = isUser1 ? trade.user2_id : trade.user1_id;
useEffect(() => { useEffect(() => {
loadTradeCards(); loadTradeCards();
@@ -73,22 +75,22 @@ export default function TradeDetail({
const cardMap = new Map<string, Card>(); const cardMap = new Map<string, Card>();
cards.forEach(card => cardMap.set(card.id, card)); cards.forEach(card => cardMap.set(card.id, card));
const senderItems: TradeCardItem[] = []; const myItems: TradeCardItem[] = [];
const receiverItems: TradeCardItem[] = []; const theirItems: TradeCardItem[] = [];
trade.items?.forEach(item => { trade.items?.forEach(item => {
const card = cardMap.get(item.card_id); const card = cardMap.get(item.card_id);
if (!card) return; if (!card) return;
if (item.owner_id === trade.sender_id) { if (item.owner_id === myUserId) {
senderItems.push({ card, quantity: item.quantity }); myItems.push({ card, quantity: item.quantity });
} else { } else {
receiverItems.push({ card, quantity: item.quantity }); theirItems.push({ card, quantity: item.quantity });
} }
}); });
setSenderCards(senderItems); setSenderCards(myItems);
setReceiverCards(receiverItems); setReceiverCards(theirItems);
} catch (error) { } catch (error) {
console.error('Error loading trade cards:', error); console.error('Error loading trade cards:', error);
toast.error('Failed to load trade details'); toast.error('Failed to load trade details');
@@ -133,7 +135,6 @@ export default function TradeDetail({
const handleEdit = async () => { const handleEdit = async () => {
try { try {
// Load the other user's collection for editing // Load the other user's collection for editing
const otherUserId = isSender ? trade.receiver_id : trade.sender_id;
const collectionMap = await getUserCollection(otherUserId); const collectionMap = await getUserCollection(otherUserId);
const cardIds = Array.from(collectionMap.keys()); const cardIds = Array.from(collectionMap.keys());
const cards = await getCardsByIds(cardIds); const cards = await getCardsByIds(cardIds);
@@ -152,8 +153,8 @@ export default function TradeDetail({
const handleCounterOffer = async () => { const handleCounterOffer = async () => {
try { try {
// For counter-offer, load sender's collection and swap the cards // For counter-offer, load the other user's collection
const collectionMap = await getUserCollection(trade.sender_id); const collectionMap = await getUserCollection(otherUserId);
const cardIds = Array.from(collectionMap.keys()); const cardIds = Array.from(collectionMap.keys());
const cards = await getCardsByIds(cardIds); const cards = await getCardsByIds(cardIds);
const collection = cards.map((card) => ({ const collection = cards.map((card) => ({
@@ -169,13 +170,11 @@ export default function TradeDetail({
} }
}; };
const senderPrice = calculateTotalPrice(senderCards); // senderCards = myCards, receiverCards = theirCards (already calculated correctly)
const receiverPrice = calculateTotalPrice(receiverCards); const yourCards = senderCards;
const theirCards = receiverCards;
const yourCards = isSender ? senderCards : receiverCards; const yourPrice = calculateTotalPrice(yourCards);
const theirCards = isSender ? receiverCards : senderCards; const theirPrice = calculateTotalPrice(theirCards);
const yourPrice = isSender ? senderPrice : receiverPrice;
const theirPrice = isSender ? receiverPrice : senderPrice;
// For edit mode, determine initial cards based on mode // For edit mode, determine initial cards based on mode
// Include quantity in the card object so TradeCreator can preserve it // Include quantity in the card object so TradeCreator can preserve it
@@ -190,7 +189,7 @@ export default function TradeDetail({
if (showEditMode) { if (showEditMode) {
return ( return (
<TradeCreator <TradeCreator
receiverId={isSender ? trade.receiver_id : trade.sender_id} receiverId={otherUserId}
receiverUsername={otherUser?.username || 'User'} receiverUsername={otherUser?.username || 'User'}
receiverCollection={editReceiverCollection} receiverCollection={editReceiverCollection}
onClose={() => { onClose={() => {
@@ -221,7 +220,7 @@ export default function TradeDetail({
<div> <div>
<h2 className="text-lg font-bold">Trade Details {trade.version > 1 && `(v${trade.version})`}</h2> <h2 className="text-lg font-bold">Trade Details {trade.version > 1 && `(v${trade.version})`}</h2>
<p className="text-sm text-gray-400"> <p className="text-sm text-gray-400">
{isSender ? 'To' : 'From'}: {otherUser?.username} With: {otherUser?.username}
</p> </p>
</div> </div>
</div> </div>
@@ -246,7 +245,7 @@ export default function TradeDetail({
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="font-semibold text-green-400"> <h3 className="font-semibold text-green-400">
{isSender ? 'You Give' : 'You Receive'} You Give
</h3> </h3>
<div className="flex items-center gap-1 text-green-400 text-sm"> <div className="flex items-center gap-1 text-green-400 text-sm">
<DollarSign size={14} /> <DollarSign size={14} />
@@ -285,7 +284,7 @@ export default function TradeDetail({
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="font-semibold text-blue-400"> <h3 className="font-semibold text-blue-400">
{isSender ? 'You Receive' : 'They Give'} You Receive
</h3> </h3>
<div className="flex items-center gap-1 text-blue-400 text-sm"> <div className="flex items-center gap-1 text-blue-400 text-sm">
<DollarSign size={14} /> <DollarSign size={14} />
@@ -330,13 +329,13 @@ export default function TradeDetail({
)} )}
{/* Price Difference */} {/* Price Difference */}
{!loading && (senderPrice > 0 || receiverPrice > 0) && ( {!loading && (yourPrice > 0 || theirPrice > 0) && (
<div className="p-3 bg-gray-800 rounded-lg"> <div className="p-3 bg-gray-800 rounded-lg">
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Value Difference:</span> <span className="text-gray-400">Value Difference:</span>
<span className={Math.abs(senderPrice - receiverPrice) > 5 ? 'text-yellow-400' : 'text-gray-300'}> <span className={Math.abs(yourPrice - theirPrice) > 5 ? 'text-yellow-400' : 'text-gray-300'}>
${Math.abs(senderPrice - receiverPrice).toFixed(2)} ${Math.abs(yourPrice - theirPrice).toFixed(2)}
{senderPrice > receiverPrice ? ' in sender favor' : senderPrice < receiverPrice ? ' in receiver favor' : ' (balanced)'} {yourPrice > theirPrice ? ' in your favor' : yourPrice < theirPrice ? ' in their favor' : ' (balanced)'}
</span> </span>
</div> </div>
</div> </div>
@@ -422,9 +421,10 @@ export default function TradeDetail({
Waiting for {otherUser?.username} to respond... Waiting for {otherUser?.username} to respond...
</p> </p>
) : ( ) : (
/* No editor yet (version 1) - original flow */ /* No editor yet (initial trade) */
<> <>
{isSender ? ( {isUser1 ? (
/* User1 (initiator) can edit their initial offer */
<button <button
onClick={handleEdit} onClick={handleEdit}
disabled={processing} disabled={processing}
@@ -434,6 +434,7 @@ export default function TradeDetail({
Edit Trade Offer Edit Trade Offer
</button> </button>
) : ( ) : (
/* User2 (partner) can accept/decline/counter */
<> <>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button

View File

@@ -205,42 +205,55 @@ export type Database = {
trades: { trades: {
Row: { Row: {
id: string id: string
sender_id: string user1_id: string
receiver_id: string user2_id: string
status: 'pending' | 'accepted' | 'declined' | 'cancelled' status: 'pending' | 'accepted' | 'declined' | 'cancelled'
message: string | null message: string | null
created_at: string | null created_at: string | null
updated_at: string | null updated_at: string | null
version: number
editor_id: string | null
} }
Insert: { Insert: {
id?: string id?: string
sender_id: string user1_id: string
receiver_id: string user2_id: string
status?: 'pending' | 'accepted' | 'declined' | 'cancelled' status?: 'pending' | 'accepted' | 'declined' | 'cancelled'
message?: string | null message?: string | null
created_at?: string | null created_at?: string | null
updated_at?: string | null updated_at?: string | null
version?: number
editor_id?: string | null
} }
Update: { Update: {
id?: string id?: string
sender_id?: string user1_id?: string
receiver_id?: string user2_id?: string
status?: 'pending' | 'accepted' | 'declined' | 'cancelled' status?: 'pending' | 'accepted' | 'declined' | 'cancelled'
message?: string | null message?: string | null
created_at?: string | null created_at?: string | null
updated_at?: string | null updated_at?: string | null
version?: number
editor_id?: string | null
} }
Relationships: [ Relationships: [
{ {
foreignKeyName: "trades_sender_id_fkey" foreignKeyName: "trades_user1_id_fkey"
columns: ["sender_id"] columns: ["user1_id"]
isOneToOne: false isOneToOne: false
referencedRelation: "profiles" referencedRelation: "profiles"
referencedColumns: ["id"] referencedColumns: ["id"]
}, },
{ {
foreignKeyName: "trades_receiver_id_fkey" foreignKeyName: "trades_user2_id_fkey"
columns: ["receiver_id"] columns: ["user2_id"]
isOneToOne: false
referencedRelation: "profiles"
referencedColumns: ["id"]
},
{
foreignKeyName: "trades_editor_id_fkey"
columns: ["editor_id"]
isOneToOne: false isOneToOne: false
referencedRelation: "profiles" referencedRelation: "profiles"
referencedColumns: ["id"] referencedColumns: ["id"]

View File

@@ -10,16 +10,16 @@ export interface TradeItem {
export interface Trade { export interface Trade {
id: string; id: string;
sender_id: string; user1_id: string;
receiver_id: string; user2_id: string;
status: 'pending' | 'accepted' | 'declined' | 'cancelled'; status: 'pending' | 'accepted' | 'declined' | 'cancelled';
message: string | null; message: string | null;
created_at: string | null; created_at: string | null;
updated_at: string | null; updated_at: string | null;
version: number; version: number;
editor_id: string | null; editor_id: string | null;
sender?: { username: string | null }; user1?: { username: string | null };
receiver?: { username: string | null }; user2?: { username: string | null };
items?: TradeItem[]; items?: TradeItem[];
} }
@@ -43,19 +43,19 @@ export interface TradeHistoryItem {
} }
export interface CreateTradeParams { export interface CreateTradeParams {
senderId: string; user1Id: string;
receiverId: string; user2Id: string;
message?: string; message?: string;
senderCards: { cardId: string; quantity: number }[]; user1Cards: { cardId: string; quantity: number }[];
receiverCards: { cardId: string; quantity: number }[]; user2Cards: { cardId: string; quantity: number }[];
} }
export interface UpdateTradeParams { export interface UpdateTradeParams {
tradeId: string; tradeId: string;
editorId: string; editorId: string;
message?: string; message?: string;
senderCards: { cardId: string; quantity: number }[]; myCards: { cardId: string; quantity: number }[];
receiverCards: { cardId: string; quantity: number }[]; theirCards: { cardId: string; quantity: number }[];
} }
// Get all trades for a user // Get all trades for a user
@@ -64,11 +64,11 @@ export async function getTrades(userId: string): Promise<Trade[]> {
.from('trades') .from('trades')
.select(` .select(`
*, *,
sender:profiles!trades_sender_id_fkey(username), user1:profiles!trades_user1_id_fkey(username),
receiver:profiles!trades_receiver_id_fkey(username), user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*) items:trade_items(*)
`) `)
.or(`sender_id.eq.${userId},receiver_id.eq.${userId}`) .or(`user1_id.eq.${userId},user2_id.eq.${userId}`)
.order('created_at', { ascending: false }); .order('created_at', { ascending: false });
if (error) throw error; if (error) throw error;
@@ -81,12 +81,12 @@ export async function getPendingTrades(userId: string): Promise<Trade[]> {
.from('trades') .from('trades')
.select(` .select(`
*, *,
sender:profiles!trades_sender_id_fkey(username), user1:profiles!trades_user1_id_fkey(username),
receiver:profiles!trades_receiver_id_fkey(username), user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*) items:trade_items(*)
`) `)
.eq('status', 'pending') .eq('status', 'pending')
.or(`sender_id.eq.${userId},receiver_id.eq.${userId}`) .or(`user1_id.eq.${userId},user2_id.eq.${userId}`)
.order('created_at', { ascending: false }); .order('created_at', { ascending: false });
if (error) throw error; if (error) throw error;
@@ -99,8 +99,8 @@ export async function getTradeById(tradeId: string): Promise<Trade | null> {
.from('trades') .from('trades')
.select(` .select(`
*, *,
sender:profiles!trades_sender_id_fkey(username), user1:profiles!trades_user1_id_fkey(username),
receiver:profiles!trades_receiver_id_fkey(username), user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*) items:trade_items(*)
`) `)
.eq('id', tradeId) .eq('id', tradeId)
@@ -112,39 +112,40 @@ export async function getTradeById(tradeId: string): Promise<Trade | null> {
// Create a new trade with items // Create a new trade with items
export async function createTrade(params: CreateTradeParams): Promise<Trade> { export async function createTrade(params: CreateTradeParams): Promise<Trade> {
const { senderId, receiverId, message, senderCards, receiverCards } = params; const { user1Id, user2Id, message, user1Cards, user2Cards } = params;
// Create the trade // Create the trade
const { data: trade, error: tradeError } = await supabase const { data: trade, error: tradeError } = await supabase
.from('trades') .from('trades')
.insert({ .insert({
sender_id: senderId, user1_id: user1Id,
receiver_id: receiverId, user2_id: user2Id,
message, message,
status: 'pending', status: 'pending',
// editor_id starts as null - gets set when someone edits the trade
}) })
.select() .select()
.single(); .single();
if (tradeError) throw tradeError; if (tradeError) throw tradeError;
// Add sender's cards // Add user1's cards
const senderItems = senderCards.map((card) => ({ const user1Items = user1Cards.map((card) => ({
trade_id: trade.id, trade_id: trade.id,
owner_id: senderId, owner_id: user1Id,
card_id: card.cardId, card_id: card.cardId,
quantity: card.quantity, quantity: card.quantity,
})); }));
// Add receiver's cards (what sender wants) // Add user2's cards
const receiverItems = receiverCards.map((card) => ({ const user2Items = user2Cards.map((card) => ({
trade_id: trade.id, trade_id: trade.id,
owner_id: receiverId, owner_id: user2Id,
card_id: card.cardId, card_id: card.cardId,
quantity: card.quantity, quantity: card.quantity,
})); }));
const allItems = [...senderItems, ...receiverItems]; const allItems = [...user1Items, ...user2Items];
if (allItems.length > 0) { if (allItems.length > 0) {
const { error: itemsError } = await supabase const { error: itemsError } = await supabase
@@ -199,11 +200,11 @@ export async function getTradeHistory(userId: string): Promise<Trade[]> {
.from('trades') .from('trades')
.select(` .select(`
*, *,
sender:profiles!trades_sender_id_fkey(username), user1:profiles!trades_user1_id_fkey(username),
receiver:profiles!trades_receiver_id_fkey(username), user2:profiles!trades_user2_id_fkey(username),
items:trade_items(*) items:trade_items(*)
`) `)
.or(`sender_id.eq.${userId},receiver_id.eq.${userId}`) .or(`user1_id.eq.${userId},user2_id.eq.${userId}`)
.in('status', ['accepted', 'declined', 'cancelled']) .in('status', ['accepted', 'declined', 'cancelled'])
.order('updated_at', { ascending: false }) .order('updated_at', { ascending: false })
.limit(50); .limit(50);
@@ -214,12 +215,12 @@ export async function getTradeHistory(userId: string): Promise<Trade[]> {
// Update an existing trade (for edits and counter-offers) // Update an existing trade (for edits and counter-offers)
export async function updateTrade(params: UpdateTradeParams): Promise<Trade> { export async function updateTrade(params: UpdateTradeParams): Promise<Trade> {
const { tradeId, editorId, message, senderCards, receiverCards } = params; const { tradeId, editorId, message, myCards, theirCards } = params;
// Get current trade info // Get current trade info
const { data: currentTrade, error: tradeError } = await supabase const { data: currentTrade, error: tradeError } = await supabase
.from('trades') .from('trades')
.select('version, sender_id, receiver_id') .select('version, user1_id, user2_id')
.eq('id', tradeId) .eq('id', tradeId)
.single(); .single();
@@ -227,6 +228,11 @@ export async function updateTrade(params: UpdateTradeParams): Promise<Trade> {
const newVersion = (currentTrade.version || 1) + 1; const newVersion = (currentTrade.version || 1) + 1;
// Determine the other user's ID
const otherUserId = currentTrade.user1_id === editorId
? currentTrade.user2_id
: currentTrade.user1_id;
// Save current state to history before updating // Save current state to history before updating
const { data: historyEntry, error: historyError } = await supabase const { data: historyEntry, error: historyError } = await supabase
.from('trade_history') .from('trade_history')
@@ -276,22 +282,22 @@ export async function updateTrade(params: UpdateTradeParams): Promise<Trade> {
// Delete existing items // Delete existing items
await supabase.from('trade_items').delete().eq('trade_id', tradeId); await supabase.from('trade_items').delete().eq('trade_id', tradeId);
// Add new items // Add new items (myCards belong to editor, theirCards belong to other user)
const senderItems = senderCards.map((card) => ({ const myItems = myCards.map((card) => ({
trade_id: tradeId, trade_id: tradeId,
owner_id: currentTrade.sender_id, owner_id: editorId,
card_id: card.cardId, card_id: card.cardId,
quantity: card.quantity, quantity: card.quantity,
})); }));
const receiverItems = receiverCards.map((card) => ({ const theirItems = theirCards.map((card) => ({
trade_id: tradeId, trade_id: tradeId,
owner_id: currentTrade.receiver_id, owner_id: otherUserId,
card_id: card.cardId, card_id: card.cardId,
quantity: card.quantity, quantity: card.quantity,
})); }));
const allItems = [...senderItems, ...receiverItems]; const allItems = [...myItems, ...theirItems];
if (allItems.length > 0) { if (allItems.length > 0) {
const { error: itemsError } = await supabase const { error: itemsError } = await supabase