Bubbas Closet
A subscription clothing rental for kids: members build a box from a members-only closet, choose the monthly plan that covers it, wear the pieces for a month, then send them back and swap.

My role
Solo build under contract for a Kelowna client. I owned the data model, billing, member portal, admin tools, and deployment.
Stack
- Next.js 16 (App Router)
- TypeScript
- Tailwind CSS v4
- Stripe
- Firebase Auth
- Cloud Firestore
- Firebase Storage
- Firebase App Hosting
- Cloud Scheduler
- GitHub Actions
- Resend
Architecture
- Browser signs in with Firebase Auth
- ID token exchanged for an httpOnly session cookie
- Next.js route handlers verify the cookie with firebase-admin
- Firestore
- Stripe Checkout and Billing Portal
- Stripe webhooks
- /api/stripe/webhook
- Subscriptions in Firestore
- Cloud Scheduler
- Release expired holds (every 5 min) and return reminders (daily)
- Firestore and Resend email
Hardest problem: Two members can never rent the same garment
Inventory is serialized. A product is a style; a unit is one physical garment with its own status: available, reserved, out, cleaning, or retired. Members browse styles, but every piece they add has to claim a specific unit.
Adding a piece runs a Firestore transaction that reads the unit and the member's live holds together, checks that the unit is still available and the box is under the plan's item limit, then reserves the unit with a 20-minute hold. If another member wins the race, it moves on to the next available unit in that size. The tier limit is enforced inside that transaction, never in the browser.
Holds expire, so a Cloud Scheduler job sweeps them every five minutes. The first version only looked at hold documents, so a garment whose hold disappeared some other way stayed reserved forever. The sweep now also checks reserved units with a missing or expired hold and returns them to the closet.
Billing follows the same rule of keeping state on the server. Upgrades apply immediately with proration, downgrades are scheduled with a Stripe subscription schedule for the next cycle, and confirming a box creates one monthly order keyed to the Stripe billing period.
What I'd do next
- Tests
- CI runs lint, type-checking, and a production build on every push, but there are no automated tests yet. First would be unit tests for the hold, tier-limit, and shipping rules, then an end-to-end test of browse, build a box, and subscribe against Stripe test mode.
- Metrics
- Track box-to-subscription conversion and return turnaround so the client can tune hold length and stock levels with data.
- Scale
- Queries use a single equality filter and refine in memory, which avoids composite indexes at the current catalogue size. A larger catalogue would need composite indexes and paginated queries.