wip
This commit is contained in:
parent
f14f7ece54
commit
6b52aed66a
23
app/Http/Resources/BrokerRoleResource.php
Normal file
23
app/Http/Resources/BrokerRoleResource.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class BrokerRoleResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'bio' => $this->bio,
|
||||||
|
'location' => $this->location,
|
||||||
|
'phone' => $this->phone,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Http/Resources/DealResource.php
Normal file
31
app/Http/Resources/DealResource.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
|
||||||
|
class DealResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'title' => $this->title,
|
||||||
|
'description' => $this->description,
|
||||||
|
'image' => asset('storage/'.$this->image),
|
||||||
|
'link' => $this->link !== null ? URL::signedRoute('redirect', $this->id) : null,
|
||||||
|
'category' => $this->whenLoaded('category'),
|
||||||
|
'broker' => new UserResource($this->whenLoaded('broker')),
|
||||||
|
'total_likes' => $this->total_likes,
|
||||||
|
'total_redirection' => $this->total_redirection,
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/Http/Resources/UserResource.php
Normal file
24
app/Http/Resources/UserResource.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class UserResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'email' => $this->email,
|
||||||
|
'role' => new BrokerRoleResource($this->whenLoaded('role')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -94,7 +94,7 @@ public function search(Builder $query, string $search): Builder
|
|||||||
{
|
{
|
||||||
return $query
|
return $query
|
||||||
->where('title', 'LIKE', "%$search%")
|
->where('title', 'LIKE', "%$search%")
|
||||||
->orWhereRelation('broker', 'name', 'LIKE', "%$search%");
|
->orWhereRelation('broker', 'name', 'LIKE', "%$search%");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,14 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
import { setSidebarState } from '@/sidebar.js';
|
import {setSidebarState} from '@/sidebar.js';
|
||||||
|
import "./interaction.js"
|
||||||
|
import "./report-deal.js"
|
||||||
|
import "./alert.js"
|
||||||
|
import "./image-input.js"
|
||||||
|
import "./menu.js"
|
||||||
|
import "./modal.js"
|
||||||
|
import "./sidebar.js"
|
||||||
|
import "./toast.js"
|
||||||
|
import "./deal-view-modal.js"
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
const preloader = document.getElementById('preloader');
|
const preloader = document.getElementById('preloader');
|
||||||
|
|||||||
94
resources/js/deal-view-modal.js
Normal file
94
resources/js/deal-view-modal.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import {showToast} from "@/toast.js";
|
||||||
|
import {closeModal, showModal} from "@/modal.js";
|
||||||
|
import {favorite, like, redirect} from "@/interaction.js";
|
||||||
|
import {showReportModal} from "@/report-deal.js";
|
||||||
|
|
||||||
|
async function showDealModal(dealId, dealTitle) {
|
||||||
|
if (dealId === null || dealId === "") {
|
||||||
|
showToast('Something went wrong!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the deal modal and show a loading state
|
||||||
|
showModal('deal-modal')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/deals/' + dealId);
|
||||||
|
setDealDetails(response.data);
|
||||||
|
|
||||||
|
const dealModal = document.getElementById('deal-modal');
|
||||||
|
|
||||||
|
const likeBtn = dealModal.querySelector('.likeBtn');
|
||||||
|
likeBtn.addEventListener('click', () => {
|
||||||
|
like(likeBtn, dealId);
|
||||||
|
})
|
||||||
|
|
||||||
|
const favoriteBtn = dealModal.querySelector('.favoriteBtn');
|
||||||
|
favoriteBtn.addEventListener('click', () => {
|
||||||
|
favorite(favoriteBtn, dealId);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const reportBtn = dealModal.querySelector('.reportBtn');
|
||||||
|
reportBtn.addEventListener('click', async () => {
|
||||||
|
showReportModal(dealId, dealTitle)
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
closeModal('deal-modal')
|
||||||
|
showToast('Something went wrong!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDealDetails(dealDetails) {
|
||||||
|
if (dealDetails === null) {
|
||||||
|
throw new Error('DealDetails must be not null');
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Can throw error, handle it else
|
||||||
|
// dealDetails = JSON.parse(dealDetails);
|
||||||
|
console.log(dealDetails);
|
||||||
|
const deal = dealDetails.data
|
||||||
|
const {id, title, description, link, image, category, broker, total_redirection, total_likes} = deal;
|
||||||
|
const dealModal = document.getElementById('deal-modal');
|
||||||
|
|
||||||
|
dealModal.querySelector('.deal-image').src = image;
|
||||||
|
dealModal.querySelector('.deal-title').innerText = title;
|
||||||
|
dealModal.querySelector('.deal-description').innerText = description;
|
||||||
|
dealModal.querySelector('.deal-category').innerText = category.name;
|
||||||
|
dealModal.querySelector('.broker-name').innerText = broker.name;
|
||||||
|
dealModal.querySelector('.broker-email').innerText = broker.email;
|
||||||
|
dealModal.querySelector('.broker-phone').innerText = broker.role.phone;
|
||||||
|
dealModal.querySelector('.likeCount').querySelector('p').innerText = total_likes ?? '0';
|
||||||
|
dealModal.querySelector('.clickCount').querySelector('p').innerText = total_redirection ?? '0';
|
||||||
|
|
||||||
|
const dealLink = dealModal.querySelector('.deal-link');
|
||||||
|
if (link !== null && link !== "") {
|
||||||
|
dealLink.classList.remove('hidden');
|
||||||
|
dealLink.classList.add('flex');
|
||||||
|
dealLink.addEventListener('click', () => {
|
||||||
|
redirect(link);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
dealLink.classList.remove('flex');
|
||||||
|
dealLink.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const dealCards = document.querySelectorAll('.deal-card');
|
||||||
|
if (dealCards) {
|
||||||
|
dealCards.forEach(dealCard => {
|
||||||
|
let dealId = dealCard.dataset.dealId;
|
||||||
|
let dealTitle = dealCard.querySelector('.action-toolbar').dataset.dealTitle;
|
||||||
|
|
||||||
|
dealCard.addEventListener('click', async (e) => {
|
||||||
|
if (e.target.closest('button')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await showDealModal(dealId, dealTitle);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,87 +1,142 @@
|
|||||||
import {showToast} from "./toast.js";
|
import { showToast } from "./toast.js";
|
||||||
|
import { showReportModal } from "@/report-deal.js";
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
ACTION HANDLERS
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
export async function like(button, id) {
|
||||||
|
if (button.dataset.loading) return;
|
||||||
|
button.dataset.loading = 'true';
|
||||||
|
|
||||||
|
const likeBtns = button.querySelectorAll('.like');
|
||||||
|
const likeBadge = document.getElementById(`likeBadge${id}`);
|
||||||
|
|
||||||
async function like(button, id) {
|
|
||||||
// Instant feedback for user
|
|
||||||
let likeBtns = button.querySelectorAll('.like');
|
|
||||||
let likeBadge = document.getElementById("likeBadge".concat(id));
|
|
||||||
toggleHidden(likeBtns);
|
toggleHidden(likeBtns);
|
||||||
|
|
||||||
// Check if user liked the deal
|
const isLiked = button.classList.contains('liked');
|
||||||
let isLiked = button.classList.contains('liked');
|
updateLikeCount(likeBadge, isLiked ? -1 : 1);
|
||||||
updateLikeCount(likeBadge, isLiked ? -1 : 1) ;
|
button.classList.toggle('liked');
|
||||||
button.classList.toggle('liked')
|
|
||||||
try {
|
try {
|
||||||
let response = await axios.post('/like/' + id);
|
const response = await axios.post(`/like/${id}`);
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
showToast(response.data.message)
|
showToast(response.data.message);
|
||||||
// Revert the ui
|
|
||||||
toggleHidden(likeBtns);
|
toggleHidden(likeBtns);
|
||||||
|
button.classList.toggle('liked');
|
||||||
|
updateLikeCount(likeBadge, isLiked ? 1 : -1);
|
||||||
|
} else {
|
||||||
|
showToast(response.data.message);
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
showToast(response.data.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(e.response.data.message)
|
showToast(e?.response?.data?.message || 'Something went wrong');
|
||||||
toggleHidden(likeBtns);
|
toggleHidden(likeBtns);
|
||||||
|
button.classList.toggle('liked');
|
||||||
|
updateLikeCount(likeBadge, isLiked ? 1 : -1);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
delete button.dataset.loading;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function favorite(e, id) {
|
export async function favorite(button, id) {
|
||||||
// Instant feedback for user
|
if (button.dataset.loading) return;
|
||||||
let favoriteBtns = e.querySelectorAll('.favorite');
|
button.dataset.loading = 'true';
|
||||||
|
|
||||||
|
const favoriteBtns = button.querySelectorAll('.favorite');
|
||||||
toggleHidden(favoriteBtns);
|
toggleHidden(favoriteBtns);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response = await axios.post('/favorite/' + id);
|
const response = await axios.post(`/favorite/${id}`);
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
showToast(response.data.message)
|
showToast(response.data.message);
|
||||||
// Revert the ui
|
|
||||||
toggleHidden(favoriteBtns);
|
toggleHidden(favoriteBtns);
|
||||||
}else{
|
} else {
|
||||||
showToast(response.data.message);
|
showToast(response.data.message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(e.response.data.message)
|
showToast(e?.response?.data?.message || 'Something went wrong');
|
||||||
toggleHidden(favoriteBtns);
|
toggleHidden(favoriteBtns);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
delete button.dataset.loading;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleHidden(nodelist) {
|
export function redirect(url, id) {
|
||||||
nodelist.forEach((node) => node.classList.toggle('hidden'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLikeCount(badge, change){
|
|
||||||
try{
|
|
||||||
let likeCount = Math.max(parseInt( badge.dataset.count) + change, 0)
|
|
||||||
badge.querySelector('p').innerText = likeCount;
|
|
||||||
badge.dataset.count = likeCount.toString();
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function redirect(url, id){
|
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
let redirectBadge = document.getElementById("redirectBadge".concat(id));
|
const redirectBadge = document.getElementById(`redirectBadge${id}`);
|
||||||
updateRedirectCount(redirectBadge, 1);
|
updateRedirectCount(redirectBadge, 1);
|
||||||
// increment the count in ui
|
|
||||||
}
|
}
|
||||||
function updateRedirectCount(badge, change){
|
|
||||||
try{
|
/* =========================
|
||||||
let likeCount = Math.max(parseInt( badge.dataset.count) + change, 0)
|
EVENT DELEGATION (🔥 FIX)
|
||||||
badge.querySelector('p').innerText = likeCount;
|
========================= */
|
||||||
badge.dataset.count = likeCount.toString();
|
|
||||||
|
document.addEventListener('click', async (e) => {
|
||||||
|
const button = e.target.closest('button');
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
const dealCard = button.closest('.deal-card');
|
||||||
|
if (!dealCard) return;
|
||||||
|
|
||||||
|
const dealId = dealCard.dataset.dealId;
|
||||||
|
const dealTitle =
|
||||||
|
dealCard.querySelector('.action-toolbar')?.dataset.dealTitle;
|
||||||
|
|
||||||
|
if (button.classList.contains('likeBtn')) {
|
||||||
|
await like(button, dealId);
|
||||||
}
|
}
|
||||||
catch(e) {
|
|
||||||
|
if (button.classList.contains('favoriteBtn')) {
|
||||||
|
await favorite(button, dealId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button.classList.contains('reportBtn')) {
|
||||||
|
showReportModal(dealId, dealTitle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
HELPERS
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
function toggleHidden(nodes) {
|
||||||
|
nodes.forEach(node => node.classList.toggle('hidden'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLikeCount(badge, change) {
|
||||||
|
if (!badge) return;
|
||||||
|
try {
|
||||||
|
const count = Math.max(
|
||||||
|
parseInt(badge.dataset.count || '0') + change,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
badge.querySelector('p').innerText = count;
|
||||||
|
badge.dataset.count = count.toString();
|
||||||
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.like = like;
|
|
||||||
document.favorite = favorite;
|
function updateRedirectCount(badge, change) {
|
||||||
document.redirect = redirect;
|
if (!badge) return;
|
||||||
|
try {
|
||||||
|
const count = Math.max(
|
||||||
|
parseInt(badge.dataset.count || '0') + change,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
badge.querySelector('p').innerText = count;
|
||||||
|
badge.dataset.count = count.toString();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
GLOBAL (if needed)
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
window.redirect = redirect;
|
||||||
|
|||||||
@ -3,14 +3,18 @@ const closeBtn = document.getElementById('closeBtn');
|
|||||||
const mobileMenu = document.getElementById('mobileMenu');
|
const mobileMenu = document.getElementById('mobileMenu');
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
|
|
||||||
openBtn.addEventListener('click', () => {
|
if (openBtn) {
|
||||||
mobileMenu.classList.remove('translate-x-full');
|
openBtn.addEventListener('click', () => {
|
||||||
mobileMenu.classList.add('translate-x-0');
|
mobileMenu.classList.remove('translate-x-full');
|
||||||
body.style.overflow = 'hidden';
|
mobileMenu.classList.add('translate-x-0');
|
||||||
})
|
body.style.overflow = 'hidden';
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
closeBtn.addEventListener('click', () => {
|
if (closeBtn) {
|
||||||
mobileMenu.classList.add('translate-x-full');
|
closeBtn.addEventListener('click', () => {
|
||||||
mobileMenu.classList.remove('translate-x-0');
|
mobileMenu.classList.add('translate-x-full');
|
||||||
body.style.overflow = 'visible';
|
mobileMenu.classList.remove('translate-x-0');
|
||||||
})
|
body.style.overflow = 'visible';
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import {closeModal, showModal} from './modal.js';
|
import {closeModal, showModal} from './modal.js';
|
||||||
import {showToast} from './toast.js';
|
import {showToast} from './toast.js';
|
||||||
|
|
||||||
const reportModal = document.getElementById('report-modal');
|
const reportModal = document.getElementById('report-modal');
|
||||||
const reportForm = document.getElementById('report-form');
|
const reportForm = document.getElementById('report-form');
|
||||||
|
|
||||||
|
|
||||||
function showReportModal(dealId, dealTitle) {
|
export function showReportModal(dealId, dealTitle) {
|
||||||
// Clear the fields
|
// Clear the fields
|
||||||
reportForm.reset();
|
reportForm.reset();
|
||||||
const oldErrors = reportForm.querySelectorAll('.text-red-500');
|
const oldErrors = reportForm.querySelectorAll('.text-red-500');
|
||||||
@ -18,43 +19,44 @@ function showReportModal(dealId, dealTitle) {
|
|||||||
showModal('report-modal');
|
showModal('report-modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
reportForm.addEventListener('submit', async function (form) {
|
if (reportForm) {
|
||||||
form.preventDefault();
|
|
||||||
const formData = new FormData(this);
|
|
||||||
const dealId = reportModal.dataset.dealId
|
|
||||||
try {
|
|
||||||
|
|
||||||
let response = await axios.post(
|
reportForm.addEventListener('submit', async function (form) {
|
||||||
`report/${dealId}`,
|
form.preventDefault();
|
||||||
formData
|
const formData = new FormData(this);
|
||||||
);
|
const dealId = reportModal.dataset.dealId
|
||||||
|
try {
|
||||||
|
|
||||||
showToast('Report submitted. Thank you for keeping DealHub safe!');
|
let response = await axios.post(
|
||||||
closeModal('report-modal')
|
`report/${dealId}`,
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
} catch (error) {
|
showToast('Report submitted. Thank you for keeping DealHub safe!');
|
||||||
|
closeModal('report-modal')
|
||||||
|
|
||||||
if (error.response.status === 405) {
|
} catch (error) {
|
||||||
closeModal('report-modal');
|
|
||||||
showToast('You already have reported this deal !');
|
if (error.response.status === 405) {
|
||||||
return;
|
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);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// 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;
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
const toast = document.querySelector('.toast');
|
const toast = document.querySelector('.toast');
|
||||||
const toastBtn = document.querySelector('#toast-btn');
|
const toastBtn = document.querySelector('#toast-btn');
|
||||||
let toastMessage = toast.querySelector('#toast-message');
|
|
||||||
export function showToast(message) {
|
export function showToast(message) {
|
||||||
|
let toastMessage = toast.querySelector('#toast-message');
|
||||||
toast.classList.remove('translate-x-[100vw]');
|
toast.classList.remove('translate-x-[100vw]');
|
||||||
toast.classList.add('translate-x-0');
|
toast.classList.add('translate-x-0');
|
||||||
toastMessage.textContent = message;
|
toastMessage.textContent = message;
|
||||||
@ -9,9 +10,11 @@ export function showToast(message) {
|
|||||||
hideToast();
|
hideToast();
|
||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideToast() {
|
function hideToast() {
|
||||||
toast.classList.remove('translate-x-0');
|
toast.classList.remove('translate-x-0');
|
||||||
toast.classList.add('translate-x-[100vw]');
|
toast.classList.add('translate-x-[100vw]');
|
||||||
}
|
}
|
||||||
|
|
||||||
document.hideToast = hideToast;
|
document.hideToast = hideToast;
|
||||||
document.showToast = showToast;
|
document.showToast = showToast;
|
||||||
|
|||||||
@ -23,5 +23,4 @@ class=" flex flex-col space-y-4 md:space-y-8 bg-[#F9FAFB] overflow-y-auto overfl
|
|||||||
{{$slot}}
|
{{$slot}}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@vite('resources/js/nav-menu.js')
|
|
||||||
</x-layout>
|
</x-layout>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
@props(['deal_id', 'deal_title', 'like' => false, 'favourite' => false])
|
@props(['deal_id' => '', 'deal_title' => '', 'like' => false, 'favourite' => false])
|
||||||
<div class="">
|
<div class="action-toolbar" data-deal-title="{{$deal_title}}">
|
||||||
<x-ui.button-sm @class(["text-accent-600", 'liked' => $like]) onclick="like(this, {{$deal_id}})">
|
<x-ui.button-sm @class(["text-accent-600 likeBtn", 'liked' => $like])>
|
||||||
<x-heroicon-o-heart
|
<x-heroicon-o-heart
|
||||||
@class([
|
@class([
|
||||||
"like w-4",
|
"like w-4",
|
||||||
@ -9,13 +9,13 @@
|
|||||||
/>
|
/>
|
||||||
<x-heroicon-s-heart
|
<x-heroicon-s-heart
|
||||||
@class([
|
@class([
|
||||||
"like w-4 text-red-500",
|
"like active w-4 text-red-500",
|
||||||
'hidden' => !$like
|
'hidden' => !$like
|
||||||
])
|
])
|
||||||
/>
|
/>
|
||||||
</x-ui.button-sm>
|
</x-ui.button-sm>
|
||||||
|
|
||||||
<x-ui.button-sm class="text-accent-600" onclick="favorite(this, {{$deal_id}})">
|
<x-ui.button-sm class="text-accent-600 favoriteBtn">
|
||||||
<x-heroicon-o-star
|
<x-heroicon-o-star
|
||||||
@class([
|
@class([
|
||||||
"favorite w-4",
|
"favorite w-4",
|
||||||
@ -25,14 +25,14 @@
|
|||||||
|
|
||||||
<x-heroicon-s-star
|
<x-heroicon-s-star
|
||||||
@class([
|
@class([
|
||||||
"favorite w-4 text-yellow-500",
|
"favorite active w-4 text-yellow-500",
|
||||||
'hidden' => !$favourite
|
'hidden' => !$favourite
|
||||||
])
|
])
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</x-ui.button-sm>
|
</x-ui.button-sm>
|
||||||
|
|
||||||
<x-ui.button-sm class="text-accent-600" onclick="showReportModal({{$deal_id}}, '{{$deal_title}}')">
|
<x-ui.button-sm class="text-accent-600 reportBtn">
|
||||||
<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>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
@props(['broker'])
|
@props(['broker' => ''])
|
||||||
<div class="p-4 mb-4 text-sm bg-[#f9fafb] border-gray-100 border rounded-xl">
|
<div class="p-4 text-sm bg-gray-100 border-gray-200 border rounded-xl">
|
||||||
<p class="font-bold mb-2">Broker Contact</p>
|
<p class="font-bold mb-2">Broker Contact</p>
|
||||||
<div class="text-accent-600 space-y-1">
|
<div class="text-accent-600 space-y-1">
|
||||||
<p>{{$broker->name}}</p>
|
<p class="broker-name">{{$broker->name ?? ''}}</p>
|
||||||
<p>{{$broker->email}}</p>
|
<p class="broker-email">{{$broker->email ?? ''}}</p>
|
||||||
<p>{{$broker->role->phone}}</p>
|
<p class="broker-phone">{{$broker->role->phone ?? ''}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
<x-ui.modal id="deal-modal" class="w-11/12 md:w-10/12">
|
||||||
|
<form class="flex justify-between items-start mb-4" method="dialog">
|
||||||
|
<p class="text-xl font-bold">Deal Details</p>
|
||||||
|
<button type="submit" class="">
|
||||||
|
<x-heroicon-o-x-mark class="w-4"/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div class="grid md:grid-cols-12 gap-4 items-stretch">
|
||||||
|
<div class="md:col-span-8 h-0 min-h-full">
|
||||||
|
<div class="rounded-lg bg-gray-200 h-full">
|
||||||
|
<img src="" alt="Image of the deal"
|
||||||
|
class="deal-image h-full w-full object-cover rounded-lg border-none">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-4 flex flex-col gap-y-4">
|
||||||
|
<x-ui.button-sm class="w-fit deal-category" variant="neutral"/>
|
||||||
|
<p class="deal-title font-bold text-lg "></p>
|
||||||
|
<p class="deal-description text-sm text-accent-600 wrap-break-word"></p>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<x-dashboard.user.action-toolbar/>
|
||||||
|
<x-dashboard.user.deal-stats />
|
||||||
|
</div>
|
||||||
|
<x-ui.button variant="neutral" class="hidden deal-link space-x-2 items-center justify-center">
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<p>View Deal</p>
|
||||||
|
<x-heroicon-o-arrow-top-right-on-square class="w-5 ml-1"/>
|
||||||
|
</div>
|
||||||
|
</x-ui.button>
|
||||||
|
<x-dashboard.user.broker-contact/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</x-ui.modal>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
@props(['likeCount' => 0, 'clickCount' => 0])
|
||||||
|
<div class="flex space-x-3">
|
||||||
|
<x-dashboard.user.stat-badge class="likeCount" :count="$likeCount">
|
||||||
|
<x-heroicon-o-heart class="w-4"/>
|
||||||
|
</x-dashboard.user.stat-badge>
|
||||||
|
|
||||||
|
<x-dashboard.user.stat-badge class="clickCount" :count="$clickCount">
|
||||||
|
<x-heroicon-o-arrow-top-right-on-square class="w-4"/>
|
||||||
|
</x-dashboard.user.stat-badge>
|
||||||
|
</div>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
@props(['deal' => '', 'broker' => ''])
|
@props(['deal' => '', 'broker' => ''])
|
||||||
<x-ui.image-card class="shadow-lg" :image="asset('storage/'.$deal->image)">
|
<x-ui.image-card class="deal-card shadow-lg cursor-pointer" :image="asset('storage/'.$deal->image)" data-deal-id="{{$deal->id}}">
|
||||||
<div class="bg-white pt-8 p-4 h-full space-y-2 flex flex-col justify-between">
|
<div class="bg-white pt-8 p-4 h-full space-y-2 flex flex-col justify-between">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<x-ui.button-sm variant="neutral">
|
<x-ui.button-sm variant="neutral">
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</x-dashboard.user.stat-badge>
|
</x-dashboard.user.stat-badge>
|
||||||
</div>
|
</div>
|
||||||
@if(filled($deal->link))
|
@if(filled($deal->link))
|
||||||
<x-ui.button onclick="redirect('{{\Illuminate\Support\Facades\URL::signedRoute('redirect', $deal->id)}}', {{$deal->id}})" variant="neutral" class="flex space-x-2 items-center">
|
<x-ui.button onclick="redirect('{{\Illuminate\Support\Facades\URL::signedRoute('redirect', $deal->id)}}', {{$deal->id}})" variant="neutral" class="flex space-x-2 items-center mt-2">
|
||||||
<p>View Deal</p>
|
<p>View Deal</p>
|
||||||
<x-heroicon-o-arrow-top-right-on-square class="w-5 ml-1"/>
|
<x-heroicon-o-arrow-top-right-on-square class="w-5 ml-1"/>
|
||||||
</x-ui.button>
|
</x-ui.button>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<dialog
|
<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"])}} >
|
{{$attributes->merge(["class"=>"fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-2 md:p-4 shadow-lg"])}} >
|
||||||
<div>
|
<div>
|
||||||
{{$slot}}
|
{{$slot}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<x-dashboard.user.listing :deals="$deals"/>
|
<x-dashboard.user.listing :deals="$deals"/>
|
||||||
<x-dashboard.user.report-modal/>
|
<x-dashboard.user.report-modal/>
|
||||||
|
<x-dashboard.user.deal-modal />
|
||||||
</section>
|
</section>
|
||||||
<x-ui.toast/>
|
<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>
|
||||||
|
|||||||
@ -5,5 +5,4 @@
|
|||||||
<x-how-it-works />
|
<x-how-it-works />
|
||||||
<x-get-in-touch />
|
<x-get-in-touch />
|
||||||
<x-footer />
|
<x-footer />
|
||||||
@vite('resources/js/nav-menu.js')
|
|
||||||
</x-layout>
|
</x-layout>
|
||||||
|
|||||||
19
routes/api/deal.php
Normal file
19
routes/api/deal.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Resources\DealResource;
|
||||||
|
use App\Models\Deal;
|
||||||
|
use App\Queries\ExplorePageDealsQuery;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::prefix('/api')
|
||||||
|
->middleware('auth')
|
||||||
|
->group(function () {
|
||||||
|
Route::get('/deals/{deal}', function (Deal $deal) {
|
||||||
|
return new DealResource(
|
||||||
|
(new ExplorePageDealsQuery)
|
||||||
|
->builder()
|
||||||
|
->where('id', $deal->id)
|
||||||
|
->first()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -19,3 +19,10 @@
|
|||||||
->middleware(HasRole::class.':'.UserTypes::Admin->value)
|
->middleware(HasRole::class.':'.UserTypes::Admin->value)
|
||||||
->name('admin.dashboard');
|
->name('admin.dashboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This routes are accessed by JS XHR requests, and is loaded here cause
|
||||||
|
* we do not want to use sanctum for web requests
|
||||||
|
*/
|
||||||
|
// ------------- API Routes ------------
|
||||||
|
require __DIR__.'/api/deal.php';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user