How the Supabase Trades Table Works: Structure and Status Management Explained

The Supabase trades table uses a TradeStatus enum with four states—offered, accepted, declined, and finished—to track card exchanges between friends, with boolean flags offerer_ended and receiver_ended ensuring both parties confirm completion before finalizing.

The marcelpanse/tcg-pocket-collection-tracker repository implements a peer-to-peer trading system using Supabase as the backend database. The Supabase trades table structure centers on a single row that links two friends via their IDs and tracks the specific cards exchanged, while the trade status management workflow orchestrates the entire lifecycle from initial offer through dual-party confirmation.

Supabase Trades Table Schema and Type Definition

The database schema is reflected in the frontend TypeScript type TradeRow, defined in [frontend/src/types/index.ts](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/types/index.ts). This interface maps directly to the Supabase table columns:

Column Type Description
id number Auto-increment primary key identifying the unique trade record
created_at Date Timestamp when the trade was initiated
updated_at Date Timestamp of the last modification
offering_friend_id string Foreign key referencing the friend ID of the user initiating the offer
receiving_friend_id string Foreign key referencing the counter-party's friend ID
offer_card_id string Identifier of the card the offerer is giving
receiver_card_id string Identifier of the card the receiver provides in exchange
offerer_ended boolean Flag indicating whether the offering side has marked the trade complete
receiver_ended boolean Flag indicating whether the receiving side has marked the trade complete
status TradeStatus Current lifecycle stage of the trade

The TradeStatus type is defined as a const assertion in the same file, restricting values to the four valid states:

const tradeStatuses = ['offered', 'accepted', 'declined', 'finished'] as const
export type TradeStatus = (typeof tradeStatuses)[number]

Trade Lifecycle: How Statuses Are Managed

The application moves trades through discrete states using the insertTrade and updateTrade functions located in [frontend/src/services/trade/tradeService.ts](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/services/trade/tradeService.ts). Each transition corresponds to a specific user action and database update.

Creating a Trade: The offered Status

When a user initiates a card exchange, the insertTrade function creates a new row with status set to 'offered' by default. This represents a pending proposal awaiting the counter-party's response.

import { insertTrade } from '@/services/trade/tradeService'

await insertTrade({
  offering_friend_id: myFriendId,
  receiving_friend_id: otherFriendId,
  offer_card_id: 'card123',
  receiver_card_id: 'card456',
  offerer_ended: false,
  receiver_ended: false,
  status: 'offered',
})

Accepting or Declining: accepted and declined Statuses

The receiving party responds by calling updateTrade with the appropriate status. Accepting a trade updates the record to status: 'accepted', while declining sets status: 'declined'. Both actions use the same service method with different payload values.

import { updateTrade } from '@/services/trade/tradeService'

// Accept the pending offer
await updateTrade(tradeId, { status: 'accepted' })

// Or decline the offer
await updateTrade(tradeId, { status: 'declined' })

Finalizing Trades: The finished Status and Dual Confirmation

Trades reach the terminal finished state only after both participants confirm completion. The system requires both offerer_ended and receiver_ended boolean flags to be true before setting status: 'finished'. This dual-confirmation pattern prevents premature closure and ensures both parties have fulfilled their obligations.

await updateTrade(tradeId, {
  status: 'finished',
  offerer_ended: true,
  receiver_ended: true,
})

Querying Active vs. Completed Trades

The service layer provides two distinct query patterns in [frontend/src/services/trade/tradeService.ts](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/services/trade/tradeService.ts). getActiveTrades filters out completed exchanges by excluding rows where status === 'finished', returning only ongoing negotiations between specific friend pairs. getAllTrades returns the complete history, enabling UI components like [frontend/src/pages/trade/TradeOffers.tsx](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/pages/trade/TradeOffers.tsx) to group and display trades by their current status.

import { getActiveTrades } from '@/services/trade/tradeService'

const { trades } = await getActiveTrades(myFriendId, otherFriendId)
// Returns only trades where status !== 'finished'

Cache synchronization is handled by React Query hooks in [frontend/src/services/trade/useTrade.ts](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/services/trade/useTrade.ts), which automatically invalidate the ['trades'] query key after any insertion or status modification, ensuring the UI reflects the latest database state without manual refresh.

Summary

  • The Supabase trades table stores exchange records with nine core columns, including dual foreign keys for both trading parties and two boolean flags for completion tracking.
  • Trade statuses follow a strict enum of offered, accepted, declined, and finished, defined in frontend/src/types/index.ts.
  • The dual-confirmation system requires both offerer_ended and receiver_ended to be true before a trade can reach finished status, ensuring bilateral agreement.
  • Active trade queries explicitly filter out finished records, while the React Query integration in useTrade.ts maintains real-time UI synchronization through automatic cache invalidation.

Frequently Asked Questions

What are the possible values for the TradeStatus enum in the Supabase trades table?

The TradeStatus type accepts exactly four string literals: offered, accepted, declined, and finished. These are defined as a const assertion array in frontend/src/types/index.ts and represent the complete lifecycle of a card trade from initiation to completion.

How does the system prevent a trade from finishing prematurely?

The implementation uses two boolean columns, offerer_ended and receiver_ended, as safety checks. Both flags must be set to true via updateTrade before the status can legally transition to finished. This ensures neither party can unilaterally close an exchange while the other still considers it active.

Where is the trades table schema defined in the codebase?

The schema is defined implicitly through the TradeRow TypeScript interface in frontend/src/types/index.ts. While Supabase manages the actual PostgreSQL table structure, this interface serves as the source of truth for frontend developers, documenting all columns including id, created_at, offering_friend_id, and the status field.

How does the frontend distinguish between active and completed trades?

The getActiveTrades function in frontend/src/services/trade/tradeService.ts applies a filter where status !== 'finished', returning only pending or in-progress exchanges. For historical views, getAllTrades retrieves every record regardless of status, allowing components to segment the UI into active negotiations versus completed transactions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →