Picco Studio



Overview
Picco Studio is the customer-facing booking platform for picco.id, an Indonesian self-service photography studio chain. Visitors pick a location, room, background, package, and photo session time slot, then pay online to reserve the studio — without needing to call or message staff.
The site has two halves. The public side is a Prismic-powered marketing and booking flow (locations, rooms, packages, products, and a portfolio "story" gallery). The authenticated side is a Firebase-backed account area with a user dashboard for orders and loyalty points, and an admin/operator dashboard for managing schedules, orders, and order confirmations.
Payments run through Xendit Invoices: the browser never touches Xendit directly, only a Next.js API route that proxies to the Xendit API with the server-side secret key. All booking and loyalty state lives in Firebase Firestore, keyed by Firebase Auth users who sign in with Google.
Tech Stack & Rationale
Layer | Technology | Why |
|---|---|---|
Framework | Next.js 15 (App Router), React 18 | Server-rendered Prismic pages plus client-side booking and dashboards |
Language | TypeScript | Strict types for Prismic documents, Firestore order shapes, and Xendit responses |
Styling | Tailwind CSS 3.4 | Studio brand styling across slices and booking steps |
CMS | Prismic + Slice Machine | Editors manage locations, rooms, packages, products, and stories |
Auth & data | Firebase (Auth, Firestore, Storage) | Google sign-in, order/point records, and photo uploads |
Payments | Xendit Invoices API | Invoice + payment-link checkout via a server-side proxy route |
Client state | Redux Toolkit | Booking wizard (location → checkout) and auth/customer slices |
Forms | Formik + Yup | Checkout and contact form validation |
HTTP | Axios | Wrappers for the Picco API routes and Xendit |
Monitoring | Sentry | Production error tracking |
Analytics | Vercel Analytics + Speed Insights | Traffic and performance visibility |
Architecture
A booking is a multi-step wizard whose state lives in the Redux booking slice, persisted to Firestore only at the final step. The browser calls same-origin API routes that wrap Firebase Admin and Xendit, keeping both secret keys server-side.
The flow runs: Booking wizard (location → checkout) → Redux booking slice (location · session · extras) → Firebase Auth (Google sign-in) → /api/firebase/* (orders · points · promo) → Firestore (users · orders); Booking wizard (location → checkout) → Redux booking slice (location · session · extras) → /api/xendit/create-invoices (Bearer token proxy) → Xendit (/v2/invoices).
- Booking wizard — the visitor steps through LocationStep, RoomStep, DateStep, SessionStep, BackgroundStep, PackageStep, and ExtraStep. Each step reads Prismic documents (via core/prismic/fetcher.ts) and writes to the Redux booking slice.
- Checkout — the CheckoutStep validates the form with Formik/Yup, applies promo codes, referral codes, and loyalty points (fetched through /api/firebase/check-promo-code, check-referral-code, and the point endpoints), then computes the total.
- Payment — core/utils/xendit/payment.ts POSTs to /api/xendit/create-invoices with the Firebase ID token; the route verifies the token in authMiddleware, then calls Xendit POST /v2/invoices using the server-side XENDIT_SECRET_KEY, returning the invoice and payment link.
- Order persistence — the client writes the order to Firestore through /api/firebase/set-room-order (or set-extra-order / set-product-order), storing payment id, link, expiry, and status.
- Confirmation & loyalty — staff confirm orders via the admin dashboard (change-order-status, change-room-order-schedule); users submit post-visit order confirmations and earn/transfer points through the points, referrals, and promo_codes collections.
Folder Structure
picco-studio-web/
├── app/
│ ├── (routes)/(main)/(account)/(dashboard)/ # admin / operator / user dashboards + login
│ ├── (routes)/(main)/(booking)/booking/ # booking wizard entry
│ ├── (routes)/(prismic)/ # [[...slug]], announcement/[uid], package/[uid], product/[uid]
│ ├── api/ # xendit/create-invoices, firebase/*, auth, revalidate, preview
│ └── slice-simulator/ · sentry-example-page/
├── components/ # Announcement, Auth, Booking, Package, Product, User, _shared
├── core/
│ ├── cloud/ # picco.ts, xendit.ts (Axios wrappers)
│ ├── data/ # BookingData, SpaceData, ThemeData
│ ├── hooks/ # useAuth, useAutoDispatcher, useResize, useScrollDirection
│ ├── lib/ # firebaseAdmin.ts, firebaseConfig.ts
│ ├── prismic/ # fetcher.ts
│ ├── redux/ # slices + actions (auth, booking, customer)
│ ├── utils/ # firebase/*, xendit/*, general/*
│ └── @types/ # auth, booking, customer, firebase, xendit, prismic
├── slices/ # 15 slices (Hero, PackageList, Photobooth, PaymentMethod, …)
├── customtypes/ # location, room, background, package, product, extra, story, …
├── constants/ # holidays.ts
├── styles/ # globals, calendar, pagination, StorySwiper
├── public/ # assets, robots.txt, sitemap
├── prismicio.ts · firestore.indexes.json · firebase.json · next.config.mjs
└── tailwind.config.ts · tsconfig.json · package.jsonDatabase Design
Firestore is the transactional store. Seven top-level collections back the ordering and loyalty features:
Collection | Purpose |
|---|---|
users | Firebase Auth profiles — uid, display_name, role_id, is_active, timestamps |
orders | All orders (room, extra, product types) — payment id/link, status, total_payment, nested order_data |
order_confirmations | Post-visit ratings and feedback — rating, feedback, order_id, type |
points | Loyalty balances and history — total_point, history[] with in/out entries |
referrals | Referral codes — code, user_id, quantity, expired |
promo_codes | Promotional discounts — code, discount_percentage, description |
products | Physical product/print stock — locations with variation quantities |
Composite indexes for the orders, order_confirmations, and users collections are declared in firestore.indexes.json (filtering by status, type, user_id, and created_at).
Content Model
Prismic supplies all catalog and marketing content. The booking wizard resolves relationships between custom types — a location links to its rooms, each room links to backgrounds, packages, and extras, and the portfolio gallery links to story documents.
Custom type | Kind | Purpose |
|---|---|---|
location | Repeatable | Studio branches — linked rooms and products, SEO |
room | Repeatable | Photo studio rooms — linked backgrounds, packages, extras |
background | Repeatable | Backdrop choices within a room |
package | Repeatable | Photo session packages with benefits and pricing |
product | Repeatable | Prints and frames with variations |
extra | Repeatable | Add-on items (e.g. passport photo) with quantity and price |
story | Repeatable | Portfolio gallery items for the showcase |
booking_step | Repeatable | Ordered onboarding/step content for the booking wizard |
announcement | Repeatable | Site announcements with SEO metadata |
page / layout / frame | — | Generic page chrome and frame content |
Impact & Results
- In production for picco.id — an Indonesian self-service photography studio chain — handling bookings, online payment (Xendit), and operator schedules.
- Self-service booking removes the need for customers to call or message staff to reserve a studio.
My Contributions
Built on the Picco Studio booking platform as the lead frontend developer:
- I built the multi-step booking wizard (Location → Room → Date → Session → Background → Package → Extra → Checkout) driven by the Redux booking slice and Prismic relationship fetchers.
- I implemented the Xendit payment flow through the /api/xendit/create-invoices proxy route, keeping the secret key server-side and using the Firebase ID token for auth.
- I implemented Firebase Firestore order persistence and the admin/operator/user dashboards for schedules, orders, and order confirmations.
- I built the loyalty system — points, referral codes, and promo codes — including point transfer between users and the admin point management page.
- I added the Photobooth component with authentication protection, plus the passport photo (pas foto) package and the print feature.
- I optimized admin user pagination with cursor-based Firestore queries and caching.
- I added special-hours and holiday handling to the booking calendar and time-slot generation.
References
[1] Picco Studio, "Picco Studio," picco.id, 2024. [Online]. Available: https://picco.id
[2] Vercel, "Next.js Documentation," nextjs.org. [Online]. Available: https://nextjs.org/docs
[3] Prismic, "Prismic Documentation," prismic.io. [Online]. Available: https://prismic.io/docs
[4] Google, "Firebase Documentation," firebase.google.com. [Online]. Available: https://firebase.google.com/docs
[5] Xendit, "Xendit API Reference," developers.xendit.co. [Online]. Available: https://developers.xendit.co/api-reference
[6] Redux Toolkit, "Redux Toolkit Documentation," redux-toolkit.js.org. [Online]. Available: https://redux-toolkit.js.org
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!