Kapsamlı Rehber

Yeni Tenant Teması Oluşturma

Multi-Tenant Merkezi Tema Sistemi

Özet

Ne Yapıyoruz?

  • • Merkezi tema yapısı kuruyoruz
  • • "Simple" altın standart tema olacak
  • • Yeni tenantlar merkezi yapıyı kullanacak
  • • Eski sistem (Muzibu/İxtif) çalışmaya devam edecek

Dokunulmayacaklar

  • Modules/*/themes/muzibu/
  • Modules/*/themes/ixtif/
  • • Mevcut ThemeService fallback mantığı

Temel Kurallar

1. Merkezi Konum

Tüm yeni tema dosyaları resources/views/themes/ altında olacak.

2. Simple = Fallback

Tenant'ta dosya yoksa themes/simple/ kullanılır. Bu tema mükemmel olmalı.

3. Tenant Override

Tenant sadece farklı olan dosyaları override eder. Örn: sadece homepage.blade.php

4. CSS Variables

Renkler CSS variables ile. Tenant sadece :root değişkenlerini değiştirir.

5. Geriye Uyumluluk

ThemeService önce merkezi, sonra modül içi, sonra simple'a bakar.

Klasör Yapısı

resources/views/themes/
│
├── simple/                          ← ALTIN STANDART
│   │
│   ├── layouts/
│   │   ├── app.blade.php            ← Master template
│   │   ├── header.blade.php         ← Navigation bar
│   │   ├── footer.blade.php         ← Site footer
│   │   └── partials/
│   │       ├── head.blade.php       ← <head> içeriği
│   │       ├── scripts.blade.php    ← JS yüklemeleri
│   │       ├── breadcrumb.blade.php ← Breadcrumb
│   │       └── meta.blade.php       ← SEO meta tags
│   │
│   ├── homepage.blade.php           ← Anasayfa
│   │
│   ├── blog/                        ← Blog modülü
│   │   ├── index.blade.php          ← Liste
│   │   ├── show.blade.php           ← Detay
│   │   └── partials/
│   │       └── card.blade.php       ← Blog kartı
│   │
│   ├── page/                        ← Sayfa modülü
│   │   └── show.blade.php
│   │
│   ├── shop/                        ← Mağaza modülü
│   │   ├── index.blade.php
│   │   ├── show.blade.php
│   │   ├── cart.blade.php
│   │   └── partials/
│   │       └── product-card.blade.php
│   │
│   ├── portfolio/                   ← Portfolyo modülü
│   │   ├── index.blade.php
│   │   └── show.blade.php
│   │
│   ├── announcement/                ← Duyuru modülü
│   │   ├── index.blade.php
│   │   └── show.blade.php
│   │
│   ├── components/                  ← Ortak componentler
│   │   ├── card.blade.php
│   │   ├── pagination.blade.php
│   │   ├── search-box.blade.php
│   │   ├── social-share.blade.php
│   │   ├── image.blade.php
│   │   └── alert.blade.php
│   │
│   ├── auth/                        ← Giriş/Kayıt
│   │   ├── login.blade.php
│   │   ├── register.blade.php
│   │   └── forgot-password.blade.php
│   │
│   └── config.json                  ← Tema ayarları
│
├── tenant-{id}/                     ← YENİ TENANT ÖRNEK
│   ├── homepage.blade.php           ← Sadece özel anasayfa
│   ├── config.json                  ← Tenant ayarları
│   └── blog/                        ← İsterse override
│       └── show.blade.php
│
├── muzibu/                          ← MEVCUT (dokunma)
│   └── ...
│
└── ixtif/                           ← MEVCUT (dokunma)
    └── ...

Oluşturulacak Dosyalar

Dosya Açıklama Öncelik
Layouts
layouts/app.blade.php Master template - HTML yapısı P1
layouts/header.blade.php Navigation bar, logo, menü P1
layouts/footer.blade.php Footer, linkler, copyright P1
layouts/partials/head.blade.php Meta, CSS, fonts P1
layouts/partials/scripts.blade.php JS dosyaları, Livewire P1
layouts/partials/breadcrumb.blade.php Breadcrumb navigation P2
Homepage
homepage.blade.php Anasayfa - hero, sections P1
Blog Modülü
blog/index.blade.php Blog listesi P1
blog/show.blade.php Blog detay P1
blog/partials/card.blade.php Blog kartı component P2
Page Modülü
page/show.blade.php Sayfa detay P1
Shop Modülü
shop/index.blade.php Ürün listesi P2
shop/show.blade.php Ürün detay P2
shop/cart.blade.php Sepet sayfası P2
Portfolio Modülü
portfolio/index.blade.php Portfolyo listesi P3
portfolio/show.blade.php Portfolyo detay P3
Announcement Modülü
announcement/index.blade.php Duyuru listesi P3
announcement/show.blade.php Duyuru detay P3
Components
components/card.blade.php Genel kart component P2
components/pagination.blade.php Sayfalama P2
components/search-box.blade.php Arama kutusu P3
components/social-share.blade.php Sosyal medya paylaşım P3
Auth
auth/login.blade.php Giriş sayfası P3
auth/register.blade.php Kayıt sayfası P3
Config
config.json Tema ayarları (renkler, font, vs) P2
P1 Kritik - İlk aşama
P2 Önemli - İkinci aşama
P3 İsteğe bağlı - Üçüncü aşama

Layouts Yapısı

app.blade.php

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}" >
<head>
    @include('themes.simple.layouts.partials.head')
    @stack('styles')
</head>
<body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100">

    @include('themes.simple.layouts.header')

    <main class="min-h-screen">
        @yield('content')
    </main>

    @include('themes.simple.layouts.footer')
    @include('themes.simple.layouts.partials.scripts')
    @stack('scripts')

</body>
</html>

partials/head.blade.php

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

<title>{{ $title ?? setting('site_name') }}</title>

{{-- SEO Meta --}}
<x-seo-meta />

{{-- Favicon --}}
<link rel="icon" href="{{ setting('favicon') }}">

{{-- CSS --}}
<link rel="stylesheet" href="{{ tenant_css() }}">
<link rel="stylesheet" href="{{ asset('css/fontawesome.min.css') }}">

{{-- Tema CSS Variables --}}
<style>
    :root {
        --color-primary: {{ $themeConfig['primary'] ?? '#8b5cf6' }};
        --color-secondary: {{ $themeConfig['secondary'] ?? '#64748b' }};
        --color-accent: {{ $themeConfig['accent'] ?? '#f59e0b' }};
    }
</style>

{{-- Livewire Styles --}}
@livewireStyles

header.blade.php

<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
    <nav class="container mx-auto px-4 py-4">
        <div class="flex items-center justify-between">

            {{-- Logo --}}
            <a href="{{ url('/') }}" class="flex items-center">
                <img src="{{ setting('logo') }}" alt="{{ setting('site_name') }}" class="h-10">
            </a>

            {{-- Navigation --}}
            <div class="hidden md:flex items-center space-x-6">
                @foreach($menuItems as $item)
                    <a href="{{ $item->url }}" class="text-gray-600 hover:text-primary">
                        {{ $item->title }}
                    </a>
                @endforeach
            </div>

            {{-- Mobile Menu Button --}}
            <button class="md:hidden" @click="mobileMenuOpen = !mobileMenuOpen">
                <i class="fas fa-bars text-xl"></i>
            </button>

        </div>
    </nav>
</header>

Homepage Yapısı

@extends('themes.simple.layouts.app')

@section('content')

    {{-- Hero Section --}}
    <section class="bg-gradient-to-r from-primary to-secondary py-20">
        <div class="container mx-auto px-4 text-center text-white">
            <h1 class="text-4xl md:text-6xl font-bold mb-4">
                {{ setting('site_slogan') ?? 'Hoş Geldiniz' }}
            </h1>
            <p class="text-xl opacity-90">
                {{ setting('site_description') }}
            </p>
        </div>
    </section>

    {{-- Featured Content --}}
    <section class="py-16">
        <div class="container mx-auto px-4">
            <h2 class="text-3xl font-bold mb-8 text-center">Öne Çıkanlar</h2>

            <div class="grid md:grid-cols-3 gap-6">
                @foreach($featuredItems as $item)
                    @include('themes.simple.components.card', ['item' => $item])
                @endforeach
            </div>
        </div>
    </section>

    {{-- Latest Blog Posts --}}
    @if(isset($latestPosts) && $latestPosts->count())
    <section class="py-16 bg-gray-100 dark:bg-gray-800">
        <div class="container mx-auto px-4">
            <h2 class="text-3xl font-bold mb-8">Son Yazılar</h2>
            <div class="grid md:grid-cols-3 gap-6">
                @foreach($latestPosts as $post)
                    @include('themes.simple.blog.partials.card', ['post' => $post])
                @endforeach
            </div>
        </div>
    </section>
    @endif

@endsection

Modül View Yapıları

blog/index.blade.php

@extends('themes.simple.layouts.app')

@section('content')
<div class="container mx-auto px-4 py-8">

    @include('themes.simple.layouts.partials.breadcrumb')

    <h1 class="text-3xl font-bold mb-8">Blog</h1>

    <div class="grid md:grid-cols-3 gap-6">
        @foreach($posts as $post)
            @include('themes.simple.blog.partials.card')
        @endforeach
    </div>

    {{ $posts->links() }}

</div>
@endsection

blog/show.blade.php

@extends('themes.simple.layouts.app')

@section('content')
<article class="container mx-auto px-4 py-8">

    @include('themes.simple.layouts.partials.breadcrumb')

    {{-- Hero Image --}}
    @if($post->hero)
    <img src="{{ thumb($post->hero, 1200, 600) }}"
         class="w-full rounded-xl mb-8">
    @endif

    <h1 class="text-4xl font-bold mb-4">
        {{ $post->title }}
    </h1>

    <div class="prose dark:prose-invert max-w-none">
        {!! $post->content !!}
    </div>

    @include('themes.simple.components.social-share')

</article>
@endsection

CSS Variables Sistemi

config.json Örneği

{
    "name": "simple",
    "title": "Simple Theme",
    "version": "1.0.0",

    "colors": {
        "primary": "#8b5cf6",
        "secondary": "#64748b",
        "accent": "#f59e0b",
        "success": "#22c55e",
        "danger": "#ef4444",
        "warning": "#f59e0b",
        "info": "#3b82f6"
    },

    "fonts": {
        "heading": "Inter",
        "body": "Inter"
    },

    "layout": {
        "container_width": "1280px",
        "header_height": "64px",
        "sidebar_width": "280px"
    },

    "features": {
        "dark_mode": true,
        "sticky_header": true,
        "back_to_top": true
    }
}

Tenant Override Örneği

// themes/tenant-123/config.json
{
    "name": "tenant-123",
    "extends": "simple",    ← Simple'ı extend ediyor

    "colors": {
        "primary": "#f97316",   ← Sadece primary değişti
        "accent": "#eab308"
    }
    // Diğer tüm ayarlar simple'dan gelir
}

ThemeService Güncellemesi

Yeni Fallback Sırası

// getThemeViewPath() güncellemesi

public function getThemeViewPath(string $view, string $module = null): string
{
    $themeName = $this->getActiveTheme()->name;

    $paths = [];

    if ($module) {
        // 1. Merkezi tenant view
        $paths[] = "themes.{$themeName}.{$module}.{$view}";

        // 2. Modül içi tenant view (eski sistem - geriye uyumluluk)
        $paths[] = "{$module}::themes.{$themeName}.{$view}";

        // 3. Merkezi simple view
        if ($themeName !== 'simple') {
            $paths[] = "themes.simple.{$module}.{$view}";
        }

        // 4. Modül içi simple view (eski sistem)
        $paths[] = "{$module}::themes.simple.{$view}";

        // 5. Front fallback (son çare)
        $paths[] = "{$module}::front.{$view}";
    } else {
        // Modülsüz view'lar için
        $paths[] = "themes.{$themeName}.{$view}";
        if ($themeName !== 'simple') {
            $paths[] = "themes.simple.{$view}";
        }
    }

    foreach ($paths as $path) {
        if (view()->exists($path)) {
            return $path;
        }
    }

    throw new ViewNotFoundException("View not found: {$view}");
}

Dikkat

Bu değişiklik mevcut Muzibu ve İxtif temalarını etkilemez. Onlar Modules/*/themes/ içinde kalmaya devam eder ve 2. sırada kontrol edilir.

Yeni Tenant Teması Oluşturma

1

Klasör Oluştur

mkdir -p resources/views/themes/tenant-{ID}/
2

config.json Oluştur

{
    "name": "tenant-{ID}",
    "extends": "simple",
    "colors": {
        "primary": "#YOUR_COLOR"
    }
}
3

homepage.blade.php Oluştur (Opsiyonel)

@extends('themes.simple.layouts.app')

@section('content')
    {{-- Tenant'a özel anasayfa içeriği --}}
    ...
@endsection
4

Veritabanına Kaydet

-- themes tablosuna ekle
INSERT INTO themes (name, title, folder_name, is_active)
VALUES ('tenant-{ID}', 'Tenant Adı', 'tenant-{ID}', 1);

-- tenant'ı güncelle
UPDATE tenants SET theme_id = {THEME_ID} WHERE id = {TENANT_ID};
5

Cache Temizle

php artisan cache:clear
php artisan view:clear
php artisan config:clear

Sonuç

Artık tenant-{ID} teması aktif. Sadece homepage.blade.php ve config.json oluşturdun. Tüm iç sayfalar otomatik olarak simple temasından geliyor.

Uygulama Checklist

P1 - Kritik (İlk Aşama)

P2 - Önemli (İkinci Aşama)

P3 - İsteğe Bağlı (Üçüncü Aşama)

Test