# Phase 1 Technical Plan — Unified Laravel Architecture (Blade for SEO/Crawlable Pages + Inertia React for Dashboards)

**Stack:** **Laravel** (Backend Monolith, Eloquent ORM, Auth, Web Push, Gamification) · **Blade Templates** (Public SEO-first & crawlable pages: Landing, Content Browse, Content Detail with OpenGraph/WhatsApp preview tags, Series) · **Inertia.js + React (TypeScript)** (Authenticated User & Admin Dashboards) · **Tailwind CSS v4**.

UI copy is in **Swahili** throughout; this document is the authoritative technical implementation blueprint.

---

## 0. Architecture Rationale: Blade for Crawling + Inertia React for Dashboards

This architecture consolidates everything into a **single, unified Laravel codebase**, replacing the multi-repo/Next.js split while eliminating the hosting complexities of Node.js SSR daemons on cPanel/VPS.

```
+-----------------------------------------------------------------------------------+
|                               LARAVEL MONOLITH                                    |
|                                                                                   |
|  +-----------------------------------+   +-------------------------------------+  |
|  |       PUBLIC / CRAWLABLE          |   |     AUTHENTICATED / APP EXPERIENCE  |  |
|  |        (Laravel Blade)            |   |          (Inertia.js + React)       |  |
|  +-----------------------------------+   +-------------------------------------+  |
|  | - Landing Page (/)                |   | - User Dashboard (/dashboard)       |  |
|  | - Content Browse (/content)       |   | - Share History (/dashboard/shares) |  |
|  | - Content Detail (/content/{slug})|   | - Badges Showcase (/dashboard/badges|  |
|  | - Series Directory (/series)      |   | - Settings & Profile (/settings)    |  |
|  | - Series Lessons (/series/{slug}) |   | - Admin Panel (/admin/*)            |  |
|  | - 100% Static HTML + OG Meta Tags |   | - Dynamic SPA with React & Radix UI |  |
|  | - Instant WhatsApp/Social Cards   |   | - Zero SSR Node Process Needed!     |  |
|  +-----------------------------------+   +-------------------------------------+  |
|                                                                                   |
|  +-----------------------------------------------------------------------------+  |
|  |                          CORE LARAVEL BACKEND                               |  |
|  |  - Eloquent ORM & SQLite/MySQL/PostgreSQL                                   |  |
|  |  - Fortify + Socialite Auth (Session Cookies, no JWT token juggling)       |  |
|  |  - Share Tracking Engine (Native server-side `?s=` click & event logging)   |  |
|  |  - Gamification Engine (Points Ledger, Weekly Streaks, Badges)              |  |
|  |  - Spatie MediaLibrary, WebPush Notifications, Ziggy Routing                |  |
|  +-----------------------------------------------------------------------------+  |
+-----------------------------------------------------------------------------------+
```

### Key Advantages of This Architecture

1. **Flawless SEO & Link Previews (OpenGraph / Twitter Cards / WhatsApp)**:
   WhatsApp, Telegram, Facebook, and Twitter crawlers do not execute JavaScript. By serving public pages via **Blade**, every single page is delivered with rich, pre-rendered `<meta property="og:*">` tags, canonical links, and high-res media thumbnails directly from PHP with zero latency and zero Node SSR dependencies.
2. **Elimination of Next.js & Cross-Origin Friction**:
   - No separate frontend repository or Vercel deployment.
   - No CORS configuration headaches or cookie-sharing issues between domains.
   - No need for complex ISR revalidation webhooks when content is edited.
   - Standard Laravel session-based authentication works seamlessly across both Blade pages and Inertia views.
3. **Rich SPA Experience Where It Matters**:
   User and Admin dashboards operate as a modern, reactive Single Page Application (SPA) powered by **Inertia.js + React + Tailwind CSS v4**, utilizing the rich component library (Radix UI, Lucide icons, Sonner toasts) already initialized in the starter kit.
4. **Server-Side Share Tracking (`?s=code`)**:
   When visitors follow a tracked link (`/content/injili-ya-leo?s=abc12345`), the Blade controller inspects the parameter on the server, logs the unique visit in `link_clicks`, and immediately renders the page without waiting for client-side JavaScript execution.

---

## 1. Complete Site Map & Rendering Strategy

| URL Route | Template Engine | Access Level | Description & Functionality |
| :--- | :--- | :--- | :--- |
| **`/`** | **Blade** | Public | **Ukurasa wa Nyumbani (Landing Page)**: Hero section, featured series banner, latest content grid, quick filter tabs, user registration CTA. |
| **`/content`** | **Blade** | Public | **Maktaba ya Maudhui (Content Browse)**: Search by keyword, filters by media type (poster, carousel, reel, video, story), series, tags, and target channels. |
| **`/content/{slug}`** | **Blade** | Public | **Ukurasa wa Maudhui (Content Detail)**: Full-bleed media player/carousel, Swahili captions, native Web Share trigger with trackable `?s=`, related posts, dynamic OpenGraph meta tags. |
| **`/series`** | **Blade** | Public | **Mfululizo wa Masomo (Series Directory)**: List of spiritual series/topics with thumbnail covers, lesson counts, and progress teasers. |
| **`/series/{slug}`** | **Blade** | Public | **Masomo ya Mfululizo (Series Lessons)**: Sequential breakdown of weekly lessons with media attachments and direct study links. |
| **`/login`**, **`/register`** | **Inertia React** / **Blade** | Public / Guest | Standard auth screens with Google OAuth integration, email/password, and validation errors. |
| **`/auth/google`**, **`/auth/google/callback`** | **Laravel Controller** | Guest | Google Socialite OAuth redirection & callback handlers. |
| **`/complete-profile`** | **Inertia React** | Authenticated | **Kamilisha Wasifu Wako**: Post-signup onboarding for phone number and group selection (Kikundi / Tawi). |
| **`/dashboard`** | **Inertia React** | Auth (User) | **Dashibodi ya Mtumiaji**: Overview of points balance, active weekly streak, recent shared links, badge progress. |
| **`/dashboard/shares`** | **Inertia React** | Auth (User) | **Historia ya Kushiriki**: Log of all shared content, generated links, confirmed shares, and real click counts. |
| **`/dashboard/badges`** | **Inertia React** | Auth (User) | **Mkusanyiko wa Beji**: Display of unlocked achievements, locked badges, and remaining criteria. |
| **`/settings`** | **Inertia React** | Auth (User/Admin) | **Mipangilio**: Update profile info, group affiliation (hierarchical select), password, and push notification toggles. |
| **`/admin`** | **Inertia React** | Auth (Admin) | **Dashibodi ya Msimamizi**: KPI metrics (total shares, clicks, active users, top content, group activity). |
| **`/admin/content`** | **Inertia React** | Auth (Admin) | **Usimamizi wa Maudhui**: Table of all content items, status toggling (draft/published), search, and filters. |
| **`/admin/content/create`**, **`/{id}/edit`** | **Inertia React** | Auth (Admin) | **Pakia / Hariri Maudhui**: Media upload (posters, carousels, videos), Swahili caption editor, channel tagging, series assignment. |
| **`/admin/series`** | **Inertia React** | Auth (Admin) | **Usimamizi wa Mfululizo**: Create/edit series, reorder weekly lessons. |
| **`/admin/groups`** | **Inertia React** | Auth (Admin) | **Usimamizi wa Vikundi**: Manage nested groups/branches/fellowships (parent groups, sub-groups, hierarchy tree). |
| **`/admin/tags`**, **`/admin/channels`** | **Inertia React** | Auth (Admin) | **Lebo na Njia**: Manage content tags and supported distribution channels (WhatsApp, TikTok, IG, FB, X). |
| **`/admin/users`** | **Inertia React** | Auth (Admin) | **Watumiaji Waliosajiliwa**: View registered users, filter by group (including nested sub-groups), view activity stats. |

---

## 2. Database Schema & Data Models

### 2.1 Core Identity & Organization

#### `groups` (Nestable Tree Hierarchy)
Supports nesting (e.g. National > Regional > Diocese/Zone > Local Church/Parish > Youth Group/Fellowship) using a self-referencing `parent_id`.
```sql
Schema::create('groups', function (Blueprint $table) {
    $table->id();
    $table->foreignId('parent_id')->nullable()->constrained('groups')->nullOnDelete();
    $table->string('name');
    $table->string('slug')->unique();
    $table->string('location')->nullable();
    $table->text('description')->nullable();
    $table->timestamps();

    $table->index('parent_id');
});
```

**Model Hierarchy (`App\Models\Group`):**
```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Group extends Model
{
    protected $fillable = ['parent_id', 'name', 'slug', 'location', 'description'];

    public function parent(): BelongsTo
    {
        return $this->belongsTo(Group::class, 'parent_id');
    }

    public function children(): HasMany
    {
        return $this->hasMany(Group::class, 'parent_id');
    }

    public function allChildren(): HasMany
    {
        return $this->children()->with('allChildren');
    }

    public function users(): HasMany
    {
        return $this->hasMany(User::class);
    }
}
```

#### `users`
Roles are strictly `'user'` (default member/sharer) or `'admin'` (system/content manager).
```sql
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('phone')->nullable();
    $table->foreignId('group_id')->nullable()->constrained('groups')->nullOnDelete();
    $table->string('password')->nullable(); // Nullable for Google-only signups
    $table->string('google_id')->nullable()->unique();
    $table->string('avatar')->nullable();
    $table->enum('role', ['user', 'admin'])->default('user');
    $table->rememberToken();
    $table->timestamps();

    $table->index('group_id');
    $table->index('role');
});
```

---

### 2.2 Content Management System (CMS)

#### `series`
```sql
Schema::create('series', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('slug')->unique();
    $table->text('description')->nullable();
    $table->string('cover_image')->nullable();
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});
```

#### `contents`
```sql
Schema::create('contents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('slug')->unique();
    $table->text('caption'); // Swahili share message & default caption
    $table->enum('type', ['poster', 'carousel', 'reel', 'video', 'story']);
    $table->foreignId('series_id')->nullable()->constrained('series')->nullOnDelete();
    $table->unsignedInteger('week_number')->nullable();
    $table->enum('status', ['draft', 'published'])->default('draft');
    $table->timestamp('published_at')->nullable()->index();
    $table->foreignId('created_by')->constrained('users')->cascadeOnDelete();
    $table->timestamps();
});
```

#### `channels` & `content_channel` (Pivot)
```sql
Schema::create('channels', function (Blueprint $table) {
    $table->id();
    $table->string('name'); // WhatsApp, Instagram, TikTok, Facebook, X, Telegram
    $table->string('slug')->unique();
    $table->string('icon')->nullable();
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});

Schema::create('content_channel', function (Blueprint $table) {
    $table->foreignId('content_id')->constrained('contents')->cascadeOnDelete();
    $table->foreignId('channel_id')->constrained('channels')->cascadeOnDelete();
    $table->primary(['content_id', 'channel_id']);
});
```

#### `tags` & `content_tag` (Pivot)
Handled via `spatie/laravel-tags` or custom pivot table linking `contents` and `tags`.

---

### 2.3 Sharing & Reach Tracking

#### `share_links`
A unique link code generated per user per content (e.g. `https://injili.app/content/habari-njema?s=x8k2m9p1`).
```sql
Schema::create('share_links', function (Blueprint $table) {
    $table->id();
    $table->string('code', 12)->unique(); // Base62 / Nanoid shortcode
    $table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
    $table->foreignId('content_id')->constrained('contents')->cascadeOnDelete();
    $table->foreignId('channel_id')->nullable()->constrained('channels')->nullOnDelete();
    $table->timestamps();

    $table->index(['user_id', 'content_id']);
});
```

#### `share_events` (Self-Reported Shares)
Counts toward user points and streaks when the user confirms sending the content.
```sql
Schema::create('share_events', function (Blueprint $table) {
    $table->id();
    $table->foreignId('share_link_id')->constrained('share_links')->cascadeOnDelete();
    $table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
    $table->timestamp('confirmed_at')->useCurrent();

    $table->index(['user_id', 'confirmed_at']);
});
```

#### `link_clicks` (Actual Visitor Reach)
Counts unique web visits from external clicks on shared links.
```sql
Schema::create('link_clicks', function (Blueprint $table) {
    $table->id();
    $table->foreignId('share_link_id')->constrained('share_links')->cascadeOnDelete();
    $table->string('visitor_hash', 64); // SHA256(IP + UserAgent) for privacy-compliant deduplication
    $table->timestamp('clicked_at')->useCurrent();

    $table->index(['share_link_id', 'visitor_hash', 'clicked_at']);
});
```

---

### 2.4 Gamification & Growth Engine

#### `point_rules`
```sql
Schema::create('point_rules', function (Blueprint $table) {
    $table->id();
    $table->string('action_key')->unique(); // e.g. confirmed_share, streak_bonus, profile_complete
    $table->integer('points_awarded');
    $table->string('description');
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});
```

#### `points_ledger`
Immutable ledger recording every earned point with an audit trail.
```sql
Schema::create('points_ledger', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
    $table->integer('points');
    $table->string('reason'); // confirmed_share, streak_bonus, badge_bonus, etc.
    $table->nullableMorphs('reference'); // e.g. ShareEvent, Badge
    $table->timestamps();

    $table->index(['user_id', 'created_at']);
});
```

#### `streaks`
```sql
Schema::create('streaks', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->unique()->constrained('users')->cascadeOnDelete();
    $table->unsignedInteger('current_streak')->default(0);
    $table->unsignedInteger('longest_streak')->default(0);
    $table->date('last_active_period')->nullable();
    $table->string('period_type')->default('week'); // Weekly cadence matching lesson drops
    $table->timestamps();
});
```

#### `badges` & `user_badges`
```sql
Schema::create('badges', function (Blueprint $table) {
    $table->id();
    $table->string('key')->unique(); // e.g., 'first_share', 'streak_4_weeks', 'content_variety_5'
    $table->string('name');
    $table->string('description');
    $table->string('icon');
    $table->string('criteria_type'); // count_shares, streak_weeks, content_types_shared
    $table->json('criteria_value'); // e.g. {"threshold": 10}
    $table->timestamps();
});

Schema::create('user_badges', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
    $table->foreignId('badge_id')->constrained('badges')->cascadeOnDelete();
    $table->timestamp('earned_at')->useCurrent();
    $table->unique(['user_id', 'badge_id']);
});
```

#### `push_subscriptions`
```sql
Schema::create('push_subscriptions', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
    $table->string('endpoint', 500);
    $table->string('public_key')->nullable();
    $table->string('auth_token')->nullable();
    $table->string('content_encoding')->nullable();
    $table->timestamps();
});
```

---

## 3. SEO, Crawling & OpenGraph Architecture (Blade Templates)

### 3.1 OpenGraph & WhatsApp Unfurling

When a link is shared on WhatsApp, Facebook, or X, the platform sends a bot crawler (e.g. `WhatsApp/2.x`, `facebookexternalhit/1.1`) to scrape HTML meta tags. Blade renders these directly on the server:

```html
<!-- resources/views/layouts/public.blade.php -->
<!DOCTYPE html>
<html lang="sw" class="scroll-smooth">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@yield('title', 'Injili App — Injili kwa Wote')</title>
    <meta name="description" content="@yield('meta_description', 'Pata na ushiriki maudhui ya Neno la Mungu.')">

    <!-- Open Graph / WhatsApp / Facebook -->
    <meta property="og:type" content="@yield('og_type', 'website')">
    <meta property="og:site_name" content="Injili App">
    <meta property="og:title" content="@yield('og_title', 'Injili App — Injili kwa Wote')">
    <meta property="og:description" content="@yield('og_description', 'Pata picha, video, na masomo ya kiroho kwa Kiswahili na ushiriki na wengine leo!')">
    <meta property="og:url" content="{{ url()->current() }}">
    <meta property="og:image" content="@yield('og_image', asset('images/default-og.jpg'))">
    <meta property="og:image:width" content="1200">
    <meta property="og:image:height" content="630">

    <!-- Twitter Cards -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="@yield('og_title')">
    <meta name="twitter:description" content="@yield('og_description')">
    <meta name="twitter:image" content="@yield('og_image')">

    @vite(['resources/css/app.css', 'resources/js/public.js'])
</head>
<body class="bg-stone-50 text-stone-900 antialiased font-sans">
    @include('components.public.navbar')
    <main>
        @yield('content')
    </main>
    @include('components.public.footer')
</body>
</html>
```

### 3.2 Content Detail Page (`/content/{slug}`) in Blade

```html
<!-- resources/views/public/content/show.blade.php -->
@extends('layouts.public')

@section('title', $content->title . ' — Injili App')
@section('meta_description', Str::limit($content->caption, 150))
@section('og_type', 'article')
@section('og_title', $content->title)
@section('og_description', $content->caption)
@section('og_image', $content->getFirstMediaUrl('media', 'og-preview') ?: asset('images/default-og.jpg'))

@section('content')
<div class="max-w-4xl mx-auto px-4 py-8">
    <!-- Media Viewer (Poster / Video / Carousel) -->
    <div class="rounded-2xl overflow-hidden shadow-xl bg-black mb-6">
        @if($content->type === 'video' || $content->type === 'reel')
            <video controls poster="{{ $content->getFirstMediaUrl('media', 'thumb') }}" class="w-full max-h-[600px] object-contain mx-auto">
                <source src="{{ $content->getFirstMediaUrl('media') }}" type="video/mp4">
            </video>
        @elseif($content->type === 'carousel')
            <div class="carousel-container relative">
                @foreach($content->getMedia('media') as $media)
                    <img src="{{ $media->getUrl() }}" alt="{{ $content->title }}" class="w-full h-auto object-contain">
                @endforeach
            </div>
        @else
            <img src="{{ $content->getFirstMediaUrl('media') }}" alt="{{ $content->title }}" class="w-full h-auto object-contain">
        @endif
    </div>

    <!-- Title & Swahili Caption -->
    <div class="bg-white rounded-2xl p-6 shadow-sm border border-stone-100">
        <div class="flex items-center gap-2 mb-3">
            <span class="px-3 py-1 bg-amber-100 text-amber-900 text-xs font-semibold rounded-full uppercase">
                {{ $content->type }}
            </span>
            @if($content->series)
                <a href="{{ route('series.show', $content->series->slug) }}" class="text-xs text-stone-500 hover:text-stone-800 font-medium">
                    📖 {{ $content->series->title }} (Wiki ya {{ $content->week_number }})
                </a>
            @endif
        </div>

        <h1 class="text-2xl sm:text-3xl font-bold text-stone-900 mb-4">{{ $content->title }}</h1>
        <p class="text-stone-700 whitespace-pre-line text-base sm:text-lg leading-relaxed mb-6">{{ $content->caption }}</p>

        <!-- Share Actions & Trackable Link Generator -->
        <div id="share-action-box" class="p-4 bg-amber-50 rounded-xl border border-amber-200">
            @auth
                <button id="btn-native-share" 
                        data-content-id="{{ $content->id }}"
                        data-title="{{ $content->title }}"
                        data-caption="{{ $content->caption }}"
                        class="w-full py-3.5 px-6 bg-amber-600 hover:bg-amber-700 text-white font-bold rounded-xl shadow flex items-center justify-center gap-2 transition">
                    <span>📲 Shiriki Sasa (Pata Alama)</span>
                </button>

                <!-- Post-Share Confirmation Modal / Trigger -->
                <div id="confirm-share-dialog" class="hidden mt-4 pt-4 border-t border-amber-200 text-center">
                    <p class="text-sm font-medium text-stone-800 mb-2">Je, umekamilisha kushiriki maudhui haya?</p>
                    <button id="btn-confirm-share" class="px-6 py-2 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-bold rounded-lg shadow">
                        Ndio, Nimeshiriki! ✅ (+10 Pts)
                    </button>
                </div>
            @else
                <div class="flex flex-col sm:flex-row items-center justify-between gap-4">
                    <div>
                        <h4 class="font-bold text-stone-900">Je, ungependa kupata alama na beji?</h4>
                        <p class="text-xs text-stone-600">Ingia au fungua akaunti ili uanze kukusanya alama kila unaposhiriki Neno.</p>
                    </div>
                    <div class="flex gap-2 w-full sm:w-auto">
                        <a href="{{ route('login') }}" class="flex-1 sm:flex-none text-center px-4 py-2 bg-stone-900 text-white text-sm font-semibold rounded-lg">Ingia</a>
                        <a href="{{ route('register') }}" class="flex-1 sm:flex-none text-center px-4 py-2 bg-amber-600 text-white text-sm font-semibold rounded-lg">Jisajili</a>
                    </div>
                </div>
            @endauth
        </div>
    </div>
</div>
@endsection
```

---

## 4. Share Tracking Flow (`?s=code`) & Mechanics

### 4.1 Step-by-Step Execution Lifecycle

```
[User Taps "Shiriki"]
       │
       ▼
[AJAX Request: POST /share-links/generate]
       │
       ▼
[Laravel creates/finds `share_links` record with unique 8-char shortcode: e.g., 'w9x2b8m1']
       │
       ▼
[Returns Trackable URL: "https://domain.com/content/habari-njema?s=w9x2b8m1"]
       │
       ▼
[Invokes `navigator.share({ title, text, url })` -> Native WhatsApp/Instagram Sheet Opens]
       │
       ▼
[User sends message to friends/groups]
       │
       ├───────────────────────────────────────────┐
       ▼                                           ▼
[User clicks "Nimeshiriki! ✅"]             [Friend clicks link in WhatsApp]
       │                                           │
       ▼                                           ▼
[POST /share-events/confirm]                [GET /content/{slug}?s=w9x2b8m1]
       │                                           │
       ▼                                           ▼
[Laravel awards +10 Pts to points_ledger]   [Middleware/Controller matches code]
[Updates current weekly streak]             [Hashes IP + User-Agent into visitor_hash]
[Evaluates badge unlock criteria]           [Inserts unique record in `link_clicks`]
                                            [Renders full Blade HTML with OpenGraph tags]
```

### 4.2 Server-Side Click Logging Middleware / Controller Logic

```php
namespace App\Http\Controllers;

use App\Models\Content;
use App\Models\ShareLink;
use App\Models\LinkClick;
use Illuminate\Http\Request;

class ContentController extends Controller
{
    public function show(Request $request, string $slug)
    {
        $content = Content::with(['series', 'media', 'tags', 'channels'])
            ->where('slug', $slug)
            ->where('status', 'published')
            ->firstOrFail();

        // Check for trackable share code
        if ($shareCode = $request->query('s')) {
            $shareLink = ShareLink::where('code', $shareCode)->first();
            
            if ($shareLink && $shareLink->content_id === $content->id) {
                // Generate a privacy-safe hash from IP and User Agent
                $visitorHash = hash('sha256', $request->ip() . $request->userAgent());
                
                // Record click if not clicked by the same visitor in the last 24 hours
                $recentClick = LinkClick::where('share_link_id', $shareLink->id)
                    ->where('visitor_hash', $visitorHash)
                    ->where('clicked_at', '>=', now()->subHours(24))
                    ->exists();

                if (!$recentClick) {
                    LinkClick::create([
                        'share_link_id' => $shareLink->id,
                        'visitor_hash' => $visitorHash,
                        'clicked_at' => now(),
                    ]);
                }
            }
        }

        return view('public.content.show', compact('content'));
    }
}
```

---

## 5. Inertia.js + React Dashboards & Authenticated Experience

All authenticated and administrative features run as a high-performance **Inertia.js + React (TypeScript)** application using Tailwind CSS v4, Radix UI, and Lucide icons.

### 5.1 User Personal Portal (`/dashboard/*`)

1. **Dashboard Home (`/dashboard` - `resources/js/pages/dashboard.tsx`)**:
   - **Kadi za Takwimu (Stat Cards)**: Total points earned (`points_ledger` sum), active weekly streak counter with flame animation, total confirmed shares, total link click reach.
   - **Mfululizo wa Sasa (Current Active Series)**: Quick link to the current week's lesson with unshared indicator.
   - **Beji za Karibuni (Recent Badges)**: Showcase of the 3 latest unlocked badges and next milestone progress.
2. **Share History (`/dashboard/shares` - `resources/js/pages/shares/index.tsx`)**:
   - Filterable data table of every piece of content the user has shared.
   - Per-share metrics: Number of unique link clicks generated (`link_clicks`), date confirmed, and direct "Shiriki Tena" (Share Again) button.
3. **Badges Showcase (`/dashboard/badges` - `resources/js/pages/badges/index.tsx`)**:
   - Visual grid of earned badges (glowing/unlocked) and locked badges with clear Swahili progress descriptions (e.g., *"Shiriki maudhui 5 ya aina ya Video ili kufungua beji hii (3/5)"*).
4. **Profile & Group Settings (`/settings` - `resources/js/pages/settings/profile.tsx`)**:
   - Manage name, phone number, group affiliation (nested tree selection from `groups`), password change, and Web Push notifications toggle.

### 5.2 Admin Control Panel (`/admin/*`)

1. **Overview KPI Dashboard (`/admin` - `resources/js/pages/admin/dashboard.tsx`)**:
   - Top-line stats: Total registered users, active sharers this week, total link clicks generated, top-shared content items.
   - Breakdown of engagement by group and nested sub-groups.
2. **Content CMS (`/admin/content/*`)**:
   - Table of all content with real-time status toggling (Draft / Published).
   - Form for uploading and managing content:
     - Title, Swahili Caption text area (with character preview).
     - Content Type selector (`poster`, `carousel`, `reel`, `video`, `story`).
     - Spatie MediaLibrary upload zone (drag-and-drop images/videos, re-ordering carousel slides, automatic responsive thumbnail generation).
     - Series and Week Number picker.
     - Target Channels checkboxes (`WhatsApp`, `TikTok`, `Instagram`, `Facebook`, `X`, `Telegram`).
     - Tags multi-select with inline tag creation.
3. **Series Manager (`/admin/series/*`)**:
   - Create/edit spiritual series with cover art and weekly lesson structure.
4. **Groups Manager (`/admin/groups/*`)**:
   - Create, edit, and reorganize nested groups/branches/fellowships.
   - Tree view display showing parent-child hierarchy and total members per branch.
5. **User Directory (`/admin/users/*`)**:
   - Filterable list of registered users, searchable by name, email, phone, and group/sub-group.
   - View individual user activity history, points, and earned badges.

---

## 6. Gamification Engine & Business Logic

### 6.1 Point Rules Architecture

| Action / Event Key | Default Points | Description (Swahili) | Trigger Condition |
| :--- | :--- | :--- | :--- |
| `confirmed_share` | **+10 Pts** | Alama za kushiriki Neno | Fired when user confirms sharing a content item. |
| `weekly_streak_bonus`| **+25 Pts** | Zawadi ya kudumisha wiki mfululizo | Fired when maintaining a weekly streak for $\ge 2$ consecutive weeks. |
| `badge_unlocked` | **+50 Pts** | Zawadi ya kufungua beji mpya | Fired automatically when any badge criteria is met. |
| `profile_completed`| **+15 Pts** | Kamilisha wasifu (namba na kikundi) | Fired once upon completing phone and group affiliation info. |

### 6.2 Weekly Streak Evaluation Service

```php
namespace App\Services;

use App\Models\User;
use App\Models\Streak;
use Carbon\Carbon;

class StreakService
{
    public function recordActivity(User $user): void
    {
        $streak = Streak::firstOrCreate(
            ['user_id' => $user->id],
            ['current_streak' => 0, 'longest_streak' => 0, 'period_type' => 'week']
        );

        $currentWeek = Carbon::now()->startOfWeek();
        $lastActiveWeek = $streak->last_active_period 
            ? Carbon::parse($streak->last_active_period)->startOfWeek() 
            : null;

        if (!$lastActiveWeek) {
            // First time ever active
            $streak->current_streak = 1;
        } elseif ($currentWeek->equalTo($lastActiveWeek)) {
            // Already active this week, keep streak as-is
            return;
        } elseif ($currentWeek->equalTo($lastActiveWeek->copy()->addWeek())) {
            // Consecutive week!
            $streak->current_streak += 1;
        } else {
            // Missed one or more weeks, reset streak to 1
            $streak->current_streak = 1;
        }

        if ($streak->current_streak > $streak->longest_streak) {
            $streak->longest_streak = $streak->current_streak;
        }

        $streak->last_active_period = Carbon::now()->toDateString();
        $streak->save();
    }
}
```

### 6.3 Starter Badge Configuration

```php
[
    [
        'key' => 'mwanzo_mpya',
        'name' => 'Mwanzo Mpya 🌱',
        'description' => 'Umeshiriki neno la kwanza kwenye Injili App.',
        'icon' => 'sparkles',
        'criteria_type' => 'count_shares',
        'criteria_value' => ['threshold' => 1]
    ],
    [
        'key' => 'balozi_wa_injili_5',
        'name' => 'Balozi wa Injili 🌟',
        'description' => 'Umeshiriki maudhui 5 tofauti.',
        'icon' => 'flame',
        'criteria_type' => 'count_shares',
        'criteria_value' => ['threshold' => 5]
    ],
    [
        'key' => 'wiki_nne_mfululizo',
        'name' => 'Hodari wa Kiroho 🏆',
        'description' => 'Umedumisha ushiriki kwa wiki 4 mfululizo.',
        'icon' => 'trophy',
        'criteria_type' => 'streak_weeks',
        'criteria_value' => ['threshold' => 4]
    ],
    [
        'key' => 'mtaalamu_wa_video',
        'name' => 'Mhubiri wa Kidijitali 🎥',
        'description' => 'Umeshiriki video au reels 3 za Injili.',
        'icon' => 'video',
        'criteria_type' => 'content_type_shares',
        'criteria_value' => ['type' => 'video', 'threshold' => 3]
    ]
]
```

---

## 7. Web Push Notifications (PWA Ready)

- **Library**: `laravel-notification-channels/webpush` with standard VAPID public/private key pairs.
- **Service Worker (`public/sw.js`)**: Handles background push events, displaying native notification cards with action buttons:
  - `"Somo Jipya la Wiki Hii Liko Tayari! 📖"` -> Directly opens `/content/{slug}`.
  - `"Dumishe Streak Yako ya Wiki Hii! 🔥"` -> Reminder notification on Saturdays for users who have not yet shared.
- **PWA Manifest (`public/manifest.json`)**: Configured for "Add to Home Screen" on Android Chrome and iOS Safari.

---

## 8. Package & Dependency Matrix

| Component / Function | Package / Tool | Role in Project |
| :--- | :--- | :--- |
| **Framework Core** | `laravel/framework` (^12.0 / ^13.0) | Core Monolith backend |
| **SPA Dashboard Adapter** | `inertiajs/inertia-laravel` (^3.0) | Server-side Inertia bridge |
| **Frontend Framework** | `react` (^19.2) + `@inertiajs/react` | Dashboard UI and reactive state |
| **Styling** | `tailwindcss` (^4.0) | Utility CSS across Blade and React |
| **Routing Helper** | `tightenco/ziggy` | Access named Laravel routes inside React |
| **Media Attachments** | `spatie/laravel-medialibrary` | Content image, video, and carousel storage |
| **Tags Management** | `spatie/laravel-tags` | Multi-category taxonomy |
| **Slug Generation** | `spatie/laravel-sluggable` | Clean SEO URLs for contents and series |
| **Google Authentication** | `laravel/socialite` | One-tap Google sign-in |
| **Web Push (VAPID)** | `laravel-notification-channels/webpush` | PWA web push notifications |
| **Icons & UI Components** | `lucide-react` + Radix UI primitives | Dashboard icons and dialogs |

---

## 9. Phase 1 Implementation Roadmap

```mermaid
gantt
    title Mpango wa Ujenzi — Injili App (Phase 1)
    dateFormat  YYYY-MM-DD
    section Msingi & Schema
    Migrations & Seeders              :p1, 2026-09-08, 2d
    Auth & Socialite Setup            :p2, after p1, 2d
    section Public Blade (SEO)
    Blade Layout & Landing Page       :p3, after p2, 2d
    Content Browse & Search           :p4, after p3, 2d
    Content Detail & OpenGraph Tags   :p5, after p4, 2d
    Share Tracking Engine (?s=)       :p6, after p5, 2d
    section Inertia React Dashboards
    User Dashboard & Stats            :p7, after p6, 3d
    Share History & Badges UI         :p8, after p7, 2d
    Admin Content CMS & Media Upload  :p9, after p8, 3d
    Admin Groups, Series & Users      :p10, after p9, 2d
    section Gamification & Polish
    Points Ledger & Streaks Engine    :p11, after p10, 2d
    Web Push & Final QA               :p12, after p11, 2d
```

### Detailed Execution Steps

1. **Sprint 1: Schema, Database & Authentication**
   - Run migrations for `groups` (nestable with `parent_id`), `users` (`role` enum: `['user', 'admin']`), `series`, `contents`, `channels`, `share_links`, `share_events`, `link_clicks`, `points_ledger`, `streaks`, `badges`, `user_badges`.
   - Configure Laravel Socialite for Google OAuth and standard email/password authentication.
   - Implement the `"Kamilisha Wasifu"` (Complete Profile) onboarding step with group selection.

2. **Sprint 2: Public SEO & Crawlable Blade Pages**
   - Create `resources/views/layouts/public.blade.php` with full OpenGraph / Twitter Cards / WhatsApp metadata.
   - Build `/` (Landing page), `/content` (Browse/Filter library), and `/series` + `/series/{slug}`.
   - Build `/content/{slug}` with responsive video/carousel media view, Swahili typography, and Tier-2 Web Share button.

3. **Sprint 3: Share Tracking & Analytics Engine**
   - Implement `ShareLinkController` to generate unique 8-character codes (`?s=code`).
   - Implement click logging in `ContentController` / middleware with visitor IP+UA SHA256 hashing.
   - Implement the `"Umeshiriki? ✅"` confirmation modal and `ShareEventController` endpoint.

4. **Sprint 4: User Personal Dashboard (Inertia React)**
   - Build `/dashboard` overview (points balance, animated weekly streak counter, recent badges).
   - Build `/dashboard/shares` table displaying shared links and actual click analytics.
   - Build `/dashboard/badges` grid displaying earned achievements and locked criteria.
   - Build `/settings` profile and group preferences.

5. **Sprint 5: Admin Control Panel (Inertia React)**
   - Build `/admin` KPI overview dashboard (with group breakdown).
   - Build `/admin/content` CRUD with Spatie MediaLibrary upload, multi-channel checkboxes, tag management, and series selection.
   - Build `/admin/groups` tree management for nested groups and branches.
   - Build `/admin/series` and `/admin/users` views.

6. **Sprint 6: Gamification, Web Push & QA**
   - Implement `PointsService`, `StreakService`, and `BadgeEvaluator`.
   - Setup Web Push service worker and test PWA installation on mobile devices.
   - Verify link unfurling and preview cards on WhatsApp, Facebook, Telegram, and Twitter.
