mirror of
https://github.com/openclassify/openclassify.git
synced 2026-04-14 11:12:09 -05:00
Document listing updates
This commit is contained in:
parent
7e9d77c0a8
commit
165585cdc4
@ -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')
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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ı açı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,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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.',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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.',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
781
public/theme.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user