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
@ -20,11 +20,6 @@ public function execute(array $data): bool
|
||||
$user = \Session::get('otp_user_id') ? User::find(\Session::get('otp_user_id')) : null;
|
||||
throw_if(! $user, new UserNotFoundException('User not found'));
|
||||
|
||||
$isVerified = $this->otpService->verify($user, $data['otp']);
|
||||
if ($isVerified) {
|
||||
\Session::forget('otp_user_id');
|
||||
}
|
||||
|
||||
return $isVerified;
|
||||
return $this->otpService->verify($user, $data['otp']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ public function verify(Request $request, VerifyOTPAction $otpAction)
|
||||
$data = $request->validate(['otp' => 'required|string:min:5:max:6']);
|
||||
try {
|
||||
$isVerified = $otpAction->execute($data);
|
||||
if (! $isVerified) {
|
||||
if (!$isVerified) {
|
||||
return back()->with('error', 'Invalid OTP');
|
||||
}
|
||||
|
||||
@ -66,12 +66,14 @@ public function update(Request $request)
|
||||
$data = $request->validate([
|
||||
'password' => 'required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols(),
|
||||
]);
|
||||
$user = User::find(Session::get('user_id'));
|
||||
if (! $user) {
|
||||
$user = User::find(Session::get('otp_user_id'));
|
||||
if (!$user) {
|
||||
return back()->with('error', 'Session Expired');
|
||||
}
|
||||
$user->update(['password' => $data['password']]);
|
||||
|
||||
\Session::forget('otp_user_id');
|
||||
|
||||
return to_route('login.create')->with('success', 'Password updated successfully');
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ protected function deals()
|
||||
->WithLikePerDeal()
|
||||
->WithRedirectionPerDeal()
|
||||
->withViewPerDeal()
|
||||
->latest()
|
||||
->paginate();
|
||||
->latest()->take(3)->get();
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
use App\Models\Deal;
|
||||
use App\Models\DealCategory;
|
||||
use App\Services\FileService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
@ -18,7 +19,8 @@ class BrokerDealController extends Controller
|
||||
*/
|
||||
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::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) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
24
app/Http/Controllers/ContactController.php
Normal 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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
34
app/Http/Requests/ContactRequest.php
Normal 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';
|
||||
}
|
||||
}
|
||||
39
app/Notifications/NewContactNotification.php
Normal 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
|
After Width: | Height: | Size: 17 KiB |
BIN
public/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 15 KiB |
1
public/favicon.svg
Normal file
|
After Width: | Height: | Size: 45 KiB |
21
public/site.webmanifest
Normal 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"
|
||||
}
|
||||
BIN
public/web-app-manifest-192x192.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/web-app-manifest-512x512.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
@ -13,13 +13,13 @@ export async function showDealModal(dealId) {
|
||||
|
||||
showModal('deal-modal');
|
||||
|
||||
toggleShimmer(true, dealModal);
|
||||
toggleShimmer(false, dealModal);
|
||||
|
||||
try {
|
||||
const response = await axios.get('/api/deals/' + dealId);
|
||||
setDealDetails(response.data);
|
||||
|
||||
toggleShimmer(false, dealModal);
|
||||
toggleShimmer(true, dealModal);
|
||||
|
||||
dealModal.dataset.dealId = dealId;
|
||||
await axios.post(`/api/view/${dealId}`);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
@props(['deals' => []])
|
||||
<div class="wrapper">
|
||||
<div class="wrapper mb-8">
|
||||
<x-dashboard.card class="bg-white">
|
||||
<p class="font-bold mb-6">My Listings</p>
|
||||
<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>
|
||||
@endforelse
|
||||
</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>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
@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">
|
||||
<a class="flex-1 flex items-center"
|
||||
href="{{route('explore', ['search' => $item])}}">
|
||||
href="{{route('explore', ['search' => $item->query])}}">
|
||||
{{$item->query}}
|
||||
<x-heroicon-o-arrow-up-right class="w-3 ml-2"/>
|
||||
</a>
|
||||
|
||||
@ -51,18 +51,18 @@ class="fill-[#BDC1D2]"/>
|
||||
<div class="space-y-5 whitespace-nowrap">
|
||||
<h3 class="text-white text-xl font-bold">Quick Links</h3>
|
||||
<ul class="text-[#8C92AC] font-bold space-y-3">
|
||||
<li><a href="">Home</a></li>
|
||||
<li><a href="">Explore Deals</a></li>
|
||||
<li><a href="">About</a></li>
|
||||
<li><a href="">Contact</a></li>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="{{route('explore')}}">Explore Deals</a></li>
|
||||
<li><a href="#about">About</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5 whitespace-nowrap">
|
||||
<h3 class="text-white text-xl font-bold">For Brokers</h3>
|
||||
<ul class="text-[#8C92AC] font-bold space-y-3">
|
||||
<li><a href="">Sign Up</a></li>
|
||||
<li><a href="">Dashboard</a></li>
|
||||
<li><a href="{{route('register.create')}}">Sign Up</a></li>
|
||||
<li><a href="{{route('broker.dashboard')}}">Dashboard</a></li>
|
||||
<li><a href="">Resources</a></li>
|
||||
<li><a href="">Support</a></li>
|
||||
</ul>
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
<section class="wrapper mb-15">
|
||||
<section id="contact" class="wrapper mb-15">
|
||||
<h2 class="mt-15">Get In Touch</h2>
|
||||
<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/>
|
||||
as soon as possible.
|
||||
</p>
|
||||
|
||||
<section class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- left section, contact form -->
|
||||
<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="Email" name="email" placeholder="your.email@example.com" type="email"/>
|
||||
<x-ui.textarea label="Message" name="message" placeholder="Tell us how we can help..."/>
|
||||
|
||||
@ -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">
|
||||
|
||||
<!-- Left section -->
|
||||
|
||||
@ -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">
|
||||
<div class="flex items-center">
|
||||
You are impersonating as {{session()->get('impersonate_name', 'a User')}}
|
||||
|
||||
@ -14,7 +14,12 @@
|
||||
|
||||
|
||||
<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 -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet"/>
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
<ul class="flex space-x-8 text-accent-600">
|
||||
<x-nav-links :link="route('home')" name="Home"/>
|
||||
<x-nav-links :link="route('explore')" name="Explore deals"/>
|
||||
<x-nav-links :link="route('home')" name="About"/>
|
||||
<x-nav-links :link="route('home')" name="Contact"/>
|
||||
<x-nav-links link="#about" name="About"/>
|
||||
<x-nav-links link="#contact" name="Contact"/>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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>
|
||||
<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 />
|
||||
|
||||
22
resources/views/dashboards/broker/deals/index.blade.php
Normal 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>
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ContactController;
|
||||
use App\Http\Controllers\ExplorePageController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -12,7 +13,7 @@
|
||||
|
||||
Route::get('/', HomeController::class)->name('home');
|
||||
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
|
||||
* we do not want to use sanctum for web requests
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
->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::get('/', 'show')->name('show');
|
||||
|
||||
|
||||