Document listing updates

This commit is contained in:
fatihalp 2026-03-06 01:00:23 +03:00
parent 7e9d77c0a8
commit 165585cdc4
17 changed files with 1177 additions and 441 deletions

View File

@ -23,6 +23,7 @@ use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\SpatieMediaLibraryImageColumn; use Filament\Tables\Columns\SpatieMediaLibraryImageColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Filters\Filter; use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TernaryFilter; use Filament\Tables\Filters\TernaryFilter;
@ -135,15 +136,15 @@ class ListingResource extends Resource
->circular(), ->circular(),
TextColumn::make('id')->sortable(), TextColumn::make('id')->sortable(),
TextColumn::make('title')->searchable()->sortable()->limit(40), TextColumn::make('title')->searchable()->sortable()->limit(40),
TextColumn::make('category.name')->label('Category'), TextColumn::make('category.name')->label('Category')->sortable(),
TextColumn::make('user.email')->label('Owner')->searchable()->toggleable(), TextColumn::make('user.email')->label('Owner')->searchable()->toggleable()->sortable(),
TextColumn::make('price') TextColumn::make('price')
->currency(fn (Listing $record): string => $record->currency ?: ListingPanelHelper::defaultCurrency()) ->currency(fn (Listing $record): string => $record->currency ?: ListingPanelHelper::defaultCurrency())
->sortable(), ->sortable(),
StateFusionSelectColumn::make('status'), StateFusionSelectColumn::make('status')->sortable(),
IconColumn::make('is_featured')->boolean()->label('Featured'), IconColumn::make('is_featured')->boolean()->label('Featured')->sortable(),
TextColumn::make('city'), TextColumn::make('city')->sortable(),
TextColumn::make('country'), TextColumn::make('country')->sortable(),
TextColumn::make('created_at')->dateTime()->sortable(), TextColumn::make('created_at')->dateTime()->sortable(),
])->filters([ ])->filters([
StateFusionSelectFilter::make('status'), StateFusionSelectFilter::make('status'),
@ -188,7 +189,12 @@ class ListingResource extends Resource
->query(fn (Builder $query, array $data): Builder => $query ->query(fn (Builder $query, array $data): Builder => $query
->when($data['min'] ?? null, fn (Builder $query, string $amount): Builder => $query->where('price', '>=', (float) $amount)) ->when($data['min'] ?? null, fn (Builder $query, string $amount): Builder => $query->where('price', '>=', (float) $amount))
->when($data['max'] ?? null, fn (Builder $query, string $amount): Builder => $query->where('price', '<=', (float) $amount))), ->when($data['max'] ?? null, fn (Builder $query, string $amount): Builder => $query->where('price', '<=', (float) $amount))),
])->actions([ ])
->filtersLayout(FiltersLayout::AboveContent)
->filtersFormColumns(3)
->filtersFormWidth('7xl')
->persistFiltersInSession()
->actions([
EditAction::make(), EditAction::make(),
Action::make('activities') Action::make('activities')
->icon('heroicon-o-clock') ->icon('heroicon-o-clock')

View File

@ -3,6 +3,7 @@ namespace Modules\Category\Http\Controllers;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Modules\Category\Models\Category; use Modules\Category\Models\Category;
use Modules\Listing\Models\Listing;
use Modules\Theme\Support\ThemeManager; use Modules\Theme\Support\ThemeManager;
class CategoryController extends Controller class CategoryController extends Controller
@ -24,7 +25,11 @@ class CategoryController extends Controller
'children' => fn ($query) => $query->active()->ordered(), 'children' => fn ($query) => $query->active()->ordered(),
]); ]);
$listings = $category->activeListings() $categoryIds = $category->descendantAndSelfIds()->all();
$listings = Listing::query()
->where('status', 'active')
->whereIn('category_id', $categoryIds)
->with('category:id,name') ->with('category:id,name')
->latest('id') ->latest('id')
->paginate(12); ->paginate(12);

View File

@ -72,12 +72,39 @@ class Category extends Model
->active() ->active()
->whereNull('parent_id') ->whereNull('parent_id')
->with([ ->with([
'children' => fn (HasMany $query) => $query->active()->ordered(), 'children' => fn (Builder $query) => $query->active()->ordered(),
]) ])
->ordered() ->ordered()
->get(); ->get();
} }
public function descendantAndSelfIds(): Collection
{
$ids = collect([(int) $this->getKey()]);
$frontier = $ids;
while ($frontier->isNotEmpty()) {
$children = static::query()
->whereIn('parent_id', $frontier->all())
->pluck('id')
->map(fn ($id): int => (int) $id)
->values();
if ($children->isEmpty()) {
break;
}
$ids = $ids
->merge($children)
->unique()
->values();
$frontier = $children;
}
return $ids;
}
public function breadcrumbTrail(): Collection public function breadcrumbTrail(): Collection
{ {
$trail = collect(); $trail = collect();

View File

@ -5,10 +5,12 @@ namespace Modules\Conversation\App\Http\Controllers;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View; use Illuminate\View\View;
use Modules\Conversation\App\Models\Conversation; use Modules\Conversation\App\Models\Conversation;
use Modules\Conversation\App\Support\QuickMessageCatalog; use Modules\Conversation\App\Support\QuickMessageCatalog;
use Modules\Listing\Models\Listing; use Modules\Listing\Models\Listing;
use Throwable;
class ConversationController extends Controller class ConversationController extends Controller
{ {
@ -17,6 +19,11 @@ class ConversationController extends Controller
$userId = (int) $request->user()->getKey(); $userId = (int) $request->user()->getKey();
$messageFilter = $this->resolveMessageFilter($request); $messageFilter = $this->resolveMessageFilter($request);
$conversations = collect();
$selectedConversation = null;
if ($this->messagingTablesReady()) {
try {
$conversations = Conversation::inboxForUser($userId, $messageFilter); $conversations = Conversation::inboxForUser($userId, $messageFilter);
$selectedConversation = Conversation::resolveSelected($conversations, $request->integer('conversation')); $selectedConversation = Conversation::resolveSelected($conversations, $request->integer('conversation'));
@ -32,6 +39,11 @@ class ConversationController extends Controller
return $conversation; return $conversation;
}); });
} }
} catch (Throwable) {
$conversations = collect();
$selectedConversation = null;
}
}
return view('conversation::inbox', [ return view('conversation::inbox', [
'conversations' => $conversations, 'conversations' => $conversations,
@ -43,6 +55,10 @@ class ConversationController extends Controller
public function start(Request $request, Listing $listing): RedirectResponse public function start(Request $request, Listing $listing): RedirectResponse
{ {
if (! $this->messagingTablesReady()) {
return back()->with('error', 'Mesajlaşma altyapısı henüz hazır değil.');
}
$user = $request->user(); $user = $request->user();
if (! $listing->user_id) { if (! $listing->user_id) {
@ -75,6 +91,10 @@ class ConversationController extends Controller
public function send(Request $request, Conversation $conversation): RedirectResponse public function send(Request $request, Conversation $conversation): RedirectResponse
{ {
if (! $this->messagingTablesReady()) {
return back()->with('error', 'Mesajlaşma altyapısı henüz hazır değil.');
}
$user = $request->user(); $user = $request->user();
$userId = (int) $user->getKey(); $userId = (int) $user->getKey();
@ -117,4 +137,13 @@ class ConversationController extends Controller
return in_array($messageFilter, ['all', 'unread', 'important'], true) ? $messageFilter : 'all'; return in_array($messageFilter, ['all', 'unread', 'important'], true) ? $messageFilter : 'all';
} }
private function messagingTablesReady(): bool
{
try {
return Schema::hasTable('conversations') && Schema::hasTable('conversation_messages');
} catch (Throwable) {
return false;
}
}
} }

View File

@ -4,12 +4,15 @@ namespace Modules\Favorite\App\Http\Controllers;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Schema;
use Modules\Category\Models\Category; use Modules\Category\Models\Category;
use Modules\Conversation\App\Models\Conversation; use Modules\Conversation\App\Models\Conversation;
use Modules\Conversation\App\Support\QuickMessageCatalog; use Modules\Conversation\App\Support\QuickMessageCatalog;
use Modules\Favorite\App\Models\FavoriteSearch; use Modules\Favorite\App\Models\FavoriteSearch;
use Modules\Listing\Models\Listing; use Modules\Listing\Models\Listing;
use Modules\User\App\Models\User; use Modules\User\App\Models\User;
use Throwable;
class FavoriteController extends Controller class FavoriteController extends Controller
{ {
@ -37,19 +40,24 @@ class FavoriteController extends Controller
$user = $request->user(); $user = $request->user();
$categories = collect();
if ($this->tableExists('categories')) {
$categories = Category::query() $categories = Category::query()
->where('is_active', true) ->where('is_active', true)
->orderBy('name') ->orderBy('name')
->get(['id', 'name']); ->get(['id', 'name']);
}
$favoriteListings = null; $favoriteListings = $this->emptyPaginator();
$favoriteSearches = null; $favoriteSearches = $this->emptyPaginator();
$favoriteSellers = null; $favoriteSellers = $this->emptyPaginator();
$conversations = collect(); $conversations = collect();
$selectedConversation = null; $selectedConversation = null;
$buyerConversationListingMap = []; $buyerConversationListingMap = [];
if ($activeTab === 'listings') { if ($activeTab === 'listings') {
try {
if ($this->tableExists('favorite_listings')) {
$favoriteListings = $user->favoriteListings() $favoriteListings = $user->favoriteListings()
->with(['category:id,name', 'user:id,name']) ->with(['category:id,name', 'user:id,name'])
->wherePivot('created_at', '>=', now()->subYear()) ->wherePivot('created_at', '>=', now()->subYear())
@ -58,7 +66,9 @@ class FavoriteController extends Controller
->orderByPivot('created_at', 'desc') ->orderByPivot('created_at', 'desc')
->paginate(10) ->paginate(10)
->withQueryString(); ->withQueryString();
}
if ($this->tableExists('conversations') && $this->tableExists('conversation_messages')) {
$userId = (int) $user->getKey(); $userId = (int) $user->getKey();
$conversations = Conversation::inboxForUser($userId, $messageFilter); $conversations = Conversation::inboxForUser($userId, $messageFilter);
$buyerConversationListingMap = $conversations $buyerConversationListingMap = $conversations
@ -82,16 +92,31 @@ class FavoriteController extends Controller
}); });
} }
} }
} catch (Throwable) {
$favoriteListings = $this->emptyPaginator();
$conversations = collect();
$selectedConversation = null;
$buyerConversationListingMap = [];
}
}
if ($activeTab === 'searches') { if ($activeTab === 'searches') {
try {
if ($this->tableExists('favorite_searches')) {
$favoriteSearches = $user->favoriteSearches() $favoriteSearches = $user->favoriteSearches()
->with('category:id,name') ->with('category:id,name')
->latest() ->latest()
->paginate(10) ->paginate(10)
->withQueryString(); ->withQueryString();
} }
} catch (Throwable) {
$favoriteSearches = $this->emptyPaginator();
}
}
if ($activeTab === 'sellers') { if ($activeTab === 'sellers') {
try {
if ($this->tableExists('favorite_sellers')) {
$favoriteSellers = $user->favoriteSellers() $favoriteSellers = $user->favoriteSellers()
->withCount([ ->withCount([
'listings as active_listings_count' => fn ($query) => $query->where('status', 'active'), 'listings as active_listings_count' => fn ($query) => $query->where('status', 'active'),
@ -100,6 +125,10 @@ class FavoriteController extends Controller
->paginate(10) ->paginate(10)
->withQueryString(); ->withQueryString();
} }
} catch (Throwable) {
$favoriteSellers = $this->emptyPaginator();
}
}
return view('favorite::index', [ return view('favorite::index', [
'activeTab' => $activeTab, 'activeTab' => $activeTab,
@ -189,4 +218,21 @@ class FavoriteController extends Controller
return back()->with('success', 'Favori arama silindi.'); return back()->with('success', 'Favori arama silindi.');
} }
private function tableExists(string $table): bool
{
try {
return Schema::hasTable($table);
} catch (Throwable) {
return false;
}
}
private function emptyPaginator(): LengthAwarePaginator
{
return new LengthAwarePaginator([], 0, 10, 1, [
'path' => request()->url(),
'query' => request()->query(),
]);
}
} }

View File

@ -2,96 +2,54 @@
namespace Modules\Listing\Database\Seeders; namespace Modules\Listing\Database\Seeders;
use Modules\User\App\Models\User;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Modules\Category\Models\Category; use Modules\Category\Models\Category;
use Modules\Listing\Models\Listing; use Modules\Listing\Models\Listing;
use Modules\Location\Models\City;
use Modules\Location\Models\Country;
use Modules\User\App\Models\User;
class ListingSeeder extends Seeder class ListingSeeder extends Seeder
{ {
private const LISTINGS = [ private const SAMPLE_IMAGES = [
[ 'sample_image/phone.jpeg',
'title' => 'iPhone 14 Pro 256 GB, temiz kullanılmış', 'sample_image/macbook.jpg',
'description' => 'Cihaz sorunsuz çalışıyor, pil sağlığı iyi durumda. Kutusu ve şarj kablosu ile teslim edilecektir.', 'sample_image/car.jpeg',
'price' => 44999, 'sample_image/headphones.jpg',
'city' => 'İstanbul', 'sample_image/laptop.jpg',
'country' => 'Türkiye', 'sample_image/cup.jpg',
'image' => 'sample_image/phone.jpeg', 'sample_image/car2.jpeg',
], ];
[
'title' => 'MacBook Pro M2 16 GB / 512 GB', private const TITLE_PREFIXES = [
'description' => 'Yazılım geliştirme için kullanıldı. Kozmetik olarak çok iyi durumda, faturası mevcut.', 'Temiz kullanılmış',
'price' => 62999, 'Az kullanılmış',
'city' => 'Ankara', 'Fırsat ürün',
'country' => 'Türkiye', 'Uygun fiyatlı',
'image' => 'sample_image/macbook.jpg', 'Sahibinden',
], 'Kaçırılmayacak',
[ 'Bakımlı',
'title' => '2020 Toyota Corolla 1.6 Dream',
'description' => 'Boyalı parça yok, düzenli bakımlı aile aracı. Detaylı ekspertiz raporu paylaşılabilir.',
'price' => 980000,
'city' => 'İzmir',
'country' => 'Türkiye',
'image' => 'sample_image/car.jpeg',
],
[
'title' => 'Bluetooth Kulaklık - Aktif Gürültü Engelleme',
'description' => 'Uzun pil ömrü ve net mikrofon performansı. Kutu içeriği tamdır.',
'price' => 3499,
'city' => 'Bursa',
'country' => 'Türkiye',
'image' => 'sample_image/headphones.jpg',
],
[
'title' => 'Masaüstü için 15 inç dizüstü bilgisayar',
'description' => 'Günlük kullanım ve ofis işleri için ideal. SSD sayesinde hızlıılış.',
'price' => 18450,
'city' => 'Antalya',
'country' => 'Türkiye',
'image' => 'sample_image/laptop.jpg',
],
[
'title' => 'Seramik Kahve Kupası Seti (6 Adet)',
'description' => 'Az kullanıldı, kırık/çatlak yok. Mutfak yenileme nedeniyle satılıktır.',
'price' => 650,
'city' => 'Adana',
'country' => 'Türkiye',
'image' => 'sample_image/cup.jpg',
],
[
'title' => 'Sedan Araç - Düşük Kilometre',
'description' => 'Şehir içi kullanıldı, tüm bakımları zamanında yapıldı. Ciddi alıcılarla paylaşım yapılır.',
'price' => 845000,
'city' => 'Konya',
'country' => 'Türkiye',
'image' => 'sample_image/car2.jpeg',
],
]; ];
public function run(): void public function run(): void
{ {
$user = $this->resolveSeederUser(); $user = $this->resolveSeederUser();
$categories = Category::query() $categories = $this->resolveSeedableCategories();
->where('level', 0)
->orderBy('sort_order')
->orderBy('name')
->get();
if (! $user || $categories->isEmpty()) { if (! $user || $categories->isEmpty()) {
return; return;
} }
foreach (self::LISTINGS as $index => $data) { $countries = $this->resolveCountries();
$listing = $this->upsertListing( $turkeyCities = $this->resolveTurkeyCities();
index: $index,
data: $data,
categories: $categories,
user: $user,
);
$this->syncListingImage($listing, $data['image']); foreach ($categories as $index => $category) {
$listingData = $this->buildListingData($category, $index, $countries, $turkeyCities);
$listing = $this->upsertListing($index, $listingData, $category, $user);
$this->syncListingImage($listing, $listingData['image']);
} }
} }
@ -103,10 +61,160 @@ class ListingSeeder extends Seeder
->first(); ->first();
} }
private function upsertListing(int $index, array $data, Collection $categories, User $user): Listing private function resolveSeedableCategories(): Collection
{ {
$slug = Str::slug($data['title']) . '-' . ($index + 1); $leafCategories = Category::query()
$category = $categories->get($index % $categories->count()); ->where('is_active', true)
->whereDoesntHave('children')
->orderBy('sort_order')
->orderBy('name')
->get();
if ($leafCategories->isNotEmpty()) {
return $leafCategories->values();
}
return Category::query()
->where('is_active', true)
->orderBy('sort_order')
->orderBy('name')
->get()
->values();
}
private function resolveCountries(): Collection
{
if (! class_exists(Country::class) || ! Schema::hasTable('countries')) {
return collect();
}
return Country::query()
->where('is_active', true)
->orderBy('name')
->get(['id', 'name', 'code'])
->values();
}
private function resolveTurkeyCities(): Collection
{
if (! class_exists(City::class) || ! Schema::hasTable('cities') || ! Schema::hasTable('countries')) {
return collect(['İstanbul', 'Ankara', 'İzmir', 'Bursa', 'Antalya']);
}
$turkey = Country::query()
->where('code', 'TR')
->first(['id']);
if (! $turkey) {
return collect(['İstanbul', 'Ankara', 'İzmir', 'Bursa', 'Antalya']);
}
$cities = City::query()
->where('country_id', (int) $turkey->id)
->where('is_active', true)
->orderBy('name')
->pluck('name')
->map(fn ($name): string => trim((string) $name))
->filter(fn (string $name): bool => $name !== '')
->values();
return $cities->isNotEmpty()
? $cities
: collect(['İstanbul', 'Ankara', 'İzmir', 'Bursa', 'Antalya']);
}
private function buildListingData(
Category $category,
int $index,
Collection $countries,
Collection $turkeyCities
): array {
$location = $this->resolveLocation($index, $countries, $turkeyCities);
$image = self::SAMPLE_IMAGES[$index % count(self::SAMPLE_IMAGES)];
return [
'title' => $this->buildTitle($category, $index),
'description' => $this->buildDescription($category, $location['city'], $location['country']),
'price' => $this->priceForIndex($index),
'city' => $location['city'],
'country' => $location['country'],
'image' => $image,
];
}
private function resolveLocation(int $index, Collection $countries, Collection $turkeyCities): array
{
$turkeyCountry = $countries->first(fn ($country): bool => strtoupper((string) $country->code) === 'TR');
$turkeyName = trim((string) ($turkeyCountry->name ?? 'Türkiye')) ?: 'Türkiye';
$useForeignCountry = $countries->count() > 1 && $index % 4 === 0;
if ($useForeignCountry) {
$foreignCountries = $countries
->filter(fn ($country): bool => strtoupper((string) $country->code) !== 'TR')
->values();
if ($foreignCountries->isNotEmpty()) {
$selected = $foreignCountries->get($index % $foreignCountries->count());
$countryName = trim((string) ($selected->name ?? ''));
return [
'country' => $countryName !== '' ? $countryName : 'Türkiye',
'city' => $countryName !== '' ? $countryName : 'İstanbul',
];
}
}
$city = trim((string) $turkeyCities->get($index % max(1, $turkeyCities->count())));
return [
'country' => $turkeyName,
'city' => $city !== '' ? $city : 'İstanbul',
];
}
private function buildTitle(Category $category, int $index): string
{
$prefix = self::TITLE_PREFIXES[$index % count(self::TITLE_PREFIXES)];
$categoryName = trim((string) $category->name);
return sprintf('%s %s ilanı', $prefix, $categoryName !== '' ? $categoryName : 'ürün');
}
private function buildDescription(Category $category, string $city, string $country): string
{
$categoryName = trim((string) $category->name);
$location = trim(collect([$city, $country])->filter()->join(', '));
return sprintf(
'%s kategorisinde, durum olarak sorunsuz ve kullanıma hazırdır. Teslimat noktası: %s. Detaylar için mesaj atabilirsiniz.',
$categoryName !== '' ? $categoryName : 'Ürün',
$location !== '' ? $location : 'Türkiye'
);
}
private function priceForIndex(int $index): int
{
$basePrices = [
1499,
3250,
6490,
11800,
26500,
44990,
82000,
135000,
];
$base = $basePrices[$index % count($basePrices)];
$step = (int) floor($index / count($basePrices)) * 750;
return $base + $step;
}
private function upsertListing(int $index, array $data, Category $category, User $user): Listing
{
$slug = Str::slug($category->slug.'-'.$data['title']).'-'.($index + 1);
return Listing::updateOrCreate( return Listing::updateOrCreate(
['slug' => $slug], ['slug' => $slug],
@ -118,12 +226,12 @@ class ListingSeeder extends Seeder
'currency' => 'TRY', 'currency' => 'TRY',
'city' => $data['city'], 'city' => $data['city'],
'country' => $data['country'], 'country' => $data['country'],
'category_id' => $category?->id, 'category_id' => $category->id,
'user_id' => $user->id, 'user_id' => $user->id,
'status' => 'active', 'status' => 'active',
'contact_email' => $user->email, 'contact_email' => $user->email,
'contact_phone' => '+905551112233', 'contact_phone' => '+905551112233',
'is_featured' => $index < 3, 'is_featured' => $index < 8,
] ]
); );
} }

View File

@ -22,34 +22,6 @@
], $normalizeQuery); ], $normalizeQuery);
@endphp @endphp
<style>
.listing-filter-card {
border: 1px solid #d7dbe7;
border-radius: 14px;
background: #ffffff;
}
.listing-card {
border: 1px solid #d7dbe7;
border-radius: 12px;
overflow: hidden;
background: #ffffff;
transition: box-shadow .2s ease, transform .2s ease;
}
.listing-card:hover {
box-shadow: 0 16px 32px rgba(22, 29, 57, 0.11);
transform: translateY(-2px);
}
.listing-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
</style>
<div class="max-w-[1320px] mx-auto px-4 py-7 lg:py-8"> <div class="max-w-[1320px] mx-auto px-4 py-7 lg:py-8">
<h1 class="text-[30px] leading-tight font-extrabold text-slate-900 mb-6">{{ $pageTitle }}</h1> <h1 class="text-[30px] leading-tight font-extrabold text-slate-900 mb-6">{{ $pageTitle }}</h1>

View File

@ -22,34 +22,6 @@
], $normalizeQuery); ], $normalizeQuery);
@endphp @endphp
<style>
.listing-filter-card {
border: 1px solid #d7dbe7;
border-radius: 14px;
background: #ffffff;
}
.listing-card {
border: 1px solid #d7dbe7;
border-radius: 12px;
overflow: hidden;
background: #ffffff;
transition: box-shadow .2s ease, transform .2s ease;
}
.listing-card:hover {
box-shadow: 0 16px 32px rgba(22, 29, 57, 0.11);
transform: translateY(-2px);
}
.listing-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
</style>
<div class="max-w-[1320px] mx-auto px-4 py-7 lg:py-8"> <div class="max-w-[1320px] mx-auto px-4 py-7 lg:py-8">
<h1 class="text-[30px] leading-tight font-extrabold text-slate-900 mb-6">{{ $pageTitle }}</h1> <h1 class="text-[30px] leading-tight font-extrabold text-slate-900 mb-6">{{ $pageTitle }}</h1>

View File

@ -22,34 +22,6 @@
], $normalizeQuery); ], $normalizeQuery);
@endphp @endphp
<style>
.listing-filter-card {
border: 1px solid #d7dbe7;
border-radius: 14px;
background: #ffffff;
}
.listing-card {
border: 1px solid #d7dbe7;
border-radius: 12px;
overflow: hidden;
background: #ffffff;
transition: box-shadow .2s ease, transform .2s ease;
}
.listing-card:hover {
box-shadow: 0 16px 32px rgba(22, 29, 57, 0.11);
transform: translateY(-2px);
}
.listing-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
</style>
<div class="max-w-[1320px] mx-auto px-4 py-7 lg:py-8"> <div class="max-w-[1320px] mx-auto px-4 py-7 lg:py-8">
<h1 class="text-[30px] leading-tight font-extrabold text-slate-900 mb-6">{{ $pageTitle }}</h1> <h1 class="text-[30px] leading-tight font-extrabold text-slate-900 mb-6">{{ $pageTitle }}</h1>

View File

@ -28,108 +28,6 @@
: 'Yeni üye'; : 'Yeni üye';
@endphp @endphp
<style>
.lt-wrap { max-width: 1320px; margin: 0 auto; padding: 24px 16px 46px; }
.lt-breadcrumb { display: flex; flex-wrap: wrap; gap: 8px; font-size: 13px; color: #6b7280; margin-bottom: 14px; }
.lt-breadcrumb a { color: #4b5563; text-decoration: none; }
.lt-breadcrumb span:last-child { color: #111827; font-weight: 700; }
.lt-grid { display: grid; grid-template-columns: minmax(0, 1fr) 352px; gap: 18px; align-items: start; }
.lt-card { border: 1px solid #d8dce4; border-radius: 14px; background: #f7f7f8; box-shadow: 0 1px 2px rgba(16, 24, 40, .05); }
.lt-gallery-main { position: relative; border-radius: 10px; background: #1f2937; overflow: hidden; min-height: 440px; }
.lt-gallery-main img { width: 100%; height: 100%; object-fit: cover; min-height: 440px; }
.lt-gallery-main-empty { min-height: 440px; display: grid; place-items: center; color: #cbd5e1; font-size: 14px; }
.lt-gallery-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 44px; height: 44px; border: 0; border-radius: 999px; background: rgba(255,255,255,.92); color: #111827; display: grid; place-items: center; cursor: pointer; }
.lt-gallery-nav[data-gallery-prev] { left: 14px; }
.lt-gallery-nav[data-gallery-next] { right: 14px; }
.lt-gallery-top { position: absolute; top: 12px; left: 12px; right: 12px; display: flex; justify-content: space-between; align-items: center; }
.lt-badge { border-radius: 999px; background: #ffd814; color: #111827; font-size: 12px; font-weight: 700; padding: 6px 10px; }
.lt-icon-btn { width: 38px; height: 38px; border: 0; border-radius: 999px; background: rgba(17, 24, 39, .86); color: #fff; display: inline-flex; align-items: center; justify-content: center; }
.lt-thumbs { display: flex; gap: 10px; overflow-x: auto; padding: 12px 0 2px; }
.lt-thumb { width: 86px; min-width: 86px; height: 64px; border: 2px solid transparent; border-radius: 10px; overflow: hidden; background: #d1d5db; cursor: pointer; }
.lt-thumb.is-active { border-color: #ff3a59; }
.lt-thumb img { width: 100%; height: 100%; object-fit: cover; }
.lt-media-card { padding: 14px; }
.lt-detail-card { margin-top: 14px; padding: 18px 20px; }
.lt-price-row { display: flex; flex-wrap: wrap; gap: 12px; justify-content: space-between; align-items: flex-start; }
.lt-price { font-size: 46px; line-height: 1; font-weight: 900; color: #0f172a; }
.lt-title { margin-top: 8px; font-size: 21px; font-weight: 700; color: #111827; }
.lt-meta { text-align: right; color: #4b5563; font-size: 14px; }
.lt-meta strong { color: #111827; font-weight: 700; }
.lt-credit { margin-top: 14px; border: 1px solid #e3e7ee; border-radius: 12px; padding: 14px; background: #fafafb; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.lt-credit h4 { margin: 0; font-size: 20px; color: #0f172a; }
.lt-credit p { margin: 3px 0 0; font-size: 14px; color: #4b5563; }
.lt-tag { border-radius: 999px; background: #2f80ed; color: #fff; font-size: 13px; font-weight: 700; padding: 7px 12px; }
.lt-section-title { margin: 18px 0 10px; font-size: 30px; font-weight: 900; color: #111827; }
.lt-features { border-top: 1px solid #e2e8f0; margin-top: 12px; }
.lt-feature-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; border-top: 1px solid #e7ebf2; padding: 12px 0; }
.lt-feature-row:first-child { border-top: 0; }
.lt-f-item { display: flex; justify-content: space-between; gap: 8px; color: #334155; font-size: 15px; }
.lt-f-item strong { color: #111827; font-weight: 800; text-align: right; }
.lt-side-card { position: sticky; top: 96px; padding: 16px; }
.lt-seller-head { display: flex; align-items: center; gap: 10px; }
.lt-avatar { width: 44px; height: 44px; border-radius: 999px; background: #f3f4f6; color: #111827; display: grid; place-items: center; font-weight: 800; }
.lt-seller-name { margin: 0; font-size: 31px; font-weight: 800; color: #111827; line-height: 1.1; }
.lt-seller-meta { margin-top: 2px; font-size: 13px; color: #6b7280; }
.lt-actions { margin-top: 14px; display: grid; gap: 10px; }
.lt-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.lt-btn { height: 46px; border-radius: 999px; border: 1px solid #f3ced6; background: #f8e6ea; color: #e11d48; font-size: 20px; font-weight: 800; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer; }
.lt-btn:disabled { opacity: .45; cursor: not-allowed; }
.lt-btn-main { border: 0; background: #ff3a59; color: #fff; width: 100%; }
.lt-btn-soft { border-color: #efdde1; background: #f5eaed; }
.lt-btn-outline { border-color: #d4d8e0; background: #fff; color: #334155; }
.lt-report { margin-top: 16px; height: 54px; border: 1px solid #e3e7ee; border-radius: 999px; background: #f7f7f8; color: #e11d48; font-size: 16px; font-weight: 700; display: grid; place-items: center; text-decoration: none; }
.lt-policy { margin-top: 16px; text-align: center; color: #6b7280; font-size: 13px; }
.lt-related { margin-top: 26px; }
.lt-related-head { display: flex; justify-content: space-between; align-items: center; }
.lt-related-title { font-size: 30px; font-weight: 900; margin: 0; }
.lt-scroll-wrap { position: relative; margin-top: 14px; }
.lt-scroll-track { display: flex; gap: 12px; overflow-x: auto; scroll-behavior: smooth; padding: 2px 2px 8px; }
.lt-rel-card { min-width: 232px; width: 232px; border: 1px solid #d8dce4; border-radius: 10px; background: #f7f7f8; overflow: hidden; text-decoration: none; color: inherit; }
.lt-rel-photo { height: 168px; background: #d1d5db; }
.lt-rel-photo img { width: 100%; height: 100%; object-fit: cover; }
.lt-rel-body { padding: 10px; }
.lt-rel-price { font-size: 32px; font-weight: 900; color: #111827; line-height: 1.1; }
.lt-rel-title { margin-top: 4px; font-size: 20px; font-weight: 700; color: #111827; line-height: 1.3; min-height: 52px; }
.lt-rel-city { margin-top: 6px; font-size: 13px; color: #6b7280; }
.lt-scroll-btn { position: absolute; top: 42%; transform: translateY(-50%); width: 44px; height: 44px; border: 0; border-radius: 999px; background: rgba(255,255,255,.92); box-shadow: 0 1px 4px rgba(15,23,42,.18); display: grid; place-items: center; cursor: pointer; }
.lt-scroll-btn.prev { left: -16px; }
.lt-scroll-btn.next { right: -16px; }
.lt-pill-wrap { margin-top: 20px; }
.lt-pill-title { margin: 0 0 10px; font-size: 30px; font-weight: 900; }
.lt-pills { display: flex; flex-wrap: wrap; gap: 10px; }
.lt-pill { border: 1px solid #d4d8e0; background: #f4f5f7; border-radius: 999px; padding: 8px 14px; color: #374151; text-decoration: none; font-size: 14px; font-weight: 600; }
@media (max-width: 1080px) {
.lt-grid { grid-template-columns: 1fr; }
.lt-side-card { position: static; }
.lt-scroll-btn { display: none; }
.lt-price { font-size: 39px; }
.lt-seller-name, .lt-section-title, .lt-related-title, .lt-pill-title { font-size: 24px; }
}
@media (max-width: 640px) {
.lt-wrap { padding: 16px 10px 30px; }
.lt-detail-card, .lt-media-card, .lt-side-card { padding: 12px; }
.lt-gallery-main, .lt-gallery-main img, .lt-gallery-main-empty { min-height: 260px; }
.lt-feature-row { grid-template-columns: 1fr; gap: 10px; }
.lt-price-row { flex-direction: column; }
.lt-meta { text-align: left; }
.lt-rel-card { min-width: 196px; width: 196px; }
.lt-rel-photo { height: 140px; }
}
</style>
<div class="lt-wrap"> <div class="lt-wrap">
<nav class="lt-breadcrumb" aria-label="breadcrumb"> <nav class="lt-breadcrumb" aria-label="breadcrumb">
<a href="{{ route('home') }}">Anasayfa</a> <a href="{{ route('home') }}">Anasayfa</a>

View File

@ -16,7 +16,7 @@ Route::get('/locations/cities/{country}', function (string $country) {
$countryModel = Country::query() $countryModel = Country::query()
->where(function ($query) use ($lookupValue, $lookupCode, $lookupName): void { ->where(function ($query) use ($lookupValue, $lookupCode, $lookupName): void {
if (ctype_digit($lookupValue)) { if (ctype_digit($lookupValue)) {
$query->orWhereKey((int) $lookupValue); $query->orWhere('id', (int) $lookupValue);
} }
$query $query

View File

@ -479,9 +479,13 @@ class QuickCreateListing extends Page
'description' => ['required', 'string', 'max:1450'], 'description' => ['required', 'string', 'max:1450'],
'selectedCountryId' => ['required', 'integer', Rule::in(collect($this->countries)->pluck('id')->all())], 'selectedCountryId' => ['required', 'integer', Rule::in(collect($this->countries)->pluck('id')->all())],
'selectedCityId' => [ 'selectedCityId' => [
'required', 'nullable',
'integer', 'integer',
function (string $attribute, mixed $value, \Closure $fail): void { function (string $attribute, mixed $value, \Closure $fail): void {
if (is_null($value) || $value === '') {
return;
}
$cityExists = collect($this->availableCities) $cityExists = collect($this->availableCities)
->contains(fn (array $city): bool => $city['id'] === (int) $value); ->contains(fn (array $city): bool => $city['id'] === (int) $value);
@ -498,7 +502,6 @@ class QuickCreateListing extends Page
'description.required' => 'Açıklama zorunludur.', 'description.required' => 'Açıklama zorunludur.',
'description.max' => 'Açıklama en fazla 1450 karakter olabilir.', 'description.max' => 'Açıklama en fazla 1450 karakter olabilir.',
'selectedCountryId.required' => 'Ülke seçimi zorunludur.', 'selectedCountryId.required' => 'Ülke seçimi zorunludur.',
'selectedCityId.required' => 'Şehir seçimi zorunludur.',
]); ]);
} }

View File

@ -415,9 +415,13 @@ class PanelQuickListingForm extends Component
'description' => ['required', 'string', 'max:1450'], 'description' => ['required', 'string', 'max:1450'],
'selectedCountryId' => ['required', 'integer', Rule::in(collect($this->countries)->pluck('id')->all())], 'selectedCountryId' => ['required', 'integer', Rule::in(collect($this->countries)->pluck('id')->all())],
'selectedCityId' => [ 'selectedCityId' => [
'required', 'nullable',
'integer', 'integer',
function (string $attribute, mixed $value, \Closure $fail): void { function (string $attribute, mixed $value, \Closure $fail): void {
if (is_null($value) || $value === '') {
return;
}
$cityExists = collect($this->availableCities) $cityExists = collect($this->availableCities)
->contains(fn (array $city): bool => $city['id'] === (int) $value); ->contains(fn (array $city): bool => $city['id'] === (int) $value);
@ -434,7 +438,6 @@ class PanelQuickListingForm extends Component
'description.required' => 'Açıklama zorunludur.', 'description.required' => 'Açıklama zorunludur.',
'description.max' => 'Açıklama en fazla 1450 karakter olabilir.', 'description.max' => 'Açıklama en fazla 1450 karakter olabilir.',
'selectedCountryId.required' => 'Ülke seçimi zorunludur.', 'selectedCountryId.required' => 'Ülke seçimi zorunludur.',
'selectedCityId.required' => 'Şehir seçimi zorunludur.',
]); ]);
} }

View File

@ -41,7 +41,7 @@ return [
'public' => [ 'public' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app/public'), 'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', 'url' => env('APP_PUBLIC_STORAGE_URL', '/storage'),
'visibility' => 'public', 'visibility' => 'public',
'throw' => false, 'throw' => false,
'report' => false, 'report' => false,

781
public/theme.css Normal file
View File

@ -0,0 +1,781 @@
:root {
--oc-bg: #f5f5f7;
--oc-surface: #ffffff;
--oc-border: #d2d2d7;
--oc-text: #1d1d1f;
--oc-muted: #6e6e73;
--oc-primary: #0071e3;
--oc-primary-strong: #0066cc;
--oc-primary-soft: #e8f3ff;
--oc-primary-soft-border: #bfdcff;
--oc-chip: #f5f5f7;
}
html,
body {
min-height: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Helvetica, Arial, sans-serif;
background: radial-gradient(circle at top right, #eef5ff 0%, var(--oc-bg) 30%);
color: var(--oc-text);
}
h1,
h2,
h3,
h4,
h5,
h6 {
letter-spacing: -0.01em;
}
.brand-logo {
position: relative;
width: 1.45rem;
height: 1.7rem;
display: inline-block;
border-radius: 60% 60% 55% 55% / 62% 62% 70% 70%;
background: var(--oc-text);
box-shadow: 0 5px 14px rgba(29, 29, 31, 0.18);
transform: translateY(1px);
}
.brand-logo::before {
content: "";
position: absolute;
top: -0.42rem;
right: 0.14rem;
width: 0.54rem;
height: 0.31rem;
border-radius: 100% 0;
background: var(--oc-text);
transform: rotate(-32deg);
}
.brand-logo::after {
content: "";
position: absolute;
top: 0.18rem;
left: 50%;
transform: translateX(-50%);
width: 0.16rem;
height: 0.2rem;
border-radius: 999px;
background: var(--oc-surface);
}
.brand-text {
font-size: 1.7rem;
font-weight: 600;
color: var(--oc-text);
letter-spacing: -0.03em;
}
.market-nav-surface {
background: rgba(251, 251, 253, 0.88);
backdrop-filter: saturate(180%) blur(18px);
border-bottom: 1px solid var(--oc-border);
}
.search-shell {
border: 1px solid var(--oc-border);
background: #ffffff;
border-radius: 999px;
}
.chip-btn {
border: 1px solid var(--oc-border);
background: var(--oc-chip);
border-radius: 999px;
}
.btn-primary {
background: linear-gradient(180deg, #0a84ff, var(--oc-primary));
color: #ffffff;
border-radius: 999px;
box-shadow: 0 6px 16px rgba(0, 113, 227, 0.24);
transition: background 0.2s ease, box-shadow 0.2s ease;
}
.btn-primary:hover {
background: linear-gradient(180deg, var(--oc-primary), var(--oc-primary-strong));
box-shadow: 0 8px 18px rgba(0, 113, 227, 0.3);
}
.header-utility {
width: 2.75rem;
height: 2.75rem;
border-radius: 999px;
border: 1px solid var(--oc-border);
background: #ffffff;
display: inline-flex;
align-items: center;
justify-content: center;
color: #4b5563;
transition: all 0.2s ease;
}
.header-utility:hover {
border-color: var(--oc-primary-soft-border);
color: var(--oc-primary);
}
.location-panel {
width: min(90vw, 360px);
}
.location-panel select {
border: 1px solid var(--oc-border);
border-radius: 0.75rem;
background: #f7f7f8;
color: #374151;
padding: 0.55rem 0.75rem;
font-size: 0.875rem;
}
summary::-webkit-details-marker {
display: none;
}
[dir="rtl"] {
text-align: right;
}
.listing-filter-card {
border: 1px solid var(--oc-border);
border-radius: 14px;
background: var(--oc-surface);
}
.listing-card {
border: 1px solid var(--oc-border);
border-radius: 12px;
overflow: hidden;
background: var(--oc-surface);
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.listing-card:hover {
box-shadow: 0 14px 28px rgba(29, 29, 31, 0.12);
transform: translateY(-2px);
}
.listing-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.lt-wrap {
max-width: 1320px;
margin: 0 auto;
padding: 24px 16px 46px;
}
.lt-breadcrumb {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 13px;
color: var(--oc-muted);
margin-bottom: 14px;
}
.lt-breadcrumb a {
color: #4b5563;
text-decoration: none;
}
.lt-breadcrumb span:last-child {
color: var(--oc-text);
font-weight: 700;
}
.lt-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) 352px;
gap: 18px;
align-items: start;
}
.lt-card {
border: 1px solid var(--oc-border);
border-radius: 14px;
background: #f7f7f8;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}
.lt-gallery-main {
position: relative;
border-radius: 10px;
background: #1f2937;
overflow: hidden;
min-height: 440px;
}
.lt-gallery-main img {
width: 100%;
height: 100%;
object-fit: cover;
min-height: 440px;
}
.lt-gallery-main-empty {
min-height: 440px;
display: grid;
place-items: center;
color: #cbd5e1;
font-size: 14px;
}
.lt-gallery-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 44px;
height: 44px;
border: 0;
border-radius: 999px;
background: rgba(255, 255, 255, 0.92);
color: #111827;
display: grid;
place-items: center;
cursor: pointer;
}
.lt-gallery-nav[data-gallery-prev] {
left: 14px;
}
.lt-gallery-nav[data-gallery-next] {
right: 14px;
}
.lt-gallery-top {
position: absolute;
top: 12px;
left: 12px;
right: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.lt-badge {
border-radius: 999px;
background: #ffd60a;
color: #111827;
font-size: 12px;
font-weight: 700;
padding: 6px 10px;
}
.lt-icon-btn {
width: 38px;
height: 38px;
border: 0;
border-radius: 999px;
background: rgba(17, 24, 39, 0.86);
color: #ffffff;
display: inline-flex;
align-items: center;
justify-content: center;
}
.lt-thumbs {
display: flex;
gap: 10px;
overflow-x: auto;
padding: 12px 0 2px;
}
.lt-thumb {
width: 86px;
min-width: 86px;
height: 64px;
border: 2px solid transparent;
border-radius: 10px;
overflow: hidden;
background: #d1d5db;
cursor: pointer;
}
.lt-thumb.is-active {
border-color: var(--oc-primary);
}
.lt-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.lt-media-card {
padding: 14px;
}
.lt-detail-card {
margin-top: 14px;
padding: 18px 20px;
}
.lt-price-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: space-between;
align-items: flex-start;
}
.lt-price {
font-size: 46px;
line-height: 1;
font-weight: 900;
color: #0f172a;
}
.lt-title {
margin-top: 8px;
font-size: 21px;
font-weight: 700;
color: #111827;
}
.lt-meta {
text-align: right;
color: #4b5563;
font-size: 14px;
}
.lt-meta strong {
color: #111827;
font-weight: 700;
}
.lt-credit {
margin-top: 14px;
border: 1px solid #e3e7ee;
border-radius: 12px;
padding: 14px;
background: #fafafb;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.lt-credit h4 {
margin: 0;
font-size: 20px;
color: #0f172a;
}
.lt-credit p {
margin: 3px 0 0;
font-size: 14px;
color: #4b5563;
}
.lt-tag {
border-radius: 999px;
background: var(--oc-primary);
color: #ffffff;
font-size: 13px;
font-weight: 700;
padding: 7px 12px;
}
.lt-section-title {
margin: 18px 0 10px;
font-size: 30px;
font-weight: 900;
color: #111827;
}
.lt-features {
border-top: 1px solid #e2e8f0;
margin-top: 12px;
}
.lt-feature-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
border-top: 1px solid #e7ebf2;
padding: 12px 0;
}
.lt-feature-row:first-child {
border-top: 0;
}
.lt-f-item {
display: flex;
justify-content: space-between;
gap: 8px;
color: #334155;
font-size: 15px;
}
.lt-f-item strong {
color: #111827;
font-weight: 800;
text-align: right;
}
.lt-side-card {
position: sticky;
top: 96px;
padding: 16px;
}
.lt-seller-head {
display: flex;
align-items: center;
gap: 10px;
}
.lt-avatar {
width: 44px;
height: 44px;
border-radius: 999px;
background: #f3f4f6;
color: #111827;
display: grid;
place-items: center;
font-weight: 800;
}
.lt-seller-name {
margin: 0;
font-size: 31px;
font-weight: 800;
color: #111827;
line-height: 1.1;
}
.lt-seller-meta {
margin-top: 2px;
font-size: 13px;
color: #6b7280;
}
.lt-actions {
margin-top: 14px;
display: grid;
gap: 10px;
}
.lt-row-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.lt-btn {
height: 46px;
border-radius: 999px;
border: 1px solid var(--oc-primary-soft-border);
background: var(--oc-primary-soft);
color: var(--oc-primary);
font-size: 20px;
font-weight: 800;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
}
.lt-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.lt-btn-main {
border: 0;
background: var(--oc-primary);
color: #ffffff;
width: 100%;
}
.lt-btn-soft {
border-color: #d9e9ff;
background: #eef5ff;
}
.lt-btn-outline {
border-color: var(--oc-border);
background: #ffffff;
color: #334155;
}
.lt-report {
margin-top: 16px;
height: 54px;
border: 1px solid #dce9fa;
border-radius: 999px;
background: #f7fbff;
color: var(--oc-primary);
font-size: 16px;
font-weight: 700;
display: grid;
place-items: center;
text-decoration: none;
}
.lt-policy {
margin-top: 16px;
text-align: center;
color: #6b7280;
font-size: 13px;
}
.lt-related {
margin-top: 26px;
}
.lt-related-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.lt-related-title {
font-size: 30px;
font-weight: 900;
margin: 0;
}
.lt-scroll-wrap {
position: relative;
margin-top: 14px;
}
.lt-scroll-track {
display: flex;
gap: 12px;
overflow-x: auto;
scroll-behavior: smooth;
padding: 2px 2px 8px;
}
.lt-rel-card {
min-width: 232px;
width: 232px;
border: 1px solid var(--oc-border);
border-radius: 10px;
background: #f7f7f8;
overflow: hidden;
text-decoration: none;
color: inherit;
}
.lt-rel-photo {
height: 168px;
background: #d1d5db;
}
.lt-rel-photo img {
width: 100%;
height: 100%;
object-fit: cover;
}
.lt-rel-body {
padding: 10px;
}
.lt-rel-price {
font-size: 32px;
font-weight: 900;
color: #111827;
line-height: 1.1;
}
.lt-rel-title {
margin-top: 4px;
font-size: 20px;
font-weight: 700;
color: #111827;
line-height: 1.3;
min-height: 52px;
}
.lt-rel-city {
margin-top: 6px;
font-size: 13px;
color: #6b7280;
}
.lt-scroll-btn {
position: absolute;
top: 42%;
transform: translateY(-50%);
width: 44px;
height: 44px;
border: 0;
border-radius: 999px;
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.18);
display: grid;
place-items: center;
cursor: pointer;
}
.lt-scroll-btn.prev {
left: -16px;
}
.lt-scroll-btn.next {
right: -16px;
}
.lt-pill-wrap {
margin-top: 20px;
}
.lt-pill-title {
margin: 0 0 10px;
font-size: 30px;
font-weight: 900;
}
.lt-pills {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.lt-pill {
border: 1px solid var(--oc-border);
background: #f4f5f7;
border-radius: 999px;
padding: 8px 14px;
color: #374151;
text-decoration: none;
font-size: 14px;
font-weight: 600;
}
.text-rose-500,
.text-rose-600,
.text-rose-700 {
color: var(--oc-primary) !important;
}
.bg-rose-50 {
background-color: var(--oc-primary-soft) !important;
}
.bg-rose-100 {
background-color: #dcecff !important;
}
.bg-rose-500 {
background-color: var(--oc-primary) !important;
}
.bg-rose-600 {
background-color: var(--oc-primary-strong) !important;
}
.border-rose-200,
.border-rose-300 {
border-color: var(--oc-primary-soft-border) !important;
}
.hover\:text-rose-500:hover,
.hover\:text-rose-600:hover,
.hover\:text-rose-700:hover {
color: var(--oc-primary) !important;
}
.hover\:bg-rose-50:hover {
background-color: var(--oc-primary-soft) !important;
}
.hover\:bg-rose-100:hover {
background-color: #dcecff !important;
}
.hover\:bg-rose-600:hover {
background-color: var(--oc-primary-strong) !important;
}
.focus\:ring-rose-200:focus {
--tw-ring-color: rgba(0, 113, 227, 0.24) !important;
}
.accent-rose-500 {
accent-color: var(--oc-primary) !important;
}
@media (max-width: 1080px) {
.lt-grid {
grid-template-columns: 1fr;
}
.lt-side-card {
position: static;
}
.lt-scroll-btn {
display: none;
}
.lt-price {
font-size: 39px;
}
.lt-seller-name,
.lt-section-title,
.lt-related-title,
.lt-pill-title {
font-size: 24px;
}
}
@media (max-width: 640px) {
.lt-wrap {
padding: 16px 10px 30px;
}
.lt-detail-card,
.lt-media-card,
.lt-side-card {
padding: 12px;
}
.lt-gallery-main,
.lt-gallery-main img,
.lt-gallery-main-empty {
min-height: 260px;
}
.lt-feature-row {
grid-template-columns: 1fr;
gap: 10px;
}
.lt-price-row {
flex-direction: column;
}
.lt-meta {
text-align: left;
}
.lt-rel-card {
min-width: 196px;
width: 196px;
}
.lt-rel-photo {
height: 140px;
}
}

View File

@ -300,9 +300,6 @@
</div> </div>
</div> </div>
<div class="p-4"> <div class="p-4">
<div class="rounded-lg bg-emerald-50 text-emerald-700 text-xs font-semibold px-3 py-1.5 text-center mb-3">
Elden al, kartla öde!
</div>
<div class="flex items-start justify-between gap-3"> <div class="flex items-start justify-between gap-3">
<div> <div>
<p class="text-3xl font-extrabold tracking-tight text-slate-900">{{ $priceLabel }}</p> <p class="text-3xl font-extrabold tracking-tight text-slate-900">{{ $priceLabel }}</p>

View File

@ -42,103 +42,20 @@
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $siteName }} @hasSection('title') - @yield('title') @endif</title> <title>{{ $siteName }} @hasSection('title') - @yield('title') @endif</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Pacifico&family=Sora:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <link rel="stylesheet" href="{{ asset('theme.css') }}">
<style>
:root {
--oc-bg: #f5f6fa;
--oc-surface: #ffffff;
--oc-border: #e4e7ef;
--oc-text: #191e2b;
--oc-muted: #667085;
--oc-primary: #ff4365;
--oc-primary-soft: #ffe7ed;
--oc-chip: #f1f3f8;
}
body {
font-family: 'Sora', sans-serif;
background: radial-gradient(circle at top right, #fce6ef 0%, #f5f6fa 28%);
color: var(--oc-text);
}
.brand-mark {
font-family: 'Pacifico', cursive;
}
.market-nav-surface {
background: rgba(255, 255, 255, 0.88);
backdrop-filter: saturate(180%) blur(8px);
border-bottom: 1px solid var(--oc-border);
}
.search-shell {
border: 1px solid #d9ddea;
background: #fbfcff;
border-radius: 999px;
}
.chip-btn {
border: 1px solid #d9ddea;
background: var(--oc-chip);
border-radius: 999px;
}
.btn-primary {
background: linear-gradient(120deg, #ff516e, #ff2f57);
color: #fff;
border-radius: 999px;
}
.header-utility {
width: 2.75rem;
height: 2.75rem;
border-radius: 999px;
border: 1px solid #d9ddea;
background: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
color: #64748b;
transition: all 0.2s ease;
}
.header-utility:hover {
border-color: #fda4af;
color: #f43f5e;
}
.location-panel {
width: min(90vw, 360px);
}
.location-panel select {
border: 1px solid #d9ddea;
border-radius: 0.75rem;
background: #f8fafc;
color: #334155;
padding: 0.55rem 0.75rem;
font-size: 0.875rem;
}
summary::-webkit-details-marker {
display: none;
}
[dir="rtl"] {
text-align: right;
}
</style>
@livewireStyles @livewireStyles
</head> </head>
<body class="min-h-screen"> <body class="min-h-screen">
<nav class="market-nav-surface sticky top-0 z-50"> <nav class="market-nav-surface sticky top-0 z-50">
<div class="max-w-[1320px] mx-auto px-4 py-4"> <div class="max-w-[1320px] mx-auto px-4 py-4">
<div class="flex items-center gap-3 md:gap-4"> <div class="flex items-center gap-3 md:gap-4">
<a href="{{ route('home') }}" class="shrink-0 flex items-center gap-2"> <a href="{{ route('home') }}" class="shrink-0 flex items-center gap-2.5">
@if($siteLogoUrl) @if($siteLogoUrl)
<img src="{{ $siteLogoUrl }}" alt="{{ $siteName }}" class="h-9 w-auto rounded"> <img src="{{ $siteLogoUrl }}" alt="{{ $siteName }}" class="h-9 w-auto rounded">
@else
<span class="brand-logo" aria-hidden="true"></span>
@endif @endif
<span class="brand-mark text-3xl text-rose-500 leading-none">{{ $siteName }}</span> <span class="brand-text leading-none">{{ $siteName }}</span>
</a> </a>
<form action="{{ route('listings.index') }}" method="GET" class="hidden lg:flex flex-1 search-shell items-center gap-2 px-4 py-2.5"> <form action="{{ route('listings.index') }}" method="GET" class="hidden lg:flex flex-1 search-shell items-center gap-2 px-4 py-2.5">
@ -287,53 +204,53 @@
</div> </div>
@endif @endif
<main>@yield('content')</main> <main>@yield('content')</main>
<footer class="mt-14 bg-slate-900 text-slate-300"> <footer class="mt-14 bg-slate-100 text-slate-600 border-t border-slate-200">
<div class="max-w-[1320px] mx-auto px-4 py-12"> <div class="max-w-[1320px] mx-auto px-4 py-12">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8"> <div class="grid grid-cols-1 md:grid-cols-4 gap-8">
<div> <div>
<h3 class="text-white font-semibold text-lg mb-3">{{ $siteName }}</h3> <h3 class="text-slate-900 font-semibold text-lg mb-3">{{ $siteName }}</h3>
<p class="text-sm text-slate-400 leading-relaxed">{{ $siteDescription }}</p> <p class="text-sm text-slate-500 leading-relaxed">{{ $siteDescription }}</p>
</div> </div>
<div> <div>
<h4 class="text-white font-medium mb-4">Hızlı Linkler</h4> <h4 class="text-slate-900 font-medium mb-4">Hızlı Linkler</h4>
<ul class="space-y-2 text-sm"> <ul class="space-y-2 text-sm">
<li><a href="{{ route('home') }}" class="hover:text-white transition">Ana Sayfa</a></li> <li><a href="{{ route('home') }}" class="hover:text-slate-900 transition">Ana Sayfa</a></li>
<li><a href="{{ route('categories.index') }}" class="hover:text-white transition">Kategoriler</a></li> <li><a href="{{ route('categories.index') }}" class="hover:text-slate-900 transition">Kategoriler</a></li>
<li><a href="{{ route('listings.index') }}" class="hover:text-white transition">Tüm İlanlar</a></li> <li><a href="{{ route('listings.index') }}" class="hover:text-slate-900 transition">Tüm İlanlar</a></li>
</ul> </ul>
</div> </div>
<div> <div>
<h4 class="text-white font-medium mb-4">Hesap</h4> <h4 class="text-slate-900 font-medium mb-4">Hesap</h4>
<ul class="space-y-2 text-sm"> <ul class="space-y-2 text-sm">
<li><a href="{{ $loginRoute }}" class="hover:text-white transition">{{ __('messages.login') }}</a></li> <li><a href="{{ $loginRoute }}" class="hover:text-slate-900 transition">{{ __('messages.login') }}</a></li>
<li><a href="{{ $registerRoute }}" class="hover:text-white transition">{{ __('messages.register') }}</a></li> <li><a href="{{ $registerRoute }}" class="hover:text-slate-900 transition">{{ __('messages.register') }}</a></li>
</ul> </ul>
</div> </div>
<div> <div>
<h4 class="text-white font-medium mb-4">Bağlantılar</h4> <h4 class="text-slate-900 font-medium mb-4">Bağlantılar</h4>
<ul class="space-y-2 text-sm mb-4"> <ul class="space-y-2 text-sm mb-4">
@if($linkedinUrl) @if($linkedinUrl)
<li><a href="{{ $linkedinUrl }}" target="_blank" rel="noopener" class="hover:text-white transition">LinkedIn</a></li> <li><a href="{{ $linkedinUrl }}" target="_blank" rel="noopener" class="hover:text-slate-900 transition">LinkedIn</a></li>
@endif @endif
@if($instagramUrl) @if($instagramUrl)
<li><a href="{{ $instagramUrl }}" target="_blank" rel="noopener" class="hover:text-white transition">Instagram</a></li> <li><a href="{{ $instagramUrl }}" target="_blank" rel="noopener" class="hover:text-slate-900 transition">Instagram</a></li>
@endif @endif
@if($whatsappUrl) @if($whatsappUrl)
<li><a href="{{ $whatsappUrl }}" target="_blank" rel="noopener" class="hover:text-white transition">WhatsApp</a></li> <li><a href="{{ $whatsappUrl }}" target="_blank" rel="noopener" class="hover:text-slate-900 transition">WhatsApp</a></li>
@endif @endif
@if(!$linkedinUrl && !$instagramUrl && !$whatsappUrl) @if(!$linkedinUrl && !$instagramUrl && !$whatsappUrl)
<li>Henüz sosyal bağlantı eklenmedi.</li> <li>Henüz sosyal bağlantı eklenmedi.</li>
@endif @endif
</ul> </ul>
<h4 class="text-white font-medium mb-3">Diller</h4> <h4 class="text-slate-900 font-medium mb-3">Diller</h4>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@foreach($availableLocales as $locale) @foreach($availableLocales as $locale)
<a href="{{ route('lang.switch', $locale) }}" class="text-xs {{ app()->getLocale() === $locale ? 'text-white' : 'hover:text-white' }} transition">{{ strtoupper($locale) }}</a> <a href="{{ route('lang.switch', $locale) }}" class="text-xs {{ app()->getLocale() === $locale ? 'text-slate-900' : 'hover:text-slate-900' }} transition">{{ strtoupper($locale) }}</a>
@endforeach @endforeach
</div> </div>
</div> </div>
</div> </div>
<div class="border-t border-slate-700 mt-8 pt-8 text-center text-sm text-slate-400"> <div class="border-t border-slate-300 mt-8 pt-8 text-center text-sm text-slate-500">
<p>© {{ date('Y') }} {{ $siteName }}. All rights reserved.</p> <p>© {{ date('Y') }} {{ $siteName }}. All rights reserved.</p>
</div> </div>
</div> </div>