ticket-booking-frontend: QuickShow: The Movie Booking Frontend That Bets on LocalStorage, Stripe, and a Very Short Return Trip

A clean React app that turns seat selection into a lightweight payment reconciliation flow, then wraps it in a cinematic UI that feels bigger than the codebase behind it.

6 to 8 min read • View on GitHub • More from rafiafeisty

A cinema ticket gate staged like a relay checkpoint. A browser window hands booking intent into a narrow payment corridor, then the same ticket emerges on the far side stamped as confirmed, with a small storage drawer tucked beneath the browser frame. It explains how the app waits to persist the booking until the user returns from checkout.
QuickShow treats payment as a handoff, not a final step. The booking becomes real only after the return trip succeeds.
Key Takeaways

The clever part is what happens after Stripe

Most booking apps create a pending record first, then try to collect payment, then reconcile the result. QuickShow flips that order. It stores the booking intent in localStorage, sends the user through Stripe, and only writes the booking when the app sees paid=true on the return trip.

That is the thesis of the repo. It keeps the backend free of abandoned bookings, avoids a half-finished database trail, and turns checkout into a clean confirmation handshake. The cost is durability: if the tab closes, the browser clears, or the user returns somewhere else, the intent can vanish with it.

The app’s core trick is not checkout itself. It is the state bridge that survives the redirect and becomes a booking only when payment returns confirmed.

Traditional booking flowQuickShow flowTrade-off
Create a pending booking in the database first, then pay, then confirm.Save intent locally, pay first, then reconcile on return.Traditional flow is durable, but it fills the backend with abandoned records.
The backend owns the in-flight state.The browser owns the in-flight state.QuickShow is lighter, but it depends on one device and one browser session.
Failures usually leave a trace in the database.Failures can disappear with the tab.QuickShow is simpler to operate, but weaker as a recovery system.

QuickShow is really two apps in one: browsing and commitment

The app keeps discovery and checkout separate. You browse films, open a details view, choose seats, then commit. That split matters because it avoids cramming search, selection, payment, and confirmation into one overloaded screen.

A split editorial scene showing two halves of a cinema interface. One side is airy and open, with posters, cards, and navigation. The other side is denser, with a locked seat grid and a payment corridor. The contrast explains how the app separates browsing from commitment.
QuickShow uses a two-state product model. Discovery stays light. Commitment becomes narrow and deliberate.

The seat map is the product’s hardest logic

Seats.jsx is where UX and correctness collide. The grid runs from rows A to F, occupied seats are blocked, and a showtime must be set before selection is allowed. The code has to feel simple to the user while enforcing rules the user never wants to think about.

if (!time) {
  toast.error('Please select a showtime first');
  return;
}

const isOccupied = occupiedSeats.includes(seatId);
if (isOccupied) return;

setSelectedSeats(prev =>
  prev.includes(seatId)
    ? prev.filter(seat => seat !== seatId)
    : [...prev, seatId]
);

That little guardrail does a lot of work. It prevents invalid state, keeps occupied seats inert, and makes seat choice feel deterministic instead of hopeful.

Naive seat pickerQuickShow seat pickerWhy it matters
Any seat can be tapped at any time.Seats are gated by showtime and occupancy.Constraints are enforced before the user builds a broken order.
Selection is mostly visual.Selection is tied to backend-returned availability.The UI reflects real inventory, not just styling.
Errors show up after submission.Errors are prevented at the click boundary.The flow feels faster because the app rejects bad paths early.

Why the data fetching feels faster than it has any right to

The app avoids obvious waterfall traps. In Details.jsx, it uses Promise.all to fetch cast, showtimes, and movie details together, which compresses waiting time without making the code harder to follow. It also passes context through React Router location.state, so the app can navigate without re-fetching data it already knows.

const [movieRes, castRes, showtimeRes] = await Promise.all([
  fetchMovie(id),
  fetchCast(id),
  fetchShowtimes(id)
]);

navigate('/details', { state: { index, date } });

This is not a performance stunt. It is a refusal to waste latency. The app asks for what it needs in parallel, then carries forward the minimum amount of state required to keep the next screen warm.

The UI is cinematic, but the polish serves a functional job

Tailwind, AOS, Blurcircle, and tailwind-scrollbar-hide are doing brand work as much as interface work. The result feels like a premium streaming product, not a classroom demo. That matters because booking flows are trust exercises, and the UI needs to look finished before a user will hand over payment.

Generating illustration...

The seat picker is the most stateful part of the interface. Selection is not a free click grid. It is a constrained machine.

What QuickShow gets right, and what it gives up

QuickShow’s strongest choice is also its biggest constraint. By postponing persistence until payment returns, it keeps the system lean and the backend clean. By leaning on browser state, it makes the flow dependent on the same session surviving the trip out and back.

StrengthCostEditorial read
Low backend clutter from abandoned bookings.Lost state if the browser context disappears.A clean implementation of intent-first checkout.
Simple reconciliation after payment return.Weak recovery if the user does not come back the same way.Great for a focused frontend, less ideal for resilient commerce.
A polished consumer feel from a compact codebase.Visible signs of staging-to-production transition in the stack.Promising, but still clearly an early-stage build.

The mixed localhost and hosted backend references also tell a story. This is a project in motion, not a sealed product. That makes the repo interesting in a different way: it shows the shape of a real app before the hard edges have been sanded down.