Phase 1 Comprehensive Exercise—ShopMetrics Landing Page
Phase 1 Comprehensive Practice is a "Final Review"—it brings together everything you've learned in the first six lessons to deliver a real, fully functional landing page.
1. What You'll Learn
- Design a comprehensive routing blueprint
- Building Reusable Blade Layouts and Components
- Create a controller to handle page logic
- Integrating Tailwind CSS with Alpine.js
- Environment Setup and Database Connection
2. Bob's Phase 1 Acceptance Story
(1) Pain Point: Even after completing Lesson 7, I still can't build a complete page
Alice has finished the first six lessons, but while she understands each concept on its own, she gets confused when they're put together—how does routing connect to controllers? How does a controller pass data to a Blade? How do you choose between layout inheritance and components? Bob said, "It's the difference between learning to swim and actually getting in the water—you've watched seven theory lessons, but you haven't swum 25 meters yet."
(2) Solutions to the Comprehensive Exercises
This lesson ties together routes, views, controllers, Blade components, and environment configuration to build a complete, fully functional ShopMetrics landing page from scratch—this will be your first hands-on project.
# After this lesson, you will have:
# - A landing page with hero section
# - A pricing page with plan cards
# - A dashboard layout with sidebar
# - Reusable Blade components
# - Working database connection
(3) Revenue
After completing the comprehensive exercise, Alice independently built the ShopMetrics landing page, and all five pages passed acceptance testing. Bob said, "The difference between 'understanding it' and 'implementing it' is just one comprehensive exercise."
3. Routing Blueprint Design
(1) Page Layout
graph LR
A["/ (Home)"] --> B["/pricing (Pricing)"]
A --> C["/about (About)"]
A --> D["/contact (Contact)"]
A --> E["/dashboard (Dashboard)"]
E --> F["/dashboard/shops"]
E --> G["/dashboard/analytics"]
(2) Route Definitions
// routes/web.php
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/pricing', [PricingController::class, 'index'])->name('pricing');
Route::get('/about', [AboutController::class, 'index'])->name('about');
Route::get('/contact', [ContactController::class, 'create'])->name('contact.create');
Route::post('/contact', [ContactController::class, 'store'])->name('contact.store');
Route::middleware('auth')->prefix('dashboard')->name('dashboard.')->group(function () {
Route::get('/', [DashboardController::class, 'index'])->name('index');
Route::resource('shops', Dashboard\ShopController::class);
});
| Page | Route | Controller | Middleware |
|---|---|---|---|
| Home | GET / | HomeController | None |
| Pricing | GET /pricing | PricingController | None |
| About | GET /about | AboutController | None |
| Contact | GET+POST /contact | ContactController | None |
| Dashboard | GET /dashboard | DashboardController | auth |
(1) ▶ Example: Create all controllers
php artisan make:controller HomeController
php artisan make:controller PricingController
php artisan make:controller AboutController
php artisan make:controller ContactController
php artisan make:controller Dashboard/DashboardController
php artisan make:controller Dashboard/ShopController --resource
Output:
# Command executed successfully
4. Layout and Component Design
(1) Layout Hierarchy
layouts/app.blade.php ← Main layout (navbar + footer)
├── layouts/dashboard.blade.php ← Dashboard layout (sidebar + content)
│ └── dashboard/analytics.blade.php
└── home/index.blade.php ← Landing page
(2) List of Components
| Component | Type | File | Purpose |
|---|---|---|---|
<x-navbar> |
Anonymous | components/navbar.blade.php | Site-wide Navigation Bar |
<x-footer> |
Anonymous | components/footer.blade.php | Site-wide Footer |
<x-hero> |
Anonymous | components/hero.blade.php | Home Page Hero Area |
<x-plan-card> |
Anonymous | components/plan-card.blade.php | Pricing Card |
<x-stat-card> |
Anonymous | components/stat-card.blade.php | Dashboard Statistics Card |
<x-sidebar> |
Anonymous | components/sidebar.blade.php | Dashboard Sidebar |
(1) ▶ Example: Creating a Blade Component
# Create component files manually
mkdir -p resources/views/components
touch resources/views/components/navbar.blade.php
touch resources/views/components/footer.blade.php
touch resources/views/components/hero.blade.php
touch resources/views/components/plan-card.blade.php
touch resources/views/components/stat-card.blade.php
touch resources/views/components/sidebar.blade.php
Output:
# Command executed successfully
5. Core Layout and Component Implementation
(1) Main Layout
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'ShopMetrics') — E-commerce Analytics</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-50 text-gray-900">
<x-navbar />
<main>
@yield('content')
</main>
<x-footer />
</body>
</html>
(2) Navigation Bar Component
<!-- resources/views/components/navbar.blade.php -->
<nav class="bg-white shadow-sm" x-data="{ open: false }">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<a href="{{ route('home') }}" class="flex items-center font-bold text-xl text-blue-600">
ShopMetrics
</a>
<div class="hidden md:flex items-center space-x-6">
<a href="{{ route('pricing') }}" class="text-gray-600 hover:text-gray-900">Pricing</a>
<a href="{{ route('about') }}" class="text-gray-600 hover:text-gray-900">About</a>
@auth
<a href="{{ route('dashboard.index') }}" class="btn-primary">Dashboard</a>
@else
<a href="{{ route('login') }}" class="text-gray-600 hover:text-gray-900">Login</a>
<a href="{{ route('register') }}" class="btn-primary">Sign Up</a>
@endauth
</div>
<button @click="open = !open" class="md:hidden">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path x-show="!open" d="M4 6h16M4 12h16M4 18h16"/>
<path x-show="open" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div x-show="open" class="md:hidden py-2 space-y-2">
<a href="{{ route('pricing') }}" class="block py-2 text-gray-600">Pricing</a>
@auth
<a href="{{ route('dashboard.index') }}" class="block py-2 btn-primary">Dashboard</a>
@else
<a href="{{ route('login') }}" class="block py-2 text-gray-600">Login</a>
<a href="{{ route('register') }}" class="block py-2 btn-primary">Sign Up</a>
@endauth
</div>
</div>
</nav>
(3) Hero Component
<!-- resources/views/components/hero.blade.php -->
@props(['title', 'subtitle', 'cta_text' => 'Get Started', 'cta_route' => 'register'])
<section class="bg-gradient-to-r from-blue-600 to-indigo-700 text-white py-20">
<div class="max-w-7xl mx-auto px-4 text-center">
<h1 class="text-4xl md:text-6xl font-bold mb-6">{{ $title }}</h1>
<p class="text-xl md:text-2xl mb-8 text-blue-100">{{ $subtitle }}</p>
<a href="{{ route($cta_route) }}" class="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition">
{{ $cta_text }}
</a>
</div>
</section>
(1) ▶ Example: Pricing Card Component
<!-- resources/views/components/plan-card.blade.php -->
@props(['plan', 'popular' => false])
<div class="bg-white rounded-xl shadow-lg p-8 {{ $popular ? 'ring-2 ring-blue-500 relative' : '' }}">
@if($popular)
<span class="absolute -top-3 left-1/2 -translate-x-1/2 bg-blue-500 text-white px-4 py-1 rounded-full text-sm font-semibold">
Most Popular
</span>
@endif
<h3 class="text-xl font-bold mb-2">{{ $plan['name'] }}</h3>
<div class="mb-6">
<span class="text-4xl font-bold">${{ $plan['price'] }}</span>
<span class="text-gray-500">/month</span>
</div>
<ul class="space-y-3 mb-8">
@foreach($plan['features'] as $feature)
<li class="flex items-center">
<svg class="w-5 h-5 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"/>
</svg>
{{ $feature }}
</li>
@endforeach
</ul>
<a href="{{ route('register') }}" class="{{ $popular ? 'btn-primary' : 'btn-secondary' }} w-full text-center block">
Choose {{ $plan['name'] }}
</a>
</div>
Output:
// Execution Successful
6. Controller Implementation
(1) ▶ Example: HomeController and PricingController
// app/Http/Controllers/HomeController.php
class HomeController extends Controller
{
public function index(): View
{
$features = [
['icon' => 'chart', 'title' => 'Real-time Analytics', 'desc' => 'Track sales, orders and revenue in real-time.'],
['icon' => 'users', 'title' => 'Multi-tenant', 'desc' => 'Separate data for each e-commerce store.'],
['icon' => 'bell', 'title' => 'Smart Alerts', 'desc' => 'Get notified when key metrics change.'],
];
return view('home.index', compact('features'));
}
}
// app/Http/Controllers/PricingController.php
class PricingController extends Controller
{
public function index(): View
{
$plans = [
['name' => 'Starter', 'price' => 29, 'features' => ['5 Shops', '1K Orders/month', 'Basic Analytics', 'Email Support']],
['name' => 'Pro', 'price' => 79, 'features' => ['25 Shops', '10K Orders/month', 'Advanced Analytics', 'Priority Support', 'API Access']],
['name' => 'Enterprise', 'price' => 199, 'features' => ['Unlimited Shops', 'Unlimited Orders', 'Custom Analytics', 'Dedicated Support', 'API + Webhooks', 'SSO']],
];
return view('pricing.index', compact('plans'));
}
}
Output:
// Execution Successful
(2) ▶ Example: DashboardController
// app/Http/Controllers/Dashboard/DashboardController.php
class DashboardController extends Controller
{
public function index(): View
{
$user = auth()->user();
$tenant = $user->tenant;
$stats = [
['label' => 'Total Revenue', 'value' => '$' . number_format($tenant->revenue ?? 0), 'trend' => 12.5],
['label' => 'Active Shops', 'value' => $tenant->shops()->count(), 'trend' => 3.2],
['label' => 'Orders Today', 'value' => $tenant->orders()->today()->count(), 'trend' => -2.1],
];
return view('dashboard.index', compact('tenant', 'stats'));
}
}
Output:
// Execution Successful
7. Environment Setup and Database
# .env — Configure MySQL for ShopMetrics
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=shopmetrics
DB_USERNAME=root
DB_PASSWORD=secret
# Generate app key and run migrations
php artisan key:generate
php artisan migrate
# Install Breeze for auth scaffolding
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install && npm run build
| Step | Command | Function |
|---|---|---|
| 1 | key:generate |
Generate APP_KEY |
| 2 | migrate |
Create a Data Table |
| 3 | breeze:install |
Generate Login/Registration Page |
| 4 | npm run build |
Compile front-end assets |
8. Comprehensive Example: ShopMetrics Landing Page Home
<!-- ============================================
Comprehensive: ShopMetrics Landing Page
Covers: layout, components, routes, data passing
============================================ -->
<!-- resources/views/home/index.blade.php -->
@extends('layouts.app')
@section('title', 'ShopMetrics — E-commerce Analytics Platform')
@section('content')
<!-- Hero Section -->
<x-hero
title="Track Your E-commerce Performance"
subtitle="Real-time analytics for multi-tenant e-commerce platforms. Monitor revenue, orders, and customer insights — all in one dashboard."
cta_text="Start Free Trial"
cta_route="register"
/>
<!-- Features Section -->
<section class="py-20 bg-white">
<div class="max-w-7xl mx-auto px-4">
<h2 class="text-3xl font-bold text-center mb-12">Why ShopMetrics?</h2>
<div class="grid md:grid-cols-3 gap-8">
@foreach($features as $feature)
<div class="text-center p-6">
<div class="w-12 h-12 bg-blue-100 rounded-lg mx-auto mb-4 flex items-center justify-center">
<span class="text-blue-600 text-2xl">
@if($feature['icon'] === 'chart')📈@elseif($feature['icon'] === 'users')👥@else🔔@endif
</span>
</div>
<h3 class="text-xl font-semibold mb-2">{{ $feature['title'] }}</h3>
<p class="text-gray-600">{{ $feature['desc'] }}</p>
</div>
@endforeach
</div>
</div>
</section>
<!-- CTA Section -->
<section class="py-16 bg-gray-100">
<div class="max-w-4xl mx-auto text-center px-4">
<h2 class="text-3xl font-bold mb-4">Ready to grow your business?</h2>
<p class="text-gray-600 mb-8">Join 500+ merchants who trust ShopMetrics for their analytics.</p>
<div class="flex gap-4 justify-center">
<a href="{{ route('register') }}" class="btn-primary">Start Free Trial</a>
<a href="{{ route('pricing') }}" class="btn-secondary">View Pricing</a>
</div>
</div>
</section>
@endsection
❓ FAQ
npm run dev (hot reload in development mode) or npm run build (production build), and check whether the content configuration in tailwind.config.js includes the paths to all Blade files.x-show the same as Vue's v-show?v-show requires a Vue instance. Use Alpine.js for simple interactions and Vue for complex single-page applications.📖 Summary
- With the routing blueprint, you first plan the pages, then define the controllers and middleware
- Layouts handle page-level structure, while Blade components handle reusable UI fragments
- Keep controller methods simple; extract complex logic into the Service class
- Tailwind CSS is compiled using Vite, and Alpine.js handles simple interactions
- Breeze provides a ready-to-use authentication page
- Use .env for environment configuration; use debug mode for the development environment
📝 Exercises
-
Basic Task (⭐): Complete the five pages of the ShopMetrics landing page (Home/Pricing/About/Contact/Dashboard), ensuring that all routes are accessible, layout inheritance works properly, and navigation bar links are correct.
-
Advanced Exercise (⭐⭐): Add a monthly/annual payment toggle to the pricing page (using Alpine.js). The annual price should be 20% off, and the card price should transition with an animation when switching between payment options.
-
Challenge (⭐⭐⭐): Implement a dynamic menu in the dashboard sidebar—display different menu items based on the user's role (admin/tenant_owner/analyst), using View Composer to retrieve permission configurations from the database.



