Selected work

Case study · 05

2026 · Product design & full-stack development

Local City Places

A local business directory and membership rewards platform for the Phoenix metro.

Local City Places homepage showing a grid of Phoenix metro merchants with photos, cities, and categories
Year
2026
Role
Product design & full-stack development
Stack
Next.js · Drizzle · Neon Postgres · Stripe · Vercel
Status
Live and in production

The work, before the process.

Two representative views establish the range before the case study moves into the decisions behind them.

Local City Places founding merchant request form showing step one of four with category and business address fields

Founding merchant request

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

Local City Places profile for R Robinson Bookkeeping with a photo gallery, Phoenix location, business details, hours, and overview

Public merchant page

A live profile brings gallery, hours, services, contact details, and merchant media into one public record.

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

Marketplace

Every 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

Queue

Exclusivity 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 model

Every 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.

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.

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

  1. 01

    Product design and full-stack Next.js development across the public site and all three dashboards

  2. 02

    Role-based admin, merchant, and member areas on one Postgres schema

  3. 03

    Timestamped category-request queue with waitlisting, invites, and merchant onboarding

  4. 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

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.

  1. Experience

    Next.js App Router, React, TypeScript, Tailwind, and Radix primitives

  2. Application layer

    Role-based admin, merchant, and member dashboards with magic-link auth

  3. Data

    Drizzle ORM over Neon Postgres covering merchants, members, offers, and reviews

  4. Operations

    Stripe billing, transactional and campaign email, Vercel delivery

Readable views of the site and the system behind it.

Live and in production

Live in production; these screens are captured from the current build.

Discuss a similar project
Next case study

Carter Steinhoff