Ulric
Book a call

Eugene, Oregon · one person, whole builds

Case study

Booking Desk: direct booking for my five Eugene stays

A direct booking site and host desk for the five short-term rentals I host in Eugene, with a calendar that knows four of the listings are one house. Requests, approval, a signed agreement, and PayPal or Venmo after I approve.

ClientUlric studio product
Year2026
ScopeProduct design, Full-stack development, ASP.NET Core, Angular, three.js
Codegithub.com/erichers/ulric-booking-desk ↗
Booking Desk: direct booking for my five Eugene stays
5stays on one linked calendar
4listings that are the same house
48 hbefore an unanswered request releases its nights
55passing xUnit tests

Why I built it

I host five short-term rentals in Eugene, near Hayward Field. Four of them are the same house, listed at different sizes: a studio, a 2-bed, a 3-bed, and the whole house as a 4-bed. The fifth is a garden cottage that stands on its own.

The listings are on Airbnb, and a marketplace handles a lot for me. The trouble is a stay that comes to me directly instead. Without a tool, that turns into a text thread where I check calendars by hand, work out a price, send my payment handles, and then try to remember to block the nights. It is easy to get one of those steps wrong, and the one that hurts is the overlap. If someone books the 2-bed, the 3-bed and the whole house are no longer available, even though nothing on a plain calendar says so.

I wanted one place that handles those stays the same way every time. I also wanted the portfolio version to be real. The first build of Booking Desk was seeded with a made-up host and made-up guests. It proved the flow, but it was not useful to me, so I replaced all of it. The version on my Mac now shows only my five listings, with my own photos, descriptions and nightly rates.

What it does

A guest opens /book and sees the five stays. Each stay page opens on a photo grid. Show all photos opens a gallery sorted by room, and any photo opens full screen. Below that is a photo tour with one section per room, then the description, amenities, house rules as a list, and a month calendar next to a reservation card. The guest picks dates and sends a request with a name, an email, an optional phone number, a guest count and a note.

The quote is simple on purpose: nights times the nightly rate. A cleaning line appears only when that fee is already part of the listing. There is no service fee. When I set it up, the rates were $111 for the studio, $197 for the 2-bed, $220.50 for the 3-bed, $294.50 for the whole house and $119.50 for the cottage. I checked two quotes against Airbnb for the same dates. The 3-bed for November 2 to 4 came to $441, and the cottage for November 9 to 11 came to $239. Both matched.

Sending the request puts a soft hold on those nights. I approve or decline it from the host desk. If I do nothing, the request expires after 48 hours and the nights open again. Once I approve, the guest gets a private page with the total, a deposit line when one is set, separate PayPal and Venmo buttons for the exact amount, and the agreement to sign with a typed or drawn signature. The signed PDF keeps the signature image, the time and the IP address. When I mark the balance paid, the stay is confirmed and those nights become a hard block.

The desk never charges a card, and it never sends anything. Reminders for the deposit, the balance, check-in instructions and a review request are written to an outbox screen. I copy them into a message myself.

How it's built

The stack is the same as the other apps in this series: an ASP.NET Core 8 API, an Angular front end with standalone components and signals, Entity Framework Core migrations, SQLite by default, and MySQL through the Pomelo provider when it is configured. QuestPDF writes the agreements and invoices. Chart.js draws the revenue chart on the host desk. three.js draws a small 3D model of the house, loaded only when that section scrolls into view.

The linked calendar

The part that took the most thought is the job a marketplace was quietly doing for me. Each listing carries a list of the listings it contains. The whole house contains the 3-bed, the 2-bed and the studio. The 3-bed contains the 2-bed. The cottage contains nothing. The blocking rules follow from that.

Two of those rules surprised me when I wrote them down. A studio booking blocks the whole house, but the 3-bed and the 2-bed stay open, because the studio has its own entrance and bath. A 3-bed booking leaves the studio open for the same reason. The overlap check runs on the server, not in the browser, and it is written so it works on MySQL 5.7. On the public calendar, nights held by another unit are hatched and labeled with that unit's name. The checkout day does not count as a night, so it stays open for the next arrival.

The status machine

Every request moves through a small set of statuses. Requested and Approved are soft holds. Confirmed is a hard block. Declined, Cancelled and Expired release the nights. A partial payment keeps a stay Approved. Only a full payment confirms it.

Decisions I made on purpose

  • Payment handles and my phone and email come from config, not from columns in the database. The repo leaves them empty. The real values live only in a settings file on my Mac that only my user account can read.
  • Sample guests are off by default. With that setting off, startup removes any sample bookings and any contract PDFs that no booking references.
  • The page says "Eugene, OR, near Hayward Field." It does not store a street address or a map pin.
  • Every call from the front end is a relative api/ path, so the same build works under any base path.

The xUnit suite was at 55 passing tests at the last run. It covers pricing, holds, linked blocking, request expiry, the status machine, reminder scheduling, payment links, and a full API flow from quote to paid.

What I learned

A marketplace does more than sell rooms. It also makes sure two people cannot book the same bed on the same night, across listings that overlap. Taking bookings directly means taking on that job, and it is the part I would test first in anyone's booking site.

The tests passed long before the app was right. Using it on the real install is what found the rest. /book had no route of its own and quietly fell back to the home page. A demo payment handle was still in four places in the copy. The PayPal and Venmo links ran together as one word. The confirmation message had a stray space before its period. Old sample contract PDFs were still on disk. Each of those became a short note on the same pull request and a small fix.

I also learned to back up before every deploy. The new seeder replaced the old demo data on startup, which was the point, but it meant a deploy could remove rows. After that, I took a database dump before each deploy. I now run one end-to-end script on my Mac after every change. It books a stay, approves it, checks the guest page links, records the payment, confirms the nights are booked, checks that an overlapping request is refused, scans every page for leftover demo text, and then deletes the test booking.

What's next

  • Calendar sync with Airbnb, so each side blocks the other's nights. Today the desk and Airbnb do not talk to each other.
  • Putting it on a public domain. That is a separate step I have not taken yet. It needs real hosting and a privacy review before a guest's details live anywhere but my own machine.

I wrote more about why one house is five listings, and what that does to availability, in StayEugene: when one house is five bookable listings.

Tech stack

  • ASP.NET Core 8 Web API
  • Angular standalone components and signals
  • Entity Framework Core migrations
  • SQLite by default, MySQL through Pomelo
  • QuestPDF for agreements and invoices
  • Chart.js for the revenue chart
  • three.js for the nested-stay model, loaded only when it is on screen

Repository

The GitHub repository for this app is github.com/erichers/ulric-booking-desk.

Full listings grid on desktop
Full listings grid on desktop
Date picker and quote on desktop and phone
Date picker and quote on desktop and phone
3D house view on desktop and phone
3D house view on desktop and phone
Room photo tour on desktop and phone
Room photo tour on desktop and phone
Property page on three phones
Property page on three phones
Dark mode property page
Dark mode property page

Common questions

Does Booking Desk take card payments?

No. It never charges a card. After the host approves a request, the guest page shows PayPal and Venmo links for the exact amount, and the host marks the payment when it arrives.

How does it stop double bookings across listings that share a house?

Each listing carries the list of listings it contains. A booking on one blocks every listing it overlaps with, the check runs on the server, and the public calendar labels which unit holds a night.

Does it send emails or texts?

No. Reminders are written to an outbox screen, and the host copies them into a message by hand.

← All work