A full-featured tourism management platform built with Laravel 11, connecting travelers with local guides across Oman. The system includes a mobile-facing REST API and a fully operational admin dashboard.
- OTP-based registration & login via WhatsApp
- Browse events by city & category
- Select guides and book across multiple dates
- pay with cash and visa
- Leave reviews and manage favorites
- Apply to become a guide (approval flow)
- Manage bookings and availability per date
- Wallet balance & withdrawal requests
- Full management: Users, Guides, Events, Cities, Categories, Banners
- Booking & withdrawal oversight
- Firebase push notifications (custom & bulk)
- Role-based permissions (Spatie)
- Multi-language support (Arabic / English)
- Real-time statistics & monthly charts
Built end-to-end as the sole backend developer β architecture, API design, admin dashboard, payment integration, and deployment.
| Layer | Technology |
|---|---|
| Backend | Laravel 11 (PHP 8.2) |
| Auth | Laravel Sanctum + OTP (WhatsApp) |
| Admin UI | Blade + Yajra DataTables |
| Permissions | Spatie Laravel Permission (admin staff) |
| Media | Spatie Laravel MediaLibrary |
| i18n | Spatie Translatable + Laravel Localization |
| Payments | AmwalPay Integration |
| Push | Firebase Cloud Messaging (FCM) |
| Export | Maatwebsite Excel + MisterSpelik PDF |
| Monitoring | Laravel Telescope |
βββ app/
β βββ Http/Controllers/ # Thin controllers (Admin + API V1)
β βββ Core/
β β βββ Services/ # Business logic layer
β β βββ Datatables/ # Yajra DataTable definitions
β β βββ Enums/ # Type-safe constants
β β βββ Helpers/ # Firebase
β β βββ Traits/ # InteractWithResponse, HasDatatableTrait
βββ routes/
β βββ admin.php # Admin dashboard routes
β βββ apis/ # Versioned API routes (public, user, guide, auth, notification)
βββ resources/views/ # Blade templates
Challenge: The platform has two user types β travelers and guides β that share most of their data (name, phone, wallet, bookings). Creating a separate table and guard for each would mean duplicated migrations, duplicated auth logic, and unnecessary complexity.
Solution: Both types live in a single users table with a dedicated **role** column backed by a PHP enum (traveler / guide). The same User model and Sanctum guard serve both apps; controllers and services branch behavior from that enum.
Challenge: A booking is not a single βsubmit formβ action. The traveler must discover who is available, understand pricing and rules before paying, and only then lock a reservation β without creating half-valid records or race conditions between steps.
Solution: The flow has three simple steps: (1) list guides who are free for the event and dates; (2) preview β check the request again and return price breakdown for the screen before payment; (3) save the booking only when the traveler confirms. The server checks availability again on preview and on save, so an outdated pick from an earlier screen cannot become a real booking by mistake.
Challenge: When a traveler books a guide, they may select multiple dates at once. The system must guarantee that the chosen guide is fully available across every selected date β not just some of them. A guide who is booked on even one of those dates must not appear as available.
Solution: The availability check works at the database query level by counting how many of the requested dates conflict with the guide's existing bookings. Only guides with zero conflicts across all selected dates are returned as available. This prevents partial-availability issues before they reach the application layer.
Performance & scalability: Composite database indexes on guide and date fields keep those lookups cheap as bookings and calendar rows growβso the feature stays responsive under heavier load instead of degrading into full table scans.
Private commercial project β source shared for portfolio purposes only. Not for redistribution or reuse.