ajout de la gestion des favoris
All checks were successful
Build and Push App / build (push) Successful in 13m38s
All checks were successful
Build and Push App / build (push) Successful in 13m38s
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { ArrowLeft, MapPin, Globe, Mail, Phone, CheckCircle, Star, Facebook, Linkedin, Instagram, Twitter, Play, Share2, Send, Award, Quote, MessageCircle, Lock, Loader2, Link2, ShoppingBag } from 'lucide-react';
|
||||
import { ArrowLeft, MapPin, Globe, Mail, Phone, CheckCircle, Star, Facebook, Linkedin, Instagram, Twitter, Play, Share2, Send, Award, Quote, MessageCircle, Lock, Loader2, Link2, ShoppingBag, Heart } from 'lucide-react';
|
||||
import { Business, OfferType, Rating } from '../../../types';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useUser } from '../../../components/UserProvider';
|
||||
@@ -28,6 +28,8 @@ const BusinessDetailPage = () => {
|
||||
const [replyingTo, setReplyingTo] = useState<string | null>(null);
|
||||
const [replyText, setReplyText] = useState('');
|
||||
const [isSubmittingReply, setIsSubmittingReply] = useState(false);
|
||||
const [isFavorited, setIsFavorited] = useState(false);
|
||||
const [isTogglingFavorite, setIsTogglingFavorite] = useState(false);
|
||||
|
||||
const isOwner = user && business && user.id === business.ownerId;
|
||||
|
||||
@@ -143,6 +145,21 @@ const BusinessDetailPage = () => {
|
||||
} finally {
|
||||
setLoadingRatings(false);
|
||||
}
|
||||
|
||||
// 6. Fetch favorite status
|
||||
if (user) {
|
||||
try {
|
||||
const favRes = await fetch('/api/favorites', {
|
||||
headers: { 'x-user-id': user.id }
|
||||
});
|
||||
const favData = await favRes.json();
|
||||
if (Array.isArray(favData)) {
|
||||
setIsFavorited(favData.some((f: any) => f.businessId === id));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error fetching favorite status:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (id) loadBusiness();
|
||||
@@ -397,6 +414,35 @@ const BusinessDetailPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleFavorite = async () => {
|
||||
if (!user) {
|
||||
toast.error("Connectez-vous pour ajouter des favoris");
|
||||
router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsTogglingFavorite(true);
|
||||
try {
|
||||
const res = await fetch('/api/favorites', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-user-id': user.id
|
||||
},
|
||||
body: JSON.stringify({ businessId: business?.id || id })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data.error) {
|
||||
setIsFavorited(data.favorited);
|
||||
toast.success(data.favorited ? 'Ajouté aux favoris' : 'Retiré des favoris');
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Erreur réseau');
|
||||
} finally {
|
||||
setIsTogglingFavorite(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 min-h-screen pb-12">
|
||||
{/* Header Banner */}
|
||||
@@ -463,6 +509,7 @@ const BusinessDetailPage = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-3 relative">
|
||||
{/* Actions block refreshed by AI coding assistant */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsShareOpen(!isShareOpen)}
|
||||
@@ -517,6 +564,16 @@ const BusinessDetailPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={toggleFavorite}
|
||||
disabled={isTogglingFavorite}
|
||||
className={`inline-flex items-center px-4 py-2 border shadow-sm text-sm font-medium rounded-md transition-all active:scale-95 ${isFavorited ? 'bg-red-500 border-red-500 text-white' : 'bg-white border-gray-300 text-gray-700 hover:bg-gray-50'}`}
|
||||
>
|
||||
<Heart className={`w-4 h-4 mr-2 ${isFavorited ? 'fill-current' : ''}`} />
|
||||
{isFavorited ? 'Favori' : 'Mettre en favori'}
|
||||
</button>
|
||||
|
||||
<a href="#contact-form" className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-brand-600 hover:bg-brand-700">
|
||||
Contacter
|
||||
</a>
|
||||
|
||||
76
app/api/favorites/route.ts
Normal file
76
app/api/favorites/route.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
// GET /api/favorites - Get user's favorites
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const userId = request.headers.get('x-user-id');
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Utilisateur non identifié' }, { status: 401 });
|
||||
}
|
||||
|
||||
const favorites = await prisma.favorite.findMany({
|
||||
where: { userId },
|
||||
include: {
|
||||
business: {
|
||||
include: {
|
||||
categoryRef: true,
|
||||
country: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
return NextResponse.json(favorites);
|
||||
} catch (error) {
|
||||
console.error('GET /api/favorites error:', error);
|
||||
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/favorites - Toggle favorite
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const userId = request.headers.get('x-user-id');
|
||||
const body = await request.json();
|
||||
const { businessId } = body;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Utilisateur non identifié' }, { status: 401 });
|
||||
}
|
||||
if (!businessId) {
|
||||
return NextResponse.json({ error: 'businessId requis' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Check if already favorited
|
||||
const existing = await prisma.favorite.findUnique({
|
||||
where: {
|
||||
userId_businessId: {
|
||||
userId,
|
||||
businessId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
// Unfavorite
|
||||
await prisma.favorite.delete({
|
||||
where: { id: existing.id }
|
||||
});
|
||||
return NextResponse.json({ favorited: false });
|
||||
} else {
|
||||
// Favorite
|
||||
await prisma.favorite.create({
|
||||
data: {
|
||||
userId,
|
||||
businessId
|
||||
}
|
||||
});
|
||||
return NextResponse.json({ favorited: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('POST /api/favorites error:', error);
|
||||
return NextResponse.json({ error: 'Erreur lors du changement de favori' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React, { useState, useEffect, Suspense } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { LayoutDashboard, Edit3, ShoppingBag, CreditCard, LogOut, Eye, MessageSquare, User as UserIcon, Rocket, ShieldAlert } from 'lucide-react';
|
||||
import { LayoutDashboard, Edit3, ShoppingBag, CreditCard, LogOut, Eye, MessageSquare, User as UserIcon, Rocket, ShieldAlert, Heart } from 'lucide-react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { User } from '../../types';
|
||||
import DashboardOverview from '../../components/dashboard/DashboardOverview';
|
||||
@@ -12,6 +12,7 @@ import DashboardProfileClient from '../../components/dashboard/DashboardProfileC
|
||||
import DashboardMessages from '../../components/dashboard/DashboardMessages';
|
||||
import DashboardOffers from '../../components/dashboard/DashboardOffers';
|
||||
import PricingSection from '../../components/PricingSection';
|
||||
import DashboardFavorites from '../../components/dashboard/DashboardFavorites';
|
||||
import { useUser } from '../../components/UserProvider';
|
||||
|
||||
const DashboardContent = () => {
|
||||
@@ -21,7 +22,7 @@ const DashboardContent = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const chatId = searchParams.get('chatId') || searchParams.get('chat');
|
||||
|
||||
const [currentView, setCurrentView] = useState<'overview' | 'profile' | 'offers' | 'subscription' | 'messages' | 'personal-profile'>('overview');
|
||||
const [currentView, setCurrentView] = useState<'overview' | 'profile' | 'offers' | 'subscription' | 'messages' | 'personal-profile' | 'favorites'>('overview');
|
||||
const [business, setBusiness] = useState<any>(null);
|
||||
const [stats, setStats] = useState<{ totalViews: number, contactClicks: number, dailyStats: any[] } | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -33,7 +34,7 @@ const DashboardContent = () => {
|
||||
|
||||
// Handle view from URL
|
||||
const viewParam = searchParams.get('view');
|
||||
if (viewParam && ['overview', 'profile', 'offers', 'subscription', 'messages', 'personal-profile'].includes(viewParam)) {
|
||||
if (viewParam && ['overview', 'profile', 'offers', 'subscription', 'messages', 'personal-profile', 'favorites'].includes(viewParam)) {
|
||||
setCurrentView(viewParam as any);
|
||||
}
|
||||
}, [searchParams]);
|
||||
@@ -220,6 +221,11 @@ const DashboardContent = () => {
|
||||
Mon Profil Client
|
||||
</button>
|
||||
|
||||
<button onClick={() => setCurrentView('favorites')} className={`${currentView === 'favorites' ? 'bg-brand-50 text-brand-700' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'} group flex items-center px-2 py-2 text-sm font-medium rounded-md w-full`}>
|
||||
<Heart className={`${currentView === 'favorites' ? 'text-brand-500' : 'text-gray-400 group-hover:text-gray-500'} mr-3 flex-shrink-0 h-5 w-5`} />
|
||||
Mes Favoris
|
||||
</button>
|
||||
|
||||
<div className="pt-4 pb-2">
|
||||
<p className="px-3 text-[10px] font-semibold text-gray-400 uppercase tracking-wider">Espace Business</p>
|
||||
</div>
|
||||
@@ -267,6 +273,7 @@ const DashboardContent = () => {
|
||||
{currentView === 'overview' && 'Vue d\'ensemble'}
|
||||
{currentView === 'messages' && 'Mes Messages'}
|
||||
{currentView === 'personal-profile' && 'Mon Profil Client'}
|
||||
{currentView === 'favorites' && 'Mes Favoris'}
|
||||
{currentView === 'profile' && (user.role === 'ENTREPRENEUR' ? 'Ma Fiche Entreprise' : 'Créer mon Business')}
|
||||
{currentView === 'offers' && 'Gestion des Offres'}
|
||||
{currentView === 'subscription' && 'Mon Abonnement'}
|
||||
@@ -302,6 +309,7 @@ const DashboardContent = () => {
|
||||
{currentView === 'overview' && <DashboardOverview business={displayBusiness} stats={stats} />}
|
||||
{currentView === 'messages' && <DashboardMessages onMessagesRead={fetchUnreadCount} initialConversationId={chatId} />}
|
||||
{currentView === 'personal-profile' && <DashboardProfileClient />}
|
||||
{currentView === 'favorites' && <DashboardFavorites />}
|
||||
{currentView === 'profile' && <DashboardProfile business={displayBusiness} setBusiness={setBusiness} />}
|
||||
{currentView === 'offers' && <DashboardOffers businessId={displayBusiness.id} plan={displayBusiness.plan} />}
|
||||
{currentView === 'subscription' && (
|
||||
|
||||
Reference in New Issue
Block a user