mirror of
https://github.com/openclassify/openclassify.git
synced 2026-04-14 11:12:09 -05:00
Compare commits
5 Commits
d91e38d475
...
7e9d77c0a8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e9d77c0a8 | ||
|
|
72fbabb60b | ||
|
|
98c7167454 | ||
|
|
278e8063f3 | ||
|
|
8be78438d0 |
11
.ai/mcp/mcp.json
Normal file
11
.ai/mcp/mcp.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"laravel-boost": {
|
||||
"command": "php",
|
||||
"args": [
|
||||
"artisan",
|
||||
"boost:mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,19 @@
|
||||
<?php
|
||||
namespace Modules\Admin\Filament\Resources;
|
||||
|
||||
use A909M\FilamentStateFusion\Forms\Components\StateFusionSelect;
|
||||
use A909M\FilamentStateFusion\Tables\Columns\StateFusionSelectColumn;
|
||||
use A909M\FilamentStateFusion\Tables\Filters\StateFusionSelectFilter;
|
||||
use App\Models\User;
|
||||
use Modules\User\App\Models\User;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Modules\Admin\Filament\Resources\UserResource\Pages;
|
||||
use Modules\User\App\Support\Filament\UserFormFields;
|
||||
use STS\FilamentImpersonate\Actions\Impersonate;
|
||||
use UnitEnum;
|
||||
|
||||
@ -28,11 +26,11 @@ class UserResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->schema([
|
||||
TextInput::make('name')->required()->maxLength(255),
|
||||
TextInput::make('email')->email()->required()->maxLength(255)->unique(ignoreRecord: true),
|
||||
TextInput::make('password')->password()->required(fn ($livewire) => $livewire instanceof Pages\CreateUser)->dehydrateStateUsing(fn ($state) => filled($state) ? bcrypt($state) : null)->dehydrated(fn ($state) => filled($state)),
|
||||
StateFusionSelect::make('status')->required(),
|
||||
Select::make('roles')->multiple()->relationship('roles', 'name')->preload(),
|
||||
UserFormFields::name(),
|
||||
UserFormFields::email(),
|
||||
UserFormFields::password(fn ($livewire) => $livewire instanceof Pages\CreateUser),
|
||||
UserFormFields::status(),
|
||||
UserFormFields::roles(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Conversation\App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Modules\Conversation\App\Models\Conversation;
|
||||
use Modules\Conversation\App\Support\QuickMessageCatalog;
|
||||
use Modules\Listing\Models\Listing;
|
||||
|
||||
class ConversationController extends Controller
|
||||
{
|
||||
public function inbox(Request $request): View
|
||||
{
|
||||
$userId = (int) $request->user()->getKey();
|
||||
$messageFilter = $this->resolveMessageFilter($request);
|
||||
|
||||
$conversations = Conversation::inboxForUser($userId, $messageFilter);
|
||||
$selectedConversation = Conversation::resolveSelected($conversations, $request->integer('conversation'));
|
||||
|
||||
if ($selectedConversation) {
|
||||
$selectedConversation->loadThread();
|
||||
$selectedConversation->markAsReadFor($userId);
|
||||
|
||||
$conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation {
|
||||
if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) {
|
||||
$conversation->unread_count = 0;
|
||||
}
|
||||
|
||||
return $conversation;
|
||||
});
|
||||
}
|
||||
|
||||
return view('conversation::inbox', [
|
||||
'conversations' => $conversations,
|
||||
'selectedConversation' => $selectedConversation,
|
||||
'messageFilter' => $messageFilter,
|
||||
'quickMessages' => QuickMessageCatalog::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function start(Request $request, Listing $listing): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (! $listing->user_id) {
|
||||
return back()->with('error', 'Bu ilan için mesajlaşma açılamadı.');
|
||||
}
|
||||
|
||||
if ((int) $listing->user_id === (int) $user->getKey()) {
|
||||
return back()->with('error', 'Kendi ilanına mesaj gönderemezsin.');
|
||||
}
|
||||
|
||||
$conversation = Conversation::openForListingBuyer($listing, (int) $user->getKey());
|
||||
|
||||
$user->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
|
||||
|
||||
$messageBody = trim((string) $request->string('message'));
|
||||
|
||||
if ($messageBody !== '') {
|
||||
$message = $conversation->messages()->create([
|
||||
'sender_id' => $user->getKey(),
|
||||
'body' => $messageBody,
|
||||
]);
|
||||
|
||||
$conversation->forceFill(['last_message_at' => $message->created_at])->save();
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('panel.inbox.index', array_merge($this->inboxFilters($request), ['conversation' => $conversation->getKey()]))
|
||||
->with('success', $messageBody !== '' ? 'Mesaj gönderildi.' : 'Sohbet açıldı.');
|
||||
}
|
||||
|
||||
public function send(Request $request, Conversation $conversation): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$userId = (int) $user->getKey();
|
||||
|
||||
if ((int) $conversation->buyer_id !== $userId && (int) $conversation->seller_id !== $userId) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$payload = $request->validate([
|
||||
'message' => ['required', 'string', 'max:2000'],
|
||||
]);
|
||||
|
||||
$messageBody = trim($payload['message']);
|
||||
|
||||
if ($messageBody === '') {
|
||||
return back()->with('error', 'Mesaj boş olamaz.');
|
||||
}
|
||||
|
||||
$message = $conversation->messages()->create([
|
||||
'sender_id' => $userId,
|
||||
'body' => $messageBody,
|
||||
]);
|
||||
|
||||
$conversation->forceFill(['last_message_at' => $message->created_at])->save();
|
||||
|
||||
return redirect()
|
||||
->route('panel.inbox.index', array_merge($this->inboxFilters($request), ['conversation' => $conversation->getKey()]))
|
||||
->with('success', 'Mesaj gönderildi.');
|
||||
}
|
||||
|
||||
private function inboxFilters(Request $request): array
|
||||
{
|
||||
$messageFilter = $this->resolveMessageFilter($request);
|
||||
|
||||
return $messageFilter === 'all' ? [] : ['message_filter' => $messageFilter];
|
||||
}
|
||||
|
||||
private function resolveMessageFilter(Request $request): string
|
||||
{
|
||||
$messageFilter = (string) $request->string('message_filter', 'all');
|
||||
|
||||
return in_array($messageFilter, ['all', 'unread', 'important'], true) ? $messageFilter : 'all';
|
||||
}
|
||||
}
|
||||
168
Modules/Conversation/App/Models/Conversation.php
Normal file
168
Modules/Conversation/App/Models/Conversation.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Conversation\App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Modules\User\App\Models\User;
|
||||
|
||||
class Conversation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['listing_id', 'seller_id', 'buyer_id', 'last_message_at'];
|
||||
|
||||
protected $casts = ['last_message_at' => 'datetime'];
|
||||
|
||||
public function listing()
|
||||
{
|
||||
return $this->belongsTo(Listing::class);
|
||||
}
|
||||
|
||||
public function seller()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'seller_id');
|
||||
}
|
||||
|
||||
public function buyer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'buyer_id');
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return $this->hasMany(ConversationMessage::class);
|
||||
}
|
||||
|
||||
public function lastMessage()
|
||||
{
|
||||
return $this->hasOne(ConversationMessage::class)
|
||||
->latestOfMany()
|
||||
->select([
|
||||
'conversation_messages.id',
|
||||
'conversation_messages.conversation_id',
|
||||
'conversation_messages.sender_id',
|
||||
'conversation_messages.body',
|
||||
'conversation_messages.created_at',
|
||||
]);
|
||||
}
|
||||
|
||||
public function scopeForUser(Builder $query, int $userId): Builder
|
||||
{
|
||||
return $query->where(function (Builder $participantQuery) use ($userId): void {
|
||||
$participantQuery->where('buyer_id', $userId)->orWhere('seller_id', $userId);
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeApplyMessageFilter(Builder $query, int $userId, string $messageFilter): Builder
|
||||
{
|
||||
if (! in_array($messageFilter, ['unread', 'important'], true)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->whereHas('messages', function (Builder $messageQuery) use ($userId): void {
|
||||
$messageQuery->where('sender_id', '!=', $userId)->whereNull('read_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeWithInboxData(Builder $query, int $userId): Builder
|
||||
{
|
||||
return $query
|
||||
->with([
|
||||
'listing:id,title,price,currency,user_id',
|
||||
'buyer:id,name',
|
||||
'seller:id,name',
|
||||
'lastMessage',
|
||||
'lastMessage.sender:id,name',
|
||||
])
|
||||
->withCount([
|
||||
'messages as unread_count' => fn (Builder $messageQuery) => $messageQuery
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at'),
|
||||
])
|
||||
->orderByDesc('last_message_at')
|
||||
->orderByDesc('updated_at');
|
||||
}
|
||||
|
||||
public static function inboxForUser(int $userId, string $messageFilter = 'all'): EloquentCollection
|
||||
{
|
||||
return static::query()
|
||||
->forUser($userId)
|
||||
->applyMessageFilter($userId, $messageFilter)
|
||||
->withInboxData($userId)
|
||||
->get();
|
||||
}
|
||||
|
||||
public static function resolveSelected(EloquentCollection $conversations, ?int $conversationId): ?self
|
||||
{
|
||||
$selectedConversationId = $conversationId;
|
||||
|
||||
if (($selectedConversationId ?? 0) <= 0 && $conversations->isNotEmpty()) {
|
||||
$selectedConversationId = (int) $conversations->first()->getKey();
|
||||
}
|
||||
|
||||
if (($selectedConversationId ?? 0) <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$selected = $conversations->firstWhere('id', $selectedConversationId);
|
||||
|
||||
if (! $selected instanceof self) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $selected;
|
||||
}
|
||||
|
||||
public function loadThread(): void
|
||||
{
|
||||
$this->load([
|
||||
'listing:id,title,price,currency,user_id',
|
||||
'messages' => fn (Builder $query) => $query->with('sender:id,name')->orderBy('created_at'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function markAsReadFor(int $userId): void
|
||||
{
|
||||
ConversationMessage::query()
|
||||
->where('conversation_id', $this->getKey())
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at')
|
||||
->update([
|
||||
'read_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function openForListingBuyer(Listing $listing, int $buyerId): self
|
||||
{
|
||||
$conversation = static::query()->firstOrCreate(
|
||||
[
|
||||
'listing_id' => $listing->getKey(),
|
||||
'buyer_id' => $buyerId,
|
||||
],
|
||||
[
|
||||
'seller_id' => $listing->user_id,
|
||||
],
|
||||
);
|
||||
|
||||
if ((int) $conversation->seller_id !== (int) $listing->user_id) {
|
||||
$conversation->forceFill(['seller_id' => $listing->user_id])->save();
|
||||
}
|
||||
|
||||
return $conversation;
|
||||
}
|
||||
|
||||
public static function buyerListingConversationId(int $listingId, int $buyerId): ?int
|
||||
{
|
||||
$value = static::query()
|
||||
->where('listing_id', $listingId)
|
||||
->where('buyer_id', $buyerId)
|
||||
->value('id');
|
||||
|
||||
return is_null($value) ? null : (int) $value;
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Modules\Conversation\App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Modules\User\App\Models\User;
|
||||
|
||||
class ConversationMessage extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'conversation_id',
|
||||
'sender_id',
|
||||
'body',
|
||||
'read_at',
|
||||
];
|
||||
protected $fillable = ['conversation_id', 'sender_id', 'body', 'read_at'];
|
||||
|
||||
protected $casts = [
|
||||
'read_at' => 'datetime',
|
||||
];
|
||||
protected $casts = ['read_at' => 'datetime'];
|
||||
|
||||
public function conversation()
|
||||
{
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Conversation\App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ConversationServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path('Conversation', 'database/migrations'));
|
||||
$this->loadRoutesFrom(module_path('Conversation', 'routes/web.php'));
|
||||
$this->loadViewsFrom(module_path('Conversation', 'resources/views'), 'conversation');
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
16
Modules/Conversation/App/Support/QuickMessageCatalog.php
Normal file
16
Modules/Conversation/App/Support/QuickMessageCatalog.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Conversation\App\Support;
|
||||
|
||||
class QuickMessageCatalog
|
||||
{
|
||||
public static function all(): array
|
||||
{
|
||||
return [
|
||||
'Merhaba',
|
||||
'İlan hâlâ satışta mı?',
|
||||
'Son fiyat nedir?',
|
||||
'Teşekkürler',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('conversations')) {
|
||||
Schema::create('conversations', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
|
||||
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->foreignId('buyer_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->timestamp('last_message_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['listing_id', 'buyer_id']);
|
||||
$table->index(['seller_id', 'last_message_at']);
|
||||
$table->index(['buyer_id', 'last_message_at']);
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('conversation_messages')) {
|
||||
Schema::create('conversation_messages', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('conversation_id')->constrained('conversations')->cascadeOnDelete();
|
||||
$table->foreignId('sender_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->text('body');
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['conversation_id', 'created_at']);
|
||||
$table->index(['conversation_id', 'read_at']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('conversation_messages');
|
||||
Schema::dropIfExists('conversations');
|
||||
}
|
||||
};
|
||||
11
Modules/Conversation/module.json
Normal file
11
Modules/Conversation/module.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Conversation",
|
||||
"alias": "conversation",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Conversation\\App\\Providers\\ConversationServiceProvider"
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
13
Modules/Conversation/routes/web.php
Normal file
13
Modules/Conversation/routes/web.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Conversation\App\Http\Controllers\ConversationController;
|
||||
|
||||
Route::middleware('auth')->prefix('panel')->name('panel.')->group(function () {
|
||||
Route::get('/gelen-kutusu', [ConversationController::class, 'inbox'])->name('inbox.index');
|
||||
});
|
||||
|
||||
Route::middleware('auth')->name('conversations.')->group(function () {
|
||||
Route::post('/listings/{listing}/conversation', [ConversationController::class, 'start'])->name('start');
|
||||
Route::post('/conversations/{conversation}/messages', [ConversationController::class, 'send'])->name('messages.send');
|
||||
});
|
||||
@ -1,43 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace Modules\Favorite\App\Http\Controllers;
|
||||
|
||||
use App\Models\Conversation;
|
||||
use App\Models\ConversationMessage;
|
||||
use App\Models\FavoriteSearch;
|
||||
use App\Models\User;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Category\Models\Category;
|
||||
use Modules\Conversation\App\Models\Conversation;
|
||||
use Modules\Conversation\App\Support\QuickMessageCatalog;
|
||||
use Modules\Favorite\App\Models\FavoriteSearch;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Modules\User\App\Models\User;
|
||||
|
||||
class FavoriteController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$activeTab = (string) $request->string('tab', 'listings');
|
||||
|
||||
if (! in_array($activeTab, ['listings', 'searches', 'sellers'], true)) {
|
||||
$activeTab = 'listings';
|
||||
}
|
||||
|
||||
$statusFilter = (string) $request->string('status', 'all');
|
||||
|
||||
if (! in_array($statusFilter, ['all', 'active'], true)) {
|
||||
$statusFilter = 'all';
|
||||
}
|
||||
|
||||
$selectedCategoryId = $request->integer('category');
|
||||
|
||||
if ($selectedCategoryId <= 0) {
|
||||
$selectedCategoryId = null;
|
||||
}
|
||||
|
||||
$messageFilter = (string) $request->string('message_filter', 'all');
|
||||
if (! in_array($messageFilter, ['all', 'unread', 'important'], true)) {
|
||||
$messageFilter = 'all';
|
||||
}
|
||||
|
||||
$selectedCategoryId = $request->integer('category');
|
||||
if ($selectedCategoryId <= 0) {
|
||||
$selectedCategoryId = null;
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
$categories = Category::query()
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
@ -49,12 +48,6 @@ class FavoriteController extends Controller
|
||||
$conversations = collect();
|
||||
$selectedConversation = null;
|
||||
$buyerConversationListingMap = [];
|
||||
$quickMessages = [
|
||||
'Merhaba',
|
||||
'İlan hâlâ satışta mı?',
|
||||
'Son fiyat nedir?',
|
||||
'Teşekkürler',
|
||||
];
|
||||
|
||||
if ($activeTab === 'listings') {
|
||||
$favoriteListings = $user->favoriteListings()
|
||||
@ -67,67 +60,26 @@ class FavoriteController extends Controller
|
||||
->withQueryString();
|
||||
|
||||
$userId = (int) $user->getKey();
|
||||
$conversations = Conversation::query()
|
||||
->forUser($userId)
|
||||
->when(in_array($messageFilter, ['unread', 'important'], true), fn ($query) => $query->whereHas('messages', fn ($messageQuery) => $messageQuery
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at')))
|
||||
->with([
|
||||
'listing:id,title,price,currency,user_id',
|
||||
'buyer:id,name',
|
||||
'seller:id,name',
|
||||
'lastMessage',
|
||||
'lastMessage.sender:id,name',
|
||||
])
|
||||
->withCount([
|
||||
'messages as unread_count' => fn ($query) => $query
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at'),
|
||||
])
|
||||
->orderByDesc('last_message_at')
|
||||
->orderByDesc('updated_at')
|
||||
->get();
|
||||
|
||||
$conversations = Conversation::inboxForUser($userId, $messageFilter);
|
||||
$buyerConversationListingMap = $conversations
|
||||
->where('buyer_id', $userId)
|
||||
->pluck('id', 'listing_id')
|
||||
->map(fn ($conversationId) => (int) $conversationId)
|
||||
->all();
|
||||
|
||||
$selectedConversationId = $request->integer('conversation');
|
||||
$selectedConversation = Conversation::resolveSelected($conversations, $request->integer('conversation'));
|
||||
|
||||
if ($selectedConversationId <= 0 && $conversations->isNotEmpty()) {
|
||||
$selectedConversationId = (int) $conversations->first()->getKey();
|
||||
}
|
||||
if ($selectedConversation) {
|
||||
$selectedConversation->loadThread();
|
||||
$selectedConversation->markAsReadFor($userId);
|
||||
|
||||
if ($selectedConversationId > 0) {
|
||||
$selectedConversation = $conversations->firstWhere('id', $selectedConversationId);
|
||||
$conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation {
|
||||
if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) {
|
||||
$conversation->unread_count = 0;
|
||||
}
|
||||
|
||||
if ($selectedConversation) {
|
||||
$selectedConversation->load([
|
||||
'listing:id,title,price,currency,user_id',
|
||||
'messages' => fn ($query) => $query
|
||||
->with('sender:id,name')
|
||||
->orderBy('created_at'),
|
||||
]);
|
||||
|
||||
ConversationMessage::query()
|
||||
->where('conversation_id', $selectedConversation->getKey())
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at')
|
||||
->update([
|
||||
'read_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation {
|
||||
if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) {
|
||||
$conversation->unread_count = 0;
|
||||
}
|
||||
|
||||
return $conversation;
|
||||
});
|
||||
}
|
||||
return $conversation;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +101,7 @@ class FavoriteController extends Controller
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
return view('favorites.index', [
|
||||
return view('favorite::index', [
|
||||
'activeTab' => $activeTab,
|
||||
'statusFilter' => $statusFilter,
|
||||
'selectedCategoryId' => $selectedCategoryId,
|
||||
@ -161,24 +113,15 @@ class FavoriteController extends Controller
|
||||
'conversations' => $conversations,
|
||||
'selectedConversation' => $selectedConversation,
|
||||
'buyerConversationListingMap' => $buyerConversationListingMap,
|
||||
'quickMessages' => $quickMessages,
|
||||
'quickMessages' => QuickMessageCatalog::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleListing(Request $request, Listing $listing)
|
||||
{
|
||||
$user = $request->user();
|
||||
$isFavorite = $user->favoriteListings()->whereKey($listing->getKey())->exists();
|
||||
$isNowFavorite = $request->user()->toggleFavoriteListing($listing);
|
||||
|
||||
if ($isFavorite) {
|
||||
$user->favoriteListings()->detach($listing->getKey());
|
||||
|
||||
return back()->with('success', 'İlan favorilerden kaldırıldı.');
|
||||
}
|
||||
|
||||
$user->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
|
||||
|
||||
return back()->with('success', 'İlan favorilere eklendi.');
|
||||
return back()->with('success', $isNowFavorite ? 'İlan favorilere eklendi.' : 'İlan favorilerden kaldırıldı.');
|
||||
}
|
||||
|
||||
public function toggleSeller(Request $request, User $seller)
|
||||
@ -189,17 +132,9 @@ class FavoriteController extends Controller
|
||||
return back()->with('error', 'Kendi hesabını favorilere ekleyemezsin.');
|
||||
}
|
||||
|
||||
$isFavorite = $user->favoriteSellers()->whereKey($seller->getKey())->exists();
|
||||
$isNowFavorite = $user->toggleFavoriteSeller($seller);
|
||||
|
||||
if ($isFavorite) {
|
||||
$user->favoriteSellers()->detach($seller->getKey());
|
||||
|
||||
return back()->with('success', 'Satıcı favorilerden kaldırıldı.');
|
||||
}
|
||||
|
||||
$user->favoriteSellers()->syncWithoutDetaching([$seller->getKey()]);
|
||||
|
||||
return back()->with('success', 'Satıcı favorilere eklendi.');
|
||||
return back()->with('success', $isNowFavorite ? 'Satıcı favorilere eklendi.' : 'Satıcı favorilerden kaldırıldı.');
|
||||
}
|
||||
|
||||
public function storeSearch(Request $request)
|
||||
@ -225,15 +160,7 @@ class FavoriteController extends Controller
|
||||
$categoryName = Category::query()->whereKey($filters['category'])->value('name');
|
||||
}
|
||||
|
||||
$labelParts = [];
|
||||
if (! empty($filters['search'])) {
|
||||
$labelParts[] = '"'.$filters['search'].'"';
|
||||
}
|
||||
if ($categoryName) {
|
||||
$labelParts[] = $categoryName;
|
||||
}
|
||||
|
||||
$label = $labelParts !== [] ? implode(' · ', $labelParts) : 'Filtreli arama';
|
||||
$label = FavoriteSearch::labelFor($filters, is_string($categoryName) ? $categoryName : null);
|
||||
|
||||
$favoriteSearch = $request->user()->favoriteSearches()->firstOrCreate(
|
||||
['signature' => $signature],
|
||||
@ -1,23 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Modules\Favorite\App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Modules\Category\Models\Category;
|
||||
use Modules\User\App\Models\User;
|
||||
|
||||
class FavoriteSearch extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'label',
|
||||
'search_term',
|
||||
'category_id',
|
||||
'filters',
|
||||
'signature',
|
||||
];
|
||||
protected $fillable = ['user_id', 'label', 'search_term', 'category_id', 'filters', 'signature'];
|
||||
|
||||
protected $casts = [
|
||||
'filters' => 'array',
|
||||
];
|
||||
protected $casts = ['filters' => 'array'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
@ -26,7 +19,7 @@ class FavoriteSearch extends Model
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(\Modules\Category\Models\Category::class);
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
public static function normalizeFilters(array $filters): array
|
||||
@ -45,4 +38,19 @@ class FavoriteSearch extends Model
|
||||
|
||||
return hash('sha256', is_string($payload) ? $payload : '');
|
||||
}
|
||||
|
||||
public static function labelFor(array $filters, ?string $categoryName = null): string
|
||||
{
|
||||
$labelParts = [];
|
||||
|
||||
if (! empty($filters['search'])) {
|
||||
$labelParts[] = '"'.$filters['search'].'"';
|
||||
}
|
||||
|
||||
if (filled($categoryName)) {
|
||||
$labelParts[] = $categoryName;
|
||||
}
|
||||
|
||||
return $labelParts !== [] ? implode(' · ', $labelParts) : 'Filtreli arama';
|
||||
}
|
||||
}
|
||||
19
Modules/Favorite/App/Providers/FavoriteServiceProvider.php
Normal file
19
Modules/Favorite/App/Providers/FavoriteServiceProvider.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Favorite\App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class FavoriteServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path('Favorite', 'database/migrations'));
|
||||
$this->loadRoutesFrom(module_path('Favorite', 'routes/web.php'));
|
||||
$this->loadViewsFrom(module_path('Favorite', 'resources/views'), 'favorite');
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('favorite_listings')) {
|
||||
Schema::create('favorite_listings', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'listing_id']);
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('favorite_sellers')) {
|
||||
Schema::create('favorite_sellers', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'seller_id']);
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('favorite_searches')) {
|
||||
Schema::create('favorite_searches', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('label')->nullable();
|
||||
$table->string('search_term')->nullable();
|
||||
$table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
|
||||
$table->json('filters')->nullable();
|
||||
$table->string('signature', 64);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'signature']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('favorite_searches');
|
||||
Schema::dropIfExists('favorite_sellers');
|
||||
Schema::dropIfExists('favorite_listings');
|
||||
}
|
||||
};
|
||||
11
Modules/Favorite/module.json
Normal file
11
Modules/Favorite/module.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Favorite",
|
||||
"alias": "favorite",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Favorite\\App\\Providers\\FavoriteServiceProvider"
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
12
Modules/Favorite/routes/web.php
Normal file
12
Modules/Favorite/routes/web.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Favorite\App\Http\Controllers\FavoriteController;
|
||||
|
||||
Route::middleware('auth')->prefix('favorites')->name('favorites.')->group(function () {
|
||||
Route::get('/', [FavoriteController::class, 'index'])->name('index');
|
||||
Route::post('/listings/{listing}/toggle', [FavoriteController::class, 'toggleListing'])->name('listings.toggle');
|
||||
Route::post('/sellers/{seller}/toggle', [FavoriteController::class, 'toggleSeller'])->name('sellers.toggle');
|
||||
Route::post('/searches', [FavoriteController::class, 'storeSearch'])->name('searches.store');
|
||||
Route::delete('/searches/{favoriteSearch}', [FavoriteController::class, 'destroySearch'])->name('searches.destroy');
|
||||
});
|
||||
@ -1,48 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Listing\Database\Seeders;
|
||||
|
||||
use Modules\User\App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Category\Models\Category;
|
||||
use Modules\Listing\Models\Listing;
|
||||
|
||||
class ListingSeeder extends Seeder
|
||||
{
|
||||
private const LISTINGS = [
|
||||
[
|
||||
'title' => 'iPhone 14 Pro 256 GB, temiz kullanılmış',
|
||||
'description' => 'Cihaz sorunsuz çalışıyor, pil sağlığı iyi durumda. Kutusu ve şarj kablosu ile teslim edilecektir.',
|
||||
'price' => 44999,
|
||||
'city' => 'İstanbul',
|
||||
'country' => 'Türkiye',
|
||||
'image' => 'sample_image/phone.jpeg',
|
||||
],
|
||||
[
|
||||
'title' => 'MacBook Pro M2 16 GB / 512 GB',
|
||||
'description' => 'Yazılım geliştirme için kullanıldı. Kozmetik olarak çok iyi durumda, faturası mevcut.',
|
||||
'price' => 62999,
|
||||
'city' => 'Ankara',
|
||||
'country' => 'Türkiye',
|
||||
'image' => 'sample_image/macbook.jpg',
|
||||
],
|
||||
[
|
||||
'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
|
||||
{
|
||||
$user = \App\Models\User::where('email', 'partner@openclassify.com')->first();
|
||||
$categories = Category::where('level', 0)->get();
|
||||
$user = $this->resolveSeederUser();
|
||||
$categories = Category::query()
|
||||
->where('level', 0)
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
if (!$user || $categories->isEmpty()) return;
|
||||
if (! $user || $categories->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$listings = [
|
||||
['title' => 'iPhone 14 Pro - Excellent Condition', 'price' => 799, 'city' => 'Istanbul', 'country' => 'Turkey'],
|
||||
['title' => 'MacBook Pro 2023', 'price' => 1499, 'city' => 'Ankara', 'country' => 'Turkey'],
|
||||
['title' => '2020 Toyota Corolla', 'price' => 18000, 'city' => 'New York', 'country' => 'United States'],
|
||||
['title' => '3-Bedroom Apartment for Sale', 'price' => 250000, 'city' => 'Istanbul', 'country' => 'Turkey'],
|
||||
['title' => 'Nike Running Shoes Size 42', 'price' => 89, 'city' => 'Berlin', 'country' => 'Germany'],
|
||||
['title' => 'IKEA Dining Table', 'price' => 150, 'city' => 'London', 'country' => 'United Kingdom'],
|
||||
['title' => 'Yoga Mat - Brand New', 'price' => 35, 'city' => 'Paris', 'country' => 'France'],
|
||||
['title' => 'Web Developer for Hire', 'price' => 0, 'city' => 'Remote', 'country' => 'Turkey'],
|
||||
['title' => 'Samsung 55" 4K TV', 'price' => 599, 'city' => 'Madrid', 'country' => 'Spain'],
|
||||
['title' => 'Honda CBR500R Motorcycle 2021', 'price' => 6500, 'city' => 'Tokyo', 'country' => 'Japan'],
|
||||
];
|
||||
|
||||
foreach ($listings as $i => $listing) {
|
||||
$category = $categories->get($i % $categories->count());
|
||||
Listing::firstOrCreate(
|
||||
['slug' => \Illuminate\Support\Str::slug($listing['title']) . '-' . ($i + 1)],
|
||||
array_merge($listing, [
|
||||
'slug' => \Illuminate\Support\Str::slug($listing['title']) . '-' . ($i + 1),
|
||||
'description' => 'This is a sample listing description for ' . $listing['title'],
|
||||
'currency' => $listing['price'] > 0 ? 'USD' : 'USD',
|
||||
'category_id' => $category?->id,
|
||||
'user_id' => $user->id,
|
||||
'status' => 'active',
|
||||
'contact_email' => 'partner@openclassify.com',
|
||||
'contact_phone' => '+1234567890',
|
||||
'is_featured' => $i < 3,
|
||||
])
|
||||
foreach (self::LISTINGS as $index => $data) {
|
||||
$listing = $this->upsertListing(
|
||||
index: $index,
|
||||
data: $data,
|
||||
categories: $categories,
|
||||
user: $user,
|
||||
);
|
||||
|
||||
$this->syncListingImage($listing, $data['image']);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveSeederUser(): ?User
|
||||
{
|
||||
return User::query()
|
||||
->where('email', 'b@b.com')
|
||||
->orWhere('email', 'partner@openclassify.com')
|
||||
->first();
|
||||
}
|
||||
|
||||
private function upsertListing(int $index, array $data, Collection $categories, User $user): Listing
|
||||
{
|
||||
$slug = Str::slug($data['title']) . '-' . ($index + 1);
|
||||
$category = $categories->get($index % $categories->count());
|
||||
|
||||
return Listing::updateOrCreate(
|
||||
['slug' => $slug],
|
||||
[
|
||||
'slug' => $slug,
|
||||
'title' => $data['title'],
|
||||
'description' => $data['description'],
|
||||
'price' => $data['price'],
|
||||
'currency' => 'TRY',
|
||||
'city' => $data['city'],
|
||||
'country' => $data['country'],
|
||||
'category_id' => $category?->id,
|
||||
'user_id' => $user->id,
|
||||
'status' => 'active',
|
||||
'contact_email' => $user->email,
|
||||
'contact_phone' => '+905551112233',
|
||||
'is_featured' => $index < 3,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function syncListingImage(Listing $listing, string $imageRelativePath): void
|
||||
{
|
||||
$imageAbsolutePath = public_path($imageRelativePath);
|
||||
|
||||
if (! is_file($imageAbsolutePath)) {
|
||||
if ($this->command) {
|
||||
$this->command->warn("Gorsel bulunamadi: {$imageRelativePath}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$targetFileName = basename($imageAbsolutePath);
|
||||
$mediaItems = $listing->getMedia('listing-images');
|
||||
|
||||
if (! $this->hasSingleHealthyTargetMedia($mediaItems, $targetFileName)) {
|
||||
$listing->clearMediaCollection('listing-images');
|
||||
|
||||
$listing
|
||||
->addMedia($imageAbsolutePath)
|
||||
->preservingOriginal()
|
||||
->toMediaCollection('listing-images', 'public');
|
||||
}
|
||||
}
|
||||
|
||||
private function hasSingleHealthyTargetMedia(Collection $mediaItems, string $targetFileName): bool
|
||||
{
|
||||
if ($mediaItems->count() !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$media = $mediaItems->first();
|
||||
|
||||
if (
|
||||
! $media
|
||||
|| (string) $media->file_name !== $targetFileName
|
||||
|| (string) $media->disk !== 'public'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return is_file($media->getPath());
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
namespace Modules\Listing\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\FavoriteSearch;
|
||||
use Modules\Conversation\App\Models\Conversation;
|
||||
use Modules\Favorite\App\Models\FavoriteSearch;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
@ -55,18 +55,18 @@ class Listing extends Model implements HasMedia
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class);
|
||||
return $this->belongsTo(\Modules\User\App\Models\User::class);
|
||||
}
|
||||
|
||||
public function favoritedByUsers()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'favorite_listings')
|
||||
return $this->belongsToMany(\Modules\User\App\Models\User::class, 'favorite_listings')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function conversations()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Conversation::class);
|
||||
return $this->hasMany(\Modules\Conversation\App\Models\Conversation::class);
|
||||
}
|
||||
|
||||
public function scopePublicFeed(Builder $query): Builder
|
||||
|
||||
@ -1,133 +0,0 @@
|
||||
<?php
|
||||
namespace Modules\Listing\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Category\Models\Category;
|
||||
use Modules\Listing\Models\Listing;
|
||||
|
||||
class ListingSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$user = \App\Models\User::where('email', 'b@b.com')
|
||||
->orWhere('email', 'partner@openclassify.com')
|
||||
->first();
|
||||
$categories = Category::where('level', 0)->get();
|
||||
|
||||
if (!$user || $categories->isEmpty()) return;
|
||||
|
||||
$listings = [
|
||||
[
|
||||
'title' => 'iPhone 14 Pro 256 GB, temiz kullanılmış',
|
||||
'description' => 'Cihaz sorunsuz çalışıyor, pil sağlığı iyi durumda. Kutusu ve şarj kablosu ile teslim edilecektir.',
|
||||
'price' => 44999,
|
||||
'city' => 'İstanbul',
|
||||
'country' => 'Türkiye',
|
||||
],
|
||||
[
|
||||
'title' => 'MacBook Pro M2 16 GB / 512 GB',
|
||||
'description' => 'Yazılım geliştirme için kullanıldı. Kozmetik olarak çok iyi durumda, faturası mevcut.',
|
||||
'price' => 62999,
|
||||
'city' => 'Ankara',
|
||||
'country' => 'Türkiye',
|
||||
],
|
||||
[
|
||||
'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',
|
||||
],
|
||||
[
|
||||
'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',
|
||||
],
|
||||
[
|
||||
'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',
|
||||
],
|
||||
[
|
||||
'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',
|
||||
],
|
||||
[
|
||||
'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',
|
||||
],
|
||||
];
|
||||
|
||||
$sampleImages = [
|
||||
'sample_image/phone.jpeg',
|
||||
'sample_image/macbook.jpg',
|
||||
'sample_image/car.jpeg',
|
||||
'sample_image/headphones.jpg',
|
||||
'sample_image/laptop.jpg',
|
||||
'sample_image/cup.jpg',
|
||||
'sample_image/car2.jpeg',
|
||||
];
|
||||
|
||||
$sampleImageFileNames = collect($sampleImages)
|
||||
->map(fn (string $path): string => basename($path))
|
||||
->values();
|
||||
|
||||
foreach ($listings as $i => $listing) {
|
||||
$category = $categories->get($i % $categories->count());
|
||||
$slug = Str::slug($listing['title']) . '-' . ($i + 1);
|
||||
|
||||
$listingModel = Listing::updateOrCreate(
|
||||
['slug' => $slug],
|
||||
array_merge($listing, [
|
||||
'slug' => $slug,
|
||||
'description' => $listing['description'],
|
||||
'currency' => 'TRY',
|
||||
'category_id' => $category?->id,
|
||||
'user_id' => $user->id,
|
||||
'status' => 'active',
|
||||
'contact_email' => $user->email,
|
||||
'contact_phone' => '+905551112233',
|
||||
'is_featured' => $i < 3,
|
||||
])
|
||||
);
|
||||
|
||||
$imageRelativePath = $sampleImages[$i % count($sampleImages)];
|
||||
$imageAbsolutePath = public_path($imageRelativePath);
|
||||
|
||||
if (! is_file($imageAbsolutePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentMedia = $listingModel->getMedia('listing-images');
|
||||
$currentHasSampleImage = $currentMedia->contains(
|
||||
fn ($media): bool => $sampleImageFileNames->contains((string) $media->file_name)
|
||||
);
|
||||
|
||||
if (! $currentHasSampleImage) {
|
||||
$listingModel->clearMediaCollection('listing-images');
|
||||
}
|
||||
|
||||
$targetFileName = basename($imageAbsolutePath);
|
||||
$alreadyHasTargetImage = $listingModel->getMedia('listing-images')
|
||||
->contains(fn ($media): bool => (string) $media->file_name === $targetFileName);
|
||||
|
||||
if (! $alreadyHasTargetImage) {
|
||||
$listingModel
|
||||
->addMedia($imageAbsolutePath)
|
||||
->preservingOriginal()
|
||||
->toMediaCollection('listing-images');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,45 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Location\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Modules\Location\Models\City;
|
||||
use Modules\Location\Models\Country;
|
||||
use Tapp\FilamentCountryCodeField\Enums\CountriesEnum;
|
||||
|
||||
class LocationSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$countries = [
|
||||
['name' => 'Turkey', 'code' => 'TR', 'phone_code' => '+90'],
|
||||
['name' => 'United States', 'code' => 'US', 'phone_code' => '+1'],
|
||||
['name' => 'Germany', 'code' => 'DE', 'phone_code' => '+49'],
|
||||
['name' => 'France', 'code' => 'FR', 'phone_code' => '+33'],
|
||||
['name' => 'United Kingdom', 'code' => 'GB', 'phone_code' => '+44'],
|
||||
['name' => 'Spain', 'code' => 'ES', 'phone_code' => '+34'],
|
||||
['name' => 'Italy', 'code' => 'IT', 'phone_code' => '+39'],
|
||||
['name' => 'Russia', 'code' => 'RU', 'phone_code' => '+7'],
|
||||
['name' => 'China', 'code' => 'CN', 'phone_code' => '+86'],
|
||||
['name' => 'Japan', 'code' => 'JP', 'phone_code' => '+81'],
|
||||
foreach ($this->countries() as $country) {
|
||||
Country::updateOrCreate(
|
||||
['code' => $country['code']],
|
||||
[
|
||||
'name' => $country['name'],
|
||||
'phone_code' => $country['phone_code'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$turkey = Country::query()->where('code', 'TR')->first();
|
||||
|
||||
if (! $turkey) {
|
||||
return;
|
||||
}
|
||||
|
||||
$turkeyCities = $this->turkeyCities();
|
||||
|
||||
foreach ($turkeyCities as $city) {
|
||||
City::updateOrCreate(
|
||||
['country_id' => (int) $turkey->id, 'name' => $city],
|
||||
['is_active' => true]
|
||||
);
|
||||
}
|
||||
|
||||
City::query()
|
||||
->where('country_id', (int) $turkey->id)
|
||||
->whereNotIn('name', $turkeyCities)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{code: string, name: string, phone_code: string}>
|
||||
*/
|
||||
private function countries(): array
|
||||
{
|
||||
$countries = [];
|
||||
|
||||
foreach (CountriesEnum::cases() as $countryEnum) {
|
||||
$value = $countryEnum->value;
|
||||
$phoneCode = $this->normalizePhoneCode($countryEnum->getCountryCode());
|
||||
|
||||
if ($value === 'us_ca') {
|
||||
$countries['US'] = [
|
||||
'code' => 'US',
|
||||
'name' => 'Amerika Birleşik Devletleri',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
$countries['CA'] = [
|
||||
'code' => 'CA',
|
||||
'name' => 'Kanada',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === 'ru_kz') {
|
||||
$countries['RU'] = [
|
||||
'code' => 'RU',
|
||||
'name' => 'Rusya',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
$countries['KZ'] = [
|
||||
'code' => 'KZ',
|
||||
'name' => 'Kazakistan',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = 'filament-country-code-field::countries.' . $value;
|
||||
$labelTr = trim((string) trans($key, [], 'tr'));
|
||||
$labelEn = trim((string) trans($key, [], 'en'));
|
||||
|
||||
$name = $labelTr !== '' && $labelTr !== $key
|
||||
? $labelTr
|
||||
: ($labelEn !== '' && $labelEn !== $key ? $labelEn : strtoupper($value));
|
||||
|
||||
$iso2 = strtoupper(explode('_', $value)[0] ?? $value);
|
||||
|
||||
$countries[$iso2] = [
|
||||
'code' => $iso2,
|
||||
'name' => $name,
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
}
|
||||
|
||||
return collect($countries)
|
||||
->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function normalizePhoneCode(string $phoneCode): string
|
||||
{
|
||||
$normalized = trim(explode(',', $phoneCode)[0]);
|
||||
$normalized = str_replace(' ', '', $normalized);
|
||||
|
||||
return substr($normalized, 0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function turkeyCities(): array
|
||||
{
|
||||
return [
|
||||
'Adana',
|
||||
'Adıyaman',
|
||||
'Afyonkarahisar',
|
||||
'Ağrı',
|
||||
'Aksaray',
|
||||
'Amasya',
|
||||
'Ankara',
|
||||
'Antalya',
|
||||
'Ardahan',
|
||||
'Artvin',
|
||||
'Aydın',
|
||||
'Balıkesir',
|
||||
'Bartın',
|
||||
'Batman',
|
||||
'Bayburt',
|
||||
'Bilecik',
|
||||
'Bingöl',
|
||||
'Bitlis',
|
||||
'Bolu',
|
||||
'Burdur',
|
||||
'Bursa',
|
||||
'Çanakkale',
|
||||
'Çankırı',
|
||||
'Çorum',
|
||||
'Denizli',
|
||||
'Diyarbakır',
|
||||
'Düzce',
|
||||
'Edirne',
|
||||
'Elazığ',
|
||||
'Erzincan',
|
||||
'Erzurum',
|
||||
'Eskişehir',
|
||||
'Gaziantep',
|
||||
'Giresun',
|
||||
'Gümüşhane',
|
||||
'Hakkari',
|
||||
'Hatay',
|
||||
'Iğdır',
|
||||
'Isparta',
|
||||
'İstanbul',
|
||||
'İzmir',
|
||||
'Kahramanmaraş',
|
||||
'Karabük',
|
||||
'Karaman',
|
||||
'Kars',
|
||||
'Kastamonu',
|
||||
'Kayseri',
|
||||
'Kilis',
|
||||
'Kırıkkale',
|
||||
'Kırklareli',
|
||||
'Kırşehir',
|
||||
'Kocaeli',
|
||||
'Konya',
|
||||
'Kütahya',
|
||||
'Malatya',
|
||||
'Manisa',
|
||||
'Mardin',
|
||||
'Mersin',
|
||||
'Muğla',
|
||||
'Muş',
|
||||
'Nevşehir',
|
||||
'Niğde',
|
||||
'Ordu',
|
||||
'Osmaniye',
|
||||
'Rize',
|
||||
'Sakarya',
|
||||
'Samsun',
|
||||
'Siirt',
|
||||
'Sinop',
|
||||
'Sivas',
|
||||
'Şanlıurfa',
|
||||
'Şırnak',
|
||||
'Tekirdağ',
|
||||
'Tokat',
|
||||
'Trabzon',
|
||||
'Tunceli',
|
||||
'Uşak',
|
||||
'Van',
|
||||
'Yalova',
|
||||
'Yozgat',
|
||||
'Zonguldak',
|
||||
];
|
||||
|
||||
foreach ($countries as $country) {
|
||||
Country::firstOrCreate(['code' => $country['code']], array_merge($country, ['is_active' => true]));
|
||||
}
|
||||
|
||||
$tr = Country::where('code', 'TR')->first();
|
||||
if ($tr) {
|
||||
$cities = ['Istanbul', 'Ankara', 'Izmir', 'Bursa', 'Antalya'];
|
||||
foreach ($cities as $city) {
|
||||
City::firstOrCreate(['name' => $city, 'country_id' => $tr->id]);
|
||||
}
|
||||
}
|
||||
|
||||
$us = Country::where('code', 'US')->first();
|
||||
if ($us) {
|
||||
$cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'];
|
||||
foreach ($cities as $city) {
|
||||
City::firstOrCreate(['name' => $city, 'country_id' => $us->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,207 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Location\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Modules\Location\Models\City;
|
||||
use Modules\Location\Models\Country;
|
||||
use Tapp\FilamentCountryCodeField\Enums\CountriesEnum;
|
||||
|
||||
class LocationSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
foreach ($this->countries() as $country) {
|
||||
Country::updateOrCreate(
|
||||
['code' => $country['code']],
|
||||
[
|
||||
'name' => $country['name'],
|
||||
'phone_code' => $country['phone_code'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$turkey = Country::query()->where('code', 'TR')->first();
|
||||
|
||||
if (! $turkey) {
|
||||
return;
|
||||
}
|
||||
|
||||
$turkeyCities = $this->turkeyCities();
|
||||
|
||||
foreach ($turkeyCities as $city) {
|
||||
City::updateOrCreate(
|
||||
['country_id' => (int) $turkey->id, 'name' => $city],
|
||||
['is_active' => true]
|
||||
);
|
||||
}
|
||||
|
||||
City::query()
|
||||
->where('country_id', (int) $turkey->id)
|
||||
->whereNotIn('name', $turkeyCities)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{code: string, name: string, phone_code: string}>
|
||||
*/
|
||||
private function countries(): array
|
||||
{
|
||||
$countries = [];
|
||||
|
||||
foreach (CountriesEnum::cases() as $countryEnum) {
|
||||
$value = $countryEnum->value;
|
||||
$phoneCode = $this->normalizePhoneCode($countryEnum->getCountryCode());
|
||||
|
||||
if ($value === 'us_ca') {
|
||||
$countries['US'] = [
|
||||
'code' => 'US',
|
||||
'name' => 'Amerika Birleşik Devletleri',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
$countries['CA'] = [
|
||||
'code' => 'CA',
|
||||
'name' => 'Kanada',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === 'ru_kz') {
|
||||
$countries['RU'] = [
|
||||
'code' => 'RU',
|
||||
'name' => 'Rusya',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
$countries['KZ'] = [
|
||||
'code' => 'KZ',
|
||||
'name' => 'Kazakistan',
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = 'filament-country-code-field::countries.' . $value;
|
||||
$labelTr = trim((string) trans($key, [], 'tr'));
|
||||
$labelEn = trim((string) trans($key, [], 'en'));
|
||||
|
||||
$name = $labelTr !== '' && $labelTr !== $key
|
||||
? $labelTr
|
||||
: ($labelEn !== '' && $labelEn !== $key ? $labelEn : strtoupper($value));
|
||||
|
||||
$iso2 = strtoupper(explode('_', $value)[0] ?? $value);
|
||||
|
||||
$countries[$iso2] = [
|
||||
'code' => $iso2,
|
||||
'name' => $name,
|
||||
'phone_code' => $phoneCode,
|
||||
];
|
||||
}
|
||||
|
||||
return collect($countries)
|
||||
->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function normalizePhoneCode(string $phoneCode): string
|
||||
{
|
||||
$normalized = trim(explode(',', $phoneCode)[0]);
|
||||
$normalized = str_replace(' ', '', $normalized);
|
||||
|
||||
return substr($normalized, 0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function turkeyCities(): array
|
||||
{
|
||||
return [
|
||||
'Adana',
|
||||
'Adıyaman',
|
||||
'Afyonkarahisar',
|
||||
'Ağrı',
|
||||
'Aksaray',
|
||||
'Amasya',
|
||||
'Ankara',
|
||||
'Antalya',
|
||||
'Ardahan',
|
||||
'Artvin',
|
||||
'Aydın',
|
||||
'Balıkesir',
|
||||
'Bartın',
|
||||
'Batman',
|
||||
'Bayburt',
|
||||
'Bilecik',
|
||||
'Bingöl',
|
||||
'Bitlis',
|
||||
'Bolu',
|
||||
'Burdur',
|
||||
'Bursa',
|
||||
'Çanakkale',
|
||||
'Çankırı',
|
||||
'Çorum',
|
||||
'Denizli',
|
||||
'Diyarbakır',
|
||||
'Düzce',
|
||||
'Edirne',
|
||||
'Elazığ',
|
||||
'Erzincan',
|
||||
'Erzurum',
|
||||
'Eskişehir',
|
||||
'Gaziantep',
|
||||
'Giresun',
|
||||
'Gümüşhane',
|
||||
'Hakkari',
|
||||
'Hatay',
|
||||
'Iğdır',
|
||||
'Isparta',
|
||||
'İstanbul',
|
||||
'İzmir',
|
||||
'Kahramanmaraş',
|
||||
'Karabük',
|
||||
'Karaman',
|
||||
'Kars',
|
||||
'Kastamonu',
|
||||
'Kayseri',
|
||||
'Kilis',
|
||||
'Kırıkkale',
|
||||
'Kırklareli',
|
||||
'Kırşehir',
|
||||
'Kocaeli',
|
||||
'Konya',
|
||||
'Kütahya',
|
||||
'Malatya',
|
||||
'Manisa',
|
||||
'Mardin',
|
||||
'Mersin',
|
||||
'Muğla',
|
||||
'Muş',
|
||||
'Nevşehir',
|
||||
'Niğde',
|
||||
'Ordu',
|
||||
'Osmaniye',
|
||||
'Rize',
|
||||
'Sakarya',
|
||||
'Samsun',
|
||||
'Siirt',
|
||||
'Sinop',
|
||||
'Sivas',
|
||||
'Şanlıurfa',
|
||||
'Şırnak',
|
||||
'Tekirdağ',
|
||||
'Tokat',
|
||||
'Trabzon',
|
||||
'Tunceli',
|
||||
'Uşak',
|
||||
'Van',
|
||||
'Yalova',
|
||||
'Yozgat',
|
||||
'Zonguldak',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ use Modules\Listing\Support\ListingPanelHelper;
|
||||
use Modules\Location\Models\City;
|
||||
use Modules\Location\Models\Country;
|
||||
use Modules\Partner\Filament\Resources\ListingResource;
|
||||
use Modules\Profile\Models\Profile;
|
||||
use Modules\User\App\Models\Profile;
|
||||
use Throwable;
|
||||
|
||||
class QuickCreateListing extends Page
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
namespace Modules\Partner\Providers;
|
||||
|
||||
use A909M\FilamentStateFusion\FilamentStateFusionPlugin;
|
||||
use App\Models\User;
|
||||
use Modules\User\App\Models\User;
|
||||
use DutchCodingCompany\FilamentDeveloperLogins\FilamentDeveloperLoginsPlugin;
|
||||
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
namespace Modules\Profile\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Modules\Profile\Models\Profile;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
public function show()
|
||||
{
|
||||
$profile = Profile::firstOrCreate(['user_id' => auth()->id()]);
|
||||
return view('profile::show', compact('profile'));
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$profile = Profile::firstOrCreate(['user_id' => auth()->id()]);
|
||||
return view('profile::edit', compact('profile'));
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'bio' => 'nullable|string|max:500',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'city' => 'nullable|string|max:100',
|
||||
'country' => 'nullable|string|max:100',
|
||||
'website' => 'nullable|url',
|
||||
]);
|
||||
Profile::updateOrCreate(['user_id' => auth()->id()], $data);
|
||||
return redirect()->route('profile.show')->with('success', 'Profile updated!');
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
namespace Modules\Profile\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ProfileServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected string $moduleName = 'Profile';
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
|
||||
$this->loadRoutesFrom(module_path($this->moduleName, 'routes/web.php'));
|
||||
$this->loadViewsFrom(module_path($this->moduleName, 'resources/views'), 'profile');
|
||||
}
|
||||
|
||||
public function register(): void {}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('profiles', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->unique()->constrained('users')->cascadeOnDelete();
|
||||
$table->string('avatar')->nullable();
|
||||
$table->text('bio')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->string('city')->nullable();
|
||||
$table->string('country')->nullable();
|
||||
$table->string('website')->nullable();
|
||||
$table->boolean('is_verified')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('profiles');
|
||||
}
|
||||
};
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "Profile",
|
||||
"alias": "profile",
|
||||
"description": "User profile management",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Profile\\Providers\\ProfileServiceProvider"
|
||||
],
|
||||
"aliases": {},
|
||||
"files": []
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
@extends('app::layouts.app')
|
||||
@section('content')
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<h1 class="text-2xl font-bold mb-6">Edit Profile</h1>
|
||||
<form method="POST" action="{{ route('profile.update') }}" class="bg-white rounded-lg shadow-md p-6 space-y-4">
|
||||
@csrf @method('PUT')
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Bio</label>
|
||||
<textarea name="bio" rows="3" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">{{ old('bio', $profile->bio) }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Phone</label>
|
||||
<input type="text" name="phone" value="{{ old('phone', $profile->phone) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">City</label>
|
||||
<input type="text" name="city" value="{{ old('city', $profile->city) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Country</label>
|
||||
<input type="text" name="country" value="{{ old('country', $profile->country) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Website</label>
|
||||
<input type="url" name="website" value="{{ old('website', $profile->website) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 transition font-medium">Save Profile</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@ -1,25 +0,0 @@
|
||||
@extends('app::layouts.app')
|
||||
@section('content')
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6">
|
||||
<div class="flex items-center space-x-4 mb-6">
|
||||
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span class="text-2xl font-bold text-blue-600">{{ substr(auth()->user()->name, 0, 1) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">{{ auth()->user()->name }}</h1>
|
||||
<p class="text-gray-500">{{ auth()->user()->email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@if($profile->bio)<p class="text-gray-700 mb-4">{{ $profile->bio }}</p>@endif
|
||||
<div class="space-y-2 text-gray-600">
|
||||
@if($profile->phone)<p>📞 {{ $profile->phone }}</p>@endif
|
||||
@if($profile->city)<p>📍 {{ $profile->city }}@if($profile->country), {{ $profile->country }}@endif</p>@endif
|
||||
@if($profile->website)<p>🌐 <a href="{{ $profile->website }}" class="text-blue-600 hover:underline">{{ $profile->website }}</a></p>@endif
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<a href="{{ route('profile.edit') }}" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition">Edit Profile</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Profile\Http\Controllers\ProfileController;
|
||||
|
||||
Route::middleware('auth')->prefix('profile')->name('profile.')->group(function () {
|
||||
Route::get('/', [ProfileController::class, 'show'])->name('show');
|
||||
Route::get('/edit', [ProfileController::class, 'edit'])->name('edit');
|
||||
Route::put('/extended', [ProfileController::class, 'update'])->name('update');
|
||||
});
|
||||
@ -1,23 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
namespace Modules\User\App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function edit(Request $request): View
|
||||
{
|
||||
return view('user::profile.edit', ['user' => $request->user()]);
|
||||
}
|
||||
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validateWithBag('updateProfile', [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique('users')->ignore($request->user()->id)],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'lowercase',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($request->user()->id),
|
||||
],
|
||||
]);
|
||||
|
||||
$request->user()->fill($validated);
|
||||
@ -28,12 +38,9 @@ class ProfileController extends Controller
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return redirect('/profile')->with('status', 'profile-updated');
|
||||
return redirect()->route('profile.edit')->with('status', 'profile-updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validateWithBag('userDeletion', [
|
||||
@ -43,7 +50,6 @@ class ProfileController extends Controller
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
namespace Modules\Profile\Models;
|
||||
|
||||
namespace Modules\User\App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
@ -10,6 +11,7 @@ class Profile extends Model
|
||||
use LogsActivity;
|
||||
|
||||
protected $fillable = ['user_id', 'avatar', 'bio', 'phone', 'city', 'country', 'website', 'is_verified'];
|
||||
|
||||
protected $casts = ['is_verified' => 'boolean'];
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
@ -22,6 +24,6 @@ class Profile extends Model
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class);
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use App\States\UserStatus;
|
||||
use Filament\Models\Contracts\HasAvatar;
|
||||
namespace Modules\User\App\Models;
|
||||
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Models\Contracts\HasAvatar;
|
||||
use Filament\Models\Contracts\HasTenants;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
@ -14,6 +15,11 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Jeffgreco13\FilamentBreezy\Traits\TwoFactorAuthenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Modules\Conversation\App\Models\Conversation;
|
||||
use Modules\Conversation\App\Models\ConversationMessage;
|
||||
use Modules\Favorite\App\Models\FavoriteSearch;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Modules\User\App\States\UserStatus;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\ModelStates\HasStates;
|
||||
@ -21,9 +27,16 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable implements FilamentUser, HasTenants, HasAvatar
|
||||
{
|
||||
use HasApiTokens, HasFactory, HasRoles, LogsActivity, Notifiable, HasStates, TwoFactorAuthenticatable;
|
||||
use HasApiTokens;
|
||||
use HasFactory;
|
||||
use HasRoles;
|
||||
use LogsActivity;
|
||||
use Notifiable;
|
||||
use HasStates;
|
||||
use TwoFactorAuthenticatable;
|
||||
|
||||
protected $fillable = ['name', 'email', 'password', 'avatar_url', 'status'];
|
||||
|
||||
protected $hidden = ['password', 'remember_token'];
|
||||
|
||||
protected function casts(): array
|
||||
@ -35,6 +48,11 @@ class User extends Authenticatable implements FilamentUser, HasTenants, HasAvata
|
||||
];
|
||||
}
|
||||
|
||||
protected static function newFactory(): Factory
|
||||
{
|
||||
return \Database\Factories\UserFactory::new();
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
@ -64,19 +82,22 @@ class User extends Authenticatable implements FilamentUser, HasTenants, HasAvata
|
||||
|
||||
public function listings()
|
||||
{
|
||||
return $this->hasMany(\Modules\Listing\Models\Listing::class);
|
||||
return $this->hasMany(Listing::class);
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->hasOne(Profile::class);
|
||||
}
|
||||
|
||||
public function favoriteListings()
|
||||
{
|
||||
return $this->belongsToMany(\Modules\Listing\Models\Listing::class, 'favorite_listings')
|
||||
->withTimestamps();
|
||||
return $this->belongsToMany(Listing::class, 'favorite_listings')->withTimestamps();
|
||||
}
|
||||
|
||||
public function favoriteSellers()
|
||||
{
|
||||
return $this->belongsToMany(self::class, 'favorite_sellers', 'user_id', 'seller_id')
|
||||
->withTimestamps();
|
||||
return $this->belongsToMany(self::class, 'favorite_sellers', 'user_id', 'seller_id')->withTimestamps();
|
||||
}
|
||||
|
||||
public function favoriteSearches()
|
||||
@ -111,8 +132,40 @@ class User extends Authenticatable implements FilamentUser, HasTenants, HasAvata
|
||||
|
||||
public function getFilamentAvatarUrl(): ?string
|
||||
{
|
||||
return filled($this->avatar_url)
|
||||
? Storage::disk('public')->url($this->avatar_url)
|
||||
: null;
|
||||
return filled($this->avatar_url) ? Storage::disk('public')->url($this->avatar_url) : null;
|
||||
}
|
||||
|
||||
public function toggleFavoriteListing(Listing $listing): bool
|
||||
{
|
||||
$isFavorite = $this->favoriteListings()->whereKey($listing->getKey())->exists();
|
||||
|
||||
if ($isFavorite) {
|
||||
$this->favoriteListings()->detach($listing->getKey());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function toggleFavoriteSeller(self $seller): bool
|
||||
{
|
||||
if ((int) $seller->getKey() === (int) $this->getKey()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$isFavorite = $this->favoriteSellers()->whereKey($seller->getKey())->exists();
|
||||
|
||||
if ($isFavorite) {
|
||||
$this->favoriteSellers()->detach($seller->getKey());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->favoriteSellers()->syncWithoutDetaching([$seller->getKey()]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
19
Modules/User/App/Providers/UserServiceProvider.php
Normal file
19
Modules/User/App/Providers/UserServiceProvider.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class UserServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path('User', 'database/migrations'));
|
||||
$this->loadRoutesFrom(module_path('User', 'routes/web.php'));
|
||||
$this->loadViewsFrom(module_path('User', 'resources/views'), 'user');
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\States;
|
||||
namespace Modules\User\App\States;
|
||||
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasDescription;
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\States;
|
||||
namespace Modules\User\App\States;
|
||||
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasDescription;
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\States;
|
||||
namespace Modules\User\App\States;
|
||||
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasDescription;
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\States;
|
||||
namespace Modules\User\App\States;
|
||||
|
||||
use A909M\FilamentStateFusion\Concerns\StateFusionInfo;
|
||||
use A909M\FilamentStateFusion\Contracts\HasFilamentStateFusion;
|
||||
39
Modules/User/App/Support/Filament/UserFormFields.php
Normal file
39
Modules/User/App/Support/Filament/UserFormFields.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\App\Support\Filament;
|
||||
|
||||
use A909M\FilamentStateFusion\Forms\Components\StateFusionSelect;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
|
||||
class UserFormFields
|
||||
{
|
||||
public static function name(): TextInput
|
||||
{
|
||||
return TextInput::make('name')->required()->maxLength(255);
|
||||
}
|
||||
|
||||
public static function email(): TextInput
|
||||
{
|
||||
return TextInput::make('email')->email()->required()->maxLength(255)->unique(ignoreRecord: true);
|
||||
}
|
||||
|
||||
public static function password(callable $requiredCallback): TextInput
|
||||
{
|
||||
return TextInput::make('password')
|
||||
->password()
|
||||
->required($requiredCallback)
|
||||
->dehydrateStateUsing(fn ($state) => filled($state) ? bcrypt($state) : null)
|
||||
->dehydrated(fn ($state) => filled($state));
|
||||
}
|
||||
|
||||
public static function status(): StateFusionSelect
|
||||
{
|
||||
return StateFusionSelect::make('status')->required();
|
||||
}
|
||||
|
||||
public static function roles(): Select
|
||||
{
|
||||
return Select::make('roles')->multiple()->relationship('roles', 'name')->preload();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('users')) {
|
||||
Schema::create('users', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('status')->default('active');
|
||||
$table->string('password');
|
||||
$table->string('avatar_url')->nullable();
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('profiles')) {
|
||||
Schema::create('profiles', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->unique()->constrained('users')->cascadeOnDelete();
|
||||
$table->string('avatar')->nullable();
|
||||
$table->text('bio')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->string('city')->nullable();
|
||||
$table->string('country')->nullable();
|
||||
$table->string('website')->nullable();
|
||||
$table->boolean('is_verified')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('password_reset_tokens')) {
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table): void {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('sessions')) {
|
||||
Schema::create('sessions', function (Blueprint $table): void {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sessions');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('profiles');
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
};
|
||||
11
Modules/User/module.json
Normal file
11
Modules/User/module.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "User",
|
||||
"alias": "user",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\User\\App\\Providers\\UserServiceProvider"
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
@ -9,19 +9,19 @@
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-profile-information-form')
|
||||
@include('user::profile.partials.update-profile-information-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-password-form')
|
||||
@include('user::profile.partials.update-password-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.delete-user-form')
|
||||
@include('user::profile.partials.delete-user-form')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
10
Modules/User/routes/web.php
Normal file
10
Modules/User/routes/web.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\User\App\Http\Controllers\ProfileController;
|
||||
|
||||
Route::middleware('auth')->prefix('profile')->name('profile.')->group(function () {
|
||||
Route::get('/', [ProfileController::class, 'edit'])->name('edit');
|
||||
Route::patch('/', [ProfileController::class, 'update'])->name('update');
|
||||
Route::delete('/', [ProfileController::class, 'destroy'])->name('destroy');
|
||||
});
|
||||
@ -3,7 +3,7 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Modules\User\App\Models\User;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@ -15,19 +15,11 @@ use Illuminate\View\View;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset view.
|
||||
*/
|
||||
public function create(Request $request): View
|
||||
{
|
||||
return view('auth.reset-password', ['request' => $request]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
@ -36,9 +28,6 @@ class NewPasswordController extends Controller
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function (User $user) use ($request) {
|
||||
@ -51,9 +40,6 @@ class NewPasswordController extends Controller
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
return $status == Password::PASSWORD_RESET
|
||||
? redirect()->route('login')->with('status', __($status))
|
||||
: back()->withInput($request->only('email'))
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Modules\User\App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@ -14,19 +14,11 @@ use Illuminate\View\View;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Modules\User\App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@ -15,9 +15,6 @@ use Throwable;
|
||||
|
||||
class SocialAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private array $allowedProviders = ['google', 'facebook', 'apple'];
|
||||
|
||||
public function redirect(string $provider): RedirectResponse
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Conversation;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Listing\Models\Listing;
|
||||
|
||||
class ConversationController extends Controller
|
||||
{
|
||||
public function start(Request $request, Listing $listing): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (! $listing->user_id) {
|
||||
return back()->with('error', 'Bu ilan için mesajlaşma açılamadı.');
|
||||
}
|
||||
|
||||
if ((int) $listing->user_id === (int) $user->getKey()) {
|
||||
return back()->with('error', 'Kendi ilanına mesaj gönderemezsin.');
|
||||
}
|
||||
|
||||
$conversation = Conversation::query()->firstOrCreate(
|
||||
[
|
||||
'listing_id' => $listing->getKey(),
|
||||
'buyer_id' => $user->getKey(),
|
||||
],
|
||||
[
|
||||
'seller_id' => $listing->user_id,
|
||||
],
|
||||
);
|
||||
|
||||
if ((int) $conversation->seller_id !== (int) $listing->user_id) {
|
||||
$conversation->forceFill([
|
||||
'seller_id' => $listing->user_id,
|
||||
])->save();
|
||||
}
|
||||
|
||||
$user->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
|
||||
|
||||
$messageBody = trim((string) $request->string('message'));
|
||||
|
||||
if ($messageBody !== '') {
|
||||
$message = $conversation->messages()->create([
|
||||
'sender_id' => $user->getKey(),
|
||||
'body' => $messageBody,
|
||||
]);
|
||||
|
||||
$conversation->forceFill([
|
||||
'last_message_at' => $message->created_at,
|
||||
])->save();
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('panel.inbox.index', array_merge(
|
||||
$this->inboxFilters($request),
|
||||
['conversation' => $conversation->getKey()],
|
||||
))
|
||||
->with('success', $messageBody !== '' ? 'Mesaj gönderildi.' : 'Sohbet açıldı.');
|
||||
}
|
||||
|
||||
public function send(Request $request, Conversation $conversation): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$userId = (int) $user->getKey();
|
||||
|
||||
if ((int) $conversation->buyer_id !== $userId && (int) $conversation->seller_id !== $userId) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$payload = $request->validate([
|
||||
'message' => ['required', 'string', 'max:2000'],
|
||||
]);
|
||||
|
||||
$message = $conversation->messages()->create([
|
||||
'sender_id' => $userId,
|
||||
'body' => trim($payload['message']),
|
||||
]);
|
||||
|
||||
$conversation->forceFill([
|
||||
'last_message_at' => $message->created_at,
|
||||
])->save();
|
||||
|
||||
return redirect()
|
||||
->route('panel.inbox.index', array_merge(
|
||||
$this->inboxFilters($request),
|
||||
['conversation' => $conversation->getKey()],
|
||||
))
|
||||
->with('success', 'Mesaj gönderildi.');
|
||||
}
|
||||
|
||||
private function inboxFilters(Request $request): array
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
$messageFilter = (string) $request->string('message_filter');
|
||||
if (in_array($messageFilter, ['all', 'unread', 'important'], true)) {
|
||||
$filters['message_filter'] = $messageFilter;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ namespace App\Http\Controllers;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Listing\Models\Listing;
|
||||
use Modules\Category\Models\Category;
|
||||
use App\Models\User;
|
||||
use Modules\User\App\Models\User;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Conversation;
|
||||
use App\Models\ConversationMessage;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@ -59,88 +57,6 @@ class PanelController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function inbox(Request $request): View
|
||||
{
|
||||
$userId = (int) $request->user()->getKey();
|
||||
|
||||
$messageFilter = (string) $request->string('message_filter', 'all');
|
||||
if (! in_array($messageFilter, ['all', 'unread', 'important'], true)) {
|
||||
$messageFilter = 'all';
|
||||
}
|
||||
|
||||
$conversations = Conversation::query()
|
||||
->forUser($userId)
|
||||
->when(
|
||||
in_array($messageFilter, ['unread', 'important'], true),
|
||||
fn ($query) => $query->whereHas('messages', fn ($messageQuery) => $messageQuery
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at'))
|
||||
)
|
||||
->with([
|
||||
'listing:id,title,price,currency,user_id',
|
||||
'buyer:id,name',
|
||||
'seller:id,name',
|
||||
'lastMessage',
|
||||
])
|
||||
->withCount([
|
||||
'messages as unread_count' => fn ($query) => $query
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at'),
|
||||
])
|
||||
->orderByDesc('last_message_at')
|
||||
->orderByDesc('updated_at')
|
||||
->get();
|
||||
|
||||
$selectedConversation = null;
|
||||
$selectedConversationId = $request->integer('conversation');
|
||||
|
||||
if ($selectedConversationId <= 0 && $conversations->isNotEmpty()) {
|
||||
$selectedConversationId = (int) $conversations->first()->getKey();
|
||||
}
|
||||
|
||||
if ($selectedConversationId > 0) {
|
||||
$selectedConversation = $conversations->firstWhere('id', $selectedConversationId);
|
||||
|
||||
if ($selectedConversation) {
|
||||
$selectedConversation->load([
|
||||
'listing:id,title,price,currency,user_id',
|
||||
'messages' => fn ($query) => $query
|
||||
->with('sender:id,name')
|
||||
->orderBy('created_at'),
|
||||
]);
|
||||
|
||||
ConversationMessage::query()
|
||||
->where('conversation_id', $selectedConversation->getKey())
|
||||
->where('sender_id', '!=', $userId)
|
||||
->whereNull('read_at')
|
||||
->update([
|
||||
'read_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation {
|
||||
if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) {
|
||||
$conversation->unread_count = 0;
|
||||
}
|
||||
|
||||
return $conversation;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return view('panel.inbox', [
|
||||
'conversations' => $conversations,
|
||||
'selectedConversation' => $selectedConversation,
|
||||
'messageFilter' => $messageFilter,
|
||||
'quickMessages' => [
|
||||
'Merhaba',
|
||||
'İlan hâlâ satışta mı?',
|
||||
'Son fiyat nedir?',
|
||||
'Teşekkürler',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroyListing(Request $request, Listing $listing): RedirectResponse
|
||||
{
|
||||
$this->guardListingOwner($request, $listing);
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's profile form.
|
||||
*/
|
||||
public function edit(Request $request): View
|
||||
{
|
||||
return view('profile.edit', [
|
||||
'user' => $request->user(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit')->with('status', 'profile-updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validateWithBag('userDeletion', [
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'lowercase',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class)->ignore($this->user()->id),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ use Modules\Listing\Support\ListingCustomFieldSchemaBuilder;
|
||||
use Modules\Listing\Support\ListingPanelHelper;
|
||||
use Modules\Location\Models\City;
|
||||
use Modules\Location\Models\Country;
|
||||
use Modules\Profile\Models\Profile;
|
||||
use Modules\User\App\Models\Profile;
|
||||
use Throwable;
|
||||
|
||||
class PanelQuickListingForm extends Component
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Modules\Listing\Models\Listing;
|
||||
|
||||
class Conversation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'listing_id',
|
||||
'seller_id',
|
||||
'buyer_id',
|
||||
'last_message_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'last_message_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function listing()
|
||||
{
|
||||
return $this->belongsTo(Listing::class);
|
||||
}
|
||||
|
||||
public function seller()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'seller_id');
|
||||
}
|
||||
|
||||
public function buyer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'buyer_id');
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return $this->hasMany(ConversationMessage::class);
|
||||
}
|
||||
|
||||
public function lastMessage()
|
||||
{
|
||||
return $this->hasOne(ConversationMessage::class)
|
||||
->latestOfMany()
|
||||
->select([
|
||||
'conversation_messages.id',
|
||||
'conversation_messages.conversation_id',
|
||||
'conversation_messages.sender_id',
|
||||
'conversation_messages.body',
|
||||
'conversation_messages.created_at',
|
||||
]);
|
||||
}
|
||||
|
||||
public function scopeForUser(Builder $query, int $userId): Builder
|
||||
{
|
||||
return $query->where(function (Builder $participantQuery) use ($userId): void {
|
||||
$participantQuery
|
||||
->where('buyer_id', $userId)
|
||||
->orWhere('seller_id', $userId);
|
||||
});
|
||||
}
|
||||
|
||||
public static function buyerListingConversationId(int $listingId, int $buyerId): ?int
|
||||
{
|
||||
$value = static::query()
|
||||
->where('listing_id', $listingId)
|
||||
->where('buyer_id', $buyerId)
|
||||
->value('id');
|
||||
|
||||
return is_null($value) ? null : (int) $value;
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ return [
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
'model' => env('AUTH_MODEL', Modules\User\App\Models\User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
|
||||
@ -5,22 +5,14 @@ namespace Database\Factories;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\User\App\Models\User;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected $model = User::class;
|
||||
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
@ -32,9 +24,6 @@ class UserFactory extends Factory
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
if (! Schema::hasColumn('users', 'avatar_url')) {
|
||||
$table->string('avatar_url')->nullable()->after('password');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('users', 'status')) {
|
||||
$table->string('status')->default('active')->after('email_verified_at');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('users', 'avatar_url')) {
|
||||
$table->dropColumn('avatar_url');
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('users', 'status')) {
|
||||
$table->dropColumn('status');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('favorite_listings', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'listing_id']);
|
||||
});
|
||||
|
||||
Schema::create('favorite_sellers', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'seller_id']);
|
||||
});
|
||||
|
||||
Schema::create('favorite_searches', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('label')->nullable();
|
||||
$table->string('search_term')->nullable();
|
||||
$table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
|
||||
$table->json('filters')->nullable();
|
||||
$table->string('signature', 64);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'signature']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('favorite_searches');
|
||||
Schema::dropIfExists('favorite_sellers');
|
||||
Schema::dropIfExists('favorite_listings');
|
||||
}
|
||||
};
|
||||
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('conversations', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
|
||||
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->foreignId('buyer_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->timestamp('last_message_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['listing_id', 'buyer_id']);
|
||||
$table->index(['seller_id', 'last_message_at']);
|
||||
$table->index(['buyer_id', 'last_message_at']);
|
||||
});
|
||||
|
||||
Schema::create('conversation_messages', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('conversation_id')->constrained('conversations')->cascadeOnDelete();
|
||||
$table->foreignId('sender_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->text('body');
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['conversation_id', 'created_at']);
|
||||
$table->index(['conversation_id', 'read_at']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('conversation_messages');
|
||||
Schema::dropIfExists('conversations');
|
||||
}
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Modules\User\App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
"Category": true,
|
||||
"Listing": true,
|
||||
"Location": true,
|
||||
"Profile": true,
|
||||
"Admin": true,
|
||||
"Partner": false,
|
||||
"Theme": true
|
||||
"Theme": true,
|
||||
"Conversation": true,
|
||||
"Favorite": true,
|
||||
"User": true
|
||||
}
|
||||
|
||||
@ -52,9 +52,6 @@ Route::middleware('auth')->group(function () {
|
||||
|
||||
Route::put('password', [PasswordController::class, 'update'])->name('password.update');
|
||||
|
||||
Route::patch('profile', [\App\Http\Controllers\Auth\ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('profile', [\App\Http\Controllers\Auth\ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
|
||||
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
|
||||
->name('logout');
|
||||
});
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
<?php
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\ConversationController;
|
||||
use App\Http\Controllers\FavoriteController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\LanguageController;
|
||||
use App\Http\Controllers\PanelController;
|
||||
@ -18,7 +16,6 @@ Route::middleware('auth')->prefix('panel')->name('panel.')->group(function () {
|
||||
Route::get('/', [PanelController::class, 'index'])->name('index');
|
||||
Route::get('/ilanlarim', [PanelController::class, 'listings'])->name('listings.index');
|
||||
Route::get('/ilan-ver', [PanelController::class, 'create'])->name('listings.create');
|
||||
Route::get('/gelen-kutusu', [PanelController::class, 'inbox'])->name('inbox.index');
|
||||
Route::post('/ilanlarim/{listing}/kaldir', [PanelController::class, 'destroyListing'])->name('listings.destroy');
|
||||
Route::post('/ilanlarim/{listing}/satildi', [PanelController::class, 'markListingAsSold'])->name('listings.mark-sold');
|
||||
Route::post('/ilanlarim/{listing}/yeniden-yayinla', [PanelController::class, 'republishListing'])->name('listings.republish');
|
||||
@ -27,17 +24,4 @@ Route::middleware('auth')->prefix('panel')->name('panel.')->group(function () {
|
||||
Route::get('/partner/{any?}', fn () => redirect()->route('panel.listings.index'))
|
||||
->where('any', '.*');
|
||||
|
||||
Route::middleware('auth')->prefix('favorites')->name('favorites.')->group(function () {
|
||||
Route::get('/', [FavoriteController::class, 'index'])->name('index');
|
||||
Route::post('/listings/{listing}/toggle', [FavoriteController::class, 'toggleListing'])->name('listings.toggle');
|
||||
Route::post('/sellers/{seller}/toggle', [FavoriteController::class, 'toggleSeller'])->name('sellers.toggle');
|
||||
Route::post('/searches', [FavoriteController::class, 'storeSearch'])->name('searches.store');
|
||||
Route::delete('/searches/{favoriteSearch}', [FavoriteController::class, 'destroySearch'])->name('searches.destroy');
|
||||
});
|
||||
|
||||
Route::middleware('auth')->name('conversations.')->group(function () {
|
||||
Route::post('/listings/{listing}/conversation', [ConversationController::class, 'start'])->name('start');
|
||||
Route::post('/conversations/{conversation}/messages', [ConversationController::class, 'send'])->name('messages.send');
|
||||
});
|
||||
|
||||
require __DIR__.'/auth.php';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user