Skip to content

Latest commit

Β 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Encore - Event & Fair Ticketing Platform

I wanted a portfolio project that went beyond another movie-ticket clone, so I built Encore around comedy shows, poetry nights, music gigs, and book/anime fairs instead β€” which meant solving two different booking problems in one app: reserved-seat concurrency for shows, and limited-quantity inventory for fairs. Everything is built to survive real concurrent demand, not just look right in a single-user demo.

Home page - marquee hero and event grid


The problem I was trying to solve

Most student ticketing projects stop at "create a booking in the database" and never actually handle the hard part:

  • Two people can select the same seat at the same instant and both "succeed"
  • Payment confirmation is trusted from the client instead of verified server-side
  • Cancellations don't actually free up the inventory they claim to
  • There's no real admin workflow β€” just a raw CRUD form
  • Nothing tells a user their ticket is about to expire, or reminds them the event is starting soon

Encore is built specifically to close these gaps with Firestore transactions, HMAC payment verification, and a full admin/notification layer.


What it does

Browsing & discovery

Events are split into two types β€” Shows (comedy, poetry, music, with reserved seating) and Fares (book/anime fairs, with limited-quantity time slots). The Explore page has category filters and a fuzzy search bar across event title, city, and venue, and automatically hides events once they've passed.

Explore page with category filters and search

Concurrency-safe seat & slot holds

This is the part I spent the most time on. Selecting seats or fare tickets creates a Firestore transaction-backed hold with an 8-minute countdown before payment must complete. Two users racing for the same seat, or the last ticket in a sold-out slot, can't both succeed - I tested this directly with two simultaneous browser sessions, and only one hold ever goes through, with the other getting a clean rejection instead of silently overwriting the first.

Seat map with live occupancy bar

Fare slots use dynamic demand pricing β€” price rises up to 50% as a slot fills, computed server-side so it can't be tampered with from the client.

Slot selector with surge pricing

Payments & tickets

Checkout runs through Razorpay. The backend independently recomputes and verifies the payment signature before ever creating a booking - the frontend's "payment succeeded" callback is never trusted on its own. Every confirmed booking gets a QR-code e-ticket, viewable in an expandable ticket modal with download-as-image and native share support.

Ticket modal with QR code, download and share Test Mode Razorpay Gateway

My Bookings

Bookings are split into Active, Expired and Cancelled tabs, with expired tickets automatically archived after 30 days (with a "view older" toggle to see the full history). Cancelling within the allowed window triggers a real Razorpay refund and frees the seat/slot back up for other users - including notifying anyone on the waitlist for that show or slot.

My Bookings with status tabs

Admin panel

A proper admin hub rather than a bare form - separate cards for managing events, running the check-in scanner and viewing analytics. Event creation cascades State β†’ City dropdowns across every Indian state and editing/deleting locks automatically once an event is within 24 hours of starting.

Admin hub Manage Events form Manage Events form

QR check-in scanner

A camera-based scanner for venue entry. Scanning a valid ticket shows a full-page confirmation with seat/ticket details; scanning an already-used or cancelled ticket is rejected outright. Start/stop controls release the camera when not in use.

QR Scanner

Analytics dashboard

Revenue by event, occupancy percentage and a 14-day booking trend, all computed live from Firestore rather than cached.

Analytics dashboard Analytics dashboard

Notifications

Email confirmations and cancellation notices via Gmail SMTP, plus an automatic reminder email 2 hours before an event starts - all handled by background cron jobs that also expire stale holds every minute.

Visual identity

A dark, marquee-theater design - chase-light borders, ticket-stub cards with punch-hole perforations, curtain-wipe page transitions and a slow drift of translucent film-reel/ticket icons across the background.


πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        Browser / Client       β”‚
β”‚       React + Vite            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
                β”‚ HTTP / API
                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚       Vite Dev Server         β”‚
β”‚           :5173               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Node.js + Express API          β”‚
β”‚                 :5000                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                         β”‚
β”‚  /api/events       Event CRUD           β”‚
β”‚  /api/holds        Seat/slot locking    β”‚
β”‚  /api/payments     Razorpay payments    β”‚
β”‚  /api/bookings     Booking management   β”‚
β”‚  /api/waitlist     Waitlist management  β”‚
β”‚  /api/analytics    Revenue & occupancy  β”‚
β”‚                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜
        β”‚              β”‚              β”‚
        β–Ό              β–Ό              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      
β”‚  Firebase       β”‚ β”‚  Razorpay   β”‚ β”‚   Gmail     β”‚
β”‚  Authentication β”‚ β”‚   Payments  β”‚ β”‚    SMTP     β”‚
β”‚  Firestore      β”‚ β”‚             | β”‚             |
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Background Jobs                β”‚
β”‚                                         β”‚
β”‚  expireHolds.js     β†’ Every 1 minute    β”‚
β”‚  sendReminders.js   β†’ Every 5 minutes   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Tech stack

Frontend

  • React 18 + Vite
  • Tailwind CSS
  • Framer Motion (page transitions)
  • Recharts (analytics charts)
  • html2canvas (ticket download/share)
  • html5-qrcode (check-in scanner)
  • React Router DOM

Backend

  • Node.js + Express
  • Firebase Admin SDK (Auth + Firestore)
  • Razorpay Node SDK
  • Nodemailer (Gmail SMTP)
  • node-cron
  • express-rate-limit
  • qrcode (ticket generation)

Getting it running

1. Clone it

git clone https://github.com/Ryzen-Starbit/Encore
cd encore

2. Set up Firebase

  1. Go to console.firebase.google.com, create a project
  2. Enable Authentication β†’ Email/Password + Google
  3. Enable Firestore Database (test mode is fine locally)
  4. Project Settings β†’ General β†’ add a Web app β†’ copy the config
  5. Project Settings β†’ Service Accounts β†’ Generate new private key β†’ save as server/serviceAccountKey.json

3. Set up Razorpay

  1. Sign up at dashboard.razorpay.com, switch to Test Mode
  2. Account & Settings β†’ API Keys β†’ Generate Test Key
  3. Copy the Key ID and Key Secret

4. Set up email

  1. Enable 2-Step Verification on a Gmail account
  2. Generate an App Password at myaccount.google.com/apppasswords

5. Environment files

client/.env:

VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_API_URL=http://localhost:5000/api

server/.env:

PORT=5000
CLIENT_URL=http://localhost:5173
RAZORPAY_KEY_ID=
RAZORPAY_KEY_SECRET=
EMAIL_USER=
EMAIL_PASS=
EMAIL_FROM=

6. Install and run

cd server && npm install && npm run dev
# new terminal
cd client && npm install && npm run dev

Open http://localhost:5173.


How to use it

  1. Sign up and make yourself an admin: node server/scripts/setAdmin.js your@email.com, then sign out/in
  2. Create a Show (reserved seating) and a Fare (limited-quantity slots) from /admin/events
  3. Book one from the Explore page β€” hold a seat or slot, pay with Razorpay's test card (4111 1111 1111 1111, any future expiry/CVV)
  4. Check /profile for the confirmed booking and QR ticket
  5. Try the check-in scanner at /admin/scanner against your own ticket's QR
  6. Check /admin/analytics for the revenue/occupancy breakdown
  7. Cancel a booking to see the refund flow and waitlist notification in action

πŸ”— Project Structure

Encore/
β”œβ”€β”€ client/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”œβ”€β”€ Navbar/
β”‚   β”‚   β”‚   β”œβ”€β”€ EventCard.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ TicketModal.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ TicketLookup.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ SeatMap/
β”‚   β”‚   β”‚   β”œβ”€β”€ SlotSelector/
β”‚   β”‚   β”‚   β”œβ”€β”€ Countdown/
β”‚   β”‚   β”‚   β”œβ”€β”€ Loader.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ SkeletonCard.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ SkeletonTicketRow.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ FloatingDecor.jsx
β”‚   β”‚   β”‚   └── CurtainTransition.jsx
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”‚   β”œβ”€β”€ Home.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ Login.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ Profile.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ EventDetails.jsx
β”‚   β”‚   β”‚   └── admin/
β”‚   β”‚   β”‚       β”œβ”€β”€ AdminHome.jsx
β”‚   β”‚   β”‚       β”œβ”€β”€ ManageEvents.jsx
β”‚   β”‚   β”‚       β”œβ”€β”€ ScannerPage.jsx
β”‚   β”‚   β”‚       └── AdminAnalytics.jsx
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ context/
β”‚   β”‚   β”‚   └── AuthContext.jsx
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   β”œβ”€β”€ firebase.js
β”‚   β”‚   β”‚   └── api.js
β”‚   β”‚   β”‚
β”‚   β”‚   └── data/
β”‚   β”‚       └── indianCities.js
β”‚   β”‚
β”‚   └── ...
β”‚
β”œβ”€β”€ server/
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   β”œβ”€β”€ firebaseAdmin.js
β”‚   β”‚   └── razorpay.js
β”‚   β”‚
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”œβ”€β”€ authMiddleware.js
β”‚   β”‚   └── validateEvent.js
β”‚   β”‚
β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”œβ”€β”€ events.js
β”‚   β”‚   β”œβ”€β”€ holds.js
β”‚   β”‚   β”œβ”€β”€ payments.js
β”‚   β”‚   β”œβ”€β”€ bookings.js
β”‚   β”‚   β”œβ”€β”€ analytics.js
β”‚   β”‚   └── waitlist.js
β”‚   β”‚
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ seatLockService.js
β”‚   β”‚   β”œβ”€β”€ slotLockService.js
β”‚   β”‚   β”œβ”€β”€ bookingService.js
β”‚   β”‚   β”œβ”€β”€ cancellationService.js
β”‚   β”‚   β”œβ”€β”€ waitlistService.js
β”‚   β”‚   β”œβ”€β”€ emailService.js
β”‚   β”‚   β”œβ”€β”€ paymentService.js
β”‚   β”‚   └── ticketService.js
β”‚   β”‚
β”‚   β”œβ”€β”€ jobs/
β”‚   β”‚   β”œβ”€β”€ expireHolds.js
β”‚   β”‚   └── sendReminders.js
β”‚   β”‚
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   └── pricing.js
β”‚   β”‚
β”‚   β”œβ”€β”€ scripts/
β”‚   β”‚   └── setAdmin.js
β”‚   β”‚
β”‚   └── firebase/
β”‚       └── firestore.rules
β”‚
β”œβ”€β”€ package.json
└── README.md

Known limitations / things I'd improve

  • Cron jobs (hold expiry, reminders) only run while the backend process is alive β€” on a free-tier host that sleeps, this needs an external keep-alive ping
  • Seat-map polling is a 10-second interval rather than a true real-time Firestore listener β€” swappable later for onSnapshot
  • No pagination yet on the admin's event list

Contributing

Contributions and suggestions are welcome.

  • Fork the repository
  • Create a feature branch
  • Commit your changes
  • Push to your branch
  • Submit a pull request

About

Full-stack event & fair ticketing platform with concurrency-safe seat or slot holds, Razorpay payments verified server-side, QR check-in, live analytics and a marquee-theater UI.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages