diff --git a/.env.example b/.env.example index c0660ea..38241be 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -APP_NAME=Laravel +APP_NAME=DealHub APP_ENV=local APP_KEY= APP_DEBUG=true diff --git a/app/Actions/GetUserFavoritesAction.php b/app/Actions/GetUserFavoritesAction.php new file mode 100644 index 0000000..7de6075 --- /dev/null +++ b/app/Actions/GetUserFavoritesAction.php @@ -0,0 +1,20 @@ +interactedDeals() + ->where('interactions.type', InteractionType::Favorite) + ->tap(fn ($q) => (new Deal)->withActiveDeals($q)) + ->select('deals.id', 'deals.title') + ->get(); + } +} diff --git a/app/Actions/GetUserReportedDealsAction.php b/app/Actions/GetUserReportedDealsAction.php new file mode 100644 index 0000000..5f3d78f --- /dev/null +++ b/app/Actions/GetUserReportedDealsAction.php @@ -0,0 +1,17 @@ +reports() + ->select('reports.id') + ->with('deals:id,title') + ->get(); + } +} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 7374d52..20d9fda 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -25,22 +25,22 @@ public function store(StoreRegisterdUser $request) DB::transaction(function () use ($data) { switch ($data['role']) { case UserTypes::Broker->value: - { + $data['status'] = UserStatus::Pending->value; // Create Broker first, then link the user $broker = Broker::create(); $broker->user()->create($data); break; - } + case UserTypes::User->value: - { + $data['status'] = UserStatus::Active->value; $customer = Customer::create(); $customer->user()->create($data); break; - } + } }); diff --git a/app/Http/Controllers/Broker/BrokerProfileController.php b/app/Http/Controllers/Broker/BrokerProfileController.php index 18a4901..f2f6216 100644 --- a/app/Http/Controllers/Broker/BrokerProfileController.php +++ b/app/Http/Controllers/Broker/BrokerProfileController.php @@ -9,7 +9,6 @@ use App\Services\ProfileInitialsService; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Str; class BrokerProfileController extends Controller { diff --git a/app/Http/Controllers/ExplorePageController.php b/app/Http/Controllers/ExplorePageController.php index ec1a1fb..3c3f0b9 100644 --- a/app/Http/Controllers/ExplorePageController.php +++ b/app/Http/Controllers/ExplorePageController.php @@ -33,7 +33,7 @@ protected function deals(FormRequest $request, Builder $query, AddRecentSearchAc { // Add a search query if ($request->has('search') && $request->get('search') !== null) { - $query->tap(fn($q) => (new Deal)->search($q, $request->search)); + $query->tap(fn ($q) => (new Deal)->search($q, $request->search)); \Illuminate\Support\defer(function () use ($action, $request) { $action->execute($request->user(), ['query' => $request->search]); @@ -42,7 +42,7 @@ protected function deals(FormRequest $request, Builder $query, AddRecentSearchAc // Add category sorting filter if ($request->has('category') && $request->get('category') !== null) { - $query->tap(fn($q) => (new Deal)->filterByCategory($q, $request->category)); + $query->tap(fn ($q) => (new Deal)->filterByCategory($q, $request->category)); } // Add sorting filters diff --git a/app/Http/Controllers/Interaction/InteractionController.php b/app/Http/Controllers/Interaction/InteractionController.php index 824a9ab..31fd599 100644 --- a/app/Http/Controllers/Interaction/InteractionController.php +++ b/app/Http/Controllers/Interaction/InteractionController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Deal; use App\Models\Interaction; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; @@ -15,9 +16,8 @@ class InteractionController extends Controller * Interact to a deal by Like or Favorite state * * @param InteractionType $type [InteractionType::Like, InteractionType::Favorite] - * @return \Illuminate\Http\JsonResponse */ - public function togglesState(Deal $deal, InteractionType $type) + public function togglesState(Deal $deal, InteractionType $type, Request $request) { if (! in_array($type, [InteractionType::Like, InteractionType::Favorite])) { return response()->json(['error' => 'This interaction is not supported'], 400); @@ -47,8 +47,11 @@ public function togglesState(Deal $deal, InteractionType $type) $message = ucfirst($type->value).' added to deal'; } - - return response()->json(['message' => $message]); + if ($request->expectsJson()) { + return response()->json(['message' => $message]); + } else { + return back()->with('success', $message); + } } catch (\Throwable $e) { Log::error('Error when liked a deal', @@ -60,8 +63,11 @@ public function togglesState(Deal $deal, InteractionType $type) 'trace' => $e->getTraceAsString(), ] ); + if ($request->expectsJson()) { + return response()->json(['error' => 'Something went wrong.'], 500); + } - return response()->json(['error' => 'Something went wrong.'], 500); + return back()->with('error', 'Something went wrong.'); } } diff --git a/app/Http/Controllers/Interaction/ReportController.php b/app/Http/Controllers/Interaction/ReportController.php index 7e5dc51..8834bec 100644 --- a/app/Http/Controllers/Interaction/ReportController.php +++ b/app/Http/Controllers/Interaction/ReportController.php @@ -78,6 +78,21 @@ public function update(Request $request, Report $report) */ public function destroy(Report $report) { - // + try { + DB::transaction(function () use ($report) { + $report->deals()->detach(); + $report->delete(); + }); + + return back()->with('success', 'Report deleted successfully.'); + } catch (\Throwable $e) { + Log::error('Error deleting report', [ + 'user_id' => Auth::id(), + 'report_id' => $report->id, + 'error' => $e->getMessage(), + ]); + } + + return back()->with('error', 'Something went wrong.'); } } diff --git a/app/Http/Controllers/User/UserProfileController.php b/app/Http/Controllers/User/UserProfileController.php index 94e02e7..d2ca10c 100644 --- a/app/Http/Controllers/User/UserProfileController.php +++ b/app/Http/Controllers/User/UserProfileController.php @@ -2,13 +2,12 @@ namespace App\Http\Controllers\User; +use App\Actions\GetUserFavoritesAction; +use App\Actions\GetUserReportedDealsAction; use App\Http\Controllers\Controller; -use App\Http\Requests\StoreBrokerProfileRequest; use App\Http\Requests\StoreCustomerProfileRequest; -use App\Models\Broker; use App\Models\User; use App\Services\ProfileInitialsService; -use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -17,8 +16,12 @@ class UserProfileController extends Controller /** * Display the specified resource. */ - public function show(User $profile, ProfileInitialsService $service) - { + public function show( + User $profile, + ProfileInitialsService $service, + GetUserFavoritesAction $favoritesAction, + GetUserReportedDealsAction $reportedDealsAction + ) { // Get the user profile $user = $profile->type; @@ -32,7 +35,9 @@ public function show(User $profile, ProfileInitialsService $service) ->with('initials', $initials) ->with('location', $user->location) ->with('bio', $user->bio) - ->with('phone', $user->phone); + ->with('phone', $user->phone) + ->with('favorites', $favoritesAction->execute($profile)) + ->with('reported', $reportedDealsAction->execute($profile)); } /** diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 39390da..f36054a 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -8,6 +8,7 @@ class Customer extends Model { protected $fillable = ['bio', 'location', 'phone']; + public function user(): MorphOne { return $this->morphOne(User::class, 'role'); diff --git a/app/Models/Deal.php b/app/Models/Deal.php index 5c347f0..cec6c4b 100644 --- a/app/Models/Deal.php +++ b/app/Models/Deal.php @@ -118,5 +118,4 @@ public function filterByCategory(Builder $query, string $category): Builder { return $query->where('deal_category_id', $category); } - } diff --git a/app/Models/User.php b/app/Models/User.php index eebf54c..cffb2cd 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -89,4 +89,14 @@ public function dealsInteractions(): HasManyThrough { return $this->hasManyThrough(Interaction::class, Deal::class); } + + public function interactedDeals(): HasManyThrough + { + return $this->hasManyThrough(Deal::class, Interaction::class, 'user_id', 'id', 'id', 'deal_id'); + } + + public function reports(): HasMany + { + return $this->hasMany(Report::class); + } } diff --git a/app/Services/ProfileInitialsService.php b/app/Services/ProfileInitialsService.php index 7032af0..fffce13 100644 --- a/app/Services/ProfileInitialsService.php +++ b/app/Services/ProfileInitialsService.php @@ -6,7 +6,6 @@ class ProfileInitialsService { - /** * Create the initials from a full name (e.g. John Doe, Alex Mark, jane clerk) * to display on the profile page (e.g. JD, AM, JC). @@ -15,7 +14,7 @@ public function create(string $fullname) { return Str::of($fullname) ->explode(' ') - ->map(fn($word) => Str::substr(ucfirst($word), 0, 1)) + ->map(fn ($word) => Str::substr(ucfirst($word), 0, 1)) ->join(''); } } diff --git a/database/migrations/2026_01_22_125826_create_customers_table.php b/database/migrations/2026_01_22_125826_create_customers_table.php index 6c9d77b..ceab23b 100644 --- a/database/migrations/2026_01_22_125826_create_customers_table.php +++ b/database/migrations/2026_01_22_125826_create_customers_table.php @@ -4,7 +4,8 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration { +return new class extends Migration +{ /** * Run the migrations. */ diff --git a/resources/js/app.js b/resources/js/app.js index a062cce..91a3aae 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -9,13 +9,15 @@ import "./toast.js" import "./deal-view-modal.js" import {favorite, like, redirect} from "./interaction.js"; import {showReportModal} from "./report-deal.js"; +import {initTabs} from "./tab.js"; +import {loadModalFromQuery} from "./explore-page.js"; document.like = like; document.favorite = favorite; document.redirect = redirect; document.showReportModal = showReportModal; -window.addEventListener('load', () => { +window.addEventListener('load', async () => { const preloader = document.getElementById('preloader'); const content = document.getElementById('content'); @@ -32,4 +34,8 @@ window.addEventListener('load', () => { if (savedState) { setSidebarState(savedState); } + + initTabs(); + + await loadModalFromQuery(); }); diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 5f1390b..c3b7e3e 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -1,4 +1,7 @@ import axios from 'axios'; + window.axios = axios; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +window.axios.defaults.headers.common['Accept'] = 'application/json'; +window.axios.defaults.headers.common['Content-Type'] = 'application/json'; diff --git a/resources/js/deal-view-modal.js b/resources/js/deal-view-modal.js index 28592df..05868ea 100644 --- a/resources/js/deal-view-modal.js +++ b/resources/js/deal-view-modal.js @@ -3,7 +3,7 @@ import {closeModal, showModal} from "@/modal.js"; import {redirect} from "./interaction.js"; import {toggleShimmer} from "./shimmer.js"; -async function showDealModal(dealId) { +export async function showDealModal(dealId) { if (!dealId) { showToast('Something went wrong!'); return; diff --git a/resources/js/explore-page.js b/resources/js/explore-page.js new file mode 100644 index 0000000..92af6d9 --- /dev/null +++ b/resources/js/explore-page.js @@ -0,0 +1,11 @@ +import {showDealModal} from "./deal-view-modal.js"; + +export async function loadModalFromQuery(){ + const query = new URLSearchParams(window.location.search); + if (query.has('show')) { + const dealId = query.get('show'); + if(dealId){ + await showDealModal(dealId); + } + } +} diff --git a/resources/js/tab.js b/resources/js/tab.js new file mode 100644 index 0000000..d9348ec --- /dev/null +++ b/resources/js/tab.js @@ -0,0 +1,32 @@ +export function initTabs() { + try { + const tabs = document.querySelectorAll('.tabs'); + tabs.forEach(tab => { + const tabBtns = tab.querySelectorAll('.tab-btn'); + tabBtns.forEach(tabBtn => + tabBtn.addEventListener('click', (e) => { + // Make the current button active + tabBtns.forEach(btn => btn.classList.remove('bg-white')); + tabBtn.classList.add('bg-white'); + + // Hide all other tabs + const tabContents = tab.querySelectorAll('.tab-content'); + tabContents.forEach(content => content.removeAttribute('data-show')); + + // Show the target tab + const targetContent = tabBtn.dataset.target; + if (targetContent) { + const contentTabData = `[data-tab="${targetContent}"]`; + const tabContent = tab.querySelector(contentTabData); + if (tabContent) { + tabContent.setAttribute('data-show', '') + } + } + } + ) + ); + }) + } catch (e) { + console.error(e) + } +} diff --git a/resources/views/components/dashboard/user/broker-contact.blade.php b/resources/views/components/dashboard/user/broker-contact.blade.php index 854a2f4..9bda292 100644 --- a/resources/views/components/dashboard/user/broker-contact.blade.php +++ b/resources/views/components/dashboard/user/broker-contact.blade.php @@ -2,9 +2,9 @@
Broker Contact
{{$broker->name ?? ''}}
-{{$broker->email ?? ''}}
-{{$broker->role->phone ?? ''}}
+{{$broker->name ?? ''}}
+{{$broker->email ?? ''}}
+{{$broker->role->phone ?? ''}}
Favorites
+Reported Deals
+Your favorite deals
+Your reported deals
+