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.