feature(deal-modal): add interaction only in card
- show current user liked and favorite status in the modal
This commit is contained in:
parent
6b52aed66a
commit
9e61dd9f51
@ -23,9 +23,10 @@ public function toArray(Request $request): array
|
||||
'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,
|
||||
|
||||
'totalLikes' => $this->total_likes,
|
||||
'totalRedirection' => $this->total_redirection,
|
||||
'isLiked' => $this->is_liked,
|
||||
'isFavorite' => $this->is_favorite,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "dealhub",
|
||||
"name": "DealHub",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@ -2031,7 +2031,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -2268,7 +2267,6 @@
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import './bootstrap';
|
||||
import {setSidebarState} from '@/sidebar.js';
|
||||
import "./interaction.js"
|
||||
import "./report-deal.js"
|
||||
import "./alert.js"
|
||||
import "./image-input.js"
|
||||
import "./menu.js"
|
||||
@ -9,6 +7,13 @@ import "./modal.js"
|
||||
import "./sidebar.js"
|
||||
import "./toast.js"
|
||||
import "./deal-view-modal.js"
|
||||
import {favorite, like, redirect} from "./interaction.js";
|
||||
import {showReportModal} from "./report-deal.js";
|
||||
|
||||
document.like = like;
|
||||
document.favorite = favorite;
|
||||
document.redirect = redirect;
|
||||
document.showReportModal = showReportModal;
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const preloader = document.getElementById('preloader');
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import {showToast} from "@/toast.js";
|
||||
import {closeModal, showModal} from "@/modal.js";
|
||||
import {favorite, like, redirect} from "@/interaction.js";
|
||||
import {showReportModal} from "@/report-deal.js";
|
||||
import {redirect} from "./interaction.js";
|
||||
|
||||
async function showDealModal(dealId, dealTitle) {
|
||||
if (dealId === null || dealId === "") {
|
||||
@ -18,21 +17,8 @@ async function showDealModal(dealId, dealTitle) {
|
||||
|
||||
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)
|
||||
})
|
||||
// Add the id so that action buttons can identify the route
|
||||
dealModal.dataset.dealId = dealId;
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@ -46,11 +32,13 @@ function setDealDetails(dealDetails) {
|
||||
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 {
|
||||
id, title, description, link,
|
||||
image, category, broker,
|
||||
totalRedirection, totalLikes,
|
||||
isLiked, isFavorite
|
||||
} = deal;
|
||||
const dealModal = document.getElementById('deal-modal');
|
||||
|
||||
dealModal.querySelector('.deal-image').src = image;
|
||||
@ -60,9 +48,31 @@ function setDealDetails(dealDetails) {
|
||||
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';
|
||||
|
||||
// Set the like and click counts
|
||||
let likeCountElm = dealModal.querySelector('.likeCount');
|
||||
likeCountElm.querySelector('p').innerText = totalLikes ?? '0';
|
||||
likeCountElm.dataset.count = totalLikes ?? '0';
|
||||
dealModal.querySelector('.clickCount').querySelector('p').innerText = totalRedirection ?? '0';
|
||||
|
||||
// Set if current user has already liked the deal
|
||||
let likeBtn = dealModal.querySelector('.likeBtn');
|
||||
let likeBtnSvg = likeBtn.querySelector('svg');
|
||||
if (isLiked) {
|
||||
likeBtn.dataset.liked = 'true'
|
||||
likeBtnSvg.classList.add('text-red-500', 'fill-current');
|
||||
} else {
|
||||
likeBtn.dataset.liked = 'false'
|
||||
likeBtnSvg.classList.remove('text-red-500', 'fill-current');
|
||||
}
|
||||
|
||||
// Set if current user has already favorite the deal
|
||||
let favoriteBtnSvg = dealModal.querySelector('.favoriteBtn').querySelector('svg');
|
||||
if (isFavorite) {
|
||||
favoriteBtnSvg.classList.add('text-yellow-500', 'fill-current');
|
||||
} else {
|
||||
favoriteBtnSvg.classList.remove('text-yellow-500', 'fill-current');
|
||||
}
|
||||
const dealLink = dealModal.querySelector('.deal-link');
|
||||
if (link !== null && link !== "") {
|
||||
dealLink.classList.remove('hidden');
|
||||
|
||||
@ -1,142 +1,90 @@
|
||||
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}`);
|
||||
|
||||
toggleHidden(likeBtns);
|
||||
|
||||
const isLiked = button.classList.contains('liked');
|
||||
updateLikeCount(likeBadge, isLiked ? -1 : 1);
|
||||
button.classList.toggle('liked');
|
||||
import {showToast} from "./toast.js";
|
||||
|
||||
export async function like(button) {
|
||||
const activeClasses = ['fill-current', 'text-red-500']
|
||||
let isLiked = button.dataset.liked === 'true';
|
||||
try {
|
||||
const response = await axios.post(`/like/${id}`);
|
||||
// Update the like state and count
|
||||
let id = button.closest('.deal-identifier').dataset.dealId;
|
||||
const cardLikeBadge = document.getElementById(`likeBadge${id}`);
|
||||
|
||||
setLikeState(button, !isLiked);
|
||||
updateLikeCount(cardLikeBadge, isLiked ? -1 : 1);
|
||||
|
||||
// Update the state of like button that is clicked
|
||||
toggleState(button, !isLiked, activeClasses);
|
||||
|
||||
let response = await axios.post('/like/' + id);
|
||||
showToast(response.data.message)
|
||||
|
||||
if (response.status !== 200) {
|
||||
showToast(response.data.message);
|
||||
toggleHidden(likeBtns);
|
||||
button.classList.toggle('liked');
|
||||
updateLikeCount(likeBadge, isLiked ? 1 : -1);
|
||||
} else {
|
||||
showToast(response.data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(e?.response?.data?.message || 'Something went wrong');
|
||||
toggleHidden(likeBtns);
|
||||
button.classList.toggle('liked');
|
||||
updateLikeCount(likeBadge, isLiked ? 1 : -1);
|
||||
showToast('Something went wrong!')
|
||||
// Revert the states
|
||||
toggleState(button, !isLiked, activeClasses);
|
||||
console.error(e);
|
||||
} finally {
|
||||
delete button.dataset.loading;
|
||||
}
|
||||
}
|
||||
|
||||
export async function favorite(button, id) {
|
||||
if (button.dataset.loading) return;
|
||||
button.dataset.loading = 'true';
|
||||
|
||||
const favoriteBtns = button.querySelectorAll('.favorite');
|
||||
toggleHidden(favoriteBtns);
|
||||
|
||||
export async function favorite(button) {
|
||||
const activeClasses = ['fill-current', 'text-yellow-500']
|
||||
let isFavorite = button.dataset.favorite === 'true';
|
||||
try {
|
||||
const response = await axios.post(`/favorite/${id}`);
|
||||
let id = button.closest('.deal-identifier').dataset.dealId;
|
||||
|
||||
setFavoriteState(button, !isFavorite);
|
||||
toggleState(button, !isFavorite, activeClasses);
|
||||
|
||||
let response = await axios.post('/favorite/' + id);
|
||||
showToast(response.data.message)
|
||||
|
||||
if (response.status !== 200) {
|
||||
showToast(response.data.message);
|
||||
toggleHidden(favoriteBtns);
|
||||
} else {
|
||||
showToast(response.data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(e?.response?.data?.message || 'Something went wrong');
|
||||
toggleHidden(favoriteBtns);
|
||||
showToast(e.response.data.message)
|
||||
toggleState(button, isFavorite, activeClasses);
|
||||
console.error(e);
|
||||
} finally {
|
||||
delete button.dataset.loading;
|
||||
}
|
||||
}
|
||||
|
||||
export function redirect(url, id) {
|
||||
window.open(url, '_blank');
|
||||
const redirectBadge = document.getElementById(`redirectBadge${id}`);
|
||||
let redirectBadge = document.getElementById("redirectBadge".concat(id));
|
||||
updateRedirectCount(redirectBadge, 1);
|
||||
// increment the count in ui
|
||||
}
|
||||
|
||||
/* =========================
|
||||
EVENT DELEGATION (🔥 FIX)
|
||||
========================= */
|
||||
export function setLikeState(button, isLiked) {
|
||||
button.dataset.liked = isLiked ? 'true' : 'false';
|
||||
}
|
||||
|
||||
document.addEventListener('click', async (e) => {
|
||||
const button = e.target.closest('button');
|
||||
if (!button) return;
|
||||
export function setFavoriteState(button, isFavorite) {
|
||||
button.dataset.favorite = isFavorite ? 'true' : 'false';
|
||||
}
|
||||
|
||||
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);
|
||||
function toggleState(element, condition, activeClasses) {
|
||||
const svg = element.querySelector('svg');
|
||||
if (condition) {
|
||||
svg.classList.add(...activeClasses);
|
||||
} else {
|
||||
svg.classList.remove(...activeClasses);
|
||||
}
|
||||
|
||||
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();
|
||||
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 updateRedirectCount(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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
GLOBAL (if needed)
|
||||
========================= */
|
||||
|
||||
window.redirect = redirect;
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
@props(['deal_id' => '', 'deal_title' => '', 'like' => false, 'favourite' => false])
|
||||
<div class="action-toolbar" data-deal-title="{{$deal_title}}">
|
||||
<x-ui.button-sm @class(["text-accent-600 likeBtn", 'liked' => $like])>
|
||||
<x-heroicon-o-heart
|
||||
@class([
|
||||
"like w-4",
|
||||
'hidden' => $like
|
||||
])
|
||||
/>
|
||||
<x-heroicon-s-heart
|
||||
@class([
|
||||
"like active w-4 text-red-500",
|
||||
'hidden' => !$like
|
||||
])
|
||||
/>
|
||||
</x-ui.button-sm>
|
||||
|
||||
<x-ui.button-sm class="text-accent-600 favoriteBtn">
|
||||
<x-heroicon-o-star
|
||||
@class([
|
||||
"favorite w-4",
|
||||
'hidden' => $favourite
|
||||
])
|
||||
/>
|
||||
|
||||
<x-heroicon-s-star
|
||||
@class([
|
||||
"favorite active w-4 text-yellow-500",
|
||||
'hidden' => !$favourite
|
||||
])
|
||||
/>
|
||||
|
||||
</x-ui.button-sm>
|
||||
|
||||
<x-ui.button-sm class="text-accent-600 reportBtn">
|
||||
<x-heroicon-o-exclamation-circle class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
</div>
|
||||
@ -0,0 +1,20 @@
|
||||
@props(['isFavourite' => false, 'isInteractive'=> true])
|
||||
@php
|
||||
$actionAttributes = $isInteractive ? [
|
||||
'data-favorite' => $isFavourite ? 'true' : 'false',
|
||||
'onclick' => "favorite(this)"
|
||||
] : [] ;
|
||||
$buttonClasses = [
|
||||
'text-accent-600 favoriteBtn',
|
||||
];
|
||||
@endphp
|
||||
<x-ui.button-sm
|
||||
{{$attributes->merge($actionAttributes)->class($buttonClasses)}}
|
||||
>
|
||||
<x-heroicon-o-star
|
||||
@class([
|
||||
"favorite w-4",
|
||||
'fill-current text-yellow-500' => $isFavourite
|
||||
])
|
||||
/>
|
||||
</x-ui.button-sm>
|
||||
@ -0,0 +1,6 @@
|
||||
@props(['deal_id' => '', 'deal_title' => '', 'isLiked' => false, 'isFavourite' => false, 'isInteractive' => true])
|
||||
<div class="action-toolbar" data-deal-title="{{$deal_title}}">
|
||||
<x-dashboard.user.action-toolbar.like-button :is-liked="$isLiked" :is-interactive="$isInteractive" />
|
||||
<x-dashboard.user.action-toolbar.favorite-button :is-favourite="$isFavourite" :is-interactive="$isInteractive" />
|
||||
<x-dashboard.user.action-toolbar.report-button :id="$deal_id" :title="$deal_title" />
|
||||
</div>
|
||||
@ -0,0 +1,21 @@
|
||||
@props(['isLiked' => false, 'isInteractive' => true])
|
||||
@php
|
||||
$actionAttributes = $isInteractive ? [
|
||||
'data-liked' => $isLiked ? 'true' : 'false',
|
||||
'onclick' => "like(this)"
|
||||
] : [] ;
|
||||
$buttonClasses = [
|
||||
'text-accent-600 likeBtn',
|
||||
'liked' => $isLiked
|
||||
];
|
||||
@endphp
|
||||
<x-ui.button-sm
|
||||
{{$attributes->merge($actionAttributes)->class($buttonClasses)}}
|
||||
>
|
||||
<x-heroicon-o-heart
|
||||
@class([
|
||||
"like w-4",
|
||||
'fill-current text-red-500' => $isLiked
|
||||
])
|
||||
/>
|
||||
</x-ui.button-sm>
|
||||
@ -0,0 +1,4 @@
|
||||
@props(['id', 'title'])
|
||||
<x-ui.button-sm class="text-accent-600 reportBtn" onclick="showReportModal({{$id}}, '{{$title}}')">
|
||||
<x-heroicon-o-exclamation-circle class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
@ -1,4 +1,4 @@
|
||||
<x-ui.modal id="deal-modal" class="w-11/12 md:w-10/12">
|
||||
<x-ui.modal id="deal-modal" class="deal-identifier 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="">
|
||||
@ -17,8 +17,11 @@ class="deal-image h-full w-full object-cover rounded-lg border-none">
|
||||
<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-dashboard.user.action-toolbar.like-button :is-interactive="false"/>
|
||||
<x-dashboard.user.action-toolbar.favorite-button :is-interactive="false"/>
|
||||
</div>
|
||||
<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">
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
@props(['deal' => '', 'broker' => ''])
|
||||
<x-ui.image-card class="deal-card shadow-lg cursor-pointer" :image="asset('storage/'.$deal->image)" data-deal-id="{{$deal->id}}">
|
||||
<x-ui.image-card class="deal-identifier 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="flex justify-between">
|
||||
<x-ui.button-sm variant="neutral">
|
||||
{{$deal->category->name}}
|
||||
</x-ui.button-sm>
|
||||
<x-dashboard.user.action-toolbar :deal_title="$deal->title" :deal_id="$deal->id" :like="$deal->is_liked" :favourite="$deal->is_favorite" />
|
||||
<x-dashboard.user.action-toolbar :deal_title="$deal->title" :deal_id="$deal->id" :is-liked="$deal->is_liked" :is-favourite="$deal->is_favorite" />
|
||||
</div>
|
||||
|
||||
<p class="font-bold text-lg ">{{$deal->title}}</p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user