# TCG Pocket Collection Tracker Deck Building Architecture: How Validation Rules Are Applied

> Explore the TCG Pocket Collection Tracker's deck building architecture. Learn how React, Zod, and Supabase apply validation rules client-side and server-side for robust game rule enforcement.

- Repository: [Marcel Panse/tcg-pocket-collection-tracker](https://github.com/marcelpanse/tcg-pocket-collection-tracker)
- Tags: architecture
- Published: 2026-03-06

---

**The TCG Pocket Collection Tracker implements deck building as a layered frontend architecture using React, Zod schema validation, and Supabase, enforcing game rules through client-side validation in [`DeckBuilder.tsx`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/DeckBuilder.tsx) and business logic utilities in [`utils.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/utils.ts) before persisting to PostgreSQL.**

The deck building feature in the TCG Pocket Collection Tracker enables users to construct, validate, and share Pokémon TCG Pocket decks while ensuring compliance with game rules. This React-based application combines type-safe form handling with real-time collection integration to validate deck composition before persistence.

## Frontend Architecture Overview

The deck building system resides entirely in the frontend React application, organized into distinct layers that separate UI concerns from business logic and data persistence. This architecture ensures type safety through TypeScript and Zod schemas while maintaining responsive user experiences via React Query.

## Core Components and File Structure

### DeckBuilder.tsx – UI and Form Validation

Located at [`frontend/src/pages/decks/DeckBuilder.tsx`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/pages/decks/DeckBuilder.tsx), this component serves as the primary interface for deck construction. It integrates **react-hook-form** with **Zod** validation to enforce structural constraints before submission.

The Zod schema defined at lines 92-99 declares:

```typescript
const formSchema = z.object({
  id: z.number().optional(),
  is_public: z.boolean(),
  name: z.string().min(4),          // name must be at least 4 characters
  energy: z.array(z.enum(energies)).min(1), // at least one energy type
  cards: z.array(z.number()).length(20),    // exactly 20 card IDs
})

```

### utils.ts – Business Logic and Card Counting

The [`frontend/src/pages/decks/utils.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/pages/decks/utils.ts) file contains pure functions for deck analysis. The **`getDeckCardCounts`** function calculates how many copies of each card exist in the current deck, while **`getMissingCardsCount`** determines whether the user owns sufficient copies based on their collection data.

### React Query Hooks – Data Management

Located in [`frontend/src/services/decks/useDeck.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/services/decks/useDeck.ts), these hooks encapsulate asynchronous operations. **`useUpdateDeck`** prepares the deck payload (including the current user's email) and triggers the Supabase service layer. **`useDeleteDeck`**, **`useLikeDeck`**, and related hooks handle additional deck operations while managing query cache invalidation.

### Supabase Service Layer – Backend Integration

The [`frontend/src/services/decks/deckService.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/services/decks/deckService.ts) file implements the CRUD interface with Supabase. Functions like **`getDeck`**, **`updateDeck`**, and **`deleteDeck`** perform upsert operations on the `decks` and `public_decks` tables, merging private and public deck data as needed.

## How Deck Validation Rules Are Applied

### Client-Side Schema Validation

The Zod schema in [`DeckBuilder.tsx`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/DeckBuilder.tsx) enforces four critical rules before any network request occurs:

- **Deck name**: Minimum 4 characters
- **Energy types**: At least one energy type must be selected
- **Card count**: Exactly 20 card IDs must be present
- **Type safety**: All fields are strictly typed via TypeScript and Zod

React Hook Form's `zodResolver` prevents form submission until these constraints pass, providing immediate user feedback.

### UI-Enforced Card Copy Limits

The interface enforces the game rule of maximum 2 copies per card through interactive elements. In [`DeckBuilder.tsx`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/DeckBuilder.tsx), the add button disables when a card's count reaches 2, as implemented in the card list render loop (lines 19-21) and the `CardsTable` render callback (lines 24-26).

### Missing Card Warnings

Before saving, the application checks collection ownership via `getMissingCardsCount` in [`utils.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/utils.ts). This function compares deck requirements against the user's actual card inventory, displaying warnings when the deck includes cards the user does not own or lacks sufficient copies of.

### Server-Side Persistence Validation

While the primary validation occurs client-side, the service layer in [`deckService.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/deckService.ts) ensures data integrity during the upsert operation. The `useUpdateDeck` hook (in [`useDeck.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/useDeck.ts)) attaches the authenticated user's email to the payload, ensuring deck ownership is verified before the Supabase `upsert` modifies the `decks` table.

## Summary

- The deck building architecture resides entirely in the frontend React application, utilizing TypeScript for type safety.
- **Zod schemas** in [`DeckBuilder.tsx`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/DeckBuilder.tsx) enforce structural validation (name length, energy requirements, exactly 20 cards) before submission.
- **Business logic utilities** in [`utils.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/utils.ts) calculate card counts and verify collection ownership against game rules (max 2 copies per card).
- **React Query hooks** in [`useDeck.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/useDeck.ts) manage asynchronous operations and cache invalidation, while **deckService.ts** handles Supabase CRUD operations.
- Validation occurs in multiple layers: client-side schema validation, UI-enforced copy limits, missing card warnings, and server-side persistence checks.

## Frequently Asked Questions

### How does the TCG Pocket Collection Tracker enforce the 20-card deck limit?

The application enforces the 20-card limit through a Zod schema in [`DeckBuilder.tsx`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/DeckBuilder.tsx) that requires exactly 20 card IDs: `cards: z.array(z.number()).length(20)`. React Hook Form prevents submission until this constraint is satisfied, and the UI disables the save button when the deck size is incorrect.

### What prevents users from adding more than two copies of the same card to a deck?

The UI enforces the two-copy limit through disabled state logic in [`DeckBuilder.tsx`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/DeckBuilder.tsx). When rendering the card list and table components, the add button checks the current count for each card and disables the button when the count reaches 2, preventing users from exceeding the game rule maximum.

### Where is deck data stored in the TCG Pocket Collection Tracker?

Deck data persists in Supabase PostgreSQL tables including `decks` and `public_decks`. The [`deckService.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/deckService.ts) file in `frontend/src/services/decks/` implements the CRUD interface, performing upsert operations that merge private and public deck data while attaching user authentication via the React Query hooks in [`useDeck.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/useDeck.ts).

### How does the deck builder warn users about cards they don't own?

Before saving, the `getMissingCardsCount` function in [`frontend/src/pages/decks/utils.ts`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/pages/decks/utils.ts) compares the deck's card requirements against the user's collection data. It calculates how many copies the user is missing and displays a warning indicator next to cards that exceed the user's owned quantity, allowing players to identify incomplete decks before publication.