LandingPage Module Documentation

1. Introduction

The LandingPage module provides a comprehensive system for creating, managing, and customizing landing pages within your Laravel application. It features a dynamic section builder, theme support, menu management, and integration with Google Analytics. Its goal is to offer a flexible way to present content and can be extended to display data from other parts of your application.

2. Installation & Setup

Installation: It's assumed this module is installed via your application's main module management system (e.g., by uploading a ZIP in the admin panel or via Composer if it were a standalone package).

Migrations: After installation, run database migrations:

php artisan module:migrate LandingPage

Configuration:

3. Core Features

3.1. Page Management (LpPageController)

  • CRUD Operations: Create, read, update, and delete landing pages.
  • Content Fields: Each page has a title, slug (auto-generated or custom), main content (WYSIWYG editor for fallback/default content), and optional default hero section fields (title, subtitle, CTA).
  • SEO Settings: Meta title, meta description, and meta keywords per page.
  • Publishing: Pages can be in "Published" or "Draft" status.
  • Sort Order: Control the order of pages.
  • Theme Templates: Assign specific Blade templates from the active theme to individual pages for varied layouts.

3.2. Dynamic Section Builder (LpPageController, JavaScript)

Pages are constructed using dynamic sections. Admins can add, remove, and reorder sections on the page edit screen.

Available Section Types:

  • Hero Section: Full-width banner with titles, subtitles, CTAs, and background images. Supports multiple slides.
  • Services Overview: Display services with titles, descriptions, icons, or images.
  • Team Members: Introduce team members with names, titles, images, and social links.
  • Testimonials: Display customer quotes, authors, and author images.
  • Contact CTA: A call-to-action section.
  • Featured Companies: Highlights companies marked as featured from the BusinessDirectory module. (Uses themes/job_board/sections/featured_companies.blade.php)
  • Featured Products: Showcases products marked as featured from the BusinessDirectory module. (Uses themes/job_board/sections/featured_products.blade.php)
  • Featured Jobs: Displays job openings marked as featured from the BusinessDirectory module. (Uses themes/job_board/sections/featured_jobs.blade.php)
  • Featured Tenders: Lists tenders marked as featured from the BusinessDirectory module. (Uses themes/job_board/sections/featured_tenders.blade.php)
  • Legacy Integrations (can be adapted):
    • Company Projects (Business Directory)
  • Our Portfolio (DigitalVocano): Showcases your own company's projects from the DigitalVocano module.
  • Custom HTML: Allows embedding raw HTML content via a WYSIWYG editor.

Content Configuration:

Each section type has its own set of fields. Image uploads are supported for Hero slides, Services items, Team member photos, and Testimonial author images. The LpPageController::update() method handles storing these images in storage/app/public/landingpage/....

Frontend Rendering & Data Fetching (LandingPageSection Component & Section Partials):

The <x-landingpage::landing-page-section :section="$section" :activeTheme="$activeTheme" :businessData="$businessData" /> Blade component dynamically includes the appropriate Blade partial from the active theme's sections directory (e.g., themes/job_board/sections/hero.blade.php) based on the section's type. The Modules\LandingPage\View\Components\LandingPageSection.php class passes the section's content ($section->content) as $content to the partial. For sections requiring dynamic data (like "Featured Companies"), this data is typically fetched by FrontendPageController::getBusinessDataForPage() and passed to the main page view, then made available to the section partial. See "Data Flow for Featured Content Sections" for details.

3.4. Theme System (ThemeSettingController)

Themes are located in Modules/LandingPage/Resources/views/themes/. Each theme has its own subdirectory.

Theme Structure:

  • layouts/app.blade.php: Main layout file for most pages.
  • layouts/master.blade.php: (Optional) Alternative main layout, often used for specific page types like single product views if a different overall structure is needed.
  • partials/header.blade.php, partials/footer.blade.php: Reusable components.
  • sections/: Blade partials for each dynamic section type.
  • templates/: Optional directory for page-specific templates.
  • page.blade.php: Default view for rendering LpPage content and its sections.
  • home.blade.php: (Optional) A dedicated view for the homepage if it requires a structure different from page.blade.php.
  • page_not_found.blade.php: View for 404 errors.
  • Assets (CSS, JS, Images): Typically managed via Vite and linked in layouts/app.blade.php, but can also include theme-specific static assets.

The active theme is selected in the admin panel ("Landing Page" → "Theme Settings"). New themes, compatible with the DigitalVocano boilerplate, can be installed by uploading a ZIP package. The PublicPageController and LandingPageSection component use this setting for rendering.

Theme: job_board

The job_board theme is a specialized theme provided with this module. Its key files include:

  • Layouts: layouts/app.blade.php (main application layout with dark mode, SEO, Vite integration), layouts/master.blade.php (used by some single item templates).
  • Partials: partials/topbar.blade.php, partials/header.blade.php, partials/footer.blade.php.
  • Page Renderers: page.blade.php (default for LpPages), page_not_found.blade.php.
  • Section Partials (sections/):
    • hero.blade.php, services.blade.php, etc. (standard sections)
    • featured_companies.blade.php, featured_products.blade.php, featured_jobs.blade.php, featured_tenders.blade.php: These expect $content (from section settings) and a corresponding data variable (e.g., $featured_companies_data) passed from the controller via $businessData.
  • Templates (templates/): For single item views like company_single.blade.php, job_single.blade.php, product_single.blade.php, tender_single.blade.php, news_single.blade.php. Also includes listing pages for company-specific items: company_products_listing.blade.php, company_jobs_listing.blade.php, company_tenders_listing.blade.php.

3.5. Google Analytics Integration (ThemeSettingController, GoogleAnalyticsService)

Configuration:

In "Theme Settings", admins can:

  • Enable/disable GA tracking.
  • Enter GA4 Measurement ID (e.g., G-XXXXXXXXXX).
  • Enter GA4 Property ID (numeric, for fetching stats).
  • Provide the server path to the Google Service Account credentials JSON file.
  • A "How-To" guide is included on the settings page.

Frontend Tracking:

If enabled, the GA4 gtag.js snippet is injected into the <head> of frontend pages.

Statistics Display:

  • LandingPage Dashboard: Shows basic stats (Users, Page Views, Sessions) and Top Pages.
  • Main Admin Dashboard: Conditionally displays the same GA summary if the module is active and GA is configured.

Data Fetching:

The Modules\LandingPage\Services\GoogleAnalyticsService.php class uses the google/analytics-data package to communicate with the Google Analytics Data API V1. It caches API responses.

4. How the LandingPage Module Interacts with Other Data/Modules

The LandingPage module is primarily a content presentation system. It can be configured to display data from other modules.

4.1. Integrating External Module Data into Dynamic Sections

  1. Define a New Section Type: Add a unique key and display name to $sectionTypes in LpPageController@edit.
  2. Admin Configuration Fields: In edit.blade.php (JavaScript renderContentFields function), add a case for your new section type and generate HTML for its admin fields (e.g., multi-selects, number inputs).
  3. Data Saving: The configuration from the admin fields (e.g., section title, number of items) is saved in the section's JSON content by LpPageController@update.
  4. Create Frontend Rendering Partial: In your active theme (e.g., themes/general/sections/your_section_type.blade.php), create a new Blade file. This partial receives $section and its $content.

    For simple sections, you might query directly in the partial. However, for more complex data or to keep partials cleaner, it's recommended to fetch data in FrontendPageController::getBusinessDataForPage() and pass it via $businessData as described in the "Data Flow for Featured Content Sections" above.

    
    <!-- Example: themes/general/sections/portfolio-projects.blade.php -->
    @php
        $sectionTitle = $section->content['title'] ?? 'Our Recent Work';
        $numberOfProjects = (int)($section->content['number_of_items'] ?? 3);
    
        $portfolioProjects = \Modules\DigitalVocano\Entities\Project::query()
            // ->where('is_published', true) // Example criteria
            // ->orderBy('sort_order', 'asc')
            // ->take($numberOfProjects)
            // ->get();
        // Recommended: $portfolioProjects = $portfolio_projects_data ?? collect(); (assuming data passed via $businessData)
    @endphp
    
    @if($portfolioProjects->isNotEmpty())
        <section class="portfolio-section">
            <h2>{{ $sectionTitle }}</h2>
            <div class="project-grid">
                @foreach($portfolioProjects as $dvProject)
                    <div class="project-card">
                        <h3>{{ $dvProject->name }}</h3>
                        <!-- ... more project details ... -->
                    </div>
                @endforeach
            </div>
        </section>
    @endif
                        
  5. Automatic Rendering: The <x-landingpage::landing-page-section> component automatically includes your new section partial based on its type.

Key Principle: The LandingPage module stores references or configurations for displaying data from other modules. The actual data fetching from those other modules typically happens in FrontendPageController::getBusinessDataForPage() and is then passed to the theme's specific section partials for rendering.

4.2. Creating New Dedicated Listing Pages (e.g., /jobs, /portfolio)

To create entirely new pages that list all items of a certain type from another module (e.g., a page at /jobs to show all job listings), follow these steps:

  1. Define a Route: In Modules/LandingPage/routes/web.php, add a new route. Make sure it's placed before the generic /{slug?} catch-all route.
    
    // Example for a new "/portfolio" page
    Route::get('/portfolio', [PublicPageController::class, 'showPortfolioProjects'])->name('landingpage.portfolio.index');
                        
  2. Create a Controller Method: In Modules/LandingPage/Http/Controllers/PublicPageController.php (for general listings like all jobs, all products) or Modules/LandingPage/Http/Controllers/FrontendController.php (for company-specific listings like all products of a specific company), add a new public method. This method will:
    • Fetch the required data from the other module's entities (e.g., \Modules\DigitalVocano\Entities\Project::where('is_published', true)->paginate(10);).
    • Pass any necessary data (like page title, meta description, and the fetched items) to a new view.
    
    // Example method in PublicPageController.php for a general portfolio page
    public function showPortfolioProjects(Request $request)
    {
        $activeTheme = setting('landingpage_active_theme', 'general');
        $portfolioProjects = \Modules\DigitalVocano\Entities\Project::where('is_published', true)->orderBy('sort_order', 'asc')->paginate(9);
        $pageTitle = 'Our Portfolio';
        return view("landingpage::themes.{$activeTheme}.portfolio", compact('portfolioProjects', 'pageTitle', 'activeTheme'));
    }
    
    // Example method in FrontendController.php for company-specific products
    public function showCompanyProducts($company_slug)
    {
        $company = \Modules\BusinessDirectory\Entities\Company::where('slug', $company_slug)->firstOrFail();
        $activeTheme = setting('landingpage_active_theme', 'general');
        $products = $company->products()->where('status', 'published')->paginate(12);
        $page = (object) ['title' => 'Products from ' . $company->name, /* ... other meta ... */];
        return view("landingpage::themes.{$activeTheme}.templates.company_products_listing", compact('company', 'products', 'page', 'activeTheme'));
    }
                        
  3. Create a Blade View: In your active theme's directory (e.g., Modules/LandingPage/Resources/views/themes/job_board/ or themes/job_board/templates/), create a new Blade file for your page (e.g., portfolio.blade.php or templates/company_products_listing.blade.php). This view will extend your theme's layout and display the fetched items, including pagination links if used.
  4. (Optional) Add to Menu: Use the Menu Management system in the admin panel to add a link to your new page (e.g., using the route name landingpage.portfolio.index or the direct URL /portfolio).

4.3. Summary of Implemented Integrations:

Following the general approach, the module now integrates with:

Dedicated listing pages for general content (e.g., all jobs via /jobs, all products via /products) are handled by PublicPageController.php and corresponding theme views (e.g., themes/job_board/jobs.blade.php - if created).

5. Customization & Development

5.1. Creating New Themes

  1. Develop your theme, ideally based on or compatible with the DigitalVocano boilerplate to ensure structural consistency.
  2. Modify layouts/app.blade.php, partials/header.blade.php, partials/footer.blade.php.
  3. Customize CSS (e.g., in themes/my-new-theme/css/theme.css or integrate with Vite).
  4. Override or create new section partials in themes/my-new-theme/sections/.
  5. Package your theme files (including a theme.json manifest if required by the installer) into a ZIP file.
  6. Install the theme via the "Theme Settings" page in the LandingPage module's admin section by uploading the ZIP file.

5.2. Creating New Section Types

  1. Define Type Key: Choose a unique string (e.g., image-gallery).
  2. Admin Fields: Add a case in the renderContentFields JavaScript function (admin/pages/edit.blade.php) to define HTML for input fields.
  3. Data Handling (Controller): If special data processing is needed (e.g., image uploads), update LpPageController@update.
  4. Frontend Partial: Create a Blade file (e.g., image-gallery.blade.php) in themes/[your-active-theme]/sections/. This file receives $section and $content.

6. Important Files & Controllers

Controllers (Admin):

Controller (Frontend):

Services:

Entities (Models):

Core Views (Admin):

Core Views (Frontend - Theme Specific):

Example for job_board theme:

Blade Component:

7. Deep Dive: The Hero Section

The "Hero Section" is a prominent feature, designed for strong visual impact at the top of landing pages. It supports multiple slides, each configurable with:

On the frontend, it's typically rendered using a JavaScript slider library (like Swiper.js) within the theme's sections/hero.blade.php partial.

The LpPage model also has "Default Hero Section" fields (hero_title, hero_subtitle, etc.) which can serve as a fallback if no dynamic "Hero Section" is added to a page, or if the theme is designed to use them.

Note: The appearance and behavior of the Hero Section depend heavily on the HTML and CSS in your active theme's hero.blade.php partial.

8. Google Analytics Setup Summary

To enable Google Analytics statistics display, you need to configure it correctly:

  1. Google Cloud Project & API: Create/select a project in Google Cloud Console and enable the "Google Analytics Data API".
  2. Service Account: Create a Service Account, grant it the "Analytics Reader" role, and download its JSON key.
  3. Add Service Account to GA4: In your GA4 Property settings, add the service account's email as a user with "Viewer" permissions.
  4. Store Credentials & Configure Path: Securely store the downloaded JSON key file on your server (e.g., storage/app/google-analytics/service-account-credentials.json). Important: Add this path to your .gitignore. Enter the full, absolute path to this JSON file in the "Service Account Credentials JSON Path" field in the LandingPage Theme Settings.
  5. Enter IDs: Fill in your "Google Analytics Measurement ID" (e.g., G-XXXXXXXXXX) and "Google Analytics Property ID" (numeric) in the Theme Settings.

Detailed steps are available on the "Theme Settings" page within the admin panel of the LandingPage module.

9. Composer Dependencies

For Google Analytics data fetching, this module relies on the google/analytics-data package. Ensure it's installed in your main project:

composer require google/analytics-data

The module's own composer.json (Modules/LandingPage/composer.json) primarily defines its PSR-4 autoloading and doesn't list external dependencies, as those are expected to be managed by the main application's composer.json.

10. Routing

Frontend Routes (Modules/LandingPage/routes/web.php):


// General listing pages (handled by PublicPageController)
Route::get('/jobs', [PublicPageController::class, 'showJobs'])->name('landingpage.jobs.index');
Route::get('/products', [PublicPageController::class, 'showProducts'])->name('landingpage.products.index');
Route::get('/tenders', [PublicPageController::class, 'showTenders'])->name('landingpage.tenders.index');
Route::get('/companies', [PublicPageController::class, 'showCompanies'])->name('landingpage.companies.index');
Route::get('/portfolio', [PublicPageController::class, 'showPortfolioProjects'])->name('landingpage.portfolio.index');
Route::get('/news', [PublicPageController::class, 'showNews'])->name('landingpage.news.index');

// Single item detail pages (handled by FrontendController)
Route::get('/jobs/{slug}', [FrontendController::class, 'showJob'])->name('theme.job.show');
Route::get('/companies/{slug}', [FrontendController::class, 'showCompany'])->name('theme.company.show');
Route::get('/products/{slug}', [FrontendController::class, 'showProduct'])->name('theme.product.show');
Route::get('/tenders/{slug}', [FrontendController::class, 'showTender'])->name('theme.tender.show');
Route::get('/news/{slug}', [FrontendController::class, 'showNewsArticle'])->name('theme.news.show');

// "View All" pages for specific company's items (handled by FrontendController)
Route::get('/company/{company_slug}/products', [FrontendController::class, 'showCompanyProducts'])->name('theme.company.products');
Route::get('/company/{company_slug}/jobs', [FrontendController::class, 'showCompanyJobs'])->name('theme.company.jobs');
Route::get('/company/{company_slug}/tenders', [FrontendController::class, 'showCompanyTenders'])->name('theme.company.tenders');

// Catch-all for dynamic pages
Route::get('/{slug?}', [PublicPageController::class, 'showPage'])
    ->where('slug', '(.*)')
    ->name('landingpage.page.show');
            

This is a catch-all route and should ideally be one of the last routes registered in your application to avoid conflicts.

The FrontendPageController is responsible for handling the catch-all /{slug?} route to display dynamic LpPage entities. The PublicPageController handles predefined listing pages like /jobs. The FrontendController handles single item detail views and company-specific listings.

Admin Routes (Modules/LandingPage/routes/admin.php):

These routes are typically prefixed with /admin/landingpage/ (or similar, depending on your main application's admin route group).


Route::resource('pages', LpPageController::class);
Route::get('theme-settings', [ThemeSettingController::class, 'edit'])->name('theme.settings.edit');
Route::put('theme-settings', [ThemeSettingController::class, 'update'])->name('theme.settings.update');
Route::resource('menus', MenuController::class);
Route::post('menus/{menu}/items', [MenuItemController::class, 'store'])->name('menus.items.store');
Route::put('menus/{menu}/items/{item}', [MenuItemController::class, 'update'])->name('menus.items.update');
Route::delete('menus/{menu}/items/{item}', [MenuItemController::class, 'destroy'])->name('menus.items.destroy');
Route::post('menus/{menu}/items/reorder', [MenuItemController::class, 'reorder'])->name('menus.items.reorder');
            
_N/A_