VYBEX
A full-stack event management platform and headless CMS — built for Kolhapur's premier nightlife brand. Encompasses live ticketing, Razorpay payments, automated email dispatch, Cloudinary media management, and a multi-role admin dashboard.
573
Lines in booking engine
3
RBAC permission levels
4
Payment API routes
11
CMS service modules
01 / Project Overview
Client
Shourya Patil / VYBEX
Location
Kolhapur, Maharashtra
Deliverable
Public website + Admin CMS + Ticketing engine
Stack
Next.js · Firebase · Razorpay · Cloudinary · Resend
VYBEX is Kolhapur's fastest-growing nightlife and event brand. Before this project, the organization relied on Instagram DMs for bookings, WhatsApp for confirmations, and had no centralized way to manage events, gallery, or media. Every event was operationally manual.
KernelX was commissioned to engineer a production-grade platform that would serve two distinct audiences simultaneously: the public-facing website for event discovery, registration, and brand presence, and a private admin CMS for the VYBEX team to manage every piece of content on the site without writing code.
The result is a cohesive full-stack system where an admin can create a new event, upload a cover image via drag-and-drop, set ticket pricing and capacity limits, open registrations, and watch real-time attendee counts update — all from a single authenticated dashboard.
02 / Engineering Challenges
Preventing Double-Bookings Under Concurrency
Challenge
Two users purchasing the last ticket simultaneously could both succeed, creating an oversell condition.
Solution
Implemented Firestore runTransaction to atomically read remaining capacity and write the booking confirmation in a single database operation. The transaction automatically retries on contention and throws on capacity overflow, guaranteeing correctness under concurrent load.
Payment Confirmation Reliability
Challenge
Network failures between the Razorpay checkout callback and the verification API could leave a user who paid successfully without a confirmed ticket.
Solution
Built two independent confirmation paths: a client-initiated verify-payment route and a Razorpay webhook endpoint. Both call the same idempotent confirmBookingPayment service. If the client path fails mid-flight, the webhook fires within seconds and completes the confirmation regardless.
Admin Security Without a Traditional Backend
Challenge
The admin CMS must be fully locked down while the public site must remain open. Firebase client SDK alone cannot enforce this separation.
Solution
Deployed a two-layer security model: Firebase Security Rules enforce permissions at the database level using server-side role lookups, while the AuthGuard React component enforces section-level permissions at the UI level using the ROLE_PERMISSIONS matrix.
Media Management Without a File Server
Challenge
VYBEX requires managing hundreds of event photos, gallery videos, hero videos, and thumbnails with no self-hosted file storage.
Solution
Integrated Cloudinary with a structured folder hierarchy managed from a centralized Firestore config document. The admin can reorganize the folder structure without code changes. The MediaProvider abstraction layer decouples URL generation from the delivery infrastructure.
Public Site Availability Under Firebase Outages
Challenge
A Firebase downtime or empty collection would result in blank sections or infinite loading states visible to the public.
Solution
Every section uses the useFirestoreData generic hook, which always falls back to hardcoded constants from constants.ts if the database returns null or empty. The public website renders meaningful content in every condition.
Email Delivery After Payment
Challenge
Sending a branded confirmation email with individually numbered ticket IDs immediately after payment — without a separate email microservice.
Solution
The confirmBookingPayment service generates unique ticket IDs in the format VYBEX-DDMMYY-LAST4PAYMENTID-REGNUMBER and dispatches a branded HTML email via Resend inside the same serverless function execution, after the Firestore transaction commits.
03 / System Architecture
A unified full-stack system inside a single Next.js project.
No separate backend server. No external API gateway. The Next.js App Router collocates the public website, the admin CMS, and all API routes in one codebase — reducing deployment surface area and eliminating cross-service latency.
┌─────────────────────────────────────────────────────────────────────┐ │ VYBEX — System Overview │ ├────────────────────────┬────────────────────────────────────────────┤ │ PUBLIC WEBSITE │ ADMIN CMS (/admin/*) │ │ app/page.tsx │ app/admin/page.tsx │ │ │ │ │ ┌─────────────┐ │ ┌───────────┐ ┌──────────────────────┐ │ │ │ Hero │ │ │AuthGuard │ │ Content Sections │ │ │ │ Events │ │ │(RBAC) │ │ Events / Gallery │ │ │ │ Portfolio │ │ └───────────┘ │ Portfolio / Hero │ │ │ │ Gallery │ │ ↓ │ Testimonials │ │ │ │ Contact │ │ Role Check │ Music Settings │ │ │ └─────────────┘ │ via Firestore │ Users / Registrns │ │ │ ↓ │ └──────────────────────┘ │ │ useFirestoreData └────────────────────────────────────────────┤ │ ↓ on success ↓ on fail │ │ Live data Fallback constants │ ├────────────────────────┬────────────────────────────────────────────┤ │ API ROUTES │ SERVICES LAYER │ │ app/api/* │ src/lib/services/* │ │ │ │ │ /registrations/ │ bookings-confirm.ts (Atomic txn) │ │ create-order ──────│→ events.ts (CRUD) │ │ verify-payment──────│→ gallery.ts (CRUD) │ │ webhook ──────│→ users.ts (RBAC mgmt) │ │ /upload ──────│→ contact.ts (Config read) │ │ /media/delete ──────│→ seed.ts (DB init) │ │ /contact ──────│→ hero.ts / music.ts (Settings) │ │ /admin/delete-user────│ │ ├────────────────────────┴────────────────────────────────────────────┤ │ EXTERNAL SERVICES │ │ │ │ Firebase Auth ─── Authentication & session management │ │ Firestore ─── Primary database (public reads + admin RW) │ │ Razorpay ─── Payment orders, checkout, webhooks │ │ Cloudinary ─── Image/video CDN with folder management │ │ Resend ─── Transactional confirmation emails │ │ Vercel ─── Hosting, serverless functions, analytics │ └─────────────────────────────────────────────────────────────────────┘
Dual Firebase Architecture
VYBEX operates two distinct Firebase SDK instances simultaneously. The browser-side firebase/firestore handles public reads (events, gallery, hero content) and authentication state. The server-side firebase-admin SDK, initialized once per process with a Service Account private key, handles all privileged writes — creating bookings, confirming payments, generating ticket documents, and updating event capacities. This separation ensures that no client-side JavaScript ever has admin-level database access.
Multi-Step Booking Engine
The EventRegistrationModal is a self-contained 573-line state machine that orchestrates a 6-step user journey: ticket selection → contact input → attendee names → payment initiation → processing → success/error. State is managed with React useState hooks. The Razorpay checkout script is loaded lazily on demand using dynamic DOM injection, preventing it from impacting initial page load.
Atomic Payment Confirmation
The confirmBookingPayment service uses a Firestore runTransaction to atomically verify capacity, confirm the booking, write individual Attendee documents with generated ticket IDs, and update the event's currentAttendeeCount in a single database operation. If any step fails, the entire transaction rolls back. This prevents double-bookings and overselling even under concurrent load.
Dual Payment Confirmation Strategy
Payment confirmation is handled by two independent routes: /api/registrations/verify-payment (client-initiated, HMAC signature verified) and /api/registrations/webhook (Razorpay server push, webhook secret verified). Both delegate to the same confirmBookingPayment service, which implements idempotency — if a booking is already confirmed, subsequent calls skip all database writes safely. This provides resilience against network failures during checkout.
Role-Based Access Control (RBAC)
Three user roles are defined in TypeScript: super_admin, admin, and content_manager. A ROLE_PERMISSIONS map explicitly lists which CMS sections each role may access. The AuthGuard component enforces these permissions client-side on every admin route. Firestore Security Rules independently enforce the same permissions server-side, creating a two-layer access control system that cannot be bypassed.
CMS with Offline Fallback
Every public section fetches its content via the generic useFirestoreData hook, which accepts a fetchFn and a fallback value. If Firebase is unreachable or a collection is empty, the component renders with hardcoded defaults from constants.ts. The public website therefore never shows a blank page or loading spinner due to a database outage.
Media Abstraction Layer
All media delivery is routed through a CloudinaryProvider class that implements a MediaProvider interface. The class handles URL construction with on-the-fly transformations (width, height, crop, quality, format). Server-side uploads go through /api/upload, which resolves the destination Cloudinary folder dynamically from a centralized config document in Firestore — allowing the admin to reorganize media storage without code changes.
Emergency Kill Switch
The create-order API route reads a settings/payment Firestore document at the start of every request. If the emergencyKillSwitch field is true, the API immediately returns a 503 error and no payment order is created. This allows the Super Admin to pause ticket sales across all events instantly from the admin dashboard without a deployment.
04 / Payment & Ticketing Flow
From ticket selection to inbox confirmation in one atomic sequence.
The booking flow spans six distinct states, three React hooks, two server routes, one Firestore transaction, and one email dispatch — orchestrated without any external queue or job runner.
Ticket IDs are generated deterministically: VYBEX-DDMMYY-LAST4PAYMENTID-REGNUMBER. This encodes the event date and payment reference into every ticket, making them scannable and verifiable at the venue without a database lookup.
Booking state machine:
05 / Technology Stack
Every tool chosen for a specific engineering reason.
App Router with React Server Components for SEO, performance, and server-side API routes co-located with the frontend — eliminating the need for a separate backend server.
Implementation Detail
Dynamic imports (next/dynamic) with ssr: false are used for three browser-only components (MusicPlayer, LoadingScreen, CursorGlow) to prevent hydration errors while keeping the shell SSR-rendered.
06 / Security Architecture
Defense in depth. Every layer independently verified.
Security is enforced at three independent layers: the Next.js API routes validate all inputs and signatures before touching any data store; Firestore Security Rules enforce role permissions at the database engine level; and the React AuthGuard enforces section-level access at the UI level. Bypassing one layer does not compromise the others.
Role permission matrix:
| Section | Super Admin | Admin | Content Mgr |
|---|---|---|---|
| Events | ✓ | ✓ | ✓ |
| Portfolio | ✓ | ✓ | ✓ |
| Gallery | ✓ | ✓ | ✓ |
| Testimonials | ✓ | ✓ | ✓ |
| Hero Settings | ✓ | ✓ | — |
| Contact Config | ✓ | ✓ | — |
| Music Settings | ✓ | ✓ | — |
| User Management | ✓ | — | — |
HMAC-SHA256 Signature Verification
Every incoming payment from Razorpay is verified server-side by generating a HMAC using the Razorpay key secret and comparing it to the signature sent by the client. Rejected on mismatch.
Webhook Signature Verification
The Razorpay webhook endpoint reads the raw request body as text and verifies it using a separate RAZORPAY_WEBHOOK_SECRET before processing any database operations.
Firestore Security Rules
109-line rules file with helper functions (isSuperAdmin, isAdmin, isContentManager) that resolve roles live from Firestore at request time. Default-deny rule at the bottom rejects all unmatched paths.
Server-Side Capacity Validation
Ticket quantity and event capacity are validated on the server in create-order before the Razorpay order is created, and again atomically inside the Firestore transaction during confirmation.
Environment Variable Isolation
All secrets (Razorpay keys, Cloudinary API secret, Firebase private key, Resend API key) are exclusively in server-side environment variables. Only Firebase public config is prefixed with NEXT_PUBLIC_.
Super Admin Auto-Provisioning Guard
The isSuperAdminEmail() function hard-codes the authorized super admin email. Any other Firebase Auth user who signs in without a pre-existing Firestore profile is immediately signed out and denied access.
Unauthorized User Rejection
useAuth hook signs out and displays Access Denied to any authenticated Firebase user who does not have a corresponding profile document in the users Firestore collection.
File Size Limit on Uploads
The upload API route enforces a 4.5MB maximum file size before any bytes are sent to Cloudinary, preventing abuse of the upload endpoint.
07 / User Experience
Cinematic aesthetic. Minimal interaction cost.
VYBEX's visual language is defined by a dark, high-contrast palette (vybex-black #0A0A0A, vybex-red #B11226, vybex-gold #D4AF37) and a typography pairing of Playfair Display (serif, editorial) with Inter (sans, functional). The combination creates the impression of a premium venue, not a software product.
Every interactive element on the public site is animated with Framer Motion. Section reveals use useInView with a once flag, preventing repeated animations on scroll-back. The booking modal uses AnimatePresence to animate both the enter and exit of each step — communicating progress without cognitive overhead.
Custom Cursor
The system cursor is hidden site-wide. A Framer Motion spring-physics cursor dot tracks the mouse with configurable damping (25) and stiffness (300), giving tactile feedback to pointer movement. A separate glow trail follows with slower spring physics for a depth effect.
Ambient Music Player
A floating music player renders client-only (SSR disabled) and respects browser autoplay policies. User mute preferences are persisted in localStorage and restored on next visit. Admin-configured default play/volume settings are fetched from Firestore on initialization.
Masonry Gallery with Lightbox
Photos and videos render in a CSS columns masonry layout. Each item opens a full-screen lightbox with keyboard-accessible prev/next navigation, smooth AnimatePresence transitions, and a media counter.
Loading Screen
A branded loading screen renders on first paint, giving the hero background image, hero videos, and Firestore data time to resolve before the full layout is revealed.
08 / SEO & Performance
Static Metadata
layout.tsx exports a Next.js Metadata object with title, description, keywords, authors, openGraph (title, description, type, image), and twitter card fields — all served in the HTML <head> without client JavaScript.
Google Fonts Preconnect
Preconnect links for fonts.googleapis.com and fonts.gstatic.com are placed in the document <head>, reducing font load latency by establishing connections before CSS is parsed.
Next.js Image Optimization
All images use next/image with fill, quality, priority, and responsive sizes props. next.config.js enables AVIF and WebP format negotiation, serving the best format based on browser Accept header.
Semantic HTML Structure
Each page section uses the <section> element with an aria-label. The main content is wrapped in <main id='main-content'>. The Hero section uses a <h1> element; subsequent sections use <h2> with consistent hierarchy.
Smooth Scroll & Scroll Padding
CSS scroll-behavior: smooth and scroll-padding-top: 80px are set globally, ensuring anchor navigation accounts for the fixed navbar height without content being hidden behind it.
Custom Focus Styles
focus-visible receives a 2px VYBEX red outline, maintaining keyboard navigation accessibility while removing focus rings for mouse users.
Dynamic Imports for Performance
MusicPlayer, LoadingScreen, and CursorGlow are loaded via next/dynamic with ssr: false. These components use browser-only APIs (Audio, localStorage, mousemove events) and would crash SSR — dynamic import prevents this while keeping the main bundle lean.
Cloudinary Format Negotiation
next.config.js enables formats: ["image/avif", "image/webp"]. Next.js automatically serves AVIF to supporting browsers and WebP to others, with JPEG as fallback — reducing image payload by 30–70% vs. JPEG without quality loss.
Lazy Loading on Scroll
Gallery images use Next.js Image's default lazy loading. The SectionReveal component uses Framer Motion's useInView with a -80px margin, triggering reveal animations just before elements enter the viewport for a perceptually faster experience.
09 / Admin Content Management System
A complete operations dashboard. No code required to manage any content.
The admin panel lives at /admin/* and is a distinct application embedded inside the same Next.js project. It shares the database and service layer but has its own layout, stylesheet, authentication guard, and sidebar navigation.
Events Management
Create, edit, archive, and delete events. Configure ticket types (individual/couple), pricing, minimum and maximum booking sizes, maximum capacity, and registration status (open/paused/closed/waitlist). The currentAttendeeCount field is deliberately excluded from admin update operations — it is only modified by the payment confirmation transaction.
Registrations Dashboard
Full list of all bookings with filter by event, status, and date. Exports to PDF using jspdf and jspdf-autotable. Each registration shows primary contact, attendee list with ticket IDs, payment ID, booking status, and total amount paid. Ticket reissue capability via /api/registrations/reissue-ticket.
Gallery Management
Drag-and-drop media upload via the CloudinaryUpload component with progress feedback. Supports photos and videos. Items are assigned an order number for display sequencing. The upload route automatically detects video MIME types and routes them to the galleryVideos Cloudinary folder.
Hero Content Editor
Edit the homepage hero heading (multi-line, rendered with italic gold gradient on the last line), subheading, CTA button text and link, secondary CTA text and link, and the two hero videos displayed in the HeroVideoShowcase component.
User Management (Super Admin)
Super Admin exclusive section to invite new team members by assigning a role (admin or content_manager). Displays a list of all authorized users with their last login timestamp. Account deletion calls /api/admin/delete-user which removes both the Firestore profile and the Firebase Auth account.
Music & Contact Settings
Toggle ambient music autoplay and default volume. Update all contact information (email, phone, WhatsApp number and pre-fill message, Instagram handle, location) that is displayed live on the public contact section without redeployment.
10 / Developer Experience & Code Quality
Architecture designed to be maintained by the next engineer.
The codebase follows a strict separation of concerns: Firestore queries live exclusively in src/lib/services/, type definitions in src/lib/types.ts, custom React hooks in src/lib/hooks/, and UI components in src/components/. No component directly calls Firestore.
TypeScript strict mode
All 15+ data types are centrally defined. No implicit any in service layer.
ESLint (next/core-web-vitals)
Configured via .eslintrc.json using the Next.js recommended ruleset.
Generic useFirestoreData hook
One hook handles loading state, error recovery, and fallback for every CMS section — not duplicated per component.
Service layer abstraction
11 service modules isolate all Firestore operations. Components never import firebase/firestore directly.
Singleton Firebase initialization
Both client and admin SDKs guard against double-initialization in Next.js HMR environments using getApps().length checks.
Environment variable documentation
.env.example documents all 14 required environment variables with comments explaining their source and purpose.
Project structure:
src/
├── app/
│ ├── page.tsx ← Public homepage
│ ├── layout.tsx ← Global metadata + fonts
│ ├── globals.css ← Design tokens + utilities
│ ├── admin/
│ │ ├── layout.tsx ← Auth guard wrapper
│ │ ├── page.tsx ← Dashboard
│ │ ├── events/ ← Event CRUD
│ │ ├── registrations/ ← Booking viewer
│ │ ├── gallery/ ← Media manager
│ │ ├── hero/ ← Hero content editor
│ │ ├── users/ ← User management
│ │ ├── music/ ← Music settings
│ │ └── contact/ ← Contact config
│ └── api/
│ ├── contact/ ← Email dispatch
│ ├── upload/ ← Cloudinary upload
│ ├── media/delete/ ← Cloudinary delete
│ ├── admin/delete-user/← Auth user deletion
│ └── registrations/
│ ├── create-order/ ← Razorpay order
│ ├── verify-payment← Sig verification
│ ├── webhook/ ← Razorpay events
│ └── reissue-ticket
├── components/
│ ├── sections/ ← 10 public sections
│ ├── admin/ ← 5 admin components
│ ├── layout/ ← Navbar + Footer
│ ├── ui/ ← CursorGlow, Loading...
│ └── MusicPlayer.tsx
└── lib/
├── firebase.ts ← Client SDK init
├── firebase-admin.ts ← Admin SDK init
├── types.ts ← All type definitions
├── constants.ts ← Fallback defaults
├── services/ ← 11 Firestore modules
├── hooks/ ← useAuth, useFirestore
└── media/ ← CloudinaryProvider11 / Engineering Highlights
01
Idempotent Booking Confirmation
The confirmBookingPayment function checks if a booking is already confirmed before executing any database writes. This enables safe retry from both the client verify route and the Razorpay webhook without risk of duplicate tickets or double-charged capacities.
02
Deterministic Ticket ID Format
Ticket IDs encode the event date (DDMMYY), the last 4 characters of the Razorpay payment ID, and a zero-padded sequential registration number. This makes tickets auditable and scannable without a database lookup at the venue.
03
Atomic Capacity Management
Event capacity is incremented inside a Firestore transaction alongside booking confirmation. If capacity is exceeded mid-transaction, the entire transaction rolls back. When capacity reaches the max, the event's registrationStatus is automatically set to 'closed'.
04
currentAttendeeCount Write Exclusion
The updateEvent service function explicitly strips currentAttendeeCount from any admin-triggered update using TypeScript destructuring. This prevents an admin accidentally resetting the live attendee count to zero by saving an event form.
05
Spring-Physics Cursor
The CursorGlow component implements two Framer Motion spring instances with different stiffness/damping ratios — one for the cursor dot (fast, tight) and one for the glow trail (slow, loose). This creates natural physical separation between the two elements on fast mouse movements.
06
Razorpay Script Lazy Loading
The Razorpay checkout.js script is injected into the DOM only when the user clicks 'Pay Now', not on page load. This eliminates a ~50KB JavaScript payload from the initial bundle for users who browse events but don't register.
07
Webhook Returns 200 on Logic Errors
The webhook handler intentionally returns HTTP 200 (not 5xx) when a database update fails due to a logical error (e.g., event deleted). This prevents Razorpay from entering an infinite retry loop while still logging the error for investigation.
08
MediaProvider Interface Abstraction
Cloudinary URL generation is behind a MediaProvider interface. Swapping to a different CDN (e.g., AWS CloudFront) in the future requires only a new class implementing three methods — upload, delete, getUrl — with zero changes to components.
09
Vercel Payload Limit Awareness
The upload route enforces exactly 4.5MB (not the Cloudinary limit) because Vercel serverless functions have a 4.5MB request body limit. Without this server-side guard, uploads above this threshold silently fail at the infrastructure level before reaching application code.
12 / Scalability & Future Expansion
Architected to grow without structural changes.
The service layer abstraction means new content types can be added by creating one new Firestore service module, one new admin page, and one new public section component — without modifying any existing code. The RBAC system is driven by a config object, so new roles or permissions require a single data change.
Multi-city event expansion with per-city admin accounts
QR code generation for ticket validation at venue entry
Waitlist management with automated promotion when cancellations occur
PDF ticket downloads generated server-side with jspdf
Analytics dashboard integrating @vercel/analytics with Firestore booking data
Mobile app via React Native sharing the Firebase backend
Internationalization (i18n) support for regional language event listings
Refund workflow integration using Razorpay Refund API
Why this scales
Firestore scales horizontally with no connection pool management. Document-level locking in transactions handles concurrent bookings without application-level mutexes.
Vercel auto-scales serverless functions to zero when idle and to hundreds of instances under load — no capacity planning required for event-day traffic spikes.
Cloudinary serves media from a global CDN edge network. Traffic spikes from viral social media posts do not impact the origin server.
Firebase Auth handles session management for all admin users with no session table or token rotation logic to maintain.
13 / Conclusion
A complete engineering system, not just a website.
VYBEX is a demonstration of what production software engineering looks like for a growth-stage brand. It is not a template or a CMS plugin. Every system — authentication, payments, media management, email, and content publishing — was architected from first principles and built to handle the real operational demands of a live events business.
The platform enables VYBEX to operate its entire digital footprint from a single dashboard: publish events, open ticket sales, monitor capacity, collect payments, dispatch confirmations, manage media, and update every public-facing content element — without touching code or contacting a developer.
The engineering approach prioritized correctness (atomic transactions), reliability (idempotent confirmation, dual payment path), security (layered RBAC, HMAC verification), and maintainability (typed service layer, abstracted interfaces) — values that compound over the lifetime of the platform as VYBEX scales across more events and cities.