Independent · Product Design & Web Development
GITAM Marketplace
Independently designed, built and deployed a university marketplace using React, Next.js, TypeScript and Tailwind CSS, integrating backend services for product workflows.

The product objective
A student-only, peer-to-peer marketplace where verified GITAM students buy, sell and pass on items on campus. Listing is self-service and publishes immediately; safety is enforced by design rather than by an approval queue; payment always happens in person, never through the site.
- Role
- Independent project — product, design, front-end and integration
- Timeline
- Independent Project
- Deployment
- Deployed on Vercel; Supabase for data, auth and storage.
Technology stack
- Next.js 16 (App Router)
- React 19
- TypeScript (strict)
- Tailwind CSS v4
- shadcn/ui · Radix
- Zod · React Hook Form
- Supabase — Postgres, Auth, Storage, Realtime
- Vitest · PGlite · Playwright
Designed, implemented, integrated
What I did on each side of the project — kept separate, because they are different kinds of work. AI tools accelerated implementation; understanding the code, reviewing the output, integrating it and validating it stayed with me.
What I designed
- Product requirements written up as a PRD, an implementation plan and a requirement-by-requirement traceability matrix.
- The core journeys: university-email sign-up, a short publish-first listing form, search and filters, chat-then-meet exchange.
- Safety UX — reports with case numbers, blocks, appeals, and safe-exchange guidance on the sell form, the listing and the chat.
- Community threads with nested replies, voting, Hot/New/Top sorting and a moderation queue.
What I implemented
- Feature-module architecture: auth, listings, search, messaging, moderation, admin, account and uploads, each owning its server actions, queries and components.
- Shared UI primitives on shadcn/Radix, plus product components — cards, badges, empty, loading and error states.
- A listing lifecycle state machine (draft, active, on hold, sold, expired, removed) that drives the seller's available actions.
- A deterministic, versioned moderation rules engine in TypeScript.
- Zod validation at every boundary.
What I integrated
- Supabase Auth with the university email domain allowlist enforced in the database, and verification by link or 6-digit code.
- Postgres with row-level security on every table and explicit grants; state changes go through database functions.
- An image pipeline: upload route handler → file-type sniffing → re-encode to WebP (stripping EXIF/GPS) → thumbnail → QR-code detection; private storage served through short-lived signed URLs.
- Realtime messaging, an admin console and audit trail.
Where AI assisted
- Implementation was accelerated with AI coding assistants, directed by the PRD, implementation plan and traceability matrix kept in the repository.
- Generated code was reviewed, adapted and verified against the unit, database and end-to-end test suites and the production build.
Challenges and decisions
- 01
Challenge
Listings must go live immediately — no human approval queue — without letting clearly prohibited items through.
Approach
Deterministic automated checks run on save: clear violations are blocked with a specific reason and a review path; ambiguous signals are hidden pending review.
- 02
Challenge
The interface and the database could disagree about which status changes a seller is allowed to make.
Approach
The database function is the authority. The TypeScript mirror that drives the UI is checked against it in a parity test.
- 03
Challenge
Event-ticket photos can carry barcodes, QR codes and booking codes.
Approach
Ticket listings with photos publish quarantined until a moderator checks them; text-only ticket listings publish immediately.
How it is verified
- Unit tests for the moderation engine, schemas, state machine, search parameters and image pipeline (Vitest).
- Database tests that apply the real migrations in PGlite and verify row-level security, triggers and RPCs — no Docker needed.
- Playwright smoke tests and end-to-end MVP journeys.
From the repository
The seller-side lifecycle. The database is the authority; this mirror drives the UI and is parity-tested against it.
/**
* Seller-initiated lifecycle transitions (PRD §8.3). The database function
* public.listing_transition_allowed() is the authority; this mirror drives the UI and is checked
* against the database in tests/db/status-parity.test.ts.
*/
export const SELLER_TRANSITIONS: Record<ListingStatus, readonly ListingStatus[]> = {
draft: ["removed"],
active: ["on_hold", "sold", "removed"],
on_hold: ["active", "sold", "removed"],
sold: ["active", "removed"],
expired: ["active", "removed"],
removed: [],
};
export function canTransition(from: ListingStatus, to: ListingStatus): boolean {
return SELLER_TRANSITIONS[from].includes(to);
}Want to work together?
Let's build the next one together.