@@ -492,6 +566,7 @@
const thumbButtons = Array.from(galleryRoot.querySelectorAll('[data-gallery-thumb]'));
const prevButton = galleryRoot.querySelector('[data-gallery-prev]');
const nextButton = galleryRoot.querySelector('[data-gallery-next]');
+ const currentCounter = galleryRoot.querySelector('[data-gallery-current]');
if (!mainImage || thumbButtons.length === 0) {
return;
@@ -514,6 +589,10 @@
mainImage.src = src;
}
+ if (currentCounter) {
+ currentCounter.textContent = String(activeIndex + 1);
+ }
+
thumbButtons.forEach((button, buttonIndex) => {
button.classList.toggle('is-active', buttonIndex === activeIndex);
});
@@ -577,6 +656,27 @@
}
});
});
+
+ document.querySelectorAll('[data-detail-tabs]').forEach((tabsRoot) => {
+ const buttons = Array.from(tabsRoot.querySelectorAll('[data-detail-tab-button]'));
+ const panels = Array.from(tabsRoot.querySelectorAll('[data-detail-tab-panel]'));
+
+ const activate = (target) => {
+ buttons.forEach((button) => {
+ const active = button.dataset.tab === target;
+ button.classList.toggle('is-active', active);
+ button.setAttribute('aria-selected', active ? 'true' : 'false');
+ });
+
+ panels.forEach((panel) => {
+ panel.classList.toggle('is-active', panel.dataset.panel === target);
+ });
+ };
+
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => activate(button.dataset.tab || 'details'));
+ });
+ });
})();
@endsection
diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/Modules/User/App/Http/Controllers/Auth/ConfirmPasswordController.php
similarity index 71%
rename from app/Http/Controllers/Auth/ConfirmablePasswordController.php
rename to Modules/User/App/Http/Controllers/Auth/ConfirmPasswordController.php
index 712394a5a..d98b7b840 100644
--- a/app/Http/Controllers/Auth/ConfirmablePasswordController.php
+++ b/Modules/User/App/Http/Controllers/Auth/ConfirmPasswordController.php
@@ -1,6 +1,6 @@
validate([
'email' => $request->user()->email,
- 'password' => $request->password,
+ 'password' => $request->string('password')->toString(),
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
diff --git a/Modules/User/App/Http/Controllers/Auth/EmailVerificationController.php b/Modules/User/App/Http/Controllers/Auth/EmailVerificationController.php
new file mode 100644
index 000000000..fda6fddda
--- /dev/null
+++ b/Modules/User/App/Http/Controllers/Auth/EmailVerificationController.php
@@ -0,0 +1,46 @@
+user()->hasVerifiedEmail()) {
+ return redirect()->intended(route('dashboard', absolute: false));
+ }
+
+ return view('user::auth.verify-email');
+ }
+
+ public function send(Request $request): RedirectResponse
+ {
+ if ($request->user()->hasVerifiedEmail()) {
+ return redirect()->intended(route('dashboard', absolute: false));
+ }
+
+ $request->user()->sendEmailVerificationNotification();
+
+ return back()->with('status', 'verification-link-sent');
+ }
+
+ public function verify(EmailVerificationRequest $request): RedirectResponse
+ {
+ if ($request->user()->hasVerifiedEmail()) {
+ return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
+ }
+
+ if ($request->user()->markEmailAsVerified()) {
+ event(new Verified($request->user()));
+ }
+
+ return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
+ }
+}
diff --git a/Modules/User/App/Http/Controllers/Auth/ForgotPasswordController.php b/Modules/User/App/Http/Controllers/Auth/ForgotPasswordController.php
new file mode 100644
index 000000000..4ab8f165e
--- /dev/null
+++ b/Modules/User/App/Http/Controllers/Auth/ForgotPasswordController.php
@@ -0,0 +1,34 @@
+validate([
+ 'email' => ['required', 'email'],
+ ]);
+
+ $status = Password::sendResetLink($request->only('email'));
+
+ if ($status === Password::RESET_LINK_SENT) {
+ return back()->with('status', __($status));
+ }
+
+ return back()
+ ->withInput($request->only('email'))
+ ->withErrors(['email' => __($status)]);
+ }
+}
diff --git a/Modules/User/App/Http/Controllers/Auth/LoginController.php b/Modules/User/App/Http/Controllers/Auth/LoginController.php
new file mode 100644
index 000000000..f4bade5e5
--- /dev/null
+++ b/Modules/User/App/Http/Controllers/Auth/LoginController.php
@@ -0,0 +1,51 @@
+redirector->rememberQueryTarget($request);
+
+ return view('user::auth.login', [
+ 'redirectTo' => $redirectTo,
+ 'socialProviders' => $this->providers->enabled('login', $redirectTo),
+ ]);
+ }
+
+ public function store(LoginRequest $request): RedirectResponse
+ {
+ $this->redirector->rememberInputTarget($request);
+
+ $request->authenticate();
+ $request->session()->regenerate();
+
+ return redirect()->intended(route('dashboard', absolute: false));
+ }
+
+ public function destroy(Request $request): RedirectResponse
+ {
+ Auth::guard('web')->logout();
+
+ $request->session()->invalidate();
+ $request->session()->regenerateToken();
+
+ return redirect('/');
+ }
+}
diff --git a/app/Http/Controllers/Auth/PasswordController.php b/Modules/User/App/Http/Controllers/Auth/PasswordController.php
similarity index 76%
rename from app/Http/Controllers/Auth/PasswordController.php
rename to Modules/User/App/Http/Controllers/Auth/PasswordController.php
index 69164091a..aaa5e7c57 100644
--- a/app/Http/Controllers/Auth/PasswordController.php
+++ b/Modules/User/App/Http/Controllers/Auth/PasswordController.php
@@ -1,18 +1,14 @@
validateWithBag('updatePassword', [
@@ -21,7 +17,7 @@ class PasswordController extends Controller
]);
$request->user()->update([
- 'password' => Hash::make($validated['password']),
+ 'password' => $validated['password'],
]);
return back()->with('status', 'password-updated');
diff --git a/Modules/User/App/Http/Controllers/Auth/RegisterController.php b/Modules/User/App/Http/Controllers/Auth/RegisterController.php
new file mode 100644
index 000000000..91224a1bd
--- /dev/null
+++ b/Modules/User/App/Http/Controllers/Auth/RegisterController.php
@@ -0,0 +1,51 @@
+redirector->rememberQueryTarget($request);
+
+ return view('user::auth.register', [
+ 'redirectTo' => $redirectTo,
+ 'socialProviders' => $this->providers->enabled('register', $redirectTo),
+ ]);
+ }
+
+ public function store(RegisterRequest $request): RedirectResponse
+ {
+ $this->redirector->rememberInputTarget($request);
+
+ $user = User::query()->create([
+ 'name' => $request->fullName(),
+ 'email' => $request->string('email')->toString(),
+ 'password' => $request->string('password')->toString(),
+ ]);
+
+ event(new Registered($user));
+
+ Auth::guard('web')->login($user);
+ $request->session()->regenerate();
+
+ return redirect()->intended(route('dashboard', absolute: false));
+ }
+}
diff --git a/Modules/User/App/Http/Controllers/Auth/ResetPasswordController.php b/Modules/User/App/Http/Controllers/Auth/ResetPasswordController.php
new file mode 100644
index 000000000..3c50ca862
--- /dev/null
+++ b/Modules/User/App/Http/Controllers/Auth/ResetPasswordController.php
@@ -0,0 +1,50 @@
+ $request]);
+ }
+
+ public function store(Request $request): RedirectResponse
+ {
+ $request->validate([
+ 'token' => ['required'],
+ 'email' => ['required', 'email'],
+ 'password' => ['required', PasswordRule::defaults()],
+ ]);
+
+ $status = Password::reset(
+ $request->only('email', 'password', 'token'),
+ function (User $user) use ($request): void {
+ $user->forceFill([
+ 'password' => $request->string('password')->toString(),
+ 'remember_token' => Str::random(60),
+ ])->save();
+
+ event(new PasswordReset($user));
+ },
+ );
+
+ if ($status === Password::PASSWORD_RESET) {
+ return redirect()->route('login')->with('status', __($status));
+ }
+
+ return back()
+ ->withInput($request->only('email'))
+ ->withErrors(['email' => __($status)]);
+ }
+}
diff --git a/app/Http/Controllers/Auth/SocialAuthController.php b/Modules/User/App/Http/Controllers/Auth/SocialAuthController.php
similarity index 76%
rename from app/Http/Controllers/Auth/SocialAuthController.php
rename to Modules/User/App/Http/Controllers/Auth/SocialAuthController.php
index 4a5675bcf..34c1449c6 100644
--- a/app/Http/Controllers/Auth/SocialAuthController.php
+++ b/Modules/User/App/Http/Controllers/Auth/SocialAuthController.php
@@ -1,9 +1,8 @@
isProviderAllowed($provider), 404);
- abort_unless($this->isProviderEnabled($provider), 404);
+ abort_unless($this->providers->isAllowed($provider), 404);
+ abort_unless($this->providers->isEnabled($provider), 404);
+
+ $this->redirector->rememberQueryTarget($request);
return $this->driver($provider)->redirect();
}
public function callback(Request $request, string $provider): RedirectResponse
{
- abort_unless($this->isProviderAllowed($provider), 404);
- abort_unless($this->isProviderEnabled($provider), 404);
+ abort_unless($this->providers->isAllowed($provider), 404);
+ abort_unless($this->providers->isEnabled($provider), 404);
try {
$oauthUser = $this->driver($provider)->user();
@@ -42,6 +50,16 @@ class SocialAuthController extends Controller
->withErrors(['email' => __('Unable to read social account identity.')]);
}
+ $user = $this->resolveUser($provider, $oauthUser);
+
+ Auth::guard('web')->login($user, true);
+ $request->session()->regenerate();
+
+ return redirect()->intended(route('dashboard', absolute: false));
+ }
+
+ private function resolveUser(string $provider, mixed $oauthUser): User
+ {
$socialiteUser = DB::table('socialite_users')
->where('provider', $provider)
->where('provider_id', (string) $oauthUser->getId())
@@ -81,13 +99,10 @@ class SocialAuthController extends Controller
],
);
- Auth::guard('web')->login($user, true);
- $request->session()->regenerate();
-
- return redirect()->intended(route('dashboard', absolute: false));
+ return $user;
}
- private function driver(string $provider)
+ private function driver(string $provider): mixed
{
$driver = Socialite::driver($provider)
->redirectUrl(route('auth.social.callback', ['provider' => $provider], absolute: true));
@@ -98,16 +113,4 @@ class SocialAuthController extends Controller
return $driver;
}
-
- private function isProviderAllowed(string $provider): bool
- {
- return in_array($provider, $this->allowedProviders, true);
- }
-
- private function isProviderEnabled(string $provider): bool
- {
- return (bool) config("services.{$provider}.enabled", false)
- && filled(config("services.{$provider}.client_id"))
- && filled(config("services.{$provider}.client_secret"));
- }
}
diff --git a/app/Http/Requests/Auth/LoginRequest.php b/Modules/User/App/Http/Requests/LoginRequest.php
similarity index 71%
rename from app/Http/Requests/Auth/LoginRequest.php
rename to Modules/User/App/Http/Requests/LoginRequest.php
index 257464245..c57700536 100644
--- a/app/Http/Requests/Auth/LoginRequest.php
+++ b/Modules/User/App/Http/Requests/LoginRequest.php
@@ -1,6 +1,6 @@
|string>
- */
public function rules(): array
{
return [
@@ -32,11 +24,6 @@ class LoginRequest extends FormRequest
];
}
- /**
- * Attempt to authenticate the request's credentials.
- *
- * @throws \Illuminate\Validation\ValidationException
- */
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
@@ -52,11 +39,6 @@ class LoginRequest extends FormRequest
RateLimiter::clear($this->throttleKey());
}
- /**
- * Ensure the login request is not rate limited.
- *
- * @throws \Illuminate\Validation\ValidationException
- */
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
@@ -75,9 +57,6 @@ class LoginRequest extends FormRequest
]);
}
- /**
- * Get the rate limiting throttle key for the request.
- */
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
diff --git a/Modules/User/App/Http/Requests/RegisterRequest.php b/Modules/User/App/Http/Requests/RegisterRequest.php
new file mode 100644
index 000000000..e654ed907
--- /dev/null
+++ b/Modules/User/App/Http/Requests/RegisterRequest.php
@@ -0,0 +1,33 @@
+ ['required', 'string', 'max:120'],
+ 'last_name' => ['required', 'string', 'max:120'],
+ 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique((new User())->getTable(), 'email')],
+ 'password' => ['required', Password::defaults()],
+ 'terms' => ['accepted'],
+ 'marketing_opt_in' => ['nullable', 'boolean'],
+ ];
+ }
+
+ public function fullName(): string
+ {
+ return trim($this->string('first_name')->toString().' '.$this->string('last_name')->toString());
+ }
+}
diff --git a/Modules/User/App/Support/AuthProviderCatalog.php b/Modules/User/App/Support/AuthProviderCatalog.php
new file mode 100644
index 000000000..ce33f4fda
--- /dev/null
+++ b/Modules/User/App/Support/AuthProviderCatalog.php
@@ -0,0 +1,59 @@
+ [
+ 'label' => 'Google',
+ 'login_label' => 'Sign in with Google',
+ 'register_label' => 'Create account with Google',
+ ],
+ 'apple' => [
+ 'label' => 'Apple',
+ 'login_label' => 'Sign in with Apple',
+ 'register_label' => 'Create account with Apple',
+ ],
+ 'facebook' => [
+ 'label' => 'Facebook',
+ 'login_label' => 'Sign in with Facebook',
+ 'register_label' => 'Create account with Facebook',
+ ],
+ ];
+
+ public function enabled(string $context, ?string $redirectTo = null): Collection
+ {
+ return collect(self::DEFINITIONS)
+ ->filter(fn (array $provider, string $slug): bool => $this->isEnabled($slug))
+ ->map(function (array $provider, string $slug) use ($context, $redirectTo): array {
+ return [
+ 'id' => $slug,
+ 'label' => $provider['label'],
+ 'button_label' => $context === 'register'
+ ? $provider['register_label']
+ : $provider['login_label'],
+ 'url' => route('auth.social.redirect', array_filter([
+ 'provider' => $slug,
+ 'redirect' => $redirectTo,
+ ])),
+ ];
+ })
+ ->values();
+ }
+
+ public function isAllowed(string $provider): bool
+ {
+ return array_key_exists($provider, self::DEFINITIONS);
+ }
+
+ public function isEnabled(string $provider): bool
+ {
+ return $this->isAllowed($provider)
+ && (bool) config("services.{$provider}.enabled", false)
+ && filled(config("services.{$provider}.client_id"))
+ && filled(config("services.{$provider}.client_secret"));
+ }
+}
diff --git a/Modules/User/App/Support/AuthRedirector.php b/Modules/User/App/Support/AuthRedirector.php
new file mode 100644
index 000000000..fe16f5759
--- /dev/null
+++ b/Modules/User/App/Support/AuthRedirector.php
@@ -0,0 +1,59 @@
+remember($request, $request->query($key));
+ }
+
+ public function rememberInputTarget(Request $request, string $key = 'redirect'): ?string
+ {
+ return $this->remember($request, $request->input($key));
+ }
+
+ public function sanitize(?string $target): ?string
+ {
+ $target = trim((string) $target);
+
+ if ($target === '' || str_starts_with($target, '//')) {
+ return null;
+ }
+
+ if (str_starts_with($target, '/')) {
+ return $target;
+ }
+
+ if (! filter_var($target, FILTER_VALIDATE_URL)) {
+ return null;
+ }
+
+ $applicationUrl = parse_url(url('/'));
+ $targetUrl = parse_url($target);
+
+ if (($applicationUrl['host'] ?? null) !== ($targetUrl['host'] ?? null)) {
+ return null;
+ }
+
+ $path = $targetUrl['path'] ?? '/';
+ $query = isset($targetUrl['query']) ? '?'.$targetUrl['query'] : '';
+ $fragment = isset($targetUrl['fragment']) ? '#'.$targetUrl['fragment'] : '';
+
+ return $path.$query.$fragment;
+ }
+
+ private function remember(Request $request, ?string $target): ?string
+ {
+ $sanitized = $this->sanitize($target);
+
+ if ($sanitized !== null) {
+ $request->session()->put('url.intended', $sanitized);
+ }
+
+ return $sanitized;
+ }
+}
diff --git a/Modules/User/resources/views/auth/confirm-password.blade.php b/Modules/User/resources/views/auth/confirm-password.blade.php
new file mode 100644
index 000000000..a84ef8aca
--- /dev/null
+++ b/Modules/User/resources/views/auth/confirm-password.blade.php
@@ -0,0 +1,45 @@
+@extends('user::layouts.auth')
+
+@section('title', 'Confirm password')
+
+@section('content')
+
+
Security
+
Confirm password
+
This is a secure area. Enter your password once to continue.
+
+
+
+@endsection
diff --git a/Modules/User/resources/views/auth/forgot-password.blade.php b/Modules/User/resources/views/auth/forgot-password.blade.php
new file mode 100644
index 000000000..3465b17f8
--- /dev/null
+++ b/Modules/User/resources/views/auth/forgot-password.blade.php
@@ -0,0 +1,43 @@
+@extends('user::layouts.auth')
+
+@section('title', 'Reset password')
+
+@section('content')
+
+
Password
+
Reset password
+
Enter your email and we will send a secure reset link.
+
+
+@if (session('status'))
+
{{ session('status') }}
+@endif
+
+
+
+
+ Remembered your password?
+ Back to sign in
+
+@endsection
diff --git a/Modules/User/resources/views/auth/login.blade.php b/Modules/User/resources/views/auth/login.blade.php
new file mode 100644
index 000000000..c8e60e157
--- /dev/null
+++ b/Modules/User/resources/views/auth/login.blade.php
@@ -0,0 +1,87 @@
+@extends('user::layouts.auth')
+
+@section('title', 'Sign in')
+
+@section('content')
+
+
+@if (session('status'))
+
{{ session('status') }}
+@endif
+
+
+
+
+ New here?
+ Create account
+
+
+@include('user::auth.partials.social-buttons', ['socialProviders' => $socialProviders])
+@endsection
diff --git a/Modules/User/resources/views/auth/partials/social-buttons.blade.php b/Modules/User/resources/views/auth/partials/social-buttons.blade.php
new file mode 100644
index 000000000..312a3fb33
--- /dev/null
+++ b/Modules/User/resources/views/auth/partials/social-buttons.blade.php
@@ -0,0 +1,40 @@
+@php
+ $socialProviders = collect($socialProviders ?? [])->values();
+@endphp
+
+@if($socialProviders->isNotEmpty())
+
+ OR
+
+
+
+@endif
diff --git a/Modules/User/resources/views/auth/register.blade.php b/Modules/User/resources/views/auth/register.blade.php
new file mode 100644
index 000000000..0850a88ce
--- /dev/null
+++ b/Modules/User/resources/views/auth/register.blade.php
@@ -0,0 +1,121 @@
+@extends('user::layouts.auth')
+
+@section('title', 'Create account')
+
+@section('content')
+
+
Account
+
Create account
+
Open your account once and manage listings, messages, and saved items from one place.
+
+
+
+
+
+ Already have an account?
+ Sign in
+
+
+@include('user::auth.partials.social-buttons', ['socialProviders' => $socialProviders])
+@endsection
diff --git a/Modules/User/resources/views/auth/reset-password.blade.php b/Modules/User/resources/views/auth/reset-password.blade.php
new file mode 100644
index 000000000..b4ebaad83
--- /dev/null
+++ b/Modules/User/resources/views/auth/reset-password.blade.php
@@ -0,0 +1,64 @@
+@extends('user::layouts.auth')
+
+@section('title', 'Choose a new password')
+
+@section('content')
+
+
Password
+
Choose a new password
+
Set a fresh password for your account and continue to your dashboard.
+
+
+
+@endsection
diff --git a/Modules/User/resources/views/auth/verify-email.blade.php b/Modules/User/resources/views/auth/verify-email.blade.php
new file mode 100644
index 000000000..fdd3e4a53
--- /dev/null
+++ b/Modules/User/resources/views/auth/verify-email.blade.php
@@ -0,0 +1,27 @@
+@extends('user::layouts.auth')
+
+@section('title', 'Verify email')
+
+@section('content')
+
+
Verification
+
Verify your email
+
Before you continue, confirm your email address from the link we sent you.
+
+
+@if (session('status') === 'verification-link-sent')
+
A fresh verification link has been sent to your inbox.
+@endif
+
+
+
+
+
+
+@endsection
diff --git a/Modules/User/resources/views/layouts/auth.blade.php b/Modules/User/resources/views/layouts/auth.blade.php
new file mode 100644
index 000000000..a0ce90d2e
--- /dev/null
+++ b/Modules/User/resources/views/layouts/auth.blade.php
@@ -0,0 +1,35 @@
+@php
+ $siteName = $generalSettings['site_name'] ?? config('app.name', 'OpenClassify');
+ $siteLogoUrl = $generalSettings['site_logo_url'] ?? null;
+ $pageTitle = trim($__env->yieldContent('title'));
+@endphp
+
+
+
+
+
+
+
{{ $pageTitle !== '' ? $pageTitle.' - ' : '' }}{{ $siteName }}
+ @vite(['resources/css/app.css', 'resources/js/app.js'])
+
+
+
+
+
+
+
diff --git a/Modules/User/routes/web.php b/Modules/User/routes/web.php
index 0518a3b87..a8d46b1df 100644
--- a/Modules/User/routes/web.php
+++ b/Modules/User/routes/web.php
@@ -1,10 +1,52 @@
group(function () {
- Route::redirect('/profile', '/panel/my-profile')->name('profile.edit');
- Route::patch('/panel/my-profile', [ProfileController::class, 'update'])->name('profile.update');
- Route::delete('/panel/my-profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
+Route::middleware('web')->group(function () {
+ Route::middleware('guest')->group(function () {
+ Route::get('/register', [RegisterController::class, 'create'])->name('register');
+ Route::post('/register', [RegisterController::class, 'store']);
+
+ Route::get('/login', [LoginController::class, 'create'])->name('login');
+ Route::post('/login', [LoginController::class, 'store']);
+
+ Route::get('/forgot-password', [ForgotPasswordController::class, 'create'])->name('password.request');
+ Route::post('/forgot-password', [ForgotPasswordController::class, 'store'])->name('password.email');
+
+ Route::get('/reset-password/{token}', [ResetPasswordController::class, 'create'])->name('password.reset');
+ Route::post('/reset-password', [ResetPasswordController::class, 'store'])->name('password.store');
+
+ Route::prefix('/auth/social')->name('auth.social.')->group(function () {
+ Route::get('/{provider}', [SocialAuthController::class, 'redirect'])->name('redirect');
+ Route::get('/{provider}/callback', [SocialAuthController::class, 'callback'])->name('callback');
+ });
+ });
+
+ Route::middleware('auth')->group(function () {
+ Route::get('/verify-email', [EmailVerificationController::class, 'notice'])->name('verification.notice');
+ Route::get('/verify-email/{id}/{hash}', [EmailVerificationController::class, 'verify'])
+ ->middleware(['signed', 'throttle:6,1'])
+ ->name('verification.verify');
+ Route::post('/email/verification-notification', [EmailVerificationController::class, 'send'])
+ ->middleware('throttle:6,1')
+ ->name('verification.send');
+
+ Route::get('/confirm-password', [ConfirmPasswordController::class, 'show'])->name('password.confirm');
+ Route::post('/confirm-password', [ConfirmPasswordController::class, 'store']);
+ Route::put('/password', [PasswordController::class, 'update'])->name('password.update');
+ Route::post('/logout', [LoginController::class, 'destroy'])->name('logout');
+
+ Route::redirect('/profile', '/panel/my-profile')->name('profile.edit');
+ Route::patch('/panel/my-profile', [ProfileController::class, 'update'])->name('profile.update');
+ Route::delete('/panel/my-profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
+ });
});
diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php
deleted file mode 100644
index 7ccfe0708..000000000
--- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php
+++ /dev/null
@@ -1,82 +0,0 @@
-sanitizeRedirectTarget(request()->query('redirect'));
-
- if ($redirectTo) {
- request()->session()->put('url.intended', $redirectTo);
- }
-
- return view('auth.login', [
- 'redirectTo' => $redirectTo,
- ]);
- }
-
- public function store(LoginRequest $request): RedirectResponse
- {
- $redirectTo = $this->sanitizeRedirectTarget($request->input('redirect'));
-
- if ($redirectTo) {
- $request->session()->put('url.intended', $redirectTo);
- }
-
- $request->authenticate();
-
- $request->session()->regenerate();
-
- return redirect()->intended(route('dashboard', absolute: false));
- }
-
- public function destroy(Request $request): RedirectResponse
- {
- Auth::guard('web')->logout();
-
- $request->session()->invalidate();
-
- $request->session()->regenerateToken();
-
- return redirect('/');
- }
-
- private function sanitizeRedirectTarget(?string $target): ?string
- {
- $target = trim((string) $target);
-
- if ($target === '' || str_starts_with($target, '//')) {
- return null;
- }
-
- if (str_starts_with($target, '/')) {
- return $target;
- }
-
- if (! filter_var($target, FILTER_VALIDATE_URL)) {
- return null;
- }
-
- $applicationUrl = parse_url(url('/'));
- $targetUrl = parse_url($target);
-
- if (($applicationUrl['host'] ?? null) !== ($targetUrl['host'] ?? null)) {
- return null;
- }
-
- $path = $targetUrl['path'] ?? '/';
- $query = isset($targetUrl['query']) ? '?' . $targetUrl['query'] : '';
- $fragment = isset($targetUrl['fragment']) ? '#' . $targetUrl['fragment'] : '';
-
- return $path . $query . $fragment;
- }
-}
diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php
deleted file mode 100644
index f64fa9ba7..000000000
--- a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php
+++ /dev/null
@@ -1,24 +0,0 @@
-user()->hasVerifiedEmail()) {
- return redirect()->intended(route('dashboard', absolute: false));
- }
-
- $request->user()->sendEmailVerificationNotification();
-
- return back()->with('status', 'verification-link-sent');
- }
-}
diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php
deleted file mode 100644
index ee3cb6fac..000000000
--- a/app/Http/Controllers/Auth/EmailVerificationPromptController.php
+++ /dev/null
@@ -1,21 +0,0 @@
-user()->hasVerifiedEmail()
- ? redirect()->intended(route('dashboard', absolute: false))
- : view('auth.verify-email');
- }
-}
diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php
deleted file mode 100644
index afbf46467..000000000
--- a/app/Http/Controllers/Auth/NewPasswordController.php
+++ /dev/null
@@ -1,48 +0,0 @@
- $request]);
- }
-
- public function store(Request $request): RedirectResponse
- {
- $request->validate([
- 'token' => ['required'],
- 'email' => ['required', 'email'],
- 'password' => ['required', 'confirmed', Rules\Password::defaults()],
- ]);
-
- $status = Password::reset(
- $request->only('email', 'password', 'password_confirmation', 'token'),
- function (User $user) use ($request) {
- $user->forceFill([
- 'password' => Hash::make($request->password),
- 'remember_token' => Str::random(60),
- ])->save();
-
- event(new PasswordReset($user));
- }
- );
-
- return $status == Password::PASSWORD_RESET
- ? redirect()->route('login')->with('status', __($status))
- : back()->withInput($request->only('email'))
- ->withErrors(['email' => __($status)]);
- }
-}
diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php
deleted file mode 100644
index bf1ebfa78..000000000
--- a/app/Http/Controllers/Auth/PasswordResetLinkController.php
+++ /dev/null
@@ -1,44 +0,0 @@
-validate([
- 'email' => ['required', 'email'],
- ]);
-
- // We will send the password reset link to this user. Once we have attempted
- // to send the link, we will examine the response then see the message we
- // need to show to the user. Finally, we'll send out a proper response.
- $status = Password::sendResetLink(
- $request->only('email')
- );
-
- return $status == Password::RESET_LINK_SENT
- ? back()->with('status', __($status))
- : back()->withInput($request->only('email'))
- ->withErrors(['email' => __($status)]);
- }
-}
diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php
deleted file mode 100644
index a478e9c5a..000000000
--- a/app/Http/Controllers/Auth/RegisteredUserController.php
+++ /dev/null
@@ -1,42 +0,0 @@
-validate([
- 'name' => ['required', 'string', 'max:255'],
- 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
- 'password' => ['required', 'confirmed', Rules\Password::defaults()],
- ]);
-
- $user = User::create([
- 'name' => $request->name,
- 'email' => $request->email,
- 'password' => Hash::make($request->password),
- ]);
-
- event(new Registered($user));
-
- Auth::login($user);
-
- return redirect(route('dashboard', absolute: false));
- }
-}
diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php
deleted file mode 100644
index 784765e3a..000000000
--- a/app/Http/Controllers/Auth/VerifyEmailController.php
+++ /dev/null
@@ -1,27 +0,0 @@
-user()->hasVerifiedEmail()) {
- return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
- }
-
- if ($request->user()->markEmailAsVerified()) {
- event(new Verified($request->user()));
- }
-
- return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
- }
-}
diff --git a/app/Http/Controllers/PanelController.php b/app/Http/Controllers/PanelController.php
index d902a4240..053d444cd 100644
--- a/app/Http/Controllers/PanelController.php
+++ b/app/Http/Controllers/PanelController.php
@@ -46,7 +46,7 @@ class PanelController extends Controller
VideoStatus::Processing->value,
]),
])
- ->when($search !== '', fn ($query) => $query->where('title', 'like', "%{$search}%"))
+ ->searchTerm($search)
->forPanelStatus($status)
->latest('id')
->paginate(10)
diff --git a/app/View/Components/GuestLayout.php b/app/View/Components/GuestLayout.php
deleted file mode 100644
index d1f62539f..000000000
--- a/app/View/Components/GuestLayout.php
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- {{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
-
-
-
-
diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php
deleted file mode 100644
index cb32e08f3..000000000
--- a/resources/views/auth/forgot-password.blade.php
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
- {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
-
-
-
-
-
-
-
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php
deleted file mode 100644
index d0961a57c..000000000
--- a/resources/views/auth/login.blade.php
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
- @php
- $socialProviders = collect([
- 'google' => 'Google',
- 'facebook' => 'Facebook',
- 'apple' => 'Apple',
- ])->filter(
- fn ($label, $provider) => (bool) config("services.{$provider}.enabled")
- && filled(config("services.{$provider}.client_id"))
- && filled(config("services.{$provider}.client_secret"))
- );
- @endphp
-
-
-
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php
deleted file mode 100644
index a857242c8..000000000
--- a/resources/views/auth/register.blade.php
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
diff --git a/resources/views/auth/registration-disabled.blade.php b/resources/views/auth/registration-disabled.blade.php
deleted file mode 100644
index a19429a32..000000000
--- a/resources/views/auth/registration-disabled.blade.php
+++ /dev/null
@@ -1,23 +0,0 @@
-@extends('app::layouts.app')
-
-@section('title', 'Registration Disabled')
-
-@section('content')
-
-
-
Registration is currently disabled
-
- Registration is available only when at least one social login provider is enabled by the admin.
-
-
-
-
-
-@endsection
diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php
deleted file mode 100644
index a6494ccaa..000000000
--- a/resources/views/auth/reset-password.blade.php
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
diff --git a/resources/views/auth/verify-email.blade.php b/resources/views/auth/verify-email.blade.php
deleted file mode 100644
index eaf811d1b..000000000
--- a/resources/views/auth/verify-email.blade.php
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
- {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
-
-
- @if (session('status') == 'verification-link-sent')
-
- {{ __('A new verification link has been sent to the email address you provided during registration.') }}
-
- @endif
-
-
-
diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/guest.blade.php
deleted file mode 100644
index b04797808..000000000
--- a/resources/views/layouts/guest.blade.php
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
{{ config('app.name', 'Laravel') }}
-
- @vite(['resources/css/app.css', 'resources/js/app.js'])
-
-
-
-
-
diff --git a/resources/views/panel/listings.blade.php b/resources/views/panel/listings.blade.php
index f5a3470c2..5887b0444 100644
--- a/resources/views/panel/listings.blade.php
+++ b/resources/views/panel/listings.blade.php
@@ -3,163 +3,297 @@
@section('title', 'İlanlarım')
@section('content')
-
-
- @include('panel.partials.sidebar', ['activeMenu' => 'listings'])
+@php
+ $statusTabs = [
+ [
+ 'key' => 'all',
+ 'label' => 'Tüm İlanlar',
+ 'count' => (int) ($counts['all'] ?? 0),
+ 'description' => 'Hesabındaki tüm ilanlar',
+ ],
+ [
+ 'key' => 'sold',
+ 'label' => 'Satıldı',
+ 'count' => (int) ($counts['sold'] ?? 0),
+ 'description' => 'Kapanan satışlar',
+ ],
+ [
+ 'key' => 'expired',
+ 'label' => 'Süresi Doldu',
+ 'count' => (int) ($counts['expired'] ?? 0),
+ 'description' => 'Yeniden yayın bekleyenler',
+ ],
+ ];
+ $overviewCards = [
+ [
+ 'label' => 'Toplam İlan',
+ 'value' => (int) ($counts['all'] ?? 0),
+ 'hint' => 'Panelindeki tüm kayıtlar',
+ ],
+ [
+ 'label' => 'Yayında',
+ 'value' => (int) ($counts['active'] ?? 0),
+ 'hint' => 'Şu anda ziyaretçilere açık',
+ ],
+ [
+ 'label' => 'Satıldı',
+ 'value' => (int) ($counts['sold'] ?? 0),
+ 'hint' => 'Satışla kapanan ilanlar',
+ ],
+ [
+ 'label' => 'Süresi Doldu',
+ 'value' => (int) ($counts['expired'] ?? 0),
+ 'hint' => 'Yeniden yayın bekleyen ilanlar',
+ ],
+ ];
+ $hasFilters = $search !== '' || $status !== 'all';
+ $pendingCount = (int) ($counts['pending'] ?? 0);
+@endphp
-