Skip to content
Jan HřebíčekEnaro
← Case studies

Modernization + automation

Chata Roklinka: Modernising and automating the reservation system

I took the project over when its original developer had stopped maintaining it. The cabin’s site looked good, but booking was just a form that sent an email. Dates, confirmations and payments were then handled by hand, and underneath ran Next.js 12 from 2022. Step by step it became a booking system in which a guest picks dates, pays, and can cancel on their own if needed, without the operator having to do anything.

Manual → auto
booking, payment and cancellation
170
automated tests
RoleTakeover, modernisation, development & maintenanceStackNext.js · MongoDB · Stripe · Resend

The challenge

The booking form emailed the owners and that was the end of its job. Every booking meant extra work. The owners had to:

  • check by hand that the dates were free
  • confirm the booking and arrange payment by hand
  • block the dates in the calendar by hand
  • release the dates by hand when a guest cancelled
  • work out by hand how much to refund

On top of that: an outdated stack, deploys that kept failing, and a gallery that uploaded hundreds of megabytes of photos the site never displayed on every deploy.

Stabilisation and a faster gallery

Before anything could be added, the site had to deploy reliably. Image optimisation through next/image failed on the hosting, so I replaced it with my own build-time thumbnail pipeline.

  • every photo in three sizes: two for the grid, one for the viewer
  • WebP is used only where it is actually smaller than JPEG. On 17 of 30 photos it was not, and browsers were downloading the larger file
  • opening a photo costs ~421 kB instead of ~675 kB, and the blur-up placeholder costs nothing, because it is the grid thumbnail already loaded
  • the originals moved out of the public folder, so they are no longer uploaded to the CDN on every deploy
  • thumbnails are cached for a year, since they only change with a new deploy

Booking with online payment

The guest picks arrival and departure in half-day steps, the system prices the stay including the pet fee and hands them over to Stripe. Once paid, both the guest and the operator get a confirmation and the dates are blocked. No phone calls, no copying into a spreadsheet.

  • the price is computed only on the server, and the quote on the site and the amount charged come from a single definition, so they cannot drift apart
  • dates are blocked the moment the guest goes to pay, so nobody else can buy them in the meantime
  • unpaid bookings release themselves when the payment window expires (a scheduled job every 5 minutes) and instantly when the payment is abandoned
  • if two payments for the same dates go through at once, the second is refunded automatically
  • the webhook is idempotent: a repeated delivery from Stripe never sends two confirmations or issues two refunds

Security audit

Once the site started taking money, I went through the whole codebase as a code review. The most serious findings were in the admin and in cancellations, and none of them was visible from the outside until someone tried to abuse it.

  • admin sign-in rebuilt from scratch: hashed passwords, a signed session in an httpOnly cookie, and an increasing lockout against password guessing
  • the calendar endpoint accepted more than it should. Input is now strictly limited to what the calendar actually sends
  • the cancellation link from the email is protected against injected database queries
  • a configuration check that stops the build before a bad setting can reach production

Modernising the stack

Next.js 12 → 16, React 18 → 19, Mongoose 6 → 9 and styled-components 5 → 6. Four major versions at once on code that takes payments. Tests and the build were the gate, not reading changelogs. The upgrade also removed a custom Babel config that had been keeping the whole project off the fast SWC compiler.

Two languages, email and legal

  • Czech and English, including the payment page and confirmation emails in the guest’s language
  • hreflang, canonical links, sitemap and social previews. Visitors are never redirected automatically; they switch language with a link
  • an automated test keeps both language versions in sync, down to the refund percentages
  • email moved to Resend and was redesigned around what the guest opens it for
  • outside production every message is redirected to a test address, so a test booking never reaches a real guest
  • terms and conditions, privacy policy, cookie information and consent at booking

Cancellations and refunds

Guests could cancel through the link in their email, but only found out how much they would get back after confirming. The latest phase turned that around: the guest sees the exact amount before deciding, and the operator recalculates nothing.

  • cancellation per the terms: 100 / 90 / 70 / 50 / 0 % depending on days before arrival
  • a refund preview and a confirmation step with the exact amount
  • the refund calculation in one place for the whole app — it used to exist in four copies
  • protection against a double refund when the guest opens the link twice, or guest and operator cancel at the same moment
  • an unpaid booking never promises a refund
  • a test checks that the cancellation terms on the site match what the system actually pays out

Admin for the operator

  • the calendar shows which half-days are sold
  • erasing a whole range in two clicks
  • the calendar can never overwrite or delete a date that has been paid for
  • a visible edit mode, sign-out, and a session that survives a page reload
  • editing the calendar sends only the changed days, not the whole calendar

Quality and operations

The site has no staging environment; every change goes straight to production. So every deploy is preceded by a configuration check, lint and 170 automated tests covering pricing and refunds, data handling, sign-in and mail delivery. Every step and environment variable is documented, so operations can be handed over at any time.

An ongoing partnership

The work continues as maintenance and further development. When the owner expanded into forest weddings, he came back to me and we built a standalone wedding site, Lesní svatba Roklinka, which links to the cabin for accommodation. I now look after both sites, so an operational change such as a contact or the season is updated in both places at once.

The project also shows that custom development doesn’t have to mean building a new system from scratch. Often the most value comes from taking over an existing app, modernising it, and removing the manual work that slows the business down.

Tech used

  • Next.js 16
  • React 19
  • JavaScript
  • MongoDB · Mongoose
  • Stripe Checkout
  • Resend
  • styled-components
  • Framer Motion
  • sharp
  • Vitest
  • Serverless functions

Outcome

  • took over and stabilised a project its original developer had left
  • booking, payment, confirmation and date blocking with no manual work
  • online payments protected against selling the same dates twice
  • cancellation with a refund preview and protection against a double refund
  • security weaknesses in the admin and cancellations fixed
  • upgraded from Next.js 12 to 16 and from React 18 to 19
  • opening a gallery photo a third lighter, and no needless files on the CDN
  • Czech and English, including payments and emails
  • 170 automated tests as the gate before every deploy
  • the client came back with another project: Lesní svatba Roklinka