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.
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.
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.
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.
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.
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.
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.
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.
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.
Revenue by event, occupancy percentage and a 14-day booking trend, all computed live from Firestore rather than cached.
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.
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.
βββββββββββββββββββββββββββββββββ
β 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 β
βββββββββββββββββββββββββββββββββββββββββββ
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)
git clone https://github.com/Ryzen-Starbit/Encore
cd encore- Go to console.firebase.google.com, create a project
- Enable Authentication β Email/Password + Google
- Enable Firestore Database (test mode is fine locally)
- Project Settings β General β add a Web app β copy the config
- Project Settings β Service Accounts β Generate new private key β save as
server/serviceAccountKey.json
- Sign up at dashboard.razorpay.com, switch to Test Mode
- Account & Settings β API Keys β Generate Test Key
- Copy the Key ID and Key Secret
- Enable 2-Step Verification on a Gmail account
- Generate an App Password at myaccount.google.com/apppasswords
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/apiserver/.env:
PORT=5000
CLIENT_URL=http://localhost:5173
RAZORPAY_KEY_ID=
RAZORPAY_KEY_SECRET=
EMAIL_USER=
EMAIL_PASS=
EMAIL_FROM=cd server && npm install && npm run dev# new terminal
cd client && npm install && npm run devOpen http://localhost:5173.
- Sign up and make yourself an admin:
node server/scripts/setAdmin.js your@email.com, then sign out/in - Create a Show (reserved seating) and a Fare (limited-quantity slots) from
/admin/events - 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) - Check
/profilefor the confirmed booking and QR ticket - Try the check-in scanner at
/admin/scanneragainst your own ticket's QR - Check
/admin/analyticsfor the revenue/occupancy breakdown - Cancel a booking to see the refund flow and waitlist notification in action
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
- 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
Contributions and suggestions are welcome.
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to your branch
- Submit a pull request












