misx bugfixes:

- fix users cannot reset password due session expiration
- add a new mail when users contacts via Contact form
- add list page for all deals in broker dashboard
- fixed links in home page
This commit is contained in:
kusowl 2026-02-03 16:06:26 +05:30
parent 2fecd52d7e
commit aa7e2f245f
27 changed files with 220 additions and 39 deletions

View File

@ -20,11 +20,6 @@ public function execute(array $data): bool
$user = \Session::get('otp_user_id') ? User::find(\Session::get('otp_user_id')) : null; $user = \Session::get('otp_user_id') ? User::find(\Session::get('otp_user_id')) : null;
throw_if(! $user, new UserNotFoundException('User not found')); throw_if(! $user, new UserNotFoundException('User not found'));
$isVerified = $this->otpService->verify($user, $data['otp']); return $this->otpService->verify($user, $data['otp']);
if ($isVerified) {
\Session::forget('otp_user_id');
}
return $isVerified;
} }
} }

View File

@ -66,12 +66,14 @@ public function update(Request $request)
$data = $request->validate([ $data = $request->validate([
'password' => 'required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols(), 'password' => 'required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols(),
]); ]);
$user = User::find(Session::get('user_id')); $user = User::find(Session::get('otp_user_id'));
if (!$user) { if (!$user) {
return back()->with('error', 'Session Expired'); return back()->with('error', 'Session Expired');
} }
$user->update(['password' => $data['password']]); $user->update(['password' => $data['password']]);
\Session::forget('otp_user_id');
return to_route('login.create')->with('success', 'Password updated successfully'); return to_route('login.create')->with('success', 'Password updated successfully');
} }

View File

@ -33,7 +33,6 @@ protected function deals()
->WithLikePerDeal() ->WithLikePerDeal()
->WithRedirectionPerDeal() ->WithRedirectionPerDeal()
->withViewPerDeal() ->withViewPerDeal()
->latest() ->latest()->take(3)->get();
->paginate();
} }
} }

View File

@ -7,6 +7,7 @@
use App\Models\Deal; use App\Models\Deal;
use App\Models\DealCategory; use App\Models\DealCategory;
use App\Services\FileService; use App\Services\FileService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -18,7 +19,8 @@ class BrokerDealController extends Controller
*/ */
public function index() public function index()
{ {
// return view('dashboards.broker.deals.index')
->with('deals', $this->deals());
} }
/** /**
@ -49,7 +51,7 @@ public function store(StoreBrokerDeal $request, FileService $fileService)
Deal::create($data); Deal::create($data);
Deal::reguard(); Deal::reguard();
return to_route('broker.dashboard')->with('success', 'Deal has been created.'); return to_route('broker.deals.index')->with('success', 'Deal has been created.');
} }
/** /**
@ -89,10 +91,10 @@ public function update(StoreBrokerDeal $request, Deal $deal, FileService $fileSe
} catch (\Throwable $exception) { } catch (\Throwable $exception) {
Log::error($exception->getMessage(), $exception->getTrace()); Log::error($exception->getMessage(), $exception->getTrace());
return to_route('broker.dashboard')->with('error', 'Something gone wrong.'); return back()->with('error', 'Something gone wrong.');
} }
return to_route('broker.dashboard')->with('success', 'Deal has been updated.'); return back()->with('success', 'Deal has been updated.');
} }
/** /**
@ -112,9 +114,31 @@ public function destroy(Deal $deal, FileService $fileService)
} catch (\Throwable $exception) { } catch (\Throwable $exception) {
Log::error($exception->getMessage(), $exception->getTrace()); Log::error($exception->getMessage(), $exception->getTrace());
return to_route('broker.dashboard')->with('error', 'Something gone wrong.'); return back()->with('error', 'Something gone wrong.');
} }
return to_route('broker.dashboard')->with('success', 'Deal has been deleted.'); return back()->with('success', 'Deal has been deleted.');
}
protected function deals()
{
return Auth::user()
->deals()
->select([
'id',
'title',
'description',
'image',
'active',
'slug',
'link',
'deal_category_id',
])
->with('category:id,name')
->WithLikePerDeal()
->WithRedirectionPerDeal()
->withViewPerDeal()
->latest()
->paginate(15);
} }
} }

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ContactRequest;
use App\Models\Admin;
use App\Notifications\NewContactNotification;
class ContactController extends Controller
{
public function __invoke(ContactRequest $request)
{
try {
$data = $request->validated();
$admin = Admin::first();
$admin->user->notify(new NewContactNotification($data['name'], $data['email'], $data['message']));
return back()->with('success', 'Your message has been sent successfully.');
} catch (\Throwable $e) {
\Log::error('Error sending contact message', [$e->getMessage()]);
return back()->with('error', 'Something went wrong.');
}
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ContactRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* 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|min:3|max:255',
'email' => 'required|email|max:255',
'message' => 'required|string|min:10|max:255',
];
}
protected function getRedirectUrl(): string
{
return parent::getRedirectUrl() . '#contact';
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class NewContactNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
private readonly string $customerName,
private readonly string $customerEmail,
private readonly string $customerMessage
) {
}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('New Contact Submission: '.$this->customerName)
->greeting('Hello Admin,') // Or keep it empty if you prefer
->line('You have received a new message from your contact form:')
->line("**Name:** {$this->customerName}")
->line("**Email:** {$this->customerEmail}")
->line("**Message:**")
->line($this->customerMessage)
->action('Reply via Email', 'mailto:'.$this->customerEmail);;
}
}

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 15 KiB

1
public/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

21
public/site.webmanifest Normal file
View File

@ -0,0 +1,21 @@
{
"name": "MyWebSite",
"short_name": "MySite",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -13,13 +13,13 @@ export async function showDealModal(dealId) {
showModal('deal-modal'); showModal('deal-modal');
toggleShimmer(true, dealModal); toggleShimmer(false, dealModal);
try { try {
const response = await axios.get('/api/deals/' + dealId); const response = await axios.get('/api/deals/' + dealId);
setDealDetails(response.data); setDealDetails(response.data);
toggleShimmer(false, dealModal); toggleShimmer(true, dealModal);
dealModal.dataset.dealId = dealId; dealModal.dataset.dealId = dealId;
await axios.post(`/api/view/${dealId}`); await axios.post(`/api/view/${dealId}`);

View File

@ -1,5 +1,5 @@
@props(['deals' => []]) @props(['deals' => []])
<div class="wrapper"> <div class="wrapper mb-8">
<x-dashboard.card class="bg-white"> <x-dashboard.card class="bg-white">
<p class="font-bold mb-6">My Listings</p> <p class="font-bold mb-6">My Listings</p>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
@ -9,5 +9,10 @@
<p class="text-center text-xs text-accent-600 lg:col-span-3">No Deals created</p> <p class="text-center text-xs text-accent-600 lg:col-span-3">No Deals created</p>
@endforelse @endforelse
</div> </div>
<div class="w-full flex justify-center mt-8">
<x-ui.button class="w-fit" variant="neutral" :link="route('broker.deals.index')">
See more
</x-ui.button>
</div>
</x-dashboard.card> </x-dashboard.card>
</div> </div>

View File

@ -1,7 +1,7 @@
@props(['item']) @props(['item'])
<li class="flex items-center space-x-2 px-4 py-1 md:py-2 text-gray-600 hover:bg-gray-100 hover:font-bold hover:text-gray-900 rounded-xl"> <li class="flex items-center space-x-2 px-4 py-1 md:py-2 text-gray-600 hover:bg-gray-100 hover:font-bold hover:text-gray-900 rounded-xl">
<a class="flex-1 flex items-center" <a class="flex-1 flex items-center"
href="{{route('explore', ['search' => $item])}}"> href="{{route('explore', ['search' => $item->query])}}">
{{$item->query}} {{$item->query}}
<x-heroicon-o-arrow-up-right class="w-3 ml-2"/> <x-heroicon-o-arrow-up-right class="w-3 ml-2"/>
</a> </a>

View File

@ -51,18 +51,18 @@ class="fill-[#BDC1D2]"/>
<div class="space-y-5 whitespace-nowrap"> <div class="space-y-5 whitespace-nowrap">
<h3 class="text-white text-xl font-bold">Quick Links</h3> <h3 class="text-white text-xl font-bold">Quick Links</h3>
<ul class="text-[#8C92AC] font-bold space-y-3"> <ul class="text-[#8C92AC] font-bold space-y-3">
<li><a href="">Home</a></li> <li><a href="/">Home</a></li>
<li><a href="">Explore Deals</a></li> <li><a href="{{route('explore')}}">Explore Deals</a></li>
<li><a href="">About</a></li> <li><a href="#about">About</a></li>
<li><a href="">Contact</a></li> <li><a href="#contact">Contact</a></li>
</ul> </ul>
</div> </div>
<div class="space-y-5 whitespace-nowrap"> <div class="space-y-5 whitespace-nowrap">
<h3 class="text-white text-xl font-bold">For Brokers</h3> <h3 class="text-white text-xl font-bold">For Brokers</h3>
<ul class="text-[#8C92AC] font-bold space-y-3"> <ul class="text-[#8C92AC] font-bold space-y-3">
<li><a href="">Sign Up</a></li> <li><a href="{{route('register.create')}}">Sign Up</a></li>
<li><a href="">Dashboard</a></li> <li><a href="{{route('broker.dashboard')}}">Dashboard</a></li>
<li><a href="">Resources</a></li> <li><a href="">Resources</a></li>
<li><a href="">Support</a></li> <li><a href="">Support</a></li>
</ul> </ul>

View File

@ -1,15 +1,14 @@
<section class="wrapper mb-15"> <section id="contact" class="wrapper mb-15">
<h2 class="mt-15">Get In Touch</h2> <h2 class="mt-15">Get In Touch</h2>
<p class="text-accent-600 text-center mt-3 mb-10"> <p class="text-accent-600 text-center mt-3 mb-10">
Have questions? We'd love it hear from you. Send us a message and we'll respond <br/> Have questions? We'd love it hear from you. Send us a message and we'll respond <br/>
as soon as possible. as soon as possible.
</p> </p>
<section class="grid grid-cols-1 md:grid-cols-2 gap-8"> <section class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- left section, contact form --> <!-- left section, contact form -->
<x-ui.card class="bg-[#F9FAFB]! shadow-none!"> <x-ui.card class="bg-[#F9FAFB]! shadow-none!">
<form action="" class=" flex flex-col space-y-6"> <form action="{{route('contact')}}" method="post" class=" flex flex-col space-y-6">
@csrf
<x-ui.input label="Name" name="name" placeholder="Your name"/> <x-ui.input label="Name" name="name" placeholder="Your name"/>
<x-ui.input label="Email" name="email" placeholder="your.email@example.com" type="email"/> <x-ui.input label="Email" name="email" placeholder="your.email@example.com" type="email"/>
<x-ui.textarea label="Message" name="message" placeholder="Tell us how we can help..."/> <x-ui.textarea label="Message" name="message" placeholder="Tell us how we can help..."/>

View File

@ -1,4 +1,14 @@
<section class="hero py-30 bg-[#f5f5ff] wrapper"> <section class="hero py-10 pb-20 bg-[#f5f5ff] wrapper">
<div class="wrapper pb-10">
@session('success')
<x-ui.alert variant="success">{{$value}}</x-ui.alert>
@endsession
@session('error')
<x-ui.alert variant="error">{{$value}}</x-ui.alert>
@endsession
</div>
<div class="grid xl:grid-cols-2 gap-y-10"> <div class="grid xl:grid-cols-2 gap-y-10">
<!-- Left section --> <!-- Left section -->

View File

@ -1,4 +1,4 @@
<div class="fixed z-10 top-5 left-10 shadow-xl rounded-xl"> <div class="fixed z-10 top-5 left-3/8 rounded-xl border-2 border-red-200">
<x-ui.alert variant="error"> <x-ui.alert variant="error">
<div class="flex items-center"> <div class="flex items-center">
You are impersonating as {{session()->get('impersonate_name', 'a User')}} You are impersonating as {{session()->get('impersonate_name', 'a User')}}

View File

@ -14,7 +14,12 @@
<title>{{ $pageTitle }}</title> <title>{{ $pageTitle }}</title>
<link rel="shortcut icon" type="image/x-icon" href="{{asset('storage/'.'images/favicon.ico')}}"/> <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net"> <link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet"/> <link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet"/>

View File

@ -9,8 +9,8 @@
<ul class="flex space-x-8 text-accent-600"> <ul class="flex space-x-8 text-accent-600">
<x-nav-links :link="route('home')" name="Home"/> <x-nav-links :link="route('home')" name="Home"/>
<x-nav-links :link="route('explore')" name="Explore deals"/> <x-nav-links :link="route('explore')" name="Explore deals"/>
<x-nav-links :link="route('home')" name="About"/> <x-nav-links link="#about" name="About"/>
<x-nav-links :link="route('home')" name="Contact"/> <x-nav-links link="#contact" name="Contact"/>
</ul> </ul>
</div> </div>

View File

@ -1,4 +1,4 @@
<section class="wrapper mb-15"> <section id="about" class="wrapper mb-15">
<h2 class="mt-15">Why Choose {{config('app.name')}}?</h2> <h2 class="mt-15">Why Choose {{config('app.name')}}?</h2>
<p class="text-accent-600 text-center mt-3 mb-10"> <p class="text-accent-600 text-center mt-3 mb-10">
A trusted platform connecting you with verified experts who share the best deals, <br /> A trusted platform connecting you with verified experts who share the best deals, <br />

View File

@ -0,0 +1,22 @@
<x-dashboard.broker.layout title="All Deals">
<x-slot:heading>
<x-dashboard.page-heading
title="Manage All Deals"
description="Edit, Delete all deals of yours"
/>
</x-slot:heading>
<div class="wrapper mb-8">
<x-dashboard.card class="bg-white">
<p class="font-bold mb-6">My Deals</p>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
@forelse($deals as $deal)
<x-dashboard.broker.listing-card :deal="$deal"/>
@empty
<p class="text-center text-xs text-accent-600 lg:col-span-3">No Deals created</p>
@endforelse
</div>
{{ $deals->links() }}
</x-dashboard.card>
</div>
</x-dashboard.broker.layout>

View File

@ -1,5 +1,6 @@
<?php <?php
use App\Http\Controllers\ContactController;
use App\Http\Controllers\ExplorePageController; use App\Http\Controllers\ExplorePageController;
use App\Http\Controllers\HomeController; use App\Http\Controllers\HomeController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -12,7 +13,7 @@
Route::get('/', HomeController::class)->name('home'); Route::get('/', HomeController::class)->name('home');
Route::get('/explore', ExplorePageController::class)->name('explore'); Route::get('/explore', ExplorePageController::class)->name('explore');
Route::post('/contact', ContactController::class)->name('contact');
/** /**
* This routes are accessed by JS XHR requests, and is loaded here cause * This routes are accessed by JS XHR requests, and is loaded here cause
* we do not want to use sanctum for web requests * we do not want to use sanctum for web requests

View File

@ -13,7 +13,7 @@
->only(['create', 'store']); ->only(['create', 'store']);
Route::resource('/register', RegisteredUserController::class)->only(['create', 'store']); Route::resource('/register', RegisteredUserController::class)->only(['create', 'store']);
Route::prefix('password/reset')->name('password.reset.')->middleware('throttle:20,1')->group(function () { Route::prefix('password/reset')->name('password.reset.')->middleware('throttle:30,1')->group(function () {
Route::controller(PasswordResetController::class)->group(function () { Route::controller(PasswordResetController::class)->group(function () {
Route::get('/', 'show')->name('show'); Route::get('/', 'show')->name('show');