Skip to content
Rafi Akmal Widikta

fullstack

Rental Cosplay

Costume rental marketplace with three roles (admin, cosrent, and user) where owners list costumes with photo variants and renters book them through a moderated request flow.

Date
December 2024

Technologies

  • laravel
  • inertia
  • react
  • tailwind css
  • laravel breeze
  • sanctum
  • octane
  • dompdf

Problem

A costume rental marketplace has to hold three kinds of people at once, and each of them can do exactly one thing: an admin curates categories and moderates the accounts that rent costumes out, a cosrent manages their own listings, and a user browses, requests, and pays. The starter that Laravel Breeze hands you has a single dashboard behind a single login and no notion of a role: the boundary between those three is something the application has to impose rather than inherit. Until it does, every page reachable by URL is reachable by anyone who is signed in.

Most of the rest was not features but repairs, and several of them were only visible from a distance. The gallery migration was dated before the table it references, so a fresh migrate would fail on a foreign key pointing at something not yet created. Uploaded photos and the site logo were never committed, because .gitignore files inside storage/ excluded them; the images worked on the machine that uploaded them and nowhere else. The price field was a type="number" input whose value arrives as a string either way, and the phone fields accepted whatever was typed into them. Currency was printed with a bare toLocaleString(), which is neither a format nor a currency. A search that matched nothing returned an empty grid and no explanation. Flash messages were plain text with nothing for a screen reader to announce. And the admin and cosrent areas were single files large enough that finding anything in them took longer than changing it.

Solution

Roles are data here rather than a guess. A role model, a foreign key on the user with a belongsTo relation, and seeders that establish the three accounts; then a dashboard controller with one entry point per role, each checking the authenticated user’s role name and aborting with a 403 when it does not match. Signing in lands you on the dashboard that belongs to you, and every route is filed under an admin/, cosrent/, or user/ prefix: the separation lives in the route table instead of in whether a controller remembered to check.

The repairs were taken one root at a time. Migration order was fixed by renaming the gallery migration so it runs last: blunt, but honest. Both foreign keys it points at exist by the time it executes, whatever the file names claim. The ignore files inside storage/ were deleted and the logo committed, so the repository finally carries the images its pages depend on; two earlier attempts had patched the symptom, this one removed the cause. Closing URLs were made null safe, so a missing file yields nothing rather than an error. Price moved to a text input with an input filter that keeps digits only (in JavaScript the value is a string regardless, so it may as well be a string containing only what it should), and the phone fields took the same filter. Currency is now a single Intl.NumberFormat configured for id-ID and IDR, so every rupiah figure is formatted by one rule instead of by whichever component remembered.

Search gained an empty state, so a keyword that matches nothing returns a sentence rather than a blank grid. Flash messages became alerts with an icon and role="alert". And the pages were reorganised into an App.jsx per module under Admin/, Cosrent/, and User/: the change that made the rest of the cleanup safe to do, since a file holding one module can be rewritten without dragging the others along. The work also had to be integrated rather than only written: several branches ran in parallel, and the final state only existed once they met in one place.

Contact

Interested in working together?

Tell me what you are building and what you need. A short note is enough.

Back to top