A modern, high-performance Laravel 12 application for booking Dubai and UAE activities and experiences. Built with server-side rendering, session-based cart system, bilingual support (French/English), email notifications, and seamless WhatsApp integration for instant booking confirmations.
- π Multi-Page Application: Complete Laravel routing with dedicated pages for home, activities, categories, blog, about, contact, and cart
- π Bilingual Support: French and English language switching with persistent session state
- π Advanced Filtering: Server-side category, emirate, and sorting filters with instant results
- π Session-Based Cart: Secure server-side cart with quantity management per activity
- π§ Email Notifications: Automated confirmation emails for customers and admin notifications for new orders
- π± WhatsApp Integration: Instant booking confirmations via WhatsApp with pre-filled order details
- π¬ Contact Form: Multi-recipient contact form with CC functionality
- πΌοΈ Rich Media Gallery: Multi-image support for each activity with automatic fallback handling
- β Rating System: Decimal-precision rating display (0.0-5.0) with visual star ratings
- π¨ Modern UI/UX: Smooth animations with AOS library and responsive Bootstrap 5 design
- π High Performance: Server-side rendering with optimized database queries and eager loading
- π SEO Optimized: Auto-generated XML sitemap and SEO-friendly URLs with slugs
- π Emirate Filtering: Filter activities by UAE emirates (Dubai, Abu Dhabi, Sharjah, etc.)
- Backend: Laravel 12 (PHP 8.2+)
- Database: MySQL with optimized indexes
- Frontend: jQuery, Blade templating, Bootstrap Icons
- Styling: Bootstrap 5.3.3 (loaded via CDN)
- Build Tool: Vite
- Image Processing: Intervention Image Laravel
- Animations: AOS (Animate On Scroll)
- Email: Laravel Mail with SMTP/Log drivers
- Session Management: Laravel session for cart and locale
- DataTables: Yajra DataTables (installed but not actively used in views)
- PHP >= 8.2
- Composer
- Node.js >= 18
- MySQL >= 8.0
- Git
- SMTP Server (for production emails)
git clone https://github.com/yourusername/dubai-activities_antigravity.git
cd dubai-activities_antigravitycomposer setupThis command will:
- Install PHP dependencies
- Create
.envfile from example - Generate application key
- Run database migrations
- Install Node.js dependencies
- Build frontend assets
Edit .env file with your database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=dubai_activities
DB_USERNAME=your_username
DB_PASSWORD=your_password
APP_TIMEZONE=Africa/Kinshasa
APP_LOCALE=fr
APP_FAKER_LOCALE=fr_FR
# Email Configuration (Development)
MAIL_MAILER=log
MAIL_FROM_ADDRESS="admin@kendeatravel.com"
MAIL_FROM_NAME="${APP_NAME}"For production SMTP, uncomment and configure:
MAIL_MAILER=smtp
MAIL_HOST=your-smtp-host.com
MAIL_PORT=465
MAIL_USERNAME=admin@kendeatravel.com
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS="admin@kendeatravel.com"
MAIL_FROM_NAME="${APP_NAME}"php artisan db:seedThis populates the database with realistic Dubai activities across multiple categories (Desert Safari, Water Parks, City Tours, etc.).
composer devThis concurrently runs:
- Laravel development server (http://localhost:8000)
- Queue worker for background jobs
- Real-time log monitoring (Laravel Pail)
- Vite dev server with hot-reload
π Visit http://localhost:8000 to see your application!
dubai-activities_antigravity/
βββ app/
β βββ Http/Controllers/
β β βββ HomeController.php # Homepage with hero slider
β β βββ ActivityController.php # Activities listing & detail pages
β β βββ CategoryController.php # Category-specific pages
β β βββ BlogController.php # Blog section
β β βββ AboutController.php # About page
β β βββ ContactController.php # Contact form handler
β β βββ CommandeController.php # Order processing & emails
β βββ Mail/
β β βββ ContactFormMail.php # Contact form email
β β βββ OrderConfirmation.php # Customer confirmation
β β βββ OrderNotificationAdmin.php # Admin notification
β βββ Models/
β βββ Activity.php # Activity model with slug generation
β βββ Category.php # Activity categories
β βββ Client.php # Customer information
β βββ Commande.php # Orders with activity quantities
βββ database/
β βββ migrations/ # Database schema
β βββ seeders/
β βββ CategorySeeder.php # UAE categories seeder
β βββ ActivitySeeder.php # Sample activities seeder
βββ public/
β βββ js/
β β βββ app.js # Main application logic
β β βββ activities.js # Activities filtering & AJAX
β β βββ cart.js # Cart management
β β βββ session-cart.js # Session cart utilities
β βββ images/ # Activity images storage
βββ resources/
β βββ views/
β βββ home/index.blade.php # Homepage with hero slider
β βββ activities/
β β βββ index.blade.php # Activities listing page (SSR)
β β βββ show.blade.php # Activity detail page
β βββ categories/show.blade.php # Category detail page
β βββ blog/index.blade.php # Blog page
β βββ about/index.blade.php # About page
β βββ contact/index.blade.php # Contact form
β βββ cart/index.blade.php # Shopping cart page
β βββ emails/ # Email templates
β β βββ contact.blade.php
β β βββ order-confirmation.blade.php
β β βββ order-notification-admin.blade.php
β βββ layouts/app.blade.php # Base layout
β βββ partials/ # Reusable components
β βββ header.blade.php
β βββ footer.blade.php
β βββ hero-slider.blade.php
βββ routes/
βββ web.php # All routes (web + API)
The application uses Laravel's Mailable classes with recipient configuration built-in. All email addresses are managed within the Mailable classes themselves.
1. ContactFormMail - Contact form submissions
- To: contact@kendeatravel.com
- CC: admin@kendeatravel.com, david@kendeatravel.com
- Reply-To: Customer's email (dynamic)
2. OrderConfirmation - Customer booking confirmation
- To: Customer's email (dynamic)
- Reply-To: admin@kendeatravel.com
3. OrderNotificationAdmin - New order notification
Development Mode (current):
- Emails logged to
storage/logs/laravel.log - Set
MAIL_MAILER=login.env
Production Mode:
- Uncomment SMTP configuration in
.env - Run
php artisan config:clearafter changes
See CLAUDE.md for detailed email configuration documentation.
categories
id: Primary keynom: Category name (French)nom_en: Category name (English)- Example: Desert Safari, Water Parks, City Tours, Adventure Sports
activities
id: Primary keynom: Activity name (French)nom_en: Activity name (English)slug: Auto-generated SEO-friendly URL (unique, indexed)description: Activity description (French)description_en: Activity description (English)images: JSON array of image paths (up to 5)prix: Decimal price (10,2)notes: Decimal rating (2,1) from 0.0 to 5.0location: Location (French)location_en: Location (English)emirate: UAE emirate (Dubai, Abu Dhabi, Sharjah, etc.)categorie_id: Foreign key to categories (indexed)
clients
id: Primary keyprenom: First namenom: Last nameemail: Email address (unique)telephone: Phone number
commandes
id: Order IDclient_id: Foreign key to clientsactivities: JSON array of activity IDsactivities_quantities: JSON object with activity quantities{"1": 2, "3": 4}datetime: Order timestamp (Carbon instance)montant_total: Total order amountstatut: Order statusmessage: Optional customer message
- Activity β Category (belongsTo via
categorie_id) - Commande β Client (belongsTo via
client_id) - Commande β Activities (manual via
getActivitiesDetails()method)
composer dev # Run all services concurrently
php artisan serve # Laravel server only (localhost:8000)
npm run dev # Vite dev server onlyphp artisan migrate # Run migrations
php artisan migrate:fresh --seed # Reset database with sample data
php artisan db:seed # Seed data onlycomposer test # Run PHPUnit test suite
php artisan test # Alternative test command
vendor/bin/pint # Format code with Laravel Pintnpm run build # Production build
npm run dev # Development build with hot-reloadphp artisan route:list # View all routes
php artisan tinker # Interactive REPL
php artisan config:clear # Clear configuration cache
php artisan cache:clear # Clear application cache
php artisan pail # Real-time log monitoringActivities and categories use traditional Laravel server-side rendering with Blade templates:
// ActivityController::index()
public function index(Request $request)
{
$query = Activity::with('category');
// Apply filters
if ($request->filled('category')) {
$query->where('categorie_id', $request->category);
}
$activities = $query->get();
return view('activities.index', compact('categories', 'activities', 'filters'));
}{{-- activities/index.blade.php --}}
@foreach($activities as $activity)
<div class="card activity-card">
<img src="{{ asset($activity->first_image) }}" alt="{{ $activity->nom }}">
<h5>{{ App::getLocale() == 'en' ? $activity->nom_en : $activity->nom }}</h5>
</div>
@endforeachCart data stored securely in Laravel sessions with quantity management:
// Add to cart
$cart = session('cart', []);
$cart[$activityId] = ['quantity' => $quantity];
session(['cart' => $cart]);
// Get cart items
$cartIds = array_keys(session('cart', []));
$activities = Activity::whereIn('id', $cartIds)->get();Language switching with session persistence:
// Language switch route
Route::get('/lang/{locale}', function ($locale) {
if (in_array($locale, ['fr', 'en'])) {
session(['locale' => $locale]);
}
return redirect()->back();
})->name('lang.switch');{{-- Display bilingual content --}}
<h3>{{ App::getLocale() == 'en' ? $activity->nom_en : $activity->nom }}</h3>
<p>{{ App::getLocale() == 'en' ? $activity->description_en : $activity->description }}</p>Activities automatically generate SEO-friendly slugs on creation:
// Activity Model
protected static function boot()
{
parent::boot();
static::creating(function ($activity) {
$activity->slug = Str::slug($activity->nom);
});
}All emails use Mail::send() with recipients defined in Mailable classes:
// Correct pattern - recipients in Mailable class
Mail::send(new ContactFormMail($data));
Mail::send(new OrderConfirmation($commande, $client, $activities));Orders redirect to WhatsApp with pre-filled booking details:
// After order confirmation
const whatsappNumber = '+971582032582'; // KENDEA number
const message = `Bonjour KENDEA, je confirme ma rΓ©servation:\n${orderDetails}`;
const whatsappUrl = `https://wa.me/${whatsappNumber}?text=${encodeURIComponent(message)}`;
window.location.href = whatsappUrl;Multiple images with automatic fallback:
// Activity Model
public function getFirstImageAttribute()
{
$images = $this->images ?? [];
return !empty($images) ? $images[0] : 'images/default.jpg';
}{{-- Blade template with fallback --}}
<img src="{{ asset($activity->first_image) }}"
alt="{{ $activity->nom }}"
onerror="this.src='{{ asset('images/default.jpg') }}'">| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Homepage with hero slider |
| GET | /activities |
Activities listing page (SSR) |
| GET | /activity/{slug} |
Single activity detail page |
| GET | /category/{id} |
Category-specific activities page |
| GET | /blog |
Blog section |
| GET | /about |
About page |
| GET | /contact |
Contact form page |
| POST | /contact |
Submit contact form |
| GET | /cart |
Shopping cart page |
| GET | /lang/{locale} |
Switch language (fr/en) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/activities |
Get activities with filters (JSON) |
| GET | /api/activities/{id} |
Single activity details (JSON) |
| POST | /api/activities/bulk |
Get multiple activities by IDs |
| POST | /api/commandes |
Create order, send emails, return WhatsApp URL |
| POST | /api/cart/add |
Add activity to session cart |
| POST | /api/cart/remove |
Remove activity from cart |
| POST | /api/cart/update-quantity |
Update activity quantity |
| GET | /api/cart/count |
Get cart items count |
| POST | /api/cart/clear |
Clear entire cart |
| Method | Endpoint | Description |
|---|---|---|
| GET | /sitemap.xml |
XML sitemap for search engines |
Update in your JavaScript files (e.g., /public/js/cart.js):
const WHATSAPP_NUMBER = '971582032582'; // KENDEA numberEmail recipients are configured in Mailable classes:
app/Mail/ContactFormMail.phpapp/Mail/OrderConfirmation.phpapp/Mail/OrderNotificationAdmin.php
Configured in .env:
APP_TIMEZONE=Africa/Kinshasa
APP_LOCALE=fr
APP_FAKER_LOCALE=fr_FRUsers can switch between French and English using the language switcher in the header. Their preference is stored in the session.
Images stored in public/images/ directory. Update storage path in Activity model if needed.
Run the test suite:
composer testTests located in tests/ directory using PHPUnit.
- CSRF protection enabled for all POST requests
- SQL injection prevention via Eloquent ORM
- XSS protection with Blade templating
- Environment variables for sensitive data
- Email validation and sanitization
- Database Indexes: Optimized on
slug,prix,notes,categorie_id, andemirate - Server-Side Rendering: Traditional SSR with Blade templates (no heavy client-side framework)
- Eager Loading: Prevent N+1 queries with
Activity::with('category') - Session-Based Cart: Secure server-side cart storage instead of localStorage
- Asset Pipeline: Vite for optimized builds with code splitting and hot-reload
- Image Optimization: Use Intervention Image for processing and fallback images
- Query Optimization: Filtered queries at database level, not in-memory filtering
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Use Laravel Pint for consistent formatting:
vendor/bin/pintFor detailed architecture and development guidelines, see:
- CLAUDE.md - Complete technical documentation for developers (AI assistant guidance)
- HERO_SLIDER_README.md - Hero slider component documentation (if exists)
This project is open-sourced software licensed under the MIT license.
- Built with Laravel 12
- Styled with Bootstrap 5
- Image processing by Intervention Image Laravel
- Animations by AOS
- Icons by Bootstrap Icons
For support, contact:
- Email: contact@kendeatravel.com
- Admin: admin@kendeatravel.com
- WhatsApp: +971582032582
Made with β€οΈ for Dubai Tourism by KENDEA Travel