Index / Work
Property Manager — Villas Marinović · Croatia Live site ↗
Booking & CMS platform

Property ManagerVillas Marinović · Croatia

Property Manager is a property rental management system built around three applications that share one MySQL database — a public booking site, a REST API, and a Vue admin SPA. It powers listings, a custom day-by-day pricing engine, overlap-aware bookings, multilingual content, and a polymorphic gallery/image pipeline behind a single villa-rental brand.

Property Manager is the platform behind a Croatian villa-rental brand. It splits cleanly into three apps that share a single MySQL database: a Livewire-rendered public booking site, a Laravel 11 REST API, and a Vue 3 + TypeScript admin SPA. Rather than syncing data over the wire, the two Laravel apps read the same Eloquent schema directly, so the public site and the admin tooling never disagree about a property, a price, or an availability window.

The interesting work lives in the domain logic. A single PricingService turns sparse per-night overrides into a full booking quote, and the same service feeds both the admin calculator and the public pricing calendar. Bookings are checked for overlaps using full datetimes drawn from each property’s check-in and check-out times, which quietly enables same-day turnover — a real-world need that trips up most naïve calendars. Content across roughly twenty entities is multilingual via Spatie Translatable, and a polymorphic gallery system with an Intervention Image pipeline handles resizing, thumbnails and per-gallery organization on upload.

The result is a deliberately boring-in-the-right-places deployment: three subdomains on shared hosting, a bash deploy script, and Larastan plus Pint keeping the Laravel side honest.

RoleDesign + full-stack
Year2025
Core stackLaravel 11 · Vue 3 · Livewire 3
SurfacePublic site + REST API + admin SPA
Livevillasmarinovic.com
Public villa booking site with availability calendar alongside the Vue admin panel
Screenshot soon
Pricing

Day-by-day pricing with per-night overrides

Each property has a base nightly rate, but any individual date can carry a custom price. A dedicated PricingService walks the date range night by night, applying overrides where they exist and falling back to the base rate.

  • PropertyDailyPrice rows override price_per_night for specific dates
  • Subtotal, average-per-night, cleaning fee and deposit returned as a breakdown
  • Same engine feeds the admin price calculator and the public pricing endpoint
Screenshot soon
Bookings

Overlap-aware reservations with same-day turnover

Bookings check for conflicts against confirmed reservations before saving. The public booking flow uses full datetime comparison so a checkout and a new check-in can share the same day.

  • checkOverlaps() blocks double-booking on confirmed stays
  • Time-of-day check-in/check-out enables same-day turnover
  • Status flow: pending → confirmed → completed / cancelled
Screenshot soon
Content

Multilingual content across every entity

Titles, descriptions and SEO meta are stored as JSON and resolved per locale via Spatie Translatable. Languages are managed in the admin and exposed before login so the UI can render in the right language.

  • 14 translatable field declarations across the API models
  • Properties, pages, posts, menus, amenities and locations all localized
  • Public language switcher driven from the same data
Screenshot soon
Media

Polymorphic galleries with a resize pipeline

Galleries attach to properties, pages or posts via an entity_type/entity_id pair. Uploads go through FileUploadService, which resizes full-size images, generates thumbnails and organizes files into per-gallery folders.

  • Intervention Image v3 (GD) scales and crops on upload
  • FilePond uploader with drag reorder, bulk delete and ZIP download
  • SVG sanitization for icon markup via enshrined/svg-sanitize
Screenshot soon
Maps & icons

Map-pinned locations and amenities

Locations and amenities carry both an icon name and sanitized SVG markup, with Leaflet maps for placing points. Icon pickers map names to SVG paths through marker-utils files shared by the editors.

  • Leaflet used across 5 admin modules for coordinates
  • Location and amenity icon pickers backed by marker-utils maps
  • Bulk location import endpoint for seeding points
Screenshot soon
Admin SPA

Generic CRUD across twenty resources

The Vue admin is built on a single useCrud composable and a generic service layer, so each of the twenty domain features reuses the same fetch/submit/delete plumbing instead of bespoke code.

  • 20 feature composables wrap one useCrud core
  • Cookie-based Sanctum auth across admin and public domains
  • VeeValidate + Zod schemas for typed form validation
Architecture

One MySQL database, three apps — a Livewire public site, a Sanctum REST API, and a Vue SPA admin.

Client
Browser — public site + admin SPA
Livewire-rendered booking site on the main domain; Vue 3 SPA on the admin subdomain.
Edge
Three subdomains on shared hosting
villasmarinovic.com, api.* and admin.* mapped to separate document roots via a bash deploy script.
Application
Laravel 11 × 2 + Vue
API exposes protected + public REST routes under Sanctum; public app runs its own Laravel with Livewire components.
Data & media
Single MySQL DB + image store
Both Laravel apps read the same Eloquent schema directly; uploads live under the API's storage and are served by URL.
Challenges solved
01

Three apps, one database, no API call between Laravel apps

Problem

The public site and the admin API are separate Laravel applications, but duplicating data access or syncing over HTTP would invite drift and latency. They needed to stay perfectly consistent.

Solution

Both Laravel apps define their own Eloquent models against the same MySQL schema and read it directly — the public app makes zero HTTP calls to the API (grep for Http:: in omniapp/app = 0). The Vue SPA is the only client that goes through the REST API, keeping write logic centralized while reads stay fast.

02

Pricing that varies per night without exploding storage

Problem

Nightly rates change for weekends, seasons and special dates, but storing a row for every property × every day would be wasteful and hard to keep in sync with a base rate.

Solution

Only overridden dates get a PropertyDailyPrice row; PricingService iterates the booking range with CarbonPeriod and falls back to price_per_night wherever no override exists. The same service computes the admin quote and the public pricing map, so there is one source of truth for money.

03

Preventing double-bookings while allowing same-day turnover

Problem

A naïve date-overlap check either blocks legitimate same-day turnovers (one guest leaves, another arrives) or lets two stays collide on shared days.

Solution

Conflict detection compares full datetimes built from each property's check_in_time / check_out_time, so a 10:00 checkout and a 14:00 check-in on the same calendar day don't conflict, while genuine overlaps on confirmed bookings are rejected before save.

04

Cross-subdomain auth on shared hosting

Problem

The admin SPA and public site live on different subdomains but must share a session-cookie auth backed by the API, on shared hosting where document roots can't be freely moved.

Solution

Sanctum is configured with stateful domains and a parent SESSION_DOMAIN (.villasmarinovic.com), with the public language and settings endpoints deliberately left unauthenticated so the UI can bootstrap before login. A bash deploy script wires each app to its own document root and patches index.php paths.

How it's built
Backend & API
Laravel 11, PHP 8.2+, Sanctum, Spatie Translatable, Intervention Image, MySQL
Admin frontend
Vue 3, TypeScript, Vite, Pinia, shadcn/vue (Radix), VeeValidate + Zod, Tiptap, Leaflet, FilePond, TanStack Table
Public frontend
Laravel 11, Livewire 3, Blade, Tailwind CSS, vis-timeline calendar
Infra & quality
Shared cPanel hosting, bash deploy script, Larastan, Laravel Pint, PHPUnit

One database, three front doors — and a pricing engine that always agrees with itself.