Case study · 05
2026 · Product design & full-stack development
Local City Places
A local business directory and membership rewards platform for the Phoenix metro.

- Year
- 2026
- Role
- Product design & full-stack development
- Stack
- Next.js · Drizzle · Neon Postgres · Stripe · Vercel
- Status
- Live and in production
Selected views
The work, before the process.
Two representative views establish the range before the case study moves into the decisions behind them.

Founding merchant request
A four-step request flow uses address autocomplete and timestamps each submission so category priority is first come, first served.

Public merchant page
A live profile brings gallery, hours, services, contact details, and merchant media into one public record.
Project story
A directory that makes category exclusivity operational.
Local City Places pairs a public Phoenix-metro directory with a scarce membership model: each city-and-category slot belongs to one merchant. I designed the resident experience and the request, waitlist, invitation, listing, and billing workflows that make that promise operable.
The hard part was not displaying businesses. It was keeping category availability, first-come priority, ownership, membership, and public listings consistent as residents, merchants, and administrators act on the same records.
01
MarketplaceEvery request changes what the directory can offer.
Residents browse local businesses; merchants request exclusive category positions; administrators coordinate invitations, listings, and billing. Once only one slot is available, every request can affect availability and another merchant’s place in line.
02
QueueExclusivity needs a queue, not an inbox.
A slot may be open, requested, invited, filled, or waitlisted before a merchant ever has an account. Each state has to remain understandable to the next applicant and the team operating the directory.
03
Operating modelEvery role works from the same operating model.
An accepted request can move through invitation, listing ownership, billing, and publication without being re-entered at each stage. Each role gets the controls needed to move it forward.
Decisions
Category priority is an index, not a promise
Category requests are timestamped and indexed by city, state, category, and creation time. The admin queue resolves first-come priority from data instead of inbox history or judgment.
A business exists before its owner does
Requests, invitations, and merchant accounts remain separate. A listing can exist before ownership is claimed, then gain users and billing when an invitation is accepted.
The daily entry cap is a unique index
A unique index on member and Arizona entry date enforces one sweepstakes entry per day, including concurrent requests. A plain local date avoids DST conversion.
One identity, three dashboards, no passwords
Members, merchants, and admins share one passwordless user model with role-specific views. Listings, category positions, and member activity therefore remain in the same schema.
The build
A typed product stack for a public directory and three dashboards.
The directory, role-based dashboards, merchant publishing, membership loop, and billing run in one Next.js application over a shared PostgreSQL model.
What I owned
- 01
Product design and full-stack Next.js development across the public site and all three dashboards
- 02
Role-based admin, merchant, and member areas on one Postgres schema
- 03
Timestamped category-request queue with waitlisting, invites, and merchant onboarding
- 04
Passwordless magic-link authentication and a logged email campaign system
Technology roles
Experience
- Next.jsPublic directory and role-based application routes
- ReactResident, merchant, member, and admin interfaces
- TypeScriptTyped contracts across UI, actions, and data
- Tailwind CSSResponsive product design system
Data, commerce & delivery
- Drizzle ORMSchema, migrations, and typed data access
- NeonManaged PostgreSQL for merchants, requests, members, offers, and history
- StripeMerchant subscription and billing workflows
- VercelApplication, server routes, and asset delivery
System architecture
One schema, one directory, three dashboards.
A single Next.js application serves the public directory and all three dashboards, with a typed Postgres schema underneath so merchants, members, offers, and campaigns stay consistent across every surface.
- Experience
Next.js App Router, React, TypeScript, Tailwind, and Radix primitives
- Application layer
Role-based admin, merchant, and member dashboards with magic-link auth
- Data
Drizzle ORM over Neon Postgres covering merchants, members, offers, and reviews
- Operations
Stripe billing, transactional and campaign email, Vercel delivery
Selected screens
The system, seen in practice.
Readable views of the site and the system behind it.
The category queue
Two Tempe dining requests, a week apart: the first is fulfilled, the second waitlisted. Priority is decided by submission time, not by who follows up hardest.
What the merchant buys
The merchant dashboard presents category exclusivity alongside the included campaign channels.
Member nominations
Members can see their nomination status and referral link from one dashboard.
Current status
Live and in productionLive in production; these screens are captured from the current build.
Discuss a similar project
