Tutorial Vibe Coding Laravel 12 dengan Antigravity Bikin Web Company Profile

Tutorial lengkap vibe coding menggunakan Laravel dan Antigravity untuk membangun website company profile modern. Dalam tutorial ini, Angga Risky (Freelance Web Developer & Founder BuildWithAngga) akan membimbing kamu step-by-step cara menggunakan AI untuk mempercepat development — dari setup project, membuat migrations dan seeders, hingga membangun halaman Home, About, dan Contact dengan komponen Shadcn UI yang modern. Cocok untuk freelancer yang ingin meningkatkan produktivitas dan developer yang penasaran dengan AI-assisted development.


Bagian 1: Intro — Apa Itu Vibe Coding?

Halo! Gue Angga Risky, freelance web developer sekaligus founder BuildWithAngga.

Sebagai freelancer, waktu itu uang. Literally. Setiap jam yang bisa gue hemat dari ngoding, itu profit tambahan — atau waktu yang bisa gue pakai untuk handle lebih banyak client. Dan sejak gue mulai pakai "vibe coding", produktivitas gue naik drastis. Project yang biasanya butuh 2 minggu, sekarang bisa selesai dalam 4-5 hari.

Apa itu vibe coding? Simply put: coding dengan bantuan AI.

Tapi tunggu — bukan berarti AI yang nulis semua code dan kita tinggal copy-paste. Itu misconception yang sering gue dengar. Vibe coding itu lebih ke: kita yang jadi "director", kasih context dan instruksi yang tepat ke AI, dan AI bantu eksekusi lebih cepat. Kita tetap yang in control, AI cuma tools yang sangat powerful.

Kenapa Vibe Coding Game-Changer untuk Freelancer?

BEFORE VIBE CODING:
├── Nulis migration manual → 15-20 menit
├── Bikin model + relationships → 20-30 menit
├── Setup seeders dengan data realistic → 30-45 menit
├── Bikin blade components dari scratch → 1-2 jam
└── Total setup awal: 3-4 jam

AFTER VIBE CODING:
├── Prompt: "Buatkan migration untuk..." → 2 menit review
├── Prompt: "Bikin model dengan scopes..." → 2 menit review
├── Prompt: "Generate seeders realistic..." → 3 menit review
├── Prompt: "Bikin card component shadcn..." → 2 menit review
└── Total setup awal: 30-45 menit

HASIL: 4-5x lebih cepat

Itu baru setup awal. Belum termasuk debugging, bikin views, styling, dan lain-lain. Bayangkan kalau semua itu bisa dipercepat — berapa banyak project yang bisa kamu handle dalam sebulan?

Apa Itu Antigravity?

Di tutorial ini, gue akan pakai Antigravity sebagai AI coding assistant. Antigravity ini mirip seperti Cursor atau GitHub Copilot, tapi dengan beberapa keunggulan yang gue suka:

KENAPA ANTIGRAVITY:

├── Context-aware
│   └── Paham struktur project kita secara keseluruhan
│   └── Bisa reference file lain saat generate code
│
├── Multi-file editing
│   └── Bisa edit beberapa file sekaligus
│   └── Konsisten antar file
│
├── Laravel-friendly
│   └── Paham conventions Laravel
│   └── Generate code yang idiomatic
│
└── Fast iteration
    └── Feedback loop yang cepat
    └── Easy to refine output

Tapi honestly, tools manapun yang kamu pakai — Cursor, Copilot, Claude, ChatGPT — prinsipnya sama. Yang penting adalah cara berpikir dan cara bikin prompt. Tutorial ini fokus ke workflow dan mindset, bukan tools specific.

Mindset yang Benar dalam Vibe Coding

Sebelum mulai, gue mau luruskan dulu beberapa misconception:

❌ MINDSET YANG SALAH:

├── "AI bakal replace developer"
│   └── Salah. AI butuh direction dari human yang paham
│
├── "Tinggal copy-paste hasil AI, beres"
│   └── Salah. Output AI HARUS di-review dan di-test
│
├── "Gak perlu paham code lagi, AI yang handle"
│   └── SANGAT salah. Kalau gak paham, gak bisa review
│
└── "Prompt asal-asalan juga bisa"
    └── Salah. Garbage in, garbage out

✅ MINDSET YANG BENAR:

├── AI = Junior developer yang SANGAT cepat
│   └── Cepat eksekusi, tapi butuh supervisi
│   └── Kita tetap senior yang review dan approve
│
├── Kita tetap architect & reviewer
│   └── Design decisions tetap di kita
│   └── AI bantu implementasi
│
├── Prompt engineering = Skill baru yang WAJIB
│   └── Semakin bagus prompt, semakin bagus output
│   └── Ini skill yang bisa dilatih
│
├── Output AI SELALU perlu di-review
│   └── Check logic, security, edge cases
│   └── Jangan blind trust
│
└── Understanding code tetap fundamental
    └── Kalau gak paham Laravel, gak bisa review output
    └── Belajar fundamentals dulu, baru vibe coding

Gue selalu bilang ke student di BuildWithAngga: AI bikin kamu 10x lebih produktif, TAPI hanya kalau kamu sudah paham fundamentals. Kalau belum paham Laravel sama sekali, belajar dulu basic-nya. Vibe coding bukan shortcut untuk skip learning — ini tools untuk amplify skill yang sudah kamu punya.

Apa yang Akan Kita Bangun?

Di tutorial ini, kita akan bikin website company profile yang professional dan modern. Ini tipe project yang sering banget gue dapet sebagai freelancer — dan dengan vibe coding, bisa selesai dalam waktu singkat.

PROJECT: COMPANY PROFILE WEBSITE

PAGES YANG AKAN DIBUAT:
├── Home
│   ├── Hero section dengan gradient dan stats
│   ├── Services section (grid cards)
│   ├── Testimonials section
│   └── Call-to-action section
│
├── About
│   ├── Company story
│   ├── Our values
│   └── Team members grid
│
└── Contact
    ├── Contact form (save to database)
    ├── Contact information
    └── Google Maps embed

TECH STACK:

├── Laravel 11/12
│   └── Backend framework
│
├── Shadcn UI Components
│   └── Modern, accessible UI components
│   └── Ported ke Blade
│
├── Tailwind CSS
│   └── Utility-first styling
│
├── MySQL
│   └── Database
│
└── Alpine.js
    └── Minimal interactivity (mobile menu, etc)

DEVELOPMENT PRINCIPLES:

├── SRP (Single Responsibility Principle)
│   └── Setiap class punya satu tanggung jawab
│   └── Clean, maintainable code
│
├── Clean Folder Structure
│   └── Organized dan predictable
│   └── Easy to navigate
│
├── Reusable Blade Components
│   └── DRY principle
│   └── Consistent UI
│
└── Proper Migrations & Seeders
    └── Version-controlled database
    └── Realistic dummy data

Preview Hasil Akhir

Sebelum mulai coding, ini gambaran hasil akhir yang akan kita capai:

FITUR YANG AKAN JADI:

✅ Homepage dengan hero section yang eye-catching
✅ Services showcase dengan card components
✅ Testimonials dari "clients"
✅ About page dengan team members
✅ Contact form yang save ke database
✅ Responsive design (mobile-friendly)
✅ Modern UI dengan Shadcn components
✅ Clean, maintainable codebase

DATABASE TABLES:

├── settings → Company info (nama, tagline, contact, social media)
├── services → Layanan yang ditawarkan
├── team_members → Anggota tim
├── testimonials → Testimoni client
└── contacts → Form submissions dari visitor

Struktur Tutorial

Tutorial ini dibagi jadi 12 bagian yang bisa kamu ikuti step-by-step:

ROADMAP TUTORIAL:

Bagian 1: ✓ Intro (kamu di sini)
Bagian 2: Setup Project & Tools
Bagian 3: Database Design & Migrations
Bagian 4: Models dengan SRP
Bagian 5: Seeders dengan Data Realistic
Bagian 6: Shadcn UI Components Setup
Bagian 7: Home Page — Hero & Services
Bagian 8: Home Page — Testimonials & CTA
Bagian 9: About Page
Bagian 10: Contact Page
Bagian 11: Polish & Responsive
Bagian 12: Deployment & Closing

Setiap bagian akan ada:

  • Prompt yang gue kasih ke Antigravity
  • Output code yang dihasilkan
  • Review dan adjustment yang gue lakukan
  • Struktur folder di setiap tahap

Ready? Let's start vibe coding! 🚀


Lanjut ke Bagian 2: Setup Project & Tools →

Bagian 2: Setup Project & Tools

Sekarang kita mulai setup project. Di bagian ini, gue akan tunjukin exact steps yang gue lakukan — dan di mana AI bisa bantu percepat prosesnya.

Step 1: Install Laravel

Buka terminal, jalankan:

# Create new Laravel project
composer create-project laravel/laravel company-profile

# Masuk ke folder project
cd company-profile

# Test jalankan
php artisan serve

Buka browser, akses http://localhost:8000. Kalau muncul welcome page Laravel, instalasi berhasil.

CHECKPOINT:
✅ Laravel terinstall
✅ Bisa akses di browser

Step 2: Setup Antigravity

Kalau kamu pakai Antigravity (atau AI assistant lain seperti Cursor):

SETUP ANTIGRAVITY:

1. Download dan install Antigravity
2. Open folder project (company-profile)
3. Biarkan Antigravity index semua files
4. Siapkan terminal terintegrasi
5. Siapkan browser untuk preview

TIPS:
├── Buka SELURUH folder project, bukan file individual
├── Ini penting supaya AI punya full context
├── AI bisa lihat struktur folder, existing code, dll
└── Hasilnya: output yang lebih accurate

Kalau kamu pakai ChatGPT atau Claude biasa (bukan IDE integration), kamu perlu manually copy-paste context yang relevan. Tapi prinsipnya sama — semakin banyak context yang kamu kasih, semakin bagus outputnya.

Step 3: Install Frontend Dependencies

# Install Node dependencies
npm install

# Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

# Install utility libraries untuk Shadcn-style components
npm install class-variance-authority clsx tailwind-merge

# Install Alpine.js untuk interactivity
npm install alpinejs

Step 4: Setup Tailwind Config

Di sini gue mulai pakai AI. Ini prompt pertama:

PROMPT KE ANTIGRAVITY:

"Setup Tailwind config untuk Laravel project dengan Shadcn UI design system.
Include:
- CSS variables untuk colors (background, foreground, primary, secondary, muted, border)
- Border radius variables
- Support untuk dark mode (optional)

File yang perlu diupdate:
1. tailwind.config.js
2. resources/css/app.css"

Output: tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./resources/**/*.blade.php",
    "./resources/**/*.js",
    "./resources/**/*.vue",
  ],
  theme: {
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
    },
  },
  plugins: [],
}

Output: resources/css/app.css

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
    :root {
        --background: 0 0% 100%;
        --foreground: 222.2 84% 4.9%;

        --card: 0 0% 100%;
        --card-foreground: 222.2 84% 4.9%;

        --primary: 222.2 47.4% 11.2%;
        --primary-foreground: 210 40% 98%;

        --secondary: 210 40% 96.1%;
        --secondary-foreground: 222.2 47.4% 11.2%;

        --muted: 210 40% 96.1%;
        --muted-foreground: 215.4 16.3% 46.9%;

        --accent: 210 40% 96.1%;
        --accent-foreground: 222.2 47.4% 11.2%;

        --destructive: 0 84.2% 60.2%;
        --destructive-foreground: 210 40% 98%;

        --border: 214.3 31.8% 91.4%;
        --input: 214.3 31.8% 91.4%;
        --ring: 222.2 84% 4.9%;

        --radius: 0.5rem;
    }

    .dark {
        --background: 222.2 84% 4.9%;
        --foreground: 210 40% 98%;

        --card: 222.2 84% 4.9%;
        --card-foreground: 210 40% 98%;

        --primary: 210 40% 98%;
        --primary-foreground: 222.2 47.4% 11.2%;

        --secondary: 217.2 32.6% 17.5%;
        --secondary-foreground: 210 40% 98%;

        --muted: 217.2 32.6% 17.5%;
        --muted-foreground: 215 20.2% 65.1%;

        --accent: 217.2 32.6% 17.5%;
        --accent-foreground: 210 40% 98%;

        --destructive: 0 62.8% 30.6%;
        --destructive-foreground: 210 40% 98%;

        --border: 217.2 32.6% 17.5%;
        --input: 217.2 32.6% 17.5%;
        --ring: 212.7 26.8% 83.9%;
    }
}

@layer base {
    * {
        @apply border-border;
    }

    body {
        @apply bg-background text-foreground;
    }
}

Review Output:

Output AI sudah bagus. Gue cuma perlu verify:

  • ✅ CSS variables sesuai Shadcn design system
  • ✅ Dark mode support included
  • ✅ Tailwind config extend colors correctly

Gak ada yang perlu diubah, langsung pakai.

Step 5: Setup Alpine.js

Update resources/js/app.js:

import './bootstrap';

// Import Alpine.js
import Alpine from 'alpinejs';

window.Alpine = Alpine;

Alpine.start();

Step 6: Setup Database

Buat database MySQL:

CREATE DATABASE company_profile;

Update file .env:

APP_NAME="Company Profile"
APP_URL=http://localhost:8000

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=company_profile
DB_USERNAME=root
DB_PASSWORD=

Test koneksi:

php artisan migrate

Kalau berhasil tanpa error, database sudah connected.

Step 7: Build Assets

# Run Vite dev server
npm run dev

Biarkan running di terminal terpisah.

Struktur Folder Sekarang

company-profile/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   ├── Models/
│   └── Providers/
│
├── bootstrap/
│
├── config/
│
├── database/
│   ├── factories/
│   ├── migrations/
│   └── seeders/
│
├── public/
│
├── resources/
│   ├── css/
│   │   └── app.css          ← Updated dengan CSS variables
│   ├── js/
│   │   └── app.js           ← Updated dengan Alpine.js
│   └── views/
│       └── welcome.blade.php
│
├── routes/
│   └── web.php
│
├── storage/
│
├── tests/
│
├── .env                      ← Updated database config
├── composer.json
├── package.json
├── tailwind.config.js        ← Updated dengan Shadcn colors
├── vite.config.js
└── ...

Summary Bagian 2

YANG SUDAH DIKERJAKAN:

✅ Laravel project created
✅ Tailwind CSS installed dan configured
✅ Shadcn-style CSS variables setup
✅ Alpine.js installed
✅ Database MySQL configured
✅ Vite dev server running

PROMPT YANG DIGUNAKAN: 1
└── Setup Tailwind config dengan Shadcn design system

WAKTU:
├── Manual setup (tanpa AI): ~30-45 menit
├── Dengan AI assistance: ~10-15 menit
└── Saved: ~20-30 menit

Di bagian ini, AI bantu gue generate Tailwind config yang proper. Ini task yang repetitive dan prone to typos kalau manual — perfect use case untuk AI.


Lanjut ke Bagian 3: Database Design & Migrations →

Bagian 3: Database Design & Migrations

Sekarang bagian favorit gue — database design. Di sini AI benar-benar shine karena bisa generate migrations yang proper dan consistent dalam hitungan detik.

Prompt: Database Schema Design

Pertama, gue kasih AI gambaran besar tentang apa yang gue butuhkan:

PROMPT KE ANTIGRAVITY:

"Saya sedang membuat website company profile dengan Laravel.
Tolong design database schema dan buatkan migration files untuk:

1. settings
   - Menyimpan info perusahaan (singleton, hanya 1 row)
   - Fields: company_name, tagline, email, phone, address
   - Google maps embed code
   - Social media URLs (facebook, instagram, twitter, linkedin)
   - Logo dan favicon paths

2. services
   - Layanan yang ditawarkan perusahaan
   - Fields: icon (string untuk icon name), title, slug, description
   - is_active untuk soft-disable
   - order untuk sorting

3. team_members
   - Anggota tim perusahaan
   - Fields: photo, name, position, bio
   - Social links (email, linkedin, twitter)
   - is_active dan order

4. testimonials
   - Testimoni dari clients
   - Fields: photo, name, company, position, quote
   - rating (1-5)
   - is_active dan order

5. contacts
   - Form submissions dari visitors
   - Fields: name, email, phone, subject, message
   - is_read status dengan read_at timestamp

Buatkan migration files dengan:
- Proper column types
- Nullable yang masuk akal
- Default values yang sesuai
- Foreign keys jika diperlukan

Generate 5 file migration terpisah."

Output: Migration Files

AI generate 5 migration files. Berikut output-nya:

Migration 1: Settings

php artisan make:migration create_settings_table

<?php
// database/migrations/xxxx_xx_xx_create_settings_table.php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('settings', function (Blueprint $table) {
            $table->id();

            // Company Information
            $table->string('company_name');
            $table->string('tagline')->nullable();
            $table->string('email');
            $table->string('phone')->nullable();
            $table->text('address')->nullable();

            // Maps
            $table->text('google_maps_embed')->nullable();

            // Social Media
            $table->string('facebook_url')->nullable();
            $table->string('instagram_url')->nullable();
            $table->string('twitter_url')->nullable();
            $table->string('linkedin_url')->nullable();

            // Branding
            $table->string('logo')->nullable();
            $table->string('favicon')->nullable();

            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('settings');
    }
};

Review:

  • ✅ Semua fields yang gue minta ada
  • ✅ Nullable sudah proper (email required, sisanya optional)
  • ✅ text() untuk address dan maps embed (bisa panjang)
  • ✅ Clean dan organized dengan comments

Migration 2: Services

php artisan make:migration create_services_table

<?php
// database/migrations/xxxx_xx_xx_create_services_table.php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('services', function (Blueprint $table) {
            $table->id();
            $table->string('icon')->default('sparkles');
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('description');
            $table->boolean('is_active')->default(true);
            $table->integer('order')->default(0);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('services');
    }
};

Review:

  • ✅ icon dengan default value
  • ✅ slug unique untuk URL-friendly
  • ✅ is_active default true (sensible default)
  • ✅ order untuk custom sorting

Migration 3: Team Members

php artisan make:migration create_team_members_table

<?php
// database/migrations/xxxx_xx_xx_create_team_members_table.php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('team_members', function (Blueprint $table) {
            $table->id();
            $table->string('photo')->nullable();
            $table->string('name');
            $table->string('position');
            $table->text('bio')->nullable();
            $table->string('email')->nullable();
            $table->string('linkedin_url')->nullable();
            $table->string('twitter_url')->nullable();
            $table->boolean('is_active')->default(true);
            $table->integer('order')->default(0);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('team_members');
    }
};

Review:

  • ✅ photo nullable (bisa pakai placeholder)
  • ✅ name dan position required
  • ✅ bio sebagai text (bisa panjang)
  • ✅ Consistent dengan services (is_active, order)

Migration 4: Testimonials

php artisan make:migration create_testimonials_table

<?php
// database/migrations/xxxx_xx_xx_create_testimonials_table.php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('testimonials', function (Blueprint $table) {
            $table->id();
            $table->string('photo')->nullable();
            $table->string('name');
            $table->string('company')->nullable();
            $table->string('position')->nullable();
            $table->text('quote');
            $table->tinyInteger('rating')->default(5);
            $table->boolean('is_active')->default(true);
            $table->integer('order')->default(0);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('testimonials');
    }
};

Review:

  • ✅ quote sebagai text (testimonial bisa panjang)
  • ✅ rating dengan tinyInteger (1-5, hemat space)
  • ✅ rating default 5 (optimistic default)
  • ✅ company dan position nullable (tidak semua orang mau share)

Migration 5: Contacts

php artisan make:migration create_contacts_table

<?php
// database/migrations/xxxx_xx_xx_create_contacts_table.php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('contacts', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('phone')->nullable();
            $table->string('subject');
            $table->text('message');
            $table->boolean('is_read')->default(false);
            $table->timestamp('read_at')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('contacts');
    }
};

Review:

  • ✅ name, email, subject, message required
  • ✅ phone optional
  • ✅ is_read dengan default false
  • ✅ read_at untuk track kapan dibaca

Jalankan Migrations

php artisan migrate

Output:

INFO  Running migrations.

2024_01_15_000001_create_settings_table ............ 25ms DONE
2024_01_15_000002_create_services_table ............ 18ms DONE
2024_01_15_000003_create_team_members_table ........ 15ms DONE
2024_01_15_000004_create_testimonials_table ........ 14ms DONE
2024_01_15_000005_create_contacts_table ............ 12ms DONE

Verifikasi di Database

-- Cek tables yang dibuat
SHOW TABLES;

-- Output:
-- +----------------------------+
-- | Tables_in_company_profile  |
-- +----------------------------+
-- | cache                      |
-- | cache_locks                |
-- | contacts                   |
-- | failed_jobs                |
-- | job_batches                |
-- | jobs                       |
-- | migrations                 |
-- | password_reset_tokens      |
-- | services                   |
-- | sessions                   |
-- | settings                   |
-- | team_members               |
-- | testimonials               |
-- | users                      |
-- +----------------------------+

Semua tables sudah terbuat dengan benar.

ERD (Entity Relationship Diagram)

Untuk visualisasi, ini struktur database kita:

DATABASE SCHEMA:

┌─────────────────────────────────────────────────────────┐
│                        settings                          │
├─────────────────────────────────────────────────────────┤
│ id              BIGINT PRIMARY KEY AUTO_INCREMENT        │
│ company_name    VARCHAR(255) NOT NULL                    │
│ tagline         VARCHAR(255) NULL                        │
│ email           VARCHAR(255) NOT NULL                    │
│ phone           VARCHAR(255) NULL                        │
│ address         TEXT NULL                                │
│ google_maps_embed TEXT NULL                              │
│ facebook_url    VARCHAR(255) NULL                        │
│ instagram_url   VARCHAR(255) NULL                        │
│ twitter_url     VARCHAR(255) NULL                        │
│ linkedin_url    VARCHAR(255) NULL                        │
│ logo            VARCHAR(255) NULL                        │
│ favicon         VARCHAR(255) NULL                        │
│ created_at      TIMESTAMP                                │
│ updated_at      TIMESTAMP                                │
└─────────────────────────────────────────────────────────┘
        │
        │ (singleton - hanya 1 row)
        │
┌───────┴─────────────────────────────────────────────────┐
│                        services                          │
├─────────────────────────────────────────────────────────┤
│ id              BIGINT PRIMARY KEY AUTO_INCREMENT        │
│ icon            VARCHAR(255) DEFAULT 'sparkles'          │
│ title           VARCHAR(255) NOT NULL                    │
│ slug            VARCHAR(255) UNIQUE NOT NULL             │
│ description     TEXT NOT NULL                            │
│ is_active       BOOLEAN DEFAULT TRUE                     │
│ order           INT DEFAULT 0                            │
│ created_at      TIMESTAMP                                │
│ updated_at      TIMESTAMP                                │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                      team_members                        │
├─────────────────────────────────────────────────────────┤
│ id              BIGINT PRIMARY KEY AUTO_INCREMENT        │
│ photo           VARCHAR(255) NULL                        │
│ name            VARCHAR(255) NOT NULL                    │
│ position        VARCHAR(255) NOT NULL                    │
│ bio             TEXT NULL                                │
│ email           VARCHAR(255) NULL                        │
│ linkedin_url    VARCHAR(255) NULL                        │
│ twitter_url     VARCHAR(255) NULL                        │
│ is_active       BOOLEAN DEFAULT TRUE                     │
│ order           INT DEFAULT 0                            │
│ created_at      TIMESTAMP                                │
│ updated_at      TIMESTAMP                                │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                      testimonials                        │
├─────────────────────────────────────────────────────────┤
│ id              BIGINT PRIMARY KEY AUTO_INCREMENT        │
│ photo           VARCHAR(255) NULL                        │
│ name            VARCHAR(255) NOT NULL                    │
│ company         VARCHAR(255) NULL                        │
│ position        VARCHAR(255) NULL                        │
│ quote           TEXT NOT NULL                            │
│ rating          TINYINT DEFAULT 5                        │
│ is_active       BOOLEAN DEFAULT TRUE                     │
│ order           INT DEFAULT 0                            │
│ created_at      TIMESTAMP                                │
│ updated_at      TIMESTAMP                                │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                        contacts                          │
├─────────────────────────────────────────────────────────┤
│ id              BIGINT PRIMARY KEY AUTO_INCREMENT        │
│ name            VARCHAR(255) NOT NULL                    │
│ email           VARCHAR(255) NOT NULL                    │
│ phone           VARCHAR(255) NULL                        │
│ subject         VARCHAR(255) NOT NULL                    │
│ message         TEXT NOT NULL                            │
│ is_read         BOOLEAN DEFAULT FALSE                    │
│ read_at         TIMESTAMP NULL                           │
│ created_at      TIMESTAMP                                │
│ updated_at      TIMESTAMP                                │
└─────────────────────────────────────────────────────────┘

Struktur Folder Sekarang

company-profile/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   ├── Models/
│   └── Providers/
│
├── database/
│   ├── factories/
│   ├── migrations/
│   │   ├── 0001_01_01_000000_create_users_table.php
│   │   ├── 0001_01_01_000001_create_cache_table.php
│   │   ├── 0001_01_01_000002_create_jobs_table.php
│   │   ├── xxxx_xx_xx_create_settings_table.php      ← NEW
│   │   ├── xxxx_xx_xx_create_services_table.php      ← NEW
│   │   ├── xxxx_xx_xx_create_team_members_table.php  ← NEW
│   │   ├── xxxx_xx_xx_create_testimonials_table.php  ← NEW
│   │   └── xxxx_xx_xx_create_contacts_table.php      ← NEW
│   └── seeders/
│       └── DatabaseSeeder.php
│
├── resources/
│   ├── css/
│   │   └── app.css
│   ├── js/
│   │   └── app.js
│   └── views/
│
└── ...

Summary Bagian 3

YANG SUDAH DIKERJAKAN:

✅ Database schema designed
✅ 5 migration files created
   ├── settings (company info)
   ├── services (layanan)
   ├── team_members (tim)
   ├── testimonials (testimoni)
   └── contacts (form submissions)
✅ All migrations executed successfully

PROMPT YANG DIGUNAKAN: 1
└── Design database schema + generate migrations

WAKTU:
├── Manual (tanpa AI): ~45-60 menit
│   └── Thinking schema: 15-20 menit
│   └── Writing migrations: 30-40 menit
│
├── Dengan AI: ~10 menit
│   └── Write prompt: 3 menit
│   └── Review output: 5 menit
│   └── Run migrations: 2 menit
│
└── Saved: ~35-50 menit

Tips Prompt Engineering untuk Migrations

Dari pengalaman gue, ini tips untuk prompt migrations yang bagus:

TIPS PROMPT MIGRATIONS:

1. BE SPECIFIC ABOUT FIELDS
   ❌ "buatkan table untuk users"
   ✅ "buatkan table users dengan fields: name, email, password, role (enum), is_active"

2. MENTION CONSTRAINTS
   ❌ "buatkan table products"
   ✅ "buatkan table products dengan slug UNIQUE, price DECIMAL(10,2), stock INTEGER default 0"

3. SPECIFY RELATIONSHIPS
   ❌ "buatkan table orders"
   ✅ "buatkan table orders dengan foreign key ke users, cascade on delete"

4. MENTION DEFAULTS
   ❌ "buatkan table posts dengan status"
   ✅ "buatkan table posts dengan status ENUM('draft','published') default 'draft'"

5. GROUP LOGICALLY
   ✅ Kasih context tentang semua tables yang dibutuhkan
   ✅ AI bisa lihat big picture dan bikin consistent

Semakin detail prompt kamu, semakin sedikit revisi yang diperlukan.


Lanjut ke Bagian 4: Models dengan SRP →

Bagian 4: Models dengan SRP (Single Responsibility Principle)

Sekarang kita buat Models untuk semua tables. Di sini gue akan terapkan SRP (Single Responsibility Principle) — setiap model punya satu tanggung jawab yang jelas.

Apa Itu SRP di Context Laravel Models?

SRP (SINGLE RESPONSIBILITY PRINCIPLE):

Setiap class/model HANYA punya SATU alasan untuk berubah.

DI LARAVEL MODELS, INI BERARTI:
├── ✅ Define fillable fields
├── ✅ Define casts (type conversion)
├── ✅ Define relationships
├── ✅ Query scopes (reusable filters)
├── ✅ Accessors/Mutators (formatting)
├── ✅ Simple state changes

YANG TIDAK BOLEH DI MODEL:
├── ❌ Complex business logic → Service class
├── ❌ Validation rules → FormRequest
├── ❌ Sending emails → Event/Listener
├── ❌ API calls → Dedicated service
├── ❌ Heavy calculations → Dedicated class

Kenapa ini penting? Karena kalau model kamu jadi "god object" yang handle semuanya, maintenance jadi nightmare. Trust me, gue pernah handle legacy project dengan model 2000+ lines — gak mau lagi.

Prompt: Generate All Models

PROMPT KE ANTIGRAVITY:

"Buatkan Models untuk semua tables yang sudah dibuat dengan menerapkan SRP.

Models yang dibutuhkan:

1. Setting
   - Singleton pattern (hanya 1 row di database)
   - Method static instance() yang return cached instance
   - Clear cache saat data diupdate
   - Helper method hasSocialLinks()

2. Service
   - Auto-generate slug dari title saat creating
   - Scope: active() dan ordered()
   - Accessor: excerpt (limit description)

3. TeamMember
   - Scope: active() dan ordered()
   - Helper: hasSocialLinks()
   - Accessor: photo_url (dengan fallback ke ui-avatars)

4. Testimonial
   - Scope: active() dan ordered()
   - Accessor: photo_url (dengan fallback)
   - Accessor: full_title (name + position at company)

5. Contact
   - Scope: unread(), read(), recent()
   - Method: markAsRead(), markAsUnread()

Gunakan:
- Proper $fillable
- Proper $casts
- Boot method untuk auto-generate slug
- Cache untuk Setting singleton

Generate 5 file model terpisah."

Output: Model Files

Model 1: Setting (Singleton Pattern)

<?php
// app/Models/Setting.php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Support\\Facades\\Cache;

class Setting extends Model
{
    /**
     * Mass assignable attributes
     */
    protected $fillable = [
        'company_name',
        'tagline',
        'email',
        'phone',
        'address',
        'google_maps_embed',
        'facebook_url',
        'instagram_url',
        'twitter_url',
        'linkedin_url',
        'logo',
        'favicon',
    ];

    /**
     * Get the singleton instance of settings
     * Cached for performance - no DB query on every request
     */
    public static function instance(): self
    {
        return Cache::rememberForever('app_settings', function () {
            return self::first() ?? new self([
                'company_name' => config('app.name'),
                'email' => '[email protected]',
            ]);
        });
    }

    /**
     * Clear cache when settings are updated
     */
    protected static function booted(): void
    {
        static::saved(function () {
            Cache::forget('app_settings');
        });

        static::deleted(function () {
            Cache::forget('app_settings');
        });
    }

    /**
     * Check if any social media links exist
     */
    public function hasSocialLinks(): bool
    {
        return $this->facebook_url
            || $this->instagram_url
            || $this->twitter_url
            || $this->linkedin_url;
    }

    /**
     * Get logo URL with fallback
     */
    public function getLogoUrlAttribute(): ?string
    {
        return $this->logo ? asset('storage/' . $this->logo) : null;
    }

    /**
     * Get favicon URL with fallback
     */
    public function getFaviconUrlAttribute(): ?string
    {
        return $this->favicon ? asset('storage/' . $this->favicon) : null;
    }
}

Review:

  • ✅ Singleton pattern dengan instance() method
  • ✅ Cache untuk avoid DB query setiap request
  • ✅ Auto-clear cache saat data berubah
  • ✅ Helper hasSocialLinks() untuk check di views
  • ✅ Accessors untuk logo dan favicon URLs

Model 2: Service

<?php
// app/Models/Service.php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Database\\Eloquent\\Builder;
use Illuminate\\Support\\Str;

class Service extends Model
{
    /**
     * Mass assignable attributes
     */
    protected $fillable = [
        'icon',
        'title',
        'slug',
        'description',
        'is_active',
        'order',
    ];

    /**
     * Attribute casting
     */
    protected $casts = [
        'is_active' => 'boolean',
        'order' => 'integer',
    ];

    /**
     * Boot method - auto-generate slug
     */
    protected static function booted(): void
    {
        static::creating(function (Service $service) {
            if (empty($service->slug)) {
                $service->slug = Str::slug($service->title);
            }
        });

        static::updating(function (Service $service) {
            // Only regenerate slug if title changed and slug is empty
            if ($service->isDirty('title') && empty($service->slug)) {
                $service->slug = Str::slug($service->title);
            }
        });
    }

    /**
     * Scope: Active services only
     */
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('is_active', true);
    }

    /**
     * Scope: Ordered by 'order' column
     */
    public function scopeOrdered(Builder $query): Builder
    {
        return $query->orderBy('order', 'asc');
    }

    /**
     * Scope: For display (active + ordered)
     */
    public function scopeForDisplay(Builder $query): Builder
    {
        return $query->active()->ordered();
    }

    /**
     * Get excerpt of description
     */
    public function getExcerptAttribute(): string
    {
        return Str::limit(strip_tags($this->description), 120);
    }
}

Review:

  • ✅ Auto-generate slug di boot()
  • ✅ Scopes yang reusable: active(), ordered(), forDisplay()
  • ✅ Accessor excerpt untuk preview singkat
  • ✅ Proper casts untuk boolean dan integer

Model 3: TeamMember

<?php
// app/Models/TeamMember.php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Database\\Eloquent\\Builder;

class TeamMember extends Model
{
    /**
     * The table associated with the model
     */
    protected $table = 'team_members';

    /**
     * Mass assignable attributes
     */
    protected $fillable = [
        'photo',
        'name',
        'position',
        'bio',
        'email',
        'linkedin_url',
        'twitter_url',
        'is_active',
        'order',
    ];

    /**
     * Attribute casting
     */
    protected $casts = [
        'is_active' => 'boolean',
        'order' => 'integer',
    ];

    /**
     * Scope: Active members only
     */
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('is_active', true);
    }

    /**
     * Scope: Ordered by 'order' column
     */
    public function scopeOrdered(Builder $query): Builder
    {
        return $query->orderBy('order', 'asc');
    }

    /**
     * Scope: For display (active + ordered)
     */
    public function scopeForDisplay(Builder $query): Builder
    {
        return $query->active()->ordered();
    }

    /**
     * Check if member has social links
     */
    public function hasSocialLinks(): bool
    {
        return $this->linkedin_url || $this->twitter_url || $this->email;
    }

    /**
     * Get photo URL with fallback to UI Avatars
     */
    public function getPhotoUrlAttribute(): string
    {
        if ($this->photo) {
            return asset('storage/' . $this->photo);
        }

        // Fallback to UI Avatars
        $name = urlencode($this->name);
        return "<https://ui-avatars.com/api/?name={$name}&size=200&background=random>";
    }

    /**
     * Get short bio (limited characters)
     */
    public function getShortBioAttribute(): string
    {
        return Str::limit($this->bio, 150);
    }
}

Review:

  • ✅ Explicit table name (karena model name != table name)
  • ✅ hasSocialLinks() helper
  • ✅ photo_url accessor dengan fallback ke UI Avatars
  • ✅ short_bio accessor untuk preview

Model 4: Testimonial

<?php
// app/Models/Testimonial.php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Database\\Eloquent\\Builder;

class Testimonial extends Model
{
    /**
     * Mass assignable attributes
     */
    protected $fillable = [
        'photo',
        'name',
        'company',
        'position',
        'quote',
        'rating',
        'is_active',
        'order',
    ];

    /**
     * Attribute casting
     */
    protected $casts = [
        'is_active' => 'boolean',
        'rating' => 'integer',
        'order' => 'integer',
    ];

    /**
     * Scope: Active testimonials only
     */
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('is_active', true);
    }

    /**
     * Scope: Ordered by 'order' column
     */
    public function scopeOrdered(Builder $query): Builder
    {
        return $query->orderBy('order', 'asc');
    }

    /**
     * Scope: For display (active + ordered)
     */
    public function scopeForDisplay(Builder $query): Builder
    {
        return $query->active()->ordered();
    }

    /**
     * Scope: High rated (4-5 stars)
     */
    public function scopeHighRated(Builder $query): Builder
    {
        return $query->where('rating', '>=', 4);
    }

    /**
     * Get photo URL with fallback to UI Avatars
     */
    public function getPhotoUrlAttribute(): string
    {
        if ($this->photo) {
            return asset('storage/' . $this->photo);
        }

        $name = urlencode($this->name);
        return "<https://ui-avatars.com/api/?name={$name}&size=100&background=random>";
    }

    /**
     * Get full title (Name, Position at Company)
     */
    public function getFullTitleAttribute(): string
    {
        $parts = [$this->name];

        if ($this->position && $this->company) {
            $parts[] = "{$this->position} at {$this->company}";
        } elseif ($this->company) {
            $parts[] = $this->company;
        } elseif ($this->position) {
            $parts[] = $this->position;
        }

        return implode(', ', $parts);
    }

    /**
     * Get subtitle (Position at Company, without name)
     */
    public function getSubtitleAttribute(): ?string
    {
        if ($this->position && $this->company) {
            return "{$this->position} at {$this->company}";
        } elseif ($this->company) {
            return $this->company;
        } elseif ($this->position) {
            return $this->position;
        }

        return null;
    }
}

Review:

  • ✅ highRated() scope untuk filter testimonial bagus
  • ✅ photo_url dengan fallback
  • ✅ full_title accessor yang smart (handle missing fields)
  • ✅ subtitle accessor untuk display terpisah

Model 5: Contact

<?php
// app/Models/Contact.php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Database\\Eloquent\\Builder;

class Contact extends Model
{
    /**
     * Mass assignable attributes
     */
    protected $fillable = [
        'name',
        'email',
        'phone',
        'subject',
        'message',
        'is_read',
        'read_at',
    ];

    /**
     * Attribute casting
     */
    protected $casts = [
        'is_read' => 'boolean',
        'read_at' => 'datetime',
    ];

    /**
     * Scope: Unread messages only
     */
    public function scopeUnread(Builder $query): Builder
    {
        return $query->where('is_read', false);
    }

    /**
     * Scope: Read messages only
     */
    public function scopeRead(Builder $query): Builder
    {
        return $query->where('is_read', true);
    }

    /**
     * Scope: Recent first (newest on top)
     */
    public function scopeRecent(Builder $query): Builder
    {
        return $query->orderBy('created_at', 'desc');
    }

    /**
     * Scope: From today
     */
    public function scopeToday(Builder $query): Builder
    {
        return $query->whereDate('created_at', today());
    }

    /**
     * Mark message as read
     */
    public function markAsRead(): bool
    {
        if ($this->is_read) {
            return false; // Already read
        }

        return $this->update([
            'is_read' => true,
            'read_at' => now(),
        ]);
    }

    /**
     * Mark message as unread
     */
    public function markAsUnread(): bool
    {
        if (!$this->is_read) {
            return false; // Already unread
        }

        return $this->update([
            'is_read' => false,
            'read_at' => null,
        ]);
    }

    /**
     * Get short message preview
     */
    public function getPreviewAttribute(): string
    {
        return Str::limit($this->message, 100);
    }

    /**
     * Check if message is new (less than 24 hours)
     */
    public function getIsNewAttribute(): bool
    {
        return $this->created_at->isAfter(now()->subDay());
    }
}

Review:

  • ✅ Scopes untuk filtering: unread(), read(), recent(), today()
  • ✅ markAsRead() dan markAsUnread() methods
  • ✅ Return boolean untuk indicate success
  • ✅ preview accessor untuk list view
  • ✅ is_new accessor untuk highlight new messages

Register Models

Pastikan semua models ada di app/Models/:

app/Models/
├── Contact.php       ✅
├── Service.php       ✅
├── Setting.php       ✅
├── TeamMember.php    ✅
├── Testimonial.php   ✅
└── User.php          (default Laravel)

Test Models di Tinker

php artisan tinker

// Test Setting singleton
use App\\Models\\Setting;
$setting = Setting::instance();
// Should return new instance (belum ada data)

// Test Service auto-slug
use App\\Models\\Service;
$service = Service::create([
    'title' => 'Web Development',
    'description' => 'Custom web applications'
]);
$service->slug; // "web-development"

// Test scopes
Service::active()->ordered()->get();
Service::forDisplay()->get(); // same as above

// Test TeamMember photo fallback
use App\\Models\\TeamMember;
$member = new TeamMember(['name' => 'John Doe']);
$member->photo_url; // "<https://ui-avatars.com/api/?name=John%20Doe&size=200&background=random>"

// Test Contact methods
use App\\Models\\Contact;
$contact = Contact::create([
    'name' => 'Test',
    'email' => '[email protected]',
    'subject' => 'Test',
    'message' => 'Test message'
]);
$contact->is_read; // false
$contact->markAsRead();
$contact->is_read; // true
$contact->read_at; // current timestamp

Struktur Folder Sekarang

company-profile/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   ├── Models/
│   │   ├── Contact.php        ← NEW
│   │   ├── Service.php        ← NEW
│   │   ├── Setting.php        ← NEW
│   │   ├── TeamMember.php     ← NEW
│   │   ├── Testimonial.php    ← NEW
│   │   └── User.php
│   └── Providers/
│
├── database/
│   ├── migrations/
│   │   ├── create_settings_table.php
│   │   ├── create_services_table.php
│   │   ├── create_team_members_table.php
│   │   ├── create_testimonials_table.php
│   │   └── create_contacts_table.php
│   └── seeders/
│
└── ...

Summary Bagian 4

YANG SUDAH DIKERJAKAN:

✅ 5 Models created dengan SRP:
   ├── Setting (singleton, cached)
   ├── Service (auto-slug, scopes)
   ├── TeamMember (photo fallback, scopes)
   ├── Testimonial (full_title accessor, scopes)
   └── Contact (markAsRead, scopes)

✅ Consistent patterns across models:
   ├── Proper $fillable
   ├── Proper $casts
   ├── Reusable scopes
   ├── Useful accessors
   └── Helper methods

PROMPT YANG DIGUNAKAN: 1
└── Generate 5 models dengan SRP

WAKTU:
├── Manual: ~60-90 menit
│   └── Thinking structure: 20-30 menit
│   └── Writing code: 40-60 menit
│
├── Dengan AI: ~15 menit
│   └── Write prompt: 5 menit
│   └── Review output: 8 menit
│   └── Test di tinker: 2 menit
│
└── Saved: ~45-75 menit

SRP Checklist

Untuk setiap model, pastikan:

SRP CHECKLIST:

✅ Setting
   └── Responsibility: Manage app settings (singleton)

✅ Service
   └── Responsibility: Represent a service offering

✅ TeamMember
   └── Responsibility: Represent a team member

✅ Testimonial
   └── Responsibility: Represent a client testimonial

✅ Contact
   └── Responsibility: Represent a contact form submission

TIDAK ADA:
├── ❌ Email sending di model
├── ❌ File upload handling di model
├── ❌ Complex business rules di model
├── ❌ External API calls di model
└── ❌ Heavy calculations di model

Clean models = maintainable code. 👌


Lanjut ke Bagian 5: Seeders — Data Dummy Realistic →

Bagian 5: Seeders — Data Dummy Realistic

Sekarang kita isi database dengan data dummy yang realistic. Ini penting karena:

  1. Development jadi lebih mudah — ada data untuk di-render
  2. Testing jadi lebih akurat — data mirip production
  3. Demo ke client lebih impressive — bukan lorem ipsum

Prompt: Generate Realistic Seeders

PROMPT KE ANTIGRAVITY:

"Buatkan seeders dengan data dummy yang REALISTIC untuk company profile
sebuah digital agency bernama 'Starter Digital' yang berbasis di Jakarta.

Seeders yang dibutuhkan:

1. SettingSeeder
   - Company name: Starter Digital
   - Tagline yang catchy
   - Email, phone, address di Jakarta
   - Social media URLs (pakai placeholder yang valid)

2. ServiceSeeder (6 services)
   - Web Development
   - Mobile App Development
   - UI/UX Design
   - E-Commerce Solutions
   - Cloud & DevOps
   - Maintenance & Support
   - Setiap service punya icon name dan description yang proper
   - Order 1-6

3. TeamMemberSeeder (4 members)
   - CEO/Founder
   - Head of Design
   - Lead Developer
   - Project Manager
   - Nama Indonesia yang realistic
   - Bio yang professional
   - Order 1-4

4. TestimonialSeeder (4 testimonials)
   - Dari berbagai industry (tech, fashion, healthcare, education)
   - Quote yang realistic dan specific (bukan generic)
   - Rating semua 5 stars
   - Nama dan company Indonesia

JANGAN gunakan lorem ipsum. Semua content harus readable dan masuk akal.
Untuk photo, gunakan null (akan pakai fallback UI Avatars)."

Output: Seeder Files

SettingSeeder:

<?php
// database/seeders/SettingSeeder.php

namespace Database\\Seeders;

use App\\Models\\Setting;
use Illuminate\\Database\\Seeder;

class SettingSeeder extends Seeder
{
    public function run(): void
    {
        Setting::create([
            // Company Information
            'company_name' => 'Starter Digital',
            'tagline' => 'Transforming Ideas Into Digital Reality',
            'email' => '[email protected]',
            'phone' => '+62 21 5555 1234',
            'address' => 'Gedung Wirausaha Lt. 5, Jl. HR Rasuna Said Kav. C-5, Kuningan, Jakarta Selatan 12940',

            // Google Maps Embed (Jakarta coordinates)
            'google_maps_embed' => '<iframe src="<https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3966.2881554266983!2d106.8291!3d-6.2297!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2e69f3e2b8f0b5a1%3A0x301576d14feb9d0!2sJakarta%20Selatan!5e0!3m2!1sid!2sid!4v1699999999999!5m2!1sid!2sid>" width="100%" height="400" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>',

            // Social Media
            'facebook_url' => '<https://facebook.com/starterdigital.id>',
            'instagram_url' => '<https://instagram.com/starterdigital.id>',
            'twitter_url' => '<https://twitter.com/starterdigital>',
            'linkedin_url' => '<https://linkedin.com/company/starter-digital>',

            // Branding (null for now, will use defaults)
            'logo' => null,
            'favicon' => null,
        ]);
    }
}

ServiceSeeder:

<?php
// database/seeders/ServiceSeeder.php

namespace Database\\Seeders;

use App\\Models\\Service;
use Illuminate\\Database\\Seeder;

class ServiceSeeder extends Seeder
{
    public function run(): void
    {
        $services = [
            [
                'icon' => 'code',
                'title' => 'Web Development',
                'description' => 'Kami membangun aplikasi web custom dengan teknologi modern seperti Laravel, React, dan Vue.js. Dari landing page sederhana hingga sistem enterprise yang kompleks, kami deliver solusi yang scalable, secure, dan mudah di-maintain. Setiap project dikerjakan dengan best practices dan clean code principles.',
                'is_active' => true,
                'order' => 1,
            ],
            [
                'icon' => 'smartphone',
                'title' => 'Mobile App Development',
                'description' => 'Aplikasi mobile native dan cross-platform untuk iOS dan Android. Kami menggunakan Flutter dan React Native untuk development yang efisien tanpa mengorbankan performa. Dari konsep hingga launch di App Store dan Play Store, kami handle semuanya.',
                'is_active' => true,
                'order' => 2,
            ],
            [
                'icon' => 'palette',
                'title' => 'UI/UX Design',
                'description' => 'Design yang tidak hanya cantik, tapi juga convert. Tim designer kami melakukan user research, wireframing, prototyping, dan usability testing untuk memastikan produk digital Anda memberikan pengalaman terbaik bagi pengguna. Design system yang konsisten untuk brand Anda.',
                'is_active' => true,
                'order' => 3,
            ],
            [
                'icon' => 'shopping-cart',
                'title' => 'E-Commerce Solutions',
                'description' => 'Solusi e-commerce end-to-end untuk bisnis Anda. Dari toko online sederhana hingga marketplace multi-vendor, kami integrasikan payment gateway lokal (Midtrans, Xendit), shipping APIs, dan inventory management. Optimized untuk konversi dan repeat purchase.',
                'is_active' => true,
                'order' => 4,
            ],
            [
                'icon' => 'cloud',
                'title' => 'Cloud & DevOps',
                'description' => 'Setup dan management infrastruktur cloud di AWS, Google Cloud, atau DigitalOcean. Kami implement CI/CD pipelines, containerization dengan Docker, auto-scaling, dan monitoring. Aplikasi Anda akan selalu available, secure, dan performant.',
                'is_active' => true,
                'order' => 5,
            ],
            [
                'icon' => 'headphones',
                'title' => 'Maintenance & Support',
                'description' => 'Support berkelanjutan untuk produk digital Anda. Bug fixes, security updates, performance optimization, dan feature enhancements. Kami offer berbagai support packages mulai dari basic monitoring hingga dedicated team. Response time guarantee untuk critical issues.',
                'is_active' => true,
                'order' => 6,
            ],
        ];

        foreach ($services as $service) {
            Service::create($service);
        }
    }
}

TeamMemberSeeder:

<?php
// database/seeders/TeamMemberSeeder.php

namespace Database\\Seeders;

use App\\Models\\TeamMember;
use Illuminate\\Database\\Seeder;

class TeamMemberSeeder extends Seeder
{
    public function run(): void
    {
        $members = [
            [
                'photo' => null, // Will use UI Avatars fallback
                'name' => 'Andi Prasetyo',
                'position' => 'Founder & CEO',
                'bio' => 'Serial entrepreneur dengan pengalaman lebih dari 12 tahun di industri teknologi. Sebelum mendirikan Starter Digital, Andi memimpin tim engineering di beberapa startup unicorn Indonesia. Passionate tentang building products yang memberikan dampak nyata bagi bisnis dan masyarakat. Lulusan Teknik Informatika ITB.',
                'email' => '[email protected]',
                'linkedin_url' => '<https://linkedin.com/in/andiprasetyo>',
                'twitter_url' => '<https://twitter.com/andiprasetyo>',
                'is_active' => true,
                'order' => 1,
            ],
            [
                'photo' => null,
                'name' => 'Sarah Wijaya',
                'position' => 'Head of Design',
                'bio' => 'Award-winning designer dengan fokus pada user experience dan design systems. Background di psychology dan human-computer interaction membuat Sarah sangat memahami bagaimana user berinteraksi dengan produk digital. Pernah bekerja di agency international dan startup fintech sebelum bergabung dengan Starter Digital.',
                'email' => '[email protected]',
                'linkedin_url' => '<https://linkedin.com/in/sarahwijaya>',
                'twitter_url' => null,
                'is_active' => true,
                'order' => 2,
            ],
            [
                'photo' => null,
                'name' => 'Budi Santoso',
                'position' => 'Lead Developer',
                'bio' => 'Full-stack developer yang passionate dengan clean code dan software architecture. Open source contributor aktif dan sering menjadi speaker di tech meetups. Expertise di Laravel, React, dan cloud infrastructure. Motto: "Code is poetry, but it should also work."',
                'email' => '[email protected]',
                'linkedin_url' => '<https://linkedin.com/in/budisantoso>',
                'twitter_url' => '<https://twitter.com/budisantoso>',
                'is_active' => true,
                'order' => 3,
            ],
            [
                'photo' => null,
                'name' => 'Maya Putri',
                'position' => 'Project Manager',
                'bio' => 'Certified PMP dengan track record sukses mengelola 50+ digital projects. Expert dalam Agile/Scrum methodology dan client communication. Maya memastikan setiap project delivered on time, on budget, dan exceed expectations. Background di business administration dan tech consulting.',
                'email' => '[email protected]',
                'linkedin_url' => '<https://linkedin.com/in/mayaputri>',
                'twitter_url' => null,
                'is_active' => true,
                'order' => 4,
            ],
        ];

        foreach ($members as $member) {
            TeamMember::create($member);
        }
    }
}

TestimonialSeeder:

<?php
// database/seeders/TestimonialSeeder.php

namespace Database\\Seeders;

use App\\Models\\Testimonial;
use Illuminate\\Database\\Seeder;

class TestimonialSeeder extends Seeder
{
    public function run(): void
    {
        $testimonials = [
            [
                'photo' => null, // Will use UI Avatars fallback
                'name' => 'Ricky Harun',
                'company' => 'TechVenture Indonesia',
                'position' => 'CTO',
                'quote' => 'Starter Digital berhasil mentransformasi sistem legacy kami menjadi platform modern yang scalable. Tim mereka sangat memahami kebutuhan teknis yang kompleks dan deliver hasil yang melebihi ekspektasi. Proses development yang transparan dan komunikasi yang excellent. Waktu processing berkurang 60% setelah migrasi.',
                'rating' => 5,
                'is_active' => true,
                'order' => 1,
            ],
            [
                'photo' => null,
                'name' => 'Linda Kusuma',
                'company' => 'Batik Nusantara',
                'position' => 'Founder',
                'quote' => 'Bekerja dengan Starter Digital adalah game-changer untuk bisnis e-commerce kami. Website baru yang mereka buat tidak hanya beautiful, tapi juga sangat user-friendly. Sales online meningkat 180% dalam 3 bulan pertama setelah launch. Mereka benar-benar memahami UX untuk conversion.',
                'rating' => 5,
                'is_active' => true,
                'order' => 2,
            ],
            [
                'photo' => null,
                'name' => 'Dr. Ahmad Fauzi',
                'company' => 'Klinik Sehat Sentosa',
                'position' => 'Direktur Operasional',
                'quote' => 'Sistem manajemen pasien yang dibangun Starter Digital menyederhanakan seluruh operasional klinik kami. Appointment booking, medical records, dan billing sekarang terintegrasi dalam satu platform. Staff kami sangat terbantu dan pasien lebih puas dengan layanan yang lebih cepat.',
                'rating' => 5,
                'is_active' => true,
                'order' => 3,
            ],
            [
                'photo' => null,
                'name' => 'Jessica Tanoto',
                'company' => 'EduLearn Platform',
                'position' => 'CEO',
                'quote' => 'Dari konsep hingga launch hanya dalam 10 minggu! Starter Digital assembled tim yang tepat, komunikasi sangat clear sepanjang project, dan deliver LMS platform yang students kami love. Mereka juga sangat responsif untuk iterasi berdasarkan user feedback. Highly recommended untuk startup yang butuh move fast.',
                'rating' => 5,
                'is_active' => true,
                'order' => 4,
            ],
        ];

        foreach ($testimonials as $testimonial) {
            Testimonial::create($testimonial);
        }
    }
}

Update DatabaseSeeder

<?php
// database/seeders/DatabaseSeeder.php

namespace Database\\Seeders;

use Illuminate\\Database\\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            SettingSeeder::class,
            ServiceSeeder::class,
            TeamMemberSeeder::class,
            TestimonialSeeder::class,
        ]);
    }
}

Jalankan Seeders

# Fresh migrate + seed
php artisan migrate:fresh --seed

Output:

Dropping all tables ............................... 45ms DONE

INFO  Running migrations.

2024_01_01_000000_create_users_table .............. 35ms DONE
2024_01_01_000001_create_cache_table .............. 12ms DONE
2024_01_01_000002_create_jobs_table ............... 25ms DONE
2024_01_15_000001_create_settings_table ........... 18ms DONE
2024_01_15_000002_create_services_table ........... 15ms DONE
2024_01_15_000003_create_team_members_table ....... 14ms DONE
2024_01_15_000004_create_testimonials_table ....... 13ms DONE
2024_01_15_000005_create_contacts_table ........... 12ms DONE

INFO  Seeding database.

Database\\Seeders\\SettingSeeder .................... RUNNING
Database\\Seeders\\SettingSeeder .................... 8ms DONE
Database\\Seeders\\ServiceSeeder .................... RUNNING
Database\\Seeders\\ServiceSeeder .................... 15ms DONE
Database\\Seeders\\TeamMemberSeeder ................. RUNNING
Database\\Seeders\\TeamMemberSeeder ................. 12ms DONE
Database\\Seeders\\TestimonialSeeder ................ RUNNING
Database\\Seeders\\TestimonialSeeder ................ 10ms DONE

Verifikasi Data

php artisan tinker

// Check settings
use App\\Models\\Setting;
$setting = Setting::instance();
$setting->company_name; // "Starter Digital"
$setting->tagline; // "Transforming Ideas Into Digital Reality"

// Check services
use App\\Models\\Service;
Service::count(); // 6
Service::first()->title; // "Web Development"

// Check team members
use App\\Models\\TeamMember;
TeamMember::count(); // 4
TeamMember::first()->name; // "Andi Prasetyo"
TeamMember::first()->photo_url; // UI Avatars URL

// Check testimonials
use App\\Models\\Testimonial;
Testimonial::count(); // 4
Testimonial::first()->company; // "TechVenture Indonesia"

Struktur Folder Sekarang

company-profile/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   └── Models/
│       ├── Contact.php
│       ├── Service.php
│       ├── Setting.php
│       ├── TeamMember.php
│       ├── Testimonial.php
│       └── User.php
│
├── database/
│   ├── factories/
│   ├── migrations/
│   │   ├── create_settings_table.php
│   │   ├── create_services_table.php
│   │   ├── create_team_members_table.php
│   │   ├── create_testimonials_table.php
│   │   └── create_contacts_table.php
│   └── seeders/
│       ├── DatabaseSeeder.php           ← UPDATED
│       ├── SettingSeeder.php            ← NEW
│       ├── ServiceSeeder.php            ← NEW
│       ├── TeamMemberSeeder.php         ← NEW
│       └── TestimonialSeeder.php        ← NEW
│
└── ...

Summary Bagian 5

YANG SUDAH DIKERJAKAN:

✅ 4 Seeders created dengan data realistic:
   ├── SettingSeeder (company info lengkap)
   ├── ServiceSeeder (6 services dengan description proper)
   ├── TeamMemberSeeder (4 members dengan bio professional)
   └── TestimonialSeeder (4 testimonials dari berbagai industry)

✅ Data characteristics:
   ├── Nama Indonesia yang realistic
   ├── Company names yang masuk akal
   ├── Descriptions dalam Bahasa Indonesia proper
   ├── Testimonials yang specific (bukan generic)
   └── NO lorem ipsum!

PROMPT YANG DIGUNAKAN: 1
└── Generate realistic seeders untuk digital agency

WAKTU:
├── Manual: ~60-90 menit
│   └── Thinking content: 30-45 menit
│   └── Writing seeders: 30-45 menit
│
├── Dengan AI: ~10 menit
│   └── Write prompt: 3 menit
│   └── Review output: 5 menit
│   └── Run seeders: 2 menit
│
└── Saved: ~50-80 menit

Tips untuk Realistic Seeders

TIPS SEEDER CONTENT:

1. GUNAKAN CONTEXT LOKAL
   ├── Nama Indonesia, bukan "John Doe"
   ├── Alamat real (Jakarta, dll)
   ├── Phone format Indonesia
   └── Currency IDR kalau ada

2. DESCRIPTIONS YANG SPECIFIC
   ├── ❌ "We provide great services"
   ├── ✅ "Kami membangun aplikasi web custom dengan Laravel dan React..."

3. TESTIMONIALS YANG BELIEVABLE
   ├── Mention specific results (180% increase, 60% faster)
   ├── Mention specific features yang membantu
   ├── Sound like real person, bukan marketing copy

4. CONSISTENT TONE
   ├── Semua content punya voice yang sama
   ├── Professional tapi tidak kaku
   └── Reflect brand personality

5. REAL-ISH COMPANIES
   ├── Industry yang diverse
   ├── Nama company yang plausible
   └── Position yang masuk akal

Data dummy yang bagus = development experience yang bagus.


Lanjut ke Bagian 6: Shadcn UI Components Setup →

Bagian 6: Shadcn UI Components Setup

Sekarang kita setup UI components. Gue akan pakai Shadcn-style components yang di-port ke Blade. Kenapa Shadcn? Karena design-nya clean, accessible, dan easy to customize.

Apa Itu Shadcn UI?

SHADCN UI:

├── Bukan component library (seperti Bootstrap)
├── Collection of reusable components
├── Copy-paste ke project kamu
├── Full control — kamu own the code
├── Built on Radix UI + Tailwind CSS
└── Highly customizable

KENAPA BAGUS UNTUK FREELANCER:
├── Professional look out of the box
├── Consistent design system
├── Easy to customize per client
├── Accessible by default
└── Modern dan trendy

Karena Shadcn original untuk React, kita akan port konsepnya ke Blade components.

Prompt: Generate Blade Components

PROMPT KE ANTIGRAVITY:

"Buatkan Blade components dengan style Shadcn UI untuk Laravel.

Components yang dibutuhkan:

1. Button
   - Variants: default, secondary, outline, ghost, destructive, link
   - Sizes: default, sm, lg, icon
   - Support untuk disabled state
   - Bisa sebagai button atau link (tag)

2. Card
   - Sub-components: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter
   - Clean shadow dan border radius

3. Badge
   - Variants: default, secondary, outline, destructive

4. Input
   - With optional label
   - Error state styling
   - Helper text support

5. Textarea
   - With optional label
   - Error state styling
   - Min height yang proper

6. Section Header
   - Badge, title, description
   - Centered alignment
   - Reusable untuk semua sections

Gunakan:
- CSS variables yang sudah di-setup (var(--primary), dll)
- Class Variance Authority pattern (manual dengan @php)
- Tailwind utility classes
- Proper accessibility attributes"

Output: Blade Components

Component 1: Button

{{-- resources/views/components/ui/button.blade.php --}}

@props([
    'variant' => 'default',
    'size' => 'default',
    'type' => 'button',
    'href' => null,
    'disabled' => false,
])

@php
$baseClasses = 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50';

$variants = [
    'default' => 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
    'destructive' => 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
    'outline' => 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
    'secondary' => 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
    'ghost' => 'hover:bg-accent hover:text-accent-foreground',
    'link' => 'text-primary underline-offset-4 hover:underline',
];

$sizes = [
    'default' => 'h-10 px-4 py-2',
    'sm' => 'h-9 rounded-md px-3',
    'lg' => 'h-11 rounded-md px-8 text-base',
    'icon' => 'h-10 w-10',
];

$classes = $baseClasses . ' ' . ($variants[$variant] ?? $variants['default']) . ' ' . ($sizes[$size] ?? $sizes['default']);
@endphp

@if($href && !$disabled)
    <a
        href="{{ $href }}"
        {{ $attributes->merge(['class' => $classes]) }}
    >
        {{ $slot }}
    </a>
@else
    <button
        type="{{ $type }}"
        {{ $disabled ? 'disabled' : '' }}
        {{ $attributes->merge(['class' => $classes]) }}
    >
        {{ $slot }}
    </button>
@endif

Usage Examples:

{{-- Default button --}}
<x-ui.button>Click me</x-ui.button>

{{-- Variants --}}
<x-ui.button variant="secondary">Secondary</x-ui.button>
<x-ui.button variant="outline">Outline</x-ui.button>
<x-ui.button variant="ghost">Ghost</x-ui.button>
<x-ui.button variant="destructive">Delete</x-ui.button>

{{-- Sizes --}}
<x-ui.button size="sm">Small</x-ui.button>
<x-ui.button size="lg">Large</x-ui.button>

{{-- As link --}}
<x-ui.button href="/contact">Contact Us</x-ui.button>

{{-- Disabled --}}
<x-ui.button :disabled="true">Disabled</x-ui.button>

{{-- With icon --}}
<x-ui.button>
    Submit
    <svg class="ml-2 h-4 w-4">...</svg>
</x-ui.button>

Component 2: Card (Multiple Files)

{{-- resources/views/components/ui/card.blade.php --}}

@props(['class' => ''])

<div {{ $attributes->merge(['class' => 'rounded-xl border bg-card text-card-foreground shadow ' . $class]) }}>
    {{ $slot }}
</div>

{{-- resources/views/components/ui/card-header.blade.php --}}

@props(['class' => ''])

<div {{ $attributes->merge(['class' => 'flex flex-col space-y-1.5 p-6 ' . $class]) }}>
    {{ $slot }}
</div>

{{-- resources/views/components/ui/card-title.blade.php --}}

@props(['class' => ''])

<h3 {{ $attributes->merge(['class' => 'text-xl font-semibold leading-none tracking-tight ' . $class]) }}>
    {{ $slot }}
</h3>

{{-- resources/views/components/ui/card-description.blade.php --}}

@props(['class' => ''])

<p {{ $attributes->merge(['class' => 'text-sm text-muted-foreground ' . $class]) }}>
    {{ $slot }}
</p>

{{-- resources/views/components/ui/card-content.blade.php --}}

@props(['class' => ''])

<div {{ $attributes->merge(['class' => 'p-6 pt-0 ' . $class]) }}>
    {{ $slot }}
</div>

{{-- resources/views/components/ui/card-footer.blade.php --}}

@props(['class' => ''])

<div {{ $attributes->merge(['class' => 'flex items-center p-6 pt-0 ' . $class]) }}>
    {{ $slot }}
</div>

Usage:

<x-ui.card>
    <x-ui.card-header>
        <x-ui.card-title>Card Title</x-ui.card-title>
        <x-ui.card-description>Card description here</x-ui.card-description>
    </x-ui.card-header>
    <x-ui.card-content>
        <p>Card content goes here.</p>
    </x-ui.card-content>
    <x-ui.card-footer>
        <x-ui.button>Action</x-ui.button>
    </x-ui.card-footer>
</x-ui.card>

Component 3: Badge

{{-- resources/views/components/ui/badge.blade.php --}}

@props([
    'variant' => 'default',
])

@php
$baseClasses = 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2';

$variants = [
    'default' => 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
    'secondary' => 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
    'destructive' => 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
    'outline' => 'text-foreground',
];

$classes = $baseClasses . ' ' . ($variants[$variant] ?? $variants['default']);
@endphp

<span {{ $attributes->merge(['class' => $classes]) }}>
    {{ $slot }}
</span>

Usage:

<x-ui.badge>Default</x-ui.badge>
<x-ui.badge variant="secondary">Secondary</x-ui.badge>
<x-ui.badge variant="outline">Outline</x-ui.badge>
<x-ui.badge variant="destructive">Destructive</x-ui.badge>

Component 4: Input

{{-- resources/views/components/ui/input.blade.php --}}

@props([
    'type' => 'text',
    'label' => null,
    'error' => null,
    'helper' => null,
])

<div class="space-y-2">
    @if($label)
        <label {{ $attributes->only('id')->merge(['class' => 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70']) }}>
            {{ $label }}
        </label>
    @endif

    <input
        type="{{ $type }}"
        {{ $attributes->except(['id'])->merge([
            'class' => 'flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ' . ($error ? 'border-destructive focus-visible:ring-destructive' : 'border-input')
        ]) }}
    >

    @if($error)
        <p class="text-sm text-destructive">{{ $error }}</p>
    @elseif($helper)
        <p class="text-sm text-muted-foreground">{{ $helper }}</p>
    @endif
</div>

Usage:

{{-- Basic --}}
<x-ui.input name="email" placeholder="Enter your email" />

{{-- With label --}}
<x-ui.input name="email" label="Email Address" placeholder="[email protected]" />

{{-- With error --}}
<x-ui.input name="email" label="Email" :error="$errors->first('email')" />

{{-- With helper text --}}
<x-ui.input name="phone" label="Phone" helper="Optional, for faster response" />

{{-- Different types --}}
<x-ui.input type="password" name="password" label="Password" />
<x-ui.input type="email" name="email" label="Email" />

Component 5: Textarea

{{-- resources/views/components/ui/textarea.blade.php --}}

@props([
    'label' => null,
    'error' => null,
    'helper' => null,
    'rows' => 4,
])

<div class="space-y-2">
    @if($label)
        <label {{ $attributes->only('id')->merge(['class' => 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70']) }}>
            {{ $label }}
        </label>
    @endif

    <textarea
        rows="{{ $rows }}"
        {{ $attributes->except(['id'])->merge([
            'class' => 'flex min-h-[100px] w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ' . ($error ? 'border-destructive focus-visible:ring-destructive' : 'border-input')
        ]) }}
    >{{ $slot }}</textarea>

    @if($error)
        <p class="text-sm text-destructive">{{ $error }}</p>
    @elseif($helper)
        <p class="text-sm text-muted-foreground">{{ $helper }}</p>
    @endif
</div>

Usage:

{{-- Basic --}}
<x-ui.textarea name="message" placeholder="Your message..." />

{{-- With label --}}
<x-ui.textarea name="message" label="Message" rows="6" />

{{-- With error --}}
<x-ui.textarea name="message" label="Message" :error="$errors->first('message')" />

{{-- With default value --}}
<x-ui.textarea name="bio" label="Bio">{{ old('bio', $user->bio) }}</x-ui.textarea>

Component 6: Section Header

{{-- resources/views/components/ui/section-header.blade.php --}}

@props([
    'badge' => null,
    'title',
    'description' => null,
    'align' => 'center', // center, left
])

@php
$alignClasses = [
    'center' => 'text-center mx-auto',
    'left' => 'text-left',
];
$containerAlign = $alignClasses[$align] ?? $alignClasses['center'];
@endphp

<div class="max-w-2xl mb-12 {{ $containerAlign }}">
    @if($badge)
        <x-ui.badge variant="secondary" class="mb-4">{{ $badge }}</x-ui.badge>
    @endif

    <h2 class="text-3xl md:text-4xl font-bold tracking-tight mb-4">
        {{ $title }}
    </h2>

    @if($description)
        <p class="text-lg text-muted-foreground">
            {{ $description }}
        </p>
    @endif
</div>

Usage:

{{-- Centered (default) --}}
<x-ui.section-header
    badge="Our Services"
    title="What We Do Best"
    description="Comprehensive digital solutions for your business"
/>

{{-- Left aligned --}}
<x-ui.section-header
    align="left"
    title="About Us"
    description="Learn more about our company"
/>

{{-- Without badge --}}
<x-ui.section-header
    title="Meet Our Team"
/>

Create Layout Files

Base Layout:

{{-- resources/views/layouts/app.blade.php --}}

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    @php
        $settings = \\App\\Models\\Setting::instance();
    @endphp

    <title>{{ $title ?? $settings->company_name }}</title>
    <meta name="description" content="{{ $description ?? $settings->tagline }}">

    {{-- Favicon --}}
    @if($settings->favicon_url)
        <link rel="icon" href="{{ $settings->favicon_url }}">
    @endif

    {{-- Fonts --}}
    <link rel="preconnect" href="<https://fonts.bunny.net>">
    <link href="<https://fonts.bunny.net/css?family=inter:400,500,600,700&display=swap>" rel="stylesheet" />

    {{-- Vite Assets --}}
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="min-h-screen bg-background font-sans antialiased">
    <div class="relative flex min-h-screen flex-col">
        {{-- Navbar --}}
        @include('components.layout.navbar')

        {{-- Main Content --}}
        <main class="flex-1">
            {{ $slot }}
        </main>

        {{-- Footer --}}
        @include('components.layout.footer')
    </div>
</body>
</html>

Navbar Component:

{{-- resources/views/components/layout/navbar.blade.php --}}

@php
    $settings = \\App\\Models\\Setting::instance();
@endphp

<header
    x-data="{ mobileMenuOpen: false }"
    class="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
>
    <div class="container mx-auto px-4">
        <div class="flex h-16 items-center justify-between">
            {{-- Logo --}}
            <a href="{{ route('home') }}" class="flex items-center space-x-2">
                @if($settings->logo_url)
                    <img src="{{ $settings->logo_url }}" alt="{{ $settings->company_name }}" class="h-8">
                @else
                    <span class="text-xl font-bold">{{ $settings->company_name }}</span>
                @endif
            </a>

            {{-- Desktop Navigation --}}
            <nav class="hidden md:flex items-center space-x-6">
                <a href="{{ route('home') }}"
                   class="text-sm font-medium transition-colors hover:text-primary {{ request()->routeIs('home') ? 'text-foreground' : 'text-muted-foreground' }}">
                    Home
                </a>
                <a href="{{ route('about') }}"
                   class="text-sm font-medium transition-colors hover:text-primary {{ request()->routeIs('about') ? 'text-foreground' : 'text-muted-foreground' }}">
                    About
                </a>
                <a href="{{ route('contact') }}"
                   class="text-sm font-medium transition-colors hover:text-primary {{ request()->routeIs('contact') ? 'text-foreground' : 'text-muted-foreground' }}">
                    Contact
                </a>
            </nav>

            {{-- Desktop CTA --}}
            <div class="hidden md:flex items-center space-x-4">
                <x-ui.button href="{{ route('contact') }}" size="sm">
                    Get Started
                </x-ui.button>
            </div>

            {{-- Mobile Menu Button --}}
            <button
                @click="mobileMenuOpen = !mobileMenuOpen"
                class="md:hidden p-2 rounded-md hover:bg-accent"
                aria-label="Toggle menu"
            >
                <svg x-show="!mobileMenuOpen" xmlns="<http://www.w3.org/2000/svg>" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                </svg>
                <svg x-show="mobileMenuOpen" x-cloak xmlns="<http://www.w3.org/2000/svg>" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                </svg>
            </button>
        </div>
    </div>

    {{-- Mobile Menu --}}
    <div
        x-show="mobileMenuOpen"
        x-transition:enter="transition ease-out duration-200"
        x-transition:enter-start="opacity-0 -translate-y-1"
        x-transition:enter-end="opacity-100 translate-y-0"
        x-transition:leave="transition ease-in duration-150"
        x-transition:leave-start="opacity-100 translate-y-0"
        x-transition:leave-end="opacity-0 -translate-y-1"
        x-cloak
        class="md:hidden border-t bg-background"
    >
        <nav class="container mx-auto px-4 py-4 space-y-1">
            <a href="{{ route('home') }}"
               class="block py-2 px-3 rounded-md text-sm font-medium {{ request()->routeIs('home') ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent' }}">
                Home
            </a>
            <a href="{{ route('about') }}"
               class="block py-2 px-3 rounded-md text-sm font-medium {{ request()->routeIs('about') ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent' }}">
                About
            </a>
            <a href="{{ route('contact') }}"
               class="block py-2 px-3 rounded-md text-sm font-medium {{ request()->routeIs('contact') ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent' }}">
                Contact
            </a>
            <div class="pt-2">
                <x-ui.button href="{{ route('contact') }}" class="w-full">
                    Get Started
                </x-ui.button>
            </div>
        </nav>
    </div>
</header>

Footer Component:

{{-- resources/views/components/layout/footer.blade.php --}}

@php
    $settings = \\App\\Models\\Setting::instance();
@endphp

<footer class="border-t bg-muted/40">
    <div class="container mx-auto px-4 py-12">
        <div class="grid grid-cols-1 md:grid-cols-4 gap-8">
            {{-- Company Info --}}
            <div class="md:col-span-2">
                <h3 class="text-lg font-bold mb-4">{{ $settings->company_name }}</h3>
                <p class="text-muted-foreground mb-4 max-w-md">
                    {{ $settings->tagline }}
                </p>

                {{-- Social Links --}}
                @if($settings->hasSocialLinks())
                    <div class="flex space-x-4">
                        @if($settings->facebook_url)
                            <a href="{{ $settings->facebook_url }}" target="_blank" rel="noopener"
                               class="text-muted-foreground hover:text-foreground transition-colors">
                                <span class="sr-only">Facebook</span>
                                <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
                                    <path d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z"/>
                                </svg>
                            </a>
                        @endif
                        @if($settings->instagram_url)
                            <a href="{{ $settings->instagram_url }}" target="_blank" rel="noopener"
                               class="text-muted-foreground hover:text-foreground transition-colors">
                                <span class="sr-only">Instagram</span>
                                <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
                                    <path d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"/>
                                </svg>
                            </a>
                        @endif
                        @if($settings->twitter_url)
                            <a href="{{ $settings->twitter_url }}" target="_blank" rel="noopener"
                               class="text-muted-foreground hover:text-foreground transition-colors">
                                <span class="sr-only">Twitter</span>
                                <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
                                    <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84"/>
                                </svg>
                            </a>
                        @endif
                        @if($settings->linkedin_url)
                            <a href="{{ $settings->linkedin_url }}" target="_blank" rel="noopener"
                               class="text-muted-foreground hover:text-foreground transition-colors">
                                <span class="sr-only">LinkedIn</span>
                                <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
                                    <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
                                </svg>
                            </a>
                        @endif
                    </div>
                @endif
            </div>

            {{-- Quick Links --}}
            <div>
                <h4 class="font-semibold mb-4">Quick Links</h4>
                <ul class="space-y-2 text-sm">
                    <li>
                        <a href="{{ route('home') }}" class="text-muted-foreground hover:text-foreground transition-colors">
                            Home
                        </a>
                    </li>
                    <li>
                        <a href="{{ route('about') }}" class="text-muted-foreground hover:text-foreground transition-colors">
                            About Us
                        </a>
                    </li>
                    <li>
                        <a href="{{ route('contact') }}" class="text-muted-foreground hover:text-foreground transition-colors">
                            Contact
                        </a>
                    </li>
                </ul>
            </div>

            {{-- Contact Info --}}
            <div>
                <h4 class="font-semibold mb-4">Contact</h4>
                <ul class="space-y-2 text-sm text-muted-foreground">
                    @if($settings->email)
                        <li>
                            <a href="mailto:{{ $settings->email }}" class="hover:text-foreground transition-colors">
                                {{ $settings->email }}
                            </a>
                        </li>
                    @endif
                    @if($settings->phone)
                        <li>
                            <a href="tel:{{ $settings->phone }}" class="hover:text-foreground transition-colors">
                                {{ $settings->phone }}
                            </a>
                        </li>
                    @endif
                </ul>
            </div>
        </div>

        {{-- Copyright --}}
        <div class="mt-8 pt-8 border-t text-center text-sm text-muted-foreground">
            <p>&copy; {{ date('Y') }} {{ $settings->company_name }}. All rights reserved.</p>
        </div>
    </div>
</footer>

Add Alpine.js x-cloak Style

Update resources/css/app.css:

/* ... existing styles ... */

/* Alpine.js cloak */
[x-cloak] {
    display: none !important;
}

Struktur Folder Components

resources/views/
├── components/
│   ├── layout/
│   │   ├── navbar.blade.php       ← NEW
│   │   └── footer.blade.php       ← NEW
│   │
│   └── ui/
│       ├── badge.blade.php        ← NEW
│       ├── button.blade.php       ← NEW
│       ├── card.blade.php         ← NEW
│       ├── card-header.blade.php  ← NEW
│       ├── card-title.blade.php   ← NEW
│       ├── card-description.blade.php ← NEW
│       ├── card-content.blade.php ← NEW
│       ├── card-footer.blade.php  ← NEW
│       ├── input.blade.php        ← NEW
│       ├── textarea.blade.php     ← NEW
│       └── section-header.blade.php ← NEW
│
├── layouts/
│   └── app.blade.php              ← NEW
│
└── pages/
    ├── home.blade.php             (next section)
    ├── about.blade.php            (next section)
    └── contact.blade.php          (next section)

Summary Bagian 6

YANG SUDAH DIKERJAKAN:

✅ 11 UI Components created:
   ├── Button (6 variants, 4 sizes)
   ├── Card + 5 sub-components
   ├── Badge (4 variants)
   ├── Input (with label, error, helper)
   ├── Textarea (with label, error, helper)
   └── Section Header (badge, title, description)

✅ Layout Components:
   ├── Base layout (app.blade.php)
   ├── Navbar (with mobile menu)
   └── Footer (with social links)

✅ Features:
   ├── Shadcn-style design
   ├── CSS variables integration
   ├── Responsive (mobile menu)
   ├── Accessible (sr-only, aria)
   └── Alpine.js for interactivity

PROMPT YANG DIGUNAKAN: 1
└── Generate Shadcn-style Blade components

WAKTU:
├── Manual: ~2-3 jam
│   └── Research Shadcn: 30 menit
│   └── Port to Blade: 1.5-2 jam
│   └── Create layouts: 30-60 menit
│
├── Dengan AI: ~25 menit
│   └── Write prompt: 5 menit
│   └── Review & adjust: 15 menit
│   └── Test components: 5 menit
│
└── Saved: ~1.5-2.5 jam

Sekarang kita punya foundation yang solid. Components siap dipakai, tinggal build pages-nya.


Lanjut ke Bagian 7: Home Page — Hero & Services →

Bagian 8: Home Page — Testimonials & CTA Section

Sekarang kita lengkapi Home page dengan Testimonials dan Call-to-Action section.

Prompt: Testimonials Section

PROMPT KE ANTIGRAVITY:

"Buatkan Testimonials section untuk menampilkan testimoni client:

- Section header dengan badge 'Testimonials'
- Grid 2 kolom di desktop, 1 di mobile
- Setiap testimonial dalam Card:
  - Rating stars (1-5, filled based on $testimonial->rating)
  - Quote text
  - Author info: photo, name, subtitle (position at company)
- Photo menggunakan $testimonial->photo_url (sudah ada fallback)
- Subtle shadow dan clean design
- Max 4 testimonials

Blade file: resources/views/components/sections/testimonials.blade.php"

Output: Testimonials Section

{{-- resources/views/components/sections/testimonials.blade.php --}}

@props(['testimonials'])

<section class="py-20 md:py-28 bg-muted/50">
    <div class="container mx-auto px-4">
        {{-- Section Header --}}
        <x-ui.section-header
            badge="Testimonials"
            title="What Our Clients Say"
            description="Don't just take our word for it. Here's what our clients have to say about working with us."
        />

        {{-- Testimonials Grid --}}
        <div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-5xl mx-auto">
            @foreach($testimonials as $testimonial)
                <x-ui.card class="hover:shadow-md transition-shadow duration-300">
                    <x-ui.card-content class="pt-6">
                        {{-- Rating Stars --}}
                        <div class="flex items-center gap-1 mb-4">
                            @for($i = 1; $i <= 5; $i++)
                                <svg
                                    xmlns="<http://www.w3.org/2000/svg>"
                                    class="h-5 w-5 {{ $i <= $testimonial->rating ? 'text-yellow-400 fill-yellow-400' : 'text-gray-300' }}"
                                    viewBox="0 0 24 24"
                                    stroke="currentColor"
                                    stroke-width="2"
                                >
                                    <path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
                                </svg>
                            @endfor
                        </div>

                        {{-- Quote --}}
                        <blockquote class="text-foreground mb-6 leading-relaxed">
                            "{{ $testimonial->quote }}"
                        </blockquote>

                        {{-- Author --}}
                        <div class="flex items-center gap-4">
                            <img
                                src="{{ $testimonial->photo_url }}"
                                alt="{{ $testimonial->name }}"
                                class="w-12 h-12 rounded-full object-cover"
                                loading="lazy"
                            >
                            <div>
                                <div class="font-semibold text-foreground">
                                    {{ $testimonial->name }}
                                </div>
                                @if($testimonial->subtitle)
                                    <div class="text-sm text-muted-foreground">
                                        {{ $testimonial->subtitle }}
                                    </div>
                                @endif
                            </div>
                        </div>
                    </x-ui.card-content>
                </x-ui.card>
            @endforeach
        </div>
    </div>
</section>

Review:

  • ✅ Section header dengan badge
  • ✅ 2-column grid, responsive
  • ✅ Rating stars dengan conditional fill
  • ✅ Quote styling yang clean
  • ✅ Author dengan photo (fallback ready)
  • ✅ Lazy loading untuk images

Prompt: CTA Section

PROMPT KE ANTIGRAVITY:

"Buatkan Call-to-Action section untuk closing home page:

- Full width dengan gradient background (primary color)
- Centered content
- Headline yang compelling
- Short description
- 2 buttons: primary (inverted) dan outline
- Clean dan impactful design
- Responsive

Blade file: resources/views/components/sections/cta.blade.php"

Output: CTA Section

{{-- resources/views/components/sections/cta.blade.php --}}

@props(['settings'])

<section class="py-20 md:py-28 bg-gradient-to-r from-primary via-primary to-primary/90 relative overflow-hidden">
    {{-- Background Pattern --}}
    <div class="absolute inset-0 bg-[linear-gradient(to_right,#ffffff0a_1px,transparent_1px),linear-gradient(to_bottom,#ffffff0a_1px,transparent_1px)] bg-[size:24px_24px]"></div>

    {{-- Decorative Elements --}}
    <div class="absolute top-0 left-1/4 w-64 h-64 bg-white/5 rounded-full blur-3xl"></div>
    <div class="absolute bottom-0 right-1/4 w-64 h-64 bg-white/5 rounded-full blur-3xl"></div>

    <div class="relative container mx-auto px-4">
        <div class="max-w-3xl mx-auto text-center">
            {{-- Headline --}}
            <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-primary-foreground mb-6">
                Ready to Start Your Project?
            </h2>

            {{-- Description --}}
            <p class="text-lg md:text-xl text-primary-foreground/80 mb-10 max-w-2xl mx-auto">
                Let's discuss how we can help transform your ideas into reality.
                Get a free consultation and project estimate today.
            </p>

            {{-- CTA Buttons --}}
            <div class="flex flex-col sm:flex-row items-center justify-center gap-4">
                <x-ui.button
                    href="{{ route('contact') }}"
                    variant="secondary"
                    size="lg"
                    class="w-full sm:w-auto"
                >
                    Get Free Consultation
                    <svg xmlns="<http://www.w3.org/2000/svg>" class="ml-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
                    </svg>
                </x-ui.button>

                <x-ui.button
                    href="mailto:{{ $settings->email ?? '[email protected]' }}"
                    variant="outline"
                    size="lg"
                    class="w-full sm:w-auto border-primary-foreground/30 text-primary-foreground hover:bg-primary-foreground/10"
                >
                    <svg xmlns="<http://www.w3.org/2000/svg>" class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
                    </svg>
                    {{ $settings->email ?? '[email protected]' }}
                </x-ui.button>
            </div>
        </div>
    </div>
</section>

Review:

  • ✅ Gradient background dengan pattern
  • ✅ Decorative blur elements
  • ✅ Strong headline
  • ✅ Clear CTA buttons
  • ✅ Email button dengan icon
  • ✅ Fully responsive

Complete Home Page

Sekarang kita gabungkan semua sections:

{{-- resources/views/pages/home.blade.php --}}

<x-layouts.app title="Home - {{ $settings->company_name }}">

    {{-- Hero Section --}}
    <x-sections.hero :settings="$settings" />

    {{-- Services Section --}}
    <x-sections.services :services="$services" />

    {{-- Testimonials Section --}}
    <x-sections.testimonials :testimonials="$testimonials" />

    {{-- CTA Section --}}
    <x-sections.cta :settings="$settings" />

</x-layouts.app>

Perhatikan betapa clean-nya home page ini. Setiap section adalah component terpisah, mudah di-maintain dan di-reorder kalau perlu.

Test Home Page

# Pastikan server running
php artisan serve

# Pastikan Vite running
npm run dev

Buka http://localhost:8000 di browser. Kamu akan melihat:

  • Hero section dengan gradient dan stats
  • Services grid dengan 6 cards
  • Testimonials dengan 4 reviews
  • CTA section

Fix: Update Layout Component Path

Kalau ada error "component not found", pastikan layout menggunakan path yang benar:

{{-- Jika pakai x-layouts.app --}}
{{-- File harus di: resources/views/components/layouts/app.blade.php --}}

{{-- ATAU jika pakai @extends --}}
@extends('layouts.app')

Untuk tutorial ini, kita pakai component approach. Pindahkan layout:

# Buat folder
mkdir -p resources/views/components/layouts

# Pindahkan file (atau copy)
mv resources/views/layouts/app.blade.php resources/views/components/layouts/app.blade.php

Struktur Folder Sekarang

resources/views/
├── components/
│   ├── icons/
│   │   └── service-icon.blade.php
│   ├── layout/
│   │   ├── navbar.blade.php
│   │   └── footer.blade.php
│   ├── layouts/
│   │   └── app.blade.php              ← Moved here
│   ├── sections/
│   │   ├── cta.blade.php              ← NEW
│   │   ├── hero.blade.php
│   │   ├── services.blade.php
│   │   └── testimonials.blade.php     ← NEW
│   └── ui/
│       └── ... (existing)
│
└── pages/
    └── home.blade.php                  ← NEW

Summary Bagian 8

YANG SUDAH DIKERJAKAN:

✅ Testimonials Section:
   ├── Section header
   ├── 2-column responsive grid
   ├── Rating stars (dynamic)
   ├── Quote + author info
   └── Photo dengan fallback

✅ CTA Section:
   ├── Gradient background
   ├── Decorative elements
   ├── Compelling headline
   ├── 2 CTA buttons
   └── Email integration

✅ Complete Home Page:
   ├── Hero
   ├── Services
   ├── Testimonials
   └── CTA

PROMPTS YANG DIGUNAKAN: 2
├── Testimonials section prompt
└── CTA section prompt

WAKTU:
├── Manual: ~1.5-2 jam
├── Dengan AI: ~15 menit
└── Saved: ~1-1.5 jam

Home page selesai! 🎉 Next, kita build About page.


Lanjut ke Bagian 9: About Page — Story & Team →

Bagian 8: Home Page — Testimonials & CTA Section

Sekarang kita lengkapi Home page dengan Testimonials dan Call-to-Action section.

Prompt: Testimonials Section

PROMPT KE ANTIGRAVITY:

"Buatkan Testimonials section untuk menampilkan testimoni client:

- Section header dengan badge 'Testimonials'
- Grid 2 kolom di desktop, 1 di mobile
- Setiap testimonial dalam Card:
  - Rating stars (1-5, filled based on $testimonial->rating)
  - Quote text
  - Author info: photo, name, subtitle (position at company)
- Photo menggunakan $testimonial->photo_url (sudah ada fallback)
- Subtle shadow dan clean design
- Max 4 testimonials

Blade file: resources/views/components/sections/testimonials.blade.php"

Output: Testimonials Section

{{-- resources/views/components/sections/testimonials.blade.php --}}

@props(['testimonials'])

<section class="py-20 md:py-28 bg-muted/50">
    <div class="container mx-auto px-4">
        {{-- Section Header --}}
        <x-ui.section-header
            badge="Testimonials"
            title="What Our Clients Say"
            description="Don't just take our word for it. Here's what our clients have to say about working with us."
        />

        {{-- Testimonials Grid --}}
        <div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-5xl mx-auto">
            @foreach($testimonials as $testimonial)
                <x-ui.card class="hover:shadow-md transition-shadow duration-300">
                    <x-ui.card-content class="pt-6">
                        {{-- Rating Stars --}}
                        <div class="flex items-center gap-1 mb-4">
                            @for($i = 1; $i <= 5; $i++)
                                <svg
                                    xmlns="<http://www.w3.org/2000/svg>"
                                    class="h-5 w-5 {{ $i <= $testimonial->rating ? 'text-yellow-400 fill-yellow-400' : 'text-gray-300' }}"
                                    viewBox="0 0 24 24"
                                    stroke="currentColor"
                                    stroke-width="2"
                                >
                                    <path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
                                </svg>
                            @endfor
                        </div>

                        {{-- Quote --}}
                        <blockquote class="text-foreground mb-6 leading-relaxed">
                            "{{ $testimonial->quote }}"
                        </blockquote>

                        {{-- Author --}}
                        <div class="flex items-center gap-4">
                            <img
                                src="{{ $testimonial->photo_url }}"
                                alt="{{ $testimonial->name }}"
                                class="w-12 h-12 rounded-full object-cover"
                                loading="lazy"
                            >
                            <div>
                                <div class="font-semibold text-foreground">
                                    {{ $testimonial->name }}
                                </div>
                                @if($testimonial->subtitle)
                                    <div class="text-sm text-muted-foreground">
                                        {{ $testimonial->subtitle }}
                                    </div>
                                @endif
                            </div>
                        </div>
                    </x-ui.card-content>
                </x-ui.card>
            @endforeach
        </div>
    </div>
</section>

Review:

  • ✅ Section header dengan badge
  • ✅ 2-column grid, responsive
  • ✅ Rating stars dengan conditional fill
  • ✅ Quote styling yang clean
  • ✅ Author dengan photo (fallback ready)
  • ✅ Lazy loading untuk images

Prompt: CTA Section

PROMPT KE ANTIGRAVITY:

"Buatkan Call-to-Action section untuk closing home page:

- Full width dengan gradient background (primary color)
- Centered content
- Headline yang compelling
- Short description
- 2 buttons: primary (inverted) dan outline
- Clean dan impactful design
- Responsive

Blade file: resources/views/components/sections/cta.blade.php"

Output: CTA Section

{{-- resources/views/components/sections/cta.blade.php --}}

@props(['settings'])

<section class="py-20 md:py-28 bg-gradient-to-r from-primary via-primary to-primary/90 relative overflow-hidden">
    {{-- Background Pattern --}}
    <div class="absolute inset-0 bg-[linear-gradient(to_right,#ffffff0a_1px,transparent_1px),linear-gradient(to_bottom,#ffffff0a_1px,transparent_1px)] bg-[size:24px_24px]"></div>

    {{-- Decorative Elements --}}
    <div class="absolute top-0 left-1/4 w-64 h-64 bg-white/5 rounded-full blur-3xl"></div>
    <div class="absolute bottom-0 right-1/4 w-64 h-64 bg-white/5 rounded-full blur-3xl"></div>

    <div class="relative container mx-auto px-4">
        <div class="max-w-3xl mx-auto text-center">
            {{-- Headline --}}
            <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-primary-foreground mb-6">
                Ready to Start Your Project?
            </h2>

            {{-- Description --}}
            <p class="text-lg md:text-xl text-primary-foreground/80 mb-10 max-w-2xl mx-auto">
                Let's discuss how we can help transform your ideas into reality.
                Get a free consultation and project estimate today.
            </p>

            {{-- CTA Buttons --}}
            <div class="flex flex-col sm:flex-row items-center justify-center gap-4">
                <x-ui.button
                    href="{{ route('contact') }}"
                    variant="secondary"
                    size="lg"
                    class="w-full sm:w-auto"
                >
                    Get Free Consultation
                    <svg xmlns="<http://www.w3.org/2000/svg>" class="ml-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
                    </svg>
                </x-ui.button>

                <x-ui.button
                    href="mailto:{{ $settings->email ?? '[email protected]' }}"
                    variant="outline"
                    size="lg"
                    class="w-full sm:w-auto border-primary-foreground/30 text-primary-foreground hover:bg-primary-foreground/10"
                >
                    <svg xmlns="<http://www.w3.org/2000/svg>" class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
                    </svg>
                    {{ $settings->email ?? '[email protected]' }}
                </x-ui.button>
            </div>
        </div>
    </div>
</section>

Review:

  • ✅ Gradient background dengan pattern
  • ✅ Decorative blur elements
  • ✅ Strong headline
  • ✅ Clear CTA buttons
  • ✅ Email button dengan icon
  • ✅ Fully responsive

Complete Home Page

Sekarang kita gabungkan semua sections:

{{-- resources/views/pages/home.blade.php --}}

<x-layouts.app title="Home - {{ $settings->company_name }}">

    {{-- Hero Section --}}
    <x-sections.hero :settings="$settings" />

    {{-- Services Section --}}
    <x-sections.services :services="$services" />

    {{-- Testimonials Section --}}
    <x-sections.testimonials :testimonials="$testimonials" />

    {{-- CTA Section --}}
    <x-sections.cta :settings="$settings" />

</x-layouts.app>

Perhatikan betapa clean-nya home page ini. Setiap section adalah component terpisah, mudah di-maintain dan di-reorder kalau perlu.

Test Home Page

# Pastikan server running
php artisan serve

# Pastikan Vite running
npm run dev

Buka http://localhost:8000 di browser. Kamu akan melihat:

  • Hero section dengan gradient dan stats
  • Services grid dengan 6 cards
  • Testimonials dengan 4 reviews
  • CTA section

Fix: Update Layout Component Path

Kalau ada error "component not found", pastikan layout menggunakan path yang benar:

{{-- Jika pakai x-layouts.app --}}
{{-- File harus di: resources/views/components/layouts/app.blade.php --}}

{{-- ATAU jika pakai @extends --}}
@extends('layouts.app')

Untuk tutorial ini, kita pakai component approach. Pindahkan layout:

# Buat folder
mkdir -p resources/views/components/layouts

# Pindahkan file (atau copy)
mv resources/views/layouts/app.blade.php resources/views/components/layouts/app.blade.php

Struktur Folder Sekarang

resources/views/
├── components/
│   ├── icons/
│   │   └── service-icon.blade.php
│   ├── layout/
│   │   ├── navbar.blade.php
│   │   └── footer.blade.php
│   ├── layouts/
│   │   └── app.blade.php              ← Moved here
│   ├── sections/
│   │   ├── cta.blade.php              ← NEW
│   │   ├── hero.blade.php
│   │   ├── services.blade.php
│   │   └── testimonials.blade.php     ← NEW
│   └── ui/
│       └── ... (existing)
│
└── pages/
    └── home.blade.php                  ← NEW

Summary Bagian 8

YANG SUDAH DIKERJAKAN:

✅ Testimonials Section:
   ├── Section header
   ├── 2-column responsive grid
   ├── Rating stars (dynamic)
   ├── Quote + author info
   └── Photo dengan fallback

✅ CTA Section:
   ├── Gradient background
   ├── Decorative elements
   ├── Compelling headline
   ├── 2 CTA buttons
   └── Email integration

✅ Complete Home Page:
   ├── Hero
   ├── Services
   ├── Testimonials
   └── CTA

PROMPTS YANG DIGUNAKAN: 2
├── Testimonials section prompt
└── CTA section prompt

WAKTU:
├── Manual: ~1.5-2 jam
├── Dengan AI: ~15 menit
└── Saved: ~1-1.5 jam

Home page selesai! 🎉 Next, kita build About page.


Lanjut ke Bagian 9: About Page — Story & Team →

Bagian 9: About Page — Story & Team

Sekarang kita build About page dengan Company Story, Values, dan Team sections.

Create AboutController

php artisan make:controller AboutController

<?php
// app/Http/Controllers/AboutController.php

namespace App\\Http\\Controllers;

use App\\Models\\Setting;
use App\\Models\\TeamMember;
use Illuminate\\View\\View;

class AboutController extends Controller
{
    /**
     * Display the about page.
     */
    public function index(): View
    {
        return view('pages.about', [
            'settings' => Setting::instance(),
            'teamMembers' => TeamMember::forDisplay()->get(),
        ]);
    }
}

Prompt: About Page Sections

PROMPT KE ANTIGRAVITY:

"Buatkan About page dengan 3 sections:

1. Page Header
   - Badge 'About Us'
   - Title 'Our Story'
   - Subtitle

2. Story Section
   - 2 columns: text content (left) dan image (right)
   - 3 paragraphs tentang company journey
   - Image placeholder dengan gradient background
   - Responsive (stack on mobile)

3. Values Section
   - Background muted
   - 3 values dengan icon, title, description
   - Values: Quality First, Client Partnership, Continuous Innovation
   - Centered layout

4. Team Section
   - Grid 4 kolom (responsive: 1 → 2 → 4)
   - Card untuk setiap member: photo, name, position, short bio
   - Social links (LinkedIn, Twitter)
   - Data dari $teamMembers

Blade file: resources/views/pages/about.blade.php"

Output: About Page

{{-- resources/views/pages/about.blade.php --}}

<x-layouts.app title="About Us - {{ $settings->company_name }}">

    {{-- Page Header --}}
    <section class="py-20 bg-muted/30">
        <div class="container mx-auto px-4">
            <div class="max-w-3xl mx-auto text-center">
                <x-ui.badge variant="secondary" class="mb-4">About Us</x-ui.badge>
                <h1 class="text-4xl md:text-5xl font-bold tracking-tight mb-6">
                    Our Story
                </h1>
                <p class="text-lg text-muted-foreground">
                    Building digital excellence since 2016
                </p>
            </div>
        </div>
    </section>

    {{-- Story Section --}}
    <section class="py-20 md:py-28">
        <div class="container mx-auto px-4">
            <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
                {{-- Text Content --}}
                <div>
                    <h2 class="text-3xl md:text-4xl font-bold tracking-tight mb-6">
                        From Small Team to Industry Leader
                    </h2>

                    <div class="space-y-4 text-muted-foreground leading-relaxed">
                        <p>
                            {{ $settings->company_name }} didirikan dengan misi sederhana: membantu
                            bisnis sukses di era digital. Apa yang dimulai sebagai tim kecil
                            beranggotakan dua orang di sebuah co-working space, kini telah
                            berkembang menjadi digital agency full-service yang melayani klien
                            di seluruh Indonesia dan Asia Tenggara.
                        </p>

                        <p>
                            Kami percaya bahwa teknologi hebat harus dapat diakses oleh bisnis
                            dari semua ukuran. Itulah mengapa kami fokus memberikan solusi
                            berkualitas enterprise dengan harga yang kompetitif, tanpa
                            mengorbankan kualitas atau perhatian terhadap detail.
                        </p>

                        <p>
                            Hingga saat ini, kami bangga telah membantu lebih dari 150 bisnis
                            mentransformasi kehadiran digital mereka, dari startup lokal hingga
                            korporasi multinasional. Tim ahli kami menggabungkan keahlian
                            beragam dalam desain, development, dan strategi untuk memberikan
                            hasil yang melampaui ekspektasi.
                        </p>
                    </div>
                </div>

                {{-- Image --}}
                <div class="relative">
                    <div class="aspect-square lg:aspect-[4/5] rounded-2xl overflow-hidden bg-gradient-to-br from-primary/20 via-primary/10 to-transparent">
                        <img
                            src="<https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800&q=80>"
                            alt="Our Team at Work"
                            class="w-full h-full object-cover"
                            loading="lazy"
                        >
                    </div>

                    {{-- Decorative Element --}}
                    <div class="absolute -bottom-6 -left-6 w-24 h-24 bg-primary/10 rounded-2xl -z-10"></div>
                    <div class="absolute -top-6 -right-6 w-32 h-32 bg-primary/5 rounded-full -z-10"></div>
                </div>
            </div>
        </div>
    </section>

    {{-- Values Section --}}
    <section class="py-20 md:py-28 bg-muted/30">
        <div class="container mx-auto px-4">
            {{-- Section Header --}}
            <x-ui.section-header
                title="Our Values"
                description="The principles that guide everything we do"
            />

            {{-- Values Grid --}}
            <div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
                {{-- Value 1: Quality --}}
                <div class="text-center">
                    <div class="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-6">
                        <svg xmlns="<http://www.w3.org/2000/svg>" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
                        </svg>
                    </div>
                    <h3 class="text-xl font-semibold mb-3">Quality First</h3>
                    <p class="text-muted-foreground leading-relaxed">
                        Kami tidak pernah berkompromi dengan kualitas. Setiap baris kode,
                        setiap pixel, dan setiap interaksi dibuat dengan penuh perhatian
                        dan standar tertinggi.
                    </p>
                </div>

                {{-- Value 2: Partnership --}}
                <div class="text-center">
                    <div class="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-6">
                        <svg xmlns="<http://www.w3.org/2000/svg>" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
                        </svg>
                    </div>
                    <h3 class="text-xl font-semibold mb-3">Client Partnership</h3>
                    <p class="text-muted-foreground leading-relaxed">
                        Kami tidak hanya bekerja untuk klien — kami bekerja bersama klien.
                        Kesuksesan Anda adalah kesuksesan kami. Setiap project adalah
                        kolaborasi yang bermakna.
                    </p>
                </div>

                {{-- Value 3: Innovation --}}
                <div class="text-center">
                    <div class="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-6">
                        <svg xmlns="<http://www.w3.org/2000/svg>" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
                        </svg>
                    </div>
                    <h3 class="text-xl font-semibold mb-3">Continuous Innovation</h3>
                    <p class="text-muted-foreground leading-relaxed">
                        Teknologi terus berkembang, begitu juga kami. Kami selalu
                        mengikuti tren terbaru untuk memberikan solusi terbaik
                        dan paling relevan untuk Anda.
                    </p>
                </div>
            </div>
        </div>
    </section>

    {{-- Team Section --}}
    <section class="py-20 md:py-28">
        <div class="container mx-auto px-4">
            {{-- Section Header --}}
            <x-ui.section-header
                badge="Our Team"
                title="Meet the People Behind Our Success"
                description="Talented individuals who make the magic happen"
            />

            {{-- Team Grid --}}
            <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
                @foreach($teamMembers as $member)
                    <x-ui.card class="text-center group hover:shadow-lg transition-all duration-300">
                        <x-ui.card-content class="pt-6">
                            {{-- Photo --}}
                            <div class="relative w-28 h-28 mx-auto mb-4">
                                <img
                                    src="{{ $member->photo_url }}"
                                    alt="{{ $member->name }}"
                                    class="w-full h-full rounded-full object-cover ring-4 ring-background shadow-lg"
                                    loading="lazy"
                                >
                            </div>

                            {{-- Info --}}
                            <h3 class="font-semibold text-lg mb-1">{{ $member->name }}</h3>
                            <p class="text-sm text-primary mb-3">{{ $member->position }}</p>

                            {{-- Bio --}}
                            @if($member->bio)
                                <p class="text-sm text-muted-foreground mb-4 line-clamp-3">
                                    {{ $member->short_bio }}
                                </p>
                            @endif

                            {{-- Social Links --}}
                            @if($member->hasSocialLinks())
                                <div class="flex justify-center gap-3 pt-2">
                                    @if($member->linkedin_url)
                                        <a
                                            href="{{ $member->linkedin_url }}"
                                            target="_blank"
                                            rel="noopener"
                                            class="text-muted-foreground hover:text-primary transition-colors"
                                            aria-label="{{ $member->name }} on LinkedIn"
                                        >
                                            <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                                                <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
                                            </svg>
                                        </a>
                                    @endif

                                    @if($member->twitter_url)
                                        <a
                                            href="{{ $member->twitter_url }}"
                                            target="_blank"
                                            rel="noopener"
                                            class="text-muted-foreground hover:text-primary transition-colors"
                                            aria-label="{{ $member->name }} on Twitter"
                                        >
                                            <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                                                <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84"/>
                                            </svg>
                                        </a>
                                    @endif

                                    @if($member->email)
                                        <a
                                            href="mailto:{{ $member->email }}"
                                            class="text-muted-foreground hover:text-primary transition-colors"
                                            aria-label="Email {{ $member->name }}"
                                        >
                                            <svg xmlns="<http://www.w3.org/2000/svg>" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                                <path stroke-linecap="round" stroke-linejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
                                            </svg>
                                        </a>
                                    @endif
                                </div>
                            @endif
                        </x-ui.card-content>
                    </x-ui.card>
                @endforeach
            </div>
        </div>
    </section>

    {{-- CTA Section (reuse from home) --}}
    <x-sections.cta :settings="$settings" />

</x-layouts.app>

Review:

  • ✅ Page header dengan badge
  • ✅ Story section 2-column dengan decorative elements
  • ✅ Values section dengan 3 items
  • ✅ Team grid responsive (1 → 2 → 4)
  • ✅ Team cards dengan photo, info, social links
  • ✅ Reuse CTA section dari home
  • ✅ Proper lazy loading dan accessibility

Test About Page

Buka http://localhost:8000/about:

  • Page header dengan "Our Story"
  • Company story dengan image
  • 3 values dengan icons
  • 4 team members dengan photo (UI Avatars fallback)
  • CTA section

Struktur Folder Update

resources/views/
├── components/
│   ├── icons/
│   ├── layout/
│   ├── layouts/
│   ├── sections/
│   │   ├── cta.blade.php
│   │   ├── hero.blade.php
│   │   ├── services.blade.php
│   │   └── testimonials.blade.php
│   └── ui/
│
└── pages/
    ├── about.blade.php                 ← NEW
    └── home.blade.php

Summary Bagian 9

YANG SUDAH DIKERJAKAN:

✅ AboutController dengan SRP
✅ About Page dengan 4 sections:
   ├── Page Header (badge, title, subtitle)
   ├── Story Section (2-col, text + image)
   ├── Values Section (3 values dengan icons)
   └── Team Section (4-col grid, cards)

✅ Features:
   ├── Responsive layout
   ├── Decorative elements
   ├── Team photo dengan fallback
   ├── Social links untuk team
   ├── Reusable CTA section
   └── Proper accessibility

PROMPTS YANG DIGUNAKAN: 1
└── About page sections prompt

WAKTU:
├── Manual: ~2-3 jam
├── Dengan AI: ~20 menit
└── Saved: ~1.5-2.5 jam

About page selesai! Tinggal Contact page.


Lanjut ke Bagian 10: Contact Page — Form & Info →

Bagian 10: Contact Page — Form & Info

Sekarang kita build Contact page — halaman terakhir yang akan kita buat. Di sini ada form yang save ke database.

Create ContactController

php artisan make:controller ContactController

<?php
// app/Http/Controllers/ContactController.php

namespace App\\Http\\Controllers;

use App\\Models\\Contact;
use App\\Models\\Setting;
use App\\Http\\Requests\\ContactRequest;
use Illuminate\\Http\\RedirectResponse;
use Illuminate\\View\\View;

class ContactController extends Controller
{
    /**
     * Display the contact page.
     */
    public function index(): View
    {
        return view('pages.contact', [
            'settings' => Setting::instance(),
        ]);
    }

    /**
     * Store a new contact message.
     */
    public function store(ContactRequest $request): RedirectResponse
    {
        Contact::create($request->validated());

        return redirect()
            ->route('contact')
            ->with('success', 'Terima kasih! Pesan Anda telah terkirim. Kami akan menghubungi Anda segera.');
    }
}

Create Form Request untuk Validation

php artisan make:request ContactRequest

<?php
// app/Http/Requests/ContactRequest.php

namespace App\\Http\\Requests;

use Illuminate\\Foundation\\Http\\FormRequest;

class ContactRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true; // Public form, anyone can submit
    }

    /**
     * Get the validation rules that apply to the request.
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'max:255'],
            'phone' => ['nullable', 'string', 'max:20'],
            'subject' => ['required', 'string', 'max:255'],
            'message' => ['required', 'string', 'max:2000'],
        ];
    }

    /**
     * Get custom error messages.
     */
    public function messages(): array
    {
        return [
            'name.required' => 'Mohon masukkan nama Anda.',
            'email.required' => 'Mohon masukkan alamat email.',
            'email.email' => 'Format email tidak valid.',
            'subject.required' => 'Mohon masukkan subjek pesan.',
            'message.required' => 'Mohon masukkan pesan Anda.',
            'message.max' => 'Pesan maksimal 2000 karakter.',
        ];
    }
}

SRP Note: Validation logic dipisah ke FormRequest, bukan di controller. Ini best practice Laravel.

Prompt: Contact Page

PROMPT KE ANTIGRAVITY:

"Buatkan Contact page dengan layout 2 kolom:

LEFT COLUMN (Form):
- Heading 'Send Us a Message'
- Success message alert (jika ada session 'success')
- Form fields menggunakan Input dan Textarea components:
  - Name (required)
  - Email (required)
  - Phone (optional)
  - Subject (required)
  - Message (required, textarea)
- Submit button
- Form action ke route 'contact.store' dengan method POST

RIGHT COLUMN (Info):
- Heading 'Contact Information'
- 3 info items dengan icon:
  - Email (dari $settings->email)
  - Phone (dari $settings->phone)
  - Address (dari $settings->address)
- Google Maps embed (dari $settings->google_maps_embed)

Page header dengan badge 'Contact Us', title, description.
Responsive: stack columns on mobile.

Blade file: resources/views/pages/contact.blade.php"

Output: Contact Page

{{-- resources/views/pages/contact.blade.php --}}

<x-layouts.app title="Contact Us - {{ $settings->company_name }}">

    {{-- Page Header --}}
    <section class="py-20 bg-muted/30">
        <div class="container mx-auto px-4">
            <div class="max-w-3xl mx-auto text-center">
                <x-ui.badge variant="secondary" class="mb-4">Contact Us</x-ui.badge>
                <h1 class="text-4xl md:text-5xl font-bold tracking-tight mb-6">
                    Get In Touch
                </h1>
                <p class="text-lg text-muted-foreground">
                    Have a project in mind? We'd love to hear from you.
                    Send us a message and we'll respond as soon as possible.
                </p>
            </div>
        </div>
    </section>

    {{-- Contact Section --}}
    <section class="py-20 md:py-28">
        <div class="container mx-auto px-4">
            <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16">

                {{-- Contact Form --}}
                <div>
                    <h2 class="text-2xl font-bold mb-6">Send Us a Message</h2>

                    {{-- Success Message --}}
                    @if(session('success'))
                        <div class="mb-6 p-4 rounded-lg bg-green-50 border border-green-200 text-green-800">
                            <div class="flex items-center gap-3">
                                <svg xmlns="<http://www.w3.org/2000/svg>" class="h-5 w-5 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                    <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
                                </svg>
                                <span>{{ session('success') }}</span>
                            </div>
                        </div>
                    @endif

                    {{-- Form --}}
                    <form action="{{ route('contact.store') }}" method="POST" class="space-y-6">
                        @csrf

                        {{-- Name & Email Row --}}
                        <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
                            <x-ui.input
                                type="text"
                                name="name"
                                label="Your Name"
                                placeholder="John Doe"
                                value="{{ old('name') }}"
                                :error="$errors->first('name')"
                                required
                            />

                            <x-ui.input
                                type="email"
                                name="email"
                                label="Email Address"
                                placeholder="[email protected]"
                                value="{{ old('email') }}"
                                :error="$errors->first('email')"
                                required
                            />
                        </div>

                        {{-- Phone & Subject Row --}}
                        <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
                            <x-ui.input
                                type="tel"
                                name="phone"
                                label="Phone Number"
                                placeholder="+62 812 3456 7890"
                                value="{{ old('phone') }}"
                                :error="$errors->first('phone')"
                                helper="Optional"
                            />

                            <x-ui.input
                                type="text"
                                name="subject"
                                label="Subject"
                                placeholder="Project Inquiry"
                                value="{{ old('subject') }}"
                                :error="$errors->first('subject')"
                                required
                            />
                        </div>

                        {{-- Message --}}
                        <x-ui.textarea
                            name="message"
                            label="Your Message"
                            placeholder="Tell us about your project or inquiry..."
                            rows="6"
                            :error="$errors->first('message')"
                            required
                        >{{ old('message') }}</x-ui.textarea>

                        {{-- Submit Button --}}
                        <x-ui.button type="submit" size="lg" class="w-full sm:w-auto">
                            Send Message
                            <svg xmlns="<http://www.w3.org/2000/svg>" class="ml-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                <path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
                            </svg>
                        </x-ui.button>
                    </form>
                </div>

                {{-- Contact Info --}}
                <div>
                    <h2 class="text-2xl font-bold mb-6">Contact Information</h2>

                    <div class="space-y-6">
                        {{-- Email --}}
                        @if($settings->email)
                            <div class="flex items-start gap-4">
                                <div class="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
                                    <svg xmlns="<http://www.w3.org/2000/svg>" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                        <path stroke-linecap="round" stroke-linejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
                                    </svg>
                                </div>
                                <div>
                                    <h3 class="font-semibold mb-1">Email</h3>
                                    <a
                                        href="mailto:{{ $settings->email }}"
                                        class="text-muted-foreground hover:text-primary transition-colors"
                                    >
                                        {{ $settings->email }}
                                    </a>
                                </div>
                            </div>
                        @endif

                        {{-- Phone --}}
                        @if($settings->phone)
                            <div class="flex items-start gap-4">
                                <div class="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
                                    <svg xmlns="<http://www.w3.org/2000/svg>" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                        <path stroke-linecap="round" stroke-linejoin="round" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
                                    </svg>
                                </div>
                                <div>
                                    <h3 class="font-semibold mb-1">Phone</h3>
                                    <a
                                        href="tel:{{ $settings->phone }}"
                                        class="text-muted-foreground hover:text-primary transition-colors"
                                    >
                                        {{ $settings->phone }}
                                    </a>
                                </div>
                            </div>
                        @endif

                        {{-- Address --}}
                        @if($settings->address)
                            <div class="flex items-start gap-4">
                                <div class="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
                                    <svg xmlns="<http://www.w3.org/2000/svg>" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                                        <path stroke-linecap="round" stroke-linejoin="round" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
                                        <path stroke-linecap="round" stroke-linejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
                                    </svg>
                                </div>
                                <div>
                                    <h3 class="font-semibold mb-1">Address</h3>
                                    <p class="text-muted-foreground">
                                        {{ $settings->address }}
                                    </p>
                                </div>
                            </div>
                        @endif
                    </div>

                    {{-- Google Maps --}}
                    @if($settings->google_maps_embed)
                        <div class="mt-8 rounded-lg overflow-hidden border">
                            {!! $settings->google_maps_embed !!}
                        </div>
                    @endif

                    {{-- Social Links --}}
                    @if($settings->hasSocialLinks())
                        <div class="mt-8">
                            <h3 class="font-semibold mb-4">Follow Us</h3>
                            <div class="flex gap-4">
                                @if($settings->facebook_url)
                                    <a
                                        href="{{ $settings->facebook_url }}"
                                        target="_blank"
                                        rel="noopener"
                                        class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors"
                                        aria-label="Facebook"
                                    >
                                        <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                                            <path d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z"/>
                                        </svg>
                                    </a>
                                @endif
                                @if($settings->instagram_url)
                                    <a
                                        href="{{ $settings->instagram_url }}"
                                        target="_blank"
                                        rel="noopener"
                                        class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors"
                                        aria-label="Instagram"
                                    >
                                        <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                                            <path d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"/>
                                        </svg>
                                    </a>
                                @endif
                                @if($settings->twitter_url)
                                    <a
                                        href="{{ $settings->twitter_url }}"
                                        target="_blank"
                                        rel="noopener"
                                        class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors"
                                        aria-label="Twitter"
                                    >
                                        <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                                            <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84"/>
                                        </svg>
                                    </a>
                                @endif
                                @if($settings->linkedin_url)
                                    <a
                                        href="{{ $settings->linkedin_url }}"
                                        target="_blank"
                                        rel="noopener"
                                        class="w-10 h-10 rounded-lg bg-muted flex items-center justify-center text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors"
                                        aria-label="LinkedIn"
                                    >
                                        <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                                            <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
                                        </svg>
                                    </a>
                                @endif
                            </div>
                        </div>
                    @endif
                </div>
            </div>
        </div>
    </section>

</x-layouts.app>

Test Contact Page

  1. Buka http://localhost:8000/contact
  2. Test form dengan data valid → should redirect dengan success message
  3. Test form dengan data invalid → should show error messages
  4. Check database → contact record tersimpan
php artisan tinker

use App\\Models\\Contact;
Contact::all(); // Should show submitted contacts
Contact::unread()->count(); // Count unread messages

Struktur Folder Final

resources/views/
├── components/
│   ├── icons/
│   │   └── service-icon.blade.php
│   ├── layout/
│   │   ├── navbar.blade.php
│   │   └── footer.blade.php
│   ├── layouts/
│   │   └── app.blade.php
│   ├── sections/
│   │   ├── cta.blade.php
│   │   ├── hero.blade.php
│   │   ├── services.blade.php
│   │   └── testimonials.blade.php
│   └── ui/
│       ├── badge.blade.php
│       ├── button.blade.php
│       ├── card.blade.php
│       ├── card-*.blade.php (5 files)
│       ├── input.blade.php
│       ├── section-header.blade.php
│       └── textarea.blade.php
│
└── pages/
    ├── about.blade.php
    ├── contact.blade.php               ← NEW
    └── home.blade.php

Summary Bagian 10

YANG SUDAH DIKERJAKAN:

✅ ContactController dengan SRP:
   ├── index() → display page
   └── store() → save to database

✅ ContactRequest untuk validation:
   ├── Validation rules
   └── Custom error messages (Bahasa Indonesia)

✅ Contact Page:
   ├── Page header
   ├── Contact form (5 fields)
   ├── Success message alert
   ├── Contact info (email, phone, address)
   ├── Google Maps embed
   └── Social links

✅ Features:
   ├── Form validation dengan error display
   ├── old() untuk preserve input
   ├── Session flash message
   └── Responsive 2-column layout

PROMPTS YANG DIGUNAKAN: 1
└── Contact page prompt

WAKTU:
├── Manual: ~1.5-2 jam
├── Dengan AI: ~15 menit
└── Saved: ~1-1.5 jam

Semua 3 halaman selesai! 🎉


Lanjut ke Bagian 11: Polish & Responsive →

Bagian 11: Polish & Responsive

Website sudah functional. Sekarang kita polish untuk memastikan semuanya perfect — terutama responsive design dan beberapa finishing touches.

Responsive Checklist

Sebelum deploy, pastikan semua halaman responsive di berbagai screen sizes:

BREAKPOINTS YANG DITEST:

├── Mobile S: 320px
├── Mobile M: 375px
├── Mobile L: 425px
├── Tablet: 768px
├── Laptop: 1024px
├── Laptop L: 1440px
└── Desktop: 1920px+

Mobile Navigation Enhancement

Navbar kita sudah ada mobile menu dengan Alpine.js. Tapi ada beberapa enhancement yang bisa ditambah:

{{-- Update navbar.blade.php - tambahkan close on route change --}}

<header
    x-data="{
        mobileMenuOpen: false,
        closeMenu() { this.mobileMenuOpen = false }
    }"
    @keydown.escape.window="closeMenu()"
    class="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
>
    {{-- ... existing code ... --}}

    {{-- Mobile Menu dengan animation --}}
    <div
        x-show="mobileMenuOpen"
        x-transition:enter="transition ease-out duration-200"
        x-transition:enter-start="opacity-0 -translate-y-1"
        x-transition:enter-end="opacity-100 translate-y-0"
        x-transition:leave="transition ease-in duration-150"
        x-transition:leave-start="opacity-100 translate-y-0"
        x-transition:leave-end="opacity-0 -translate-y-1"
        @click.away="closeMenu()"
        x-cloak
        class="md:hidden border-t bg-background"
    >
        {{-- Menu items dengan @click untuk close menu --}}
        <nav class="container mx-auto px-4 py-4 space-y-1">
            <a
                href="{{ route('home') }}"
                @click="closeMenu()"
                class="block py-2 px-3 rounded-md text-sm font-medium {{ request()->routeIs('home') ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent' }}"
            >
                Home
            </a>
            {{-- ... other links dengan @click="closeMenu()" ... --}}
        </nav>
    </div>
</header>

Add Scroll to Top Button

Tambahkan tombol scroll to top untuk UX yang lebih baik:

{{-- resources/views/components/layout/scroll-to-top.blade.php --}}

<button
    x-data="{ show: false }"
    x-init="window.addEventListener('scroll', () => { show = window.scrollY > 500 })"
    x-show="show"
    x-transition:enter="transition ease-out duration-300"
    x-transition:enter-start="opacity-0 translate-y-4"
    x-transition:enter-end="opacity-100 translate-y-0"
    x-transition:leave="transition ease-in duration-200"
    x-transition:leave-start="opacity-100 translate-y-0"
    x-transition:leave-end="opacity-0 translate-y-4"
    @click="window.scrollTo({ top: 0, behavior: 'smooth' })"
    class="fixed bottom-6 right-6 z-50 p-3 rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-colors"
    aria-label="Scroll to top"
>
    <svg xmlns="<http://www.w3.org/2000/svg>" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
        <path stroke-linecap="round" stroke-linejoin="round" d="M5 10l7-7m0 0l7 7m-7-7v18" />
    </svg>
</button>

Include di layout:

{{-- layouts/app.blade.php --}}

<body>
    {{-- ... --}}

    @include('components.layout.scroll-to-top')
</body>

Add Loading State untuk Form

Enhance form dengan loading state:

{{-- Update contact form button --}}

<x-ui.button
    type="submit"
    size="lg"
    class="w-full sm:w-auto"
    x-data="{ loading: false }"
    x-on:click="loading = true"
    :disabled="loading"
>
    <span x-show="!loading">
        Send Message
        <svg xmlns="<http://www.w3.org/2000/svg>" class="ml-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
            <path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
        </svg>
    </span>
    <span x-show="loading" class="flex items-center">
        <svg class="animate-spin -ml-1 mr-2 h-5 w-5" xmlns="<http://www.w3.org/2000/svg>" fill="none" viewBox="0 0 24 24">
            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        Sending...
    </span>
</x-ui.button>

Add Meta Tags untuk SEO

Update layout dengan proper meta tags:

{{-- layouts/app.blade.php --}}

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    @php
        $settings = \\App\\Models\\Setting::instance();
    @endphp

    {{-- Primary Meta Tags --}}
    <title>{{ $title ?? $settings->company_name }}</title>
    <meta name="title" content="{{ $title ?? $settings->company_name }}">
    <meta name="description" content="{{ $description ?? $settings->tagline }}">

    {{-- Open Graph / Facebook --}}
    <meta property="og:type" content="website">
    <meta property="og:url" content="{{ url()->current() }}">
    <meta property="og:title" content="{{ $title ?? $settings->company_name }}">
    <meta property="og:description" content="{{ $description ?? $settings->tagline }}">
    @if($settings->logo_url)
        <meta property="og:image" content="{{ $settings->logo_url }}">
    @endif

    {{-- Twitter --}}
    <meta property="twitter:card" content="summary_large_image">
    <meta property="twitter:url" content="{{ url()->current() }}">
    <meta property="twitter:title" content="{{ $title ?? $settings->company_name }}">
    <meta property="twitter:description" content="{{ $description ?? $settings->tagline }}">

    {{-- Favicon --}}
    @if($settings->favicon_url)
        <link rel="icon" href="{{ $settings->favicon_url }}">
    @endif

    {{-- ... rest of head ... --}}
</head>

Performance Optimization

Beberapa optimizations yang bisa dilakukan:

{{-- 1. Lazy load images --}}
<img
    src="{{ $image }}"
    alt="{{ $alt }}"
    loading="lazy"
    decoding="async"
>

{{-- 2. Preconnect to external domains --}}
<link rel="preconnect" href="<https://fonts.bunny.net>">
<link rel="preconnect" href="<https://ui-avatars.com>">

{{-- 3. Add font-display: swap --}}
<link href="<https://fonts.bunny.net/css?family=inter:400,500,600,700&display=swap>" rel="stylesheet" />

Final Testing Checklist

TESTING CHECKLIST:

FUNCTIONALITY:
├── ✅ Home page loads correctly
├── ✅ About page loads correctly
├── ✅ Contact page loads correctly
├── ✅ Contact form submits successfully
├── ✅ Contact form shows validation errors
├── ✅ Navigation works on all pages
├── ✅ Mobile menu opens/closes
├── ✅ All links work

RESPONSIVE:
├── ✅ Mobile (320px - 767px)
├── ✅ Tablet (768px - 1023px)
├── ✅ Desktop (1024px+)
├── ✅ No horizontal scroll
├── ✅ Text readable di semua sizes
├── ✅ Buttons/links tappable di mobile

PERFORMANCE:
├── ✅ Images lazy loaded
├── ✅ No console errors
├── ✅ CSS/JS minified (production)
├── ✅ Fonts optimized

ACCESSIBILITY:
├── ✅ Alt text on images
├── ✅ Proper heading hierarchy
├── ✅ Focus states visible
├── ✅ Color contrast adequate
├── ✅ Screen reader labels (sr-only)

Browser Testing

Test di berbagai browsers:

BROWSER TESTING:

├── Chrome (latest)
├── Firefox (latest)
├── Safari (latest)
├── Edge (latest)
├── Mobile Safari (iOS)
└── Chrome Mobile (Android)

Summary Bagian 11

YANG SUDAH DIKERJAKAN:

✅ Responsive Enhancements:
   ├── Mobile menu improvements
   ├── Close menu on escape/click away
   └── Smooth transitions

✅ UX Improvements:
   ├── Scroll to top button
   ├── Form loading state
   └── Better feedback

✅ SEO Optimization:
   ├── Meta tags
   ├── Open Graph tags
   └── Twitter cards

✅ Performance:
   ├── Lazy loading images
   ├── Preconnect hints
   └── Font optimization

✅ Testing Checklist:
   ├── Functionality
   ├── Responsive
   ├── Performance
   └── Accessibility

WAKTU:
├── Manual testing & fixes: ~1-2 jam
├── Polish touches: ~30 menit
└── Total: ~1.5-2.5 jam

Website sudah production-ready! 🎉


Lanjut ke Bagian 12: Deployment Tips & Closing →

Bagian 12: Deployment Tips & Closing

Selamat! Website company profile sudah selesai. Di bagian terakhir ini, gue akan share tips deployment dan beberapa insights sebagai freelancer.

Production Checklist

Sebelum deploy ke production:

# 1. Environment
# Update .env untuk production
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

# 2. Generate new app key (jika fresh install)
php artisan key:generate

# 3. Optimize untuk production
php artisan config:cache
php artisan route:cache
php artisan view:cache

# 4. Build assets untuk production
npm run build

# 5. Storage link
php artisan storage:link

# 6. Migrate database
php artisan migrate --force
php artisan db:seed --force

Server Requirements

MINIMUM SERVER REQUIREMENTS:

├── PHP >= 8.2
├── MySQL >= 8.0 atau MariaDB >= 10.3
├── Nginx atau Apache
├── Composer
├── Node.js (untuk build)
├── SSL Certificate (HTTPS)
└── 1GB RAM minimum

Recommended Hosting

Untuk project company profile seperti ini:

HOSTING RECOMMENDATIONS:

SHARED HOSTING (Budget):
├── Niagahoster
├── Hostinger
├── Dewaweb
└── ~Rp 50-150k/bulan

VPS (Lebih powerful):
├── DigitalOcean ($6/bulan)
├── Vultr ($5/bulan)
├── Linode ($5/bulan)
└── AWS Lightsail ($5/bulan)

MANAGED LARAVEL:
├── Laravel Forge + DO ($12 + $6/bulan)
├── Ploi + DO ($8 + $6/bulan)
└── Laravel Vapor (serverless)

Security Checklist

SECURITY CHECKLIST:

├── ✅ APP_DEBUG=false
├── ✅ Strong APP_KEY
├── ✅ HTTPS enabled
├── ✅ Database credentials secure
├── ✅ .env tidak ter-expose
├── ✅ CSRF protection aktif
├── ✅ Input validation proper
├── ✅ XSS protection (Blade auto-escaping)
└── ✅ SQL injection protection (Eloquent)

Backup Strategy

BACKUP STRATEGY:

YANG PERLU DI-BACKUP:
├── Database (daily)
├── Uploaded files/storage (daily)
├── .env file (secure storage)
└── Full codebase (git)

TOOLS:
├── Laravel Backup package
├── Server-level backups
├── Git (code versioning)
└── Offsite storage (S3, Google Drive)


Tips Pricing untuk Freelancer

Ini bagian yang sering ditanyakan — berapa harga yang tepat untuk project company profile?

PRICING COMPANY PROFILE:

BASIC (3-5 halaman statis):
├── Timeline: 1-2 minggu
├── Fitur: Home, About, Contact, basic SEO
├── Harga: Rp 3-7 juta
└── Target: UMKM, startup kecil

STANDARD (5-7 halaman + CMS sederhana):
├── Timeline: 2-3 minggu
├── Fitur: + Blog, Gallery, Admin panel sederhana
├── Harga: Rp 7-15 juta
└── Target: SMB, professional services

PREMIUM (Full CMS + Custom features):
├── Timeline: 4-6 minggu
├── Fitur: + Multi-language, Advanced SEO, Analytics
├── Harga: Rp 15-35 juta
└── Target: Corporate, established business

ENTERPRISE (Custom everything):
├── Timeline: 2-3 bulan
├── Fitur: Custom requirements
├── Harga: Rp 35-100+ juta
└── Target: Large corporations

Faktor yang Mempengaruhi Harga

PRICING FACTORS:

NAIK:
├── Custom design (bukan template)
├── Complex functionality
├── Tight deadline (rush fee)
├── Ongoing maintenance included
├── Multiple revisions
├── Multi-language
├── E-commerce features
└── Premium integrations

TURUN (atau tetap kompetitif):
├── Using existing template/components
├── Clear requirements dari awal
├── Longer timeline
├── Portfolio building (new client type)
└── Referral/repeat client discount

Tips Pitching ke Client

PITCHING TIPS:

SEBELUM MEETING:
├── Research company mereka
├── Lihat website kompetitor
├── Siapkan portfolio relevan
└── Siapkan ballpark pricing

SAAT MEETING:
├── Tanya dulu, banyak dengar
├── Understand pain points
├── Understand business goals
├── Jangan langsung kasih harga
└── Take notes!

SETELAH MEETING:
├── Kirim proposal dalam 24-48 jam
├── Breakdown scope dengan jelas
├── Sertakan timeline
├── Berikan 2-3 opsi harga
└── Follow up dalam 3-5 hari

RED FLAGS (hati-hati):
├── "Budget unlimited" (biasanya sebaliknya)
├── "Gampang kok, cuma..."
├── Minta revisi tanpa batas
├── Ghosting setelah proposal
└── Bandingkan dengan harga template

Contract Essentials

YANG HARUS ADA DI CONTRACT:

├── Scope of work (detail!)
├── Timeline & milestones
├── Payment terms (50% DP)
├── Revision policy (2-3 rounds)
├── What's NOT included
├── Intellectual property
├── Cancellation policy
├── Maintenance terms (jika ada)
└── Signatures both parties


Kelas Gratis di BuildWithAngga

Mau belajar lebih dalam? Di BuildWithAngga, gue dan tim sudah siapkan banyak kelas yang bisa bantu kamu level up:

KELAS LARAVEL:

📚 Laravel Fundamental
   └── Dasar-dasar Laravel dari nol

📚 Laravel Web Development
   └── Build aplikasi web lengkap

📚 Laravel API Development
   └── Build REST API professional

📚 Laravel Filament
   └── Admin panel modern

📚 Laravel Livewire
   └── Real-time interfaces

📚 Laravel Testing
   └── Test-driven development

KELAS FRONTEND:

📚 Tailwind CSS Mastery
   └── Styling modern dan efisien

📚 React.js Fundamental
   └── Library frontend populer

📚 Vue.js Development
   └── Progressive framework

📚 Next.js Full-Stack
   └── React framework production-ready

KELAS FREELANCE:

📚 Freelance Starter Guide
   └── Mulai karir freelance

📚 Upwork Mastery
   └── Sukses di platform global

📚 Client Management
   └── Handle client professionally

📚 Pricing & Negotiation
   └── Charge what you're worth

Akses semua di: buildwithangga.com


Recap: Apa yang Sudah Kita Bangun

PROJECT SUMMARY:

TECH STACK:
├── Laravel 11/12
├── Tailwind CSS
├── Shadcn UI (Blade)
├── Alpine.js
└── MySQL

PAGES:
├── Home (Hero, Services, Testimonials, CTA)
├── About (Story, Values, Team)
└── Contact (Form, Info, Map)

DATABASE:
├── settings (company info)
├── services (6 items)
├── team_members (4 members)
├── testimonials (4 reviews)
└── contacts (form submissions)

FEATURES:
├── Responsive design
├── Modern UI components
├── Form validation
├── Database integration
├── SEO optimization
└── Performance optimized

Time Comparison

DEVELOPMENT TIME COMPARISON:

TANPA AI (Manual):
├── Setup: 30-45 menit
├── Database: 45-60 menit
├── Models: 60-90 menit
├── Seeders: 60-90 menit
├── Components: 2-3 jam
├── Home page: 3-4 jam
├── About page: 2-3 jam
├── Contact page: 1.5-2 jam
├── Polish: 1-2 jam
└── TOTAL: 15-20 jam (2-3 hari kerja)

DENGAN VIBE CODING (AI-assisted):
├── Setup: 10-15 menit
├── Database: 10 menit
├── Models: 15 menit
├── Seeders: 10 menit
├── Components: 25 menit
├── Home page: 35 menit
├── About page: 20 menit
├── Contact page: 15 menit
├── Polish: 1-1.5 jam
└── TOTAL: 4-5 jam (setengah hari)

SAVED: ~10-15 jam per project
       ~3-4x lebih cepat

Workflow yang Gue Pakai

VIBE CODING WORKFLOW:

1. PLANNING (15-30 menit)
   ├── Understand requirements
   ├── Sketch database schema
   ├── List pages/features
   └── Identify components needed

2. PROMPTING (ongoing)
   ├── Write clear, specific prompts
   ├── Include context
   ├── Specify output format
   └── Mention constraints

3. REVIEWING (critical!)
   ├── Read EVERY line of code
   ├── Check for security issues
   ├── Verify logic correctness
   ├── Test functionality
   └── Adjust as needed

4. ITERATING
   ├── Refine prompts if needed
   ├── Ask for improvements
   ├── Combine multiple outputs
   └── Polish manually

5. TESTING
   ├── Test all features
   ├── Test responsive
   ├── Test edge cases
   └── Fix bugs


Closing Message

Terima kasih sudah mengikuti tutorial ini sampai selesai! 🙏

Gue harap sekarang kamu punya gambaran yang lebih jelas tentang:

  • Apa itu vibe coding dan gimana cara kerjanya
  • Mindset yang benar dalam menggunakan AI untuk coding
  • Workflow praktis untuk membangun website dengan AI assistance
  • Best practices Laravel (SRP, clean code, proper structure)
  • Modern UI dengan Shadcn components

Remember:

  • AI adalah tools, bukan replacement
  • Understanding fundamentals tetap penting
  • Review every line of AI output
  • Practice makes perfect

Kalau ada pertanyaan, feel free untuk reach out atau tinggalkan komentar.

Happy coding, dan semoga sukses dengan freelance journey kamu! 🚀


Tutorial by:

Angga Risky Setiawan Freelance Web DeveloperFounder, BuildWithAngga

buildwithangga.com