feature(report a deal): users can report a deal
This commit is contained in:
parent
f33f68cd3e
commit
3750638122
14
app/Enums/ReportStatus.php
Normal file
14
app/Enums/ReportStatus.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use App\Traits\EnumAsArray;
|
||||||
|
|
||||||
|
enum ReportStatus: string
|
||||||
|
{
|
||||||
|
use EnumAsArray;
|
||||||
|
|
||||||
|
case Pending = 'pending';
|
||||||
|
case Resolved = 'resolved';
|
||||||
|
case Rejected = 'rejected';
|
||||||
|
}
|
||||||
17
app/Enums/ReportType.php
Normal file
17
app/Enums/ReportType.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use App\Traits\EnumAsArray;
|
||||||
|
|
||||||
|
enum ReportType: string
|
||||||
|
{
|
||||||
|
use EnumAsArray;
|
||||||
|
|
||||||
|
case Spam = 'spam';
|
||||||
|
case Harmful = 'harmful';
|
||||||
|
case Nudity = 'nudity';
|
||||||
|
case Misinformation = 'misinformation';
|
||||||
|
case Unauthorized = 'unauthorized';
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,13 +12,13 @@ class InteractionController extends Controller
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Interact to a deal by Like or Favorite state
|
* Interact to a deal by Like or Favorite state
|
||||||
* @param Deal $deal
|
*
|
||||||
* @param InteractionType $type [InteractionType::Like, InteractionType::Favorite]
|
* @param InteractionType $type [InteractionType::Like, InteractionType::Favorite]
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function togglesState(Deal $deal, InteractionType $type)
|
public function togglesState(Deal $deal, InteractionType $type)
|
||||||
{
|
{
|
||||||
if (!in_array($type, [InteractionType::Like, InteractionType::Favorite])) {
|
if (! in_array($type, [InteractionType::Like, InteractionType::Favorite])) {
|
||||||
return response()->json(['error' => 'This interaction is not supported'], 400);
|
return response()->json(['error' => 'This interaction is not supported'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +46,7 @@ public function togglesState(Deal $deal, InteractionType $type)
|
|||||||
|
|
||||||
$message = "{$type->value} added to deal";
|
$message = "{$type->value} added to deal";
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['message' => $message]);
|
return response()->json(['message' => $message]);
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@ -62,5 +63,4 @@ public function togglesState(Deal $deal, InteractionType $type)
|
|||||||
return response()->json(['error' => 'Something went wrong.'], 500);
|
return response()->json(['error' => 'Something went wrong.'], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
82
app/Http/Controllers/ReportController.php
Normal file
82
app/Http/Controllers/ReportController.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\StoreReportRequest;
|
||||||
|
use App\Models\Deal;
|
||||||
|
use App\Models\Report;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class ReportController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(StoreReportRequest $request, Deal $deal)
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
$data['user_id'] = Auth::id();
|
||||||
|
|
||||||
|
// Check if the user already reported the deal
|
||||||
|
$alreadyReported = $deal->reports()->where('user_id', Auth::id())->first();
|
||||||
|
if ($alreadyReported) {
|
||||||
|
return response()->json(['message' => 'You already reported this report'], 405);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($data, $deal) {
|
||||||
|
Report::unguard();
|
||||||
|
Report::create($data)->deals()->attach($deal);
|
||||||
|
Report::reguard();
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Report created'], 201);
|
||||||
|
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
Log::error('Error creating report', [
|
||||||
|
'user_id' => Auth::id(),
|
||||||
|
'deal_id' => $deal->id,
|
||||||
|
'error' => $exception->getMessage(),
|
||||||
|
'trace' => $exception->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Error creating report'], 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(Report $report)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, Report $report)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(Report $report)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Http/Requests/StoreReportRequest.php
Normal file
31
app/Http/Requests/StoreReportRequest.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Enums\ReportType;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class StoreReportRequest 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 [
|
||||||
|
'type' => ['required', Rule::in(ReportType::values())],
|
||||||
|
'description' => 'required|string|min:10|max:500',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
@ -28,8 +29,6 @@ public function interactions(): HasMany
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get deals that are active
|
* Get deals that are active
|
||||||
* @param Builder $query
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function scopeWithActiveDeals(Builder $query): Builder
|
public function scopeWithActiveDeals(Builder $query): Builder
|
||||||
{
|
{
|
||||||
@ -38,8 +37,6 @@ public function scopeWithActiveDeals(Builder $query): Builder
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get if the current user has liked or favorite the deal
|
* Get if the current user has liked or favorite the deal
|
||||||
* @param Builder $query
|
|
||||||
* @return Builder
|
|
||||||
*/
|
*/
|
||||||
public function scopeWithCurrentUserInteractions(Builder $query): Builder
|
public function scopeWithCurrentUserInteractions(Builder $query): Builder
|
||||||
{
|
{
|
||||||
@ -51,7 +48,7 @@ public function scopeWithCurrentUserInteractions(Builder $query): Builder
|
|||||||
'interactions as is_favorite' => function ($query) {
|
'interactions as is_favorite' => function ($query) {
|
||||||
$query->where('user_id', Auth::id())
|
$query->where('user_id', Auth::id())
|
||||||
->where('type', InteractionType::Favorite);
|
->where('type', InteractionType::Favorite);
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +57,12 @@ public function scopeWithLikes(Builder $query): Builder
|
|||||||
return $query->withCount([
|
return $query->withCount([
|
||||||
'interactions as total_likes' => function ($query) {
|
'interactions as total_likes' => function ($query) {
|
||||||
$query->where('type', InteractionType::Like);
|
$query->where('type', InteractionType::Like);
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function reports(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Report::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
app/Models/Report.php
Normal file
30
app/Models/Report.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ReportStatus;
|
||||||
|
use App\Enums\ReportType;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
|
class Report extends Model
|
||||||
|
{
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deals(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Deal::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => ReportType::class,
|
||||||
|
'status' => ReportStatus::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,4 +8,11 @@ public static function values(): array
|
|||||||
{
|
{
|
||||||
return array_column(self::cases(), 'value');
|
return array_column(self::cases(), 'value');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function assocValues(): array
|
||||||
|
{
|
||||||
|
return array_map(function ($enum) {
|
||||||
|
return ['name' => $enum->name, 'value' => $enum->value];
|
||||||
|
}, self::cases());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ReportStatus;
|
||||||
|
use App\Enums\ReportType;
|
||||||
|
use App\Models\User;
|
||||||
|
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('reports', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->enum('type', ReportType::values());
|
||||||
|
$table->enum('status', ReportStatus::values())->default(ReportStatus::Pending);
|
||||||
|
$table->text('description');
|
||||||
|
$table->foreignIdFor(User::class);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('reports');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Deal;
|
||||||
|
use App\Models\Report;
|
||||||
|
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('deal_report', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignIdFor(Deal::class);
|
||||||
|
$table->foreignIdFor(Report::class);
|
||||||
|
$table->timestamp('created_at')->useCurrent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('deal_report');
|
||||||
|
}
|
||||||
|
};
|
||||||
10
resources/js/modal.js
Normal file
10
resources/js/modal.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export function showModal(id){
|
||||||
|
let modal = document.getElementById(id);
|
||||||
|
modal.showModal();
|
||||||
|
}
|
||||||
|
export function closeModal(id){
|
||||||
|
let modal = document.getElementById(id);
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
document.showModal = showModal;
|
||||||
|
document.closeModal = closeModal;
|
||||||
60
resources/js/report-deal.js
Normal file
60
resources/js/report-deal.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import {closeModal, showModal} from './modal.js';
|
||||||
|
import {showToast} from './toast.js';
|
||||||
|
const reportModal = document.getElementById('report-modal');
|
||||||
|
const reportForm = document.getElementById('report-form');
|
||||||
|
|
||||||
|
|
||||||
|
function showReportModal(dealId, dealTitle) {
|
||||||
|
// Clear the fields
|
||||||
|
reportForm.reset();
|
||||||
|
const oldErrors = reportForm.querySelectorAll('.text-red-500');
|
||||||
|
oldErrors.forEach(error => error.remove());
|
||||||
|
|
||||||
|
reportModal.dataset.dealId = dealId;
|
||||||
|
const reportModalTitle = document.getElementById('report-modal-title');
|
||||||
|
const reportModalId = document.getElementById('report-modal-id');
|
||||||
|
reportModalTitle.innerText = dealTitle;
|
||||||
|
reportModalId.innerText = dealId;
|
||||||
|
showModal('report-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
reportForm.addEventListener('submit', async function (form) {
|
||||||
|
form.preventDefault();
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const dealId = reportModal.dataset.dealId
|
||||||
|
try {
|
||||||
|
|
||||||
|
let response = await axios.post(
|
||||||
|
`report/${dealId}`,
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
|
showToast('Report submitted. Thank you for keeping DealHub safe!');
|
||||||
|
closeModal('report-modal')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
if (error.response.status === 405) {
|
||||||
|
closeModal('report-modal');
|
||||||
|
showToast('You already have reported this deal !');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the all error messages spans and show validation errors
|
||||||
|
if (error.response.status === 422) {
|
||||||
|
|
||||||
|
let errors = error.response.data.errors;
|
||||||
|
|
||||||
|
Object.keys(errors).forEach(error => {
|
||||||
|
let errorField = reportForm.querySelector(`[name="${error}"]`)
|
||||||
|
const errorSpan = document.createElement('span');
|
||||||
|
errorSpan.textContent = errors[error][0];
|
||||||
|
errorSpan.classList.add('text-red-500');
|
||||||
|
errorSpan.classList.add('text-sm');
|
||||||
|
errorField.insertAdjacentElement('afterend', errorSpan);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.showReportModal = showReportModal;
|
||||||
15
resources/js/toast.js
Normal file
15
resources/js/toast.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const toast = document.querySelector('.toast');
|
||||||
|
const toastBtn = document.querySelector('#toast-btn');
|
||||||
|
let toastMessage = toast.querySelector('#toast-message');
|
||||||
|
export function showToast(message) {
|
||||||
|
toast.classList.toggle('hidden')
|
||||||
|
toastMessage.textContent = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.toggle('hidden');
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
function hideToast() {
|
||||||
|
toast.classList.toggle('hidden')
|
||||||
|
}
|
||||||
|
document.hideToast = hideToast;
|
||||||
|
document.showToast = showToast;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
@props(['id', 'like' => false, 'favourite' => false])
|
@props(['deal_id', 'deal_title', 'like' => false, 'favourite' => false])
|
||||||
<div class="">
|
<div class="">
|
||||||
<x-ui.button-sm @class(["text-accent-600", 'liked' => $like]) onclick="like(this, {{$id}})">
|
<x-ui.button-sm @class(["text-accent-600", 'liked' => $like]) onclick="like(this, {{$deal_id}})">
|
||||||
<x-heroicon-o-heart
|
<x-heroicon-o-heart
|
||||||
@class([
|
@class([
|
||||||
"like w-4",
|
"like w-4",
|
||||||
@ -15,7 +15,7 @@
|
|||||||
/>
|
/>
|
||||||
</x-ui.button-sm>
|
</x-ui.button-sm>
|
||||||
|
|
||||||
<x-ui.button-sm class="text-accent-600" onclick="favorite(this, {{$id}})">
|
<x-ui.button-sm class="text-accent-600" onclick="favorite(this, {{$deal_id}})">
|
||||||
<x-heroicon-o-star
|
<x-heroicon-o-star
|
||||||
@class([
|
@class([
|
||||||
"favorite w-4",
|
"favorite w-4",
|
||||||
@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
</x-ui.button-sm>
|
</x-ui.button-sm>
|
||||||
|
|
||||||
<x-ui.button-sm class="text-accent-600">
|
<x-ui.button-sm class="text-accent-600" onclick="showReportModal({{$deal_id}}, '{{$deal_title}}')">
|
||||||
<x-heroicon-o-exclamation-circle class="w-4"/>
|
<x-heroicon-o-exclamation-circle class="w-4"/>
|
||||||
</x-ui.button-sm>
|
</x-ui.button-sm>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
{{$deal->category->name}}
|
{{$deal->category->name}}
|
||||||
</x-ui.button-sm>
|
</x-ui.button-sm>
|
||||||
@ds($deal)
|
@ds($deal)
|
||||||
<x-dashboard.user.action-toolbar :id="$deal->id" :like="$deal->is_liked" :favourite="$deal->is_favorite" />
|
<x-dashboard.user.action-toolbar :deal_title="$deal->title" :deal_id="$deal->id" :like="$deal->is_liked" :favourite="$deal->is_favorite" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="font-bold text-lg ">{{$deal->title}}</p>
|
<p class="font-bold text-lg ">{{$deal->title}}</p>
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
<x-ui.modal id="report-modal" class="w-130">
|
||||||
|
<form class="flex justify-between items-start" method="dialog">
|
||||||
|
<div class="flex space-x-2 items-center">
|
||||||
|
<x-icon-square variant="red" class="p-2!">
|
||||||
|
<x-heroicon-o-exclamation-circle class="w-6" />
|
||||||
|
</x-icon-square>
|
||||||
|
<p class="text-xl font-bold">Report Deal</p>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="" >
|
||||||
|
<x-heroicon-o-x-mark class="w-4" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="text-accent-600/80 mt-2 text-sm">Help us maintain a trusted platform by reporting inappropriate or suspicious content.</p>
|
||||||
|
<p class="text-accent-600 mt-2 text-sm font-bold">Deal being reported</p>
|
||||||
|
<div class="w-fit wrap-break-word px-2 py-4">
|
||||||
|
<p class="font-bold text-sm" id="report-modal-title"></p>
|
||||||
|
<p class="text-xs text-accent-600/80 pt-2">ID: #<span id="report-modal-id"></span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="" method="post" id="report-form" class="mt-4 flex flex-col space-y-4">
|
||||||
|
@csrf
|
||||||
|
<x-ui.select :options="\App\Enums\ReportType::assocValues()" label-key="name" value-key="value" name="type" placeholder="Select one reason" required label="Reason for report" />
|
||||||
|
|
||||||
|
<x-ui.textarea name="description" label="Additional Details" required placeholder="Please provide more details about why you're reporting this deal..." description="Minimum 10 characters" />
|
||||||
|
|
||||||
|
<p class="text-blue-700 bg-blue-100 text-xs p-4 rounded-xl">
|
||||||
|
<span class="font-bold">Note:</span> Your report will be reviewed by out moderation team within 24-48 hours.<br />
|
||||||
|
False reports may result in account restrictions.
|
||||||
|
</p>
|
||||||
|
<div class="w-full flex justify-end space-x-4">
|
||||||
|
<x-ui.button class="border border-accent-600/20" onclick="closeModal('report-modal')">Cancel</x-ui.button>
|
||||||
|
<x-ui.button variant="red" type="submit">Submit report</x-ui.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-ui.modal>
|
||||||
@ -2,12 +2,13 @@
|
|||||||
@php
|
@php
|
||||||
$variants = [
|
$variants = [
|
||||||
'blue' => "bg-blue-100 text-blue-700",
|
'blue' => "bg-blue-100 text-blue-700",
|
||||||
|
'red' => 'bg-red-100 text-red-700',
|
||||||
'purple' => "bg-purple-100 text-purple-700",
|
'purple' => "bg-purple-100 text-purple-700",
|
||||||
'pink' => "bg-pink-100 text-pink-700",
|
'pink' => "bg-pink-100 text-pink-700",
|
||||||
'green' => "bg-green-100 text-green-700",
|
'green' => "bg-green-100 text-green-700",
|
||||||
'ghost' => 'bg-gray-100 text-gray-900'
|
'ghost' => 'bg-gray-100 text-gray-900'
|
||||||
]
|
]
|
||||||
@endphp
|
@endphp
|
||||||
<div class="p-4 {{$variants[$variant]}} w-fit rounded-xl">
|
<div {{$attributes->merge(["class" => "$variants[$variant] p-4 w-fit rounded-xl"])}}>
|
||||||
{{$slot}}
|
{{$slot}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@else
|
@else
|
||||||
<button {{$attributes->merge(['class' => "px-4 py-2 rounded-lg font-medium hover:opacity-80 $variantClass"])}}>
|
<button {{$attributes->merge(['class' => "px-4 py-2 rounded-lg font-medium hover:opacity-80 $variantClass", 'type'=>'submit'])}}>
|
||||||
<div class="flex justify-center items-center space-x-2">
|
<div class="flex justify-center items-center space-x-2">
|
||||||
@if($icon !=='')
|
@if($icon !=='')
|
||||||
@svg("heroicon-o-$icon", 'w-5 h-5')
|
@svg("heroicon-o-$icon", 'w-5 h-5')
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@props(['name' => ''])
|
@props(['name' => ''])
|
||||||
@error($name)
|
@error($name)
|
||||||
<span class="text-xs text-red-500">{{ $message }}</span>
|
<span class="text-xs text-red-500 error-message">{{ $message }}</span>
|
||||||
@enderror
|
@enderror
|
||||||
|
|||||||
6
resources/views/components/ui/modal.blade.php
Normal file
6
resources/views/components/ui/modal.blade.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<dialog {{$attributes->merge(["class"=>"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-4 shadow-lg"])}} >
|
||||||
|
<div>
|
||||||
|
{{$slot}}
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
@vite('resources/js/modal.js')
|
||||||
@ -26,7 +26,7 @@
|
|||||||
class="bg-[#F3F3F5] py-2 px-4 rounded-lg text-sm font-bold invalid:text-accent-600 text-black h-full"
|
class="bg-[#F3F3F5] py-2 px-4 rounded-lg text-sm font-bold invalid:text-accent-600 text-black h-full"
|
||||||
>
|
>
|
||||||
@if($placeholder !== '')
|
@if($placeholder !== '')
|
||||||
<option {{old($name) === ''? 'selected' : ''}} disabled>{{$placeholder}}</option>
|
<option {{old($name) === '' || $selected === '' ? 'selected' : ''}} disabled>{{$placeholder}}</option>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@foreach($options as $option)
|
@foreach($options as $option)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@props(['label' => '', 'name' => '', 'placeholder' => '', 'required' => false, 'value' => ''])
|
@props(['label' => '', 'name' => '', 'placeholder' => '', 'required' => false, 'value' => '', 'description'])
|
||||||
@if($label !== '')
|
@if($label !== '')
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="flex flex-col space-y-2">
|
||||||
|
|
||||||
@ -11,11 +11,15 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
|
class="bg-[#F3F3F5] py-2 px-4 rounded-lg h-40"
|
||||||
name="{{$name}}" placeholder="{{$placeholder}}"
|
name="{{$name}}" placeholder="{{$placeholder}}"
|
||||||
required="{{$required?'required':''}}"
|
required="{{$required?'required':''}}"
|
||||||
>{{old($name, $value)}}</textarea>
|
>{{old($name, $value)}}</textarea>
|
||||||
|
|
||||||
|
@isset($description)
|
||||||
|
<p class="text-accent-600 text-xs">{{$description}}</p>
|
||||||
|
@endisset
|
||||||
|
|
||||||
<x-ui.inline-error :name="$name"/>
|
<x-ui.inline-error :name="$name"/>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
6
resources/views/components/ui/toast.blade.php
Normal file
6
resources/views/components/ui/toast.blade.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<div class="toast hidden fixed top-10 right-5 md:right-10 bg-black text-white rounded-xl shadow-lg p-4 flex space-x-2">
|
||||||
|
<p id="toast-message" class="max-w-70 wrap-break-word"></p>
|
||||||
|
<x-ui.button-sm onclick="hideToast()">
|
||||||
|
<x-heroicon-o-x-mark class="w-4" />
|
||||||
|
</x-ui.button-sm>
|
||||||
|
</div>
|
||||||
@ -87,6 +87,8 @@
|
|||||||
</x-ui.toggle-button-group>
|
</x-ui.toggle-button-group>
|
||||||
|
|
||||||
<x-dashboard.user.listing :deals="$deals"/>
|
<x-dashboard.user.listing :deals="$deals"/>
|
||||||
|
<x-dashboard.user.report-modal />
|
||||||
</section>
|
</section>
|
||||||
@vite(['resources/js/menu.js', 'resources/js/interaction.js'])
|
<x-ui.toast />
|
||||||
|
@vite(['resources/js/menu.js', 'resources/js/interaction.js', 'resources/js/report-deal.js', 'resources/js/toast.js'])
|
||||||
</x-layout>
|
</x-layout>
|
||||||
|
|||||||
@ -1,54 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Enums\InteractionType;
|
|
||||||
use App\Enums\UserTypes;
|
use App\Enums\UserTypes;
|
||||||
use App\Http\Controllers\AuthenticatedUserController;
|
|
||||||
use App\Http\Controllers\Broker\BrokerDashboardController;
|
|
||||||
use App\Http\Controllers\Broker\BrokerProfileController;
|
|
||||||
use App\Http\Controllers\BrokerDealController;
|
|
||||||
use App\Http\Controllers\ExplorePageController;
|
use App\Http\Controllers\ExplorePageController;
|
||||||
use App\Http\Controllers\HomeController;
|
use App\Http\Controllers\HomeController;
|
||||||
use App\Http\Controllers\InteractionController;
|
|
||||||
use App\Http\Controllers\RegisteredUserController;
|
|
||||||
use App\Http\Middleware\HasRole;
|
use App\Http\Middleware\HasRole;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
require __DIR__.'/web/auth.php';
|
||||||
|
require __DIR__.'/web/broker.php';
|
||||||
|
require __DIR__.'/web/interaction.php';
|
||||||
|
|
||||||
Route::get('/', HomeController::class)->name('home');
|
Route::get('/', HomeController::class)->name('home');
|
||||||
|
|
||||||
Route::middleware('guest')->group(function () {
|
|
||||||
Route::resource('/login', AuthenticatedUserController::class)
|
|
||||||
->middleware('throttle:6,1')
|
|
||||||
->only(['create', 'store']);
|
|
||||||
Route::resource('/register', RegisteredUserController::class)->only(['create', 'store']);
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
Route::middleware('auth')->group(function () {
|
||||||
Route::delete('/logout', [AuthenticatedUserController::class, 'destroy'])->name('logout');
|
|
||||||
|
|
||||||
Route::get('/explore', ExplorePageController::class)->name('explore');
|
Route::get('/explore', ExplorePageController::class)->name('explore');
|
||||||
|
|
||||||
Route::view('/admin/dashboard', 'dashboards.admin.index')
|
Route::view('/admin/dashboard', 'dashboards.admin.index')
|
||||||
->middleware(HasRole::class.':'.UserTypes::Admin->value)
|
->middleware(HasRole::class.':'.UserTypes::Admin->value)
|
||||||
->name('admin.dashboard');
|
->name('admin.dashboard');
|
||||||
|
|
||||||
Route::prefix('/broker')
|
|
||||||
->name('broker.')
|
|
||||||
->middleware(HasRole::class.':'.UserTypes::Broker->value)
|
|
||||||
->group(function () {
|
|
||||||
Route::get('dashboard', [BrokerDashboardController::class, 'index'])->name('dashboard');
|
|
||||||
|
|
||||||
Route::resource('deals', BrokerDealController::class)->except('show');
|
|
||||||
|
|
||||||
Route::resource('profile', BrokerProfileController::class)->except('index', 'store', 'create');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::post('/like/{deal}', [InteractionController::class, 'togglesState'])
|
|
||||||
->defaults('type', InteractionType::Like)
|
|
||||||
->middleware('throttle:30,1')
|
|
||||||
->name('like');
|
|
||||||
|
|
||||||
Route::post('/favorite/{deal}', [InteractionController::class, 'togglesState'])
|
|
||||||
->defaults('type', InteractionType::Favorite)
|
|
||||||
->middleware('throttle:30,1')
|
|
||||||
->name('like');
|
|
||||||
});
|
});
|
||||||
|
|||||||
15
routes/web/auth.php
Normal file
15
routes/web/auth.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\AuthenticatedUserController;
|
||||||
|
use App\Http\Controllers\RegisteredUserController;
|
||||||
|
|
||||||
|
Route::middleware('guest')->group(function () {
|
||||||
|
Route::resource('/login', AuthenticatedUserController::class)
|
||||||
|
->middleware('throttle:6,1')
|
||||||
|
->only(['create', 'store']);
|
||||||
|
Route::resource('/register', RegisteredUserController::class)->only(['create', 'store']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::delete('/logout', [AuthenticatedUserController::class, 'destroy'])
|
||||||
|
->middleware('auth')
|
||||||
|
->name('logout');
|
||||||
18
routes/web/broker.php
Normal file
18
routes/web/broker.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\UserTypes;
|
||||||
|
use App\Http\Controllers\Broker\BrokerDashboardController;
|
||||||
|
use App\Http\Controllers\Broker\BrokerProfileController;
|
||||||
|
use App\Http\Controllers\BrokerDealController;
|
||||||
|
use App\Http\Middleware\HasRole;
|
||||||
|
|
||||||
|
Route::prefix('/broker')
|
||||||
|
->name('broker.')
|
||||||
|
->middleware([HasRole::class.':'.UserTypes::Broker->value, 'auth'])
|
||||||
|
->group(function () {
|
||||||
|
Route::get('dashboard', [BrokerDashboardController::class, 'index'])->name('dashboard');
|
||||||
|
|
||||||
|
Route::resource('deals', BrokerDealController::class)->except('show');
|
||||||
|
|
||||||
|
Route::resource('profile', BrokerProfileController::class)->except('index', 'store', 'create');
|
||||||
|
});
|
||||||
21
routes/web/interaction.php
Normal file
21
routes/web/interaction.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\InteractionType;
|
||||||
|
use App\Http\Controllers\InteractionController;
|
||||||
|
use App\Http\Controllers\ReportController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware('auth')->group(function () {
|
||||||
|
|
||||||
|
Route::post('/like/{deal}', [InteractionController::class, 'togglesState'])
|
||||||
|
->defaults('type', InteractionType::Like)
|
||||||
|
->middleware('throttle:30,1')
|
||||||
|
->name('like');
|
||||||
|
|
||||||
|
Route::post('/favorite/{deal}', [InteractionController::class, 'togglesState'])
|
||||||
|
->defaults('type', InteractionType::Favorite)
|
||||||
|
->middleware('throttle:30,1')
|
||||||
|
->name('favorite');
|
||||||
|
|
||||||
|
Route::post('/report/{deal}', [ReportController::class, 'store'])->name('report.store');
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user