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
tradestable 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, andfinished, defined infrontend/src/types/index.ts. - The dual-confirmation system requires both
offerer_endedandreceiver_endedto betruebefore a trade can reachfinishedstatus, ensuring bilateral agreement. - Active trade queries explicitly filter out
finishedrecords, while the React Query integration inuseTrade.tsmaintains 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →