1. Système d'Analytics (Nouveauté Majeure)
Modèle de Données : Ajout de la table AnalyticsEvent dans prisma/schema.prisma pour suivre les vues de pages, les clics et le temps de rétention (DWELL_TIME). API Backend : Création de la route app/api/analytics/route.ts pour enregistrer les événements en base de données. Tracking Client : Création du composant AnalyticsTracker.tsx (intégré dans le layout principal) qui capture automatiquement : Le changement de page (Page Views). Les clics sur les boutons et liens importants. Le temps passé sur chaque page. 2. Intégration Base de Données (PostgreSQL/Prisma) Fiches Entreprises : Mise à jour de app/directory/[id]/page.tsx pour récupérer les données réelles depuis PostgreSQL via Prisma au lieu d'utiliser les données de test (mockData). Navigation Dashboard : Correction du bouton "Voir ma fiche" dans le tableau de bord pour qu'il redirige correctement vers le profil public de l'entrepreneur. 3. Administration & Metrics Nouvelle Application Admin : Initialisation d'un dossier admin/ (application Next.js séparée) destinée à la gestion de la plateforme et à la visualisation des statistiques (Metrics & Analytics). 4. Authentification & Inscription Nouvelle Page : Création de app/register/page.tsx pour permettre l'inscription des nouveaux utilisateurs. Gestion de Session : Amélioration de UserProvider.tsx pour une meilleure gestion de l'état utilisateur à travers l'application. 5. Maintenance & Types Mise à jour des types globaux dans types.ts et rafraîchissement du package-lock.json suite à l'ajout de dépendances.
This commit is contained in:
32
app/api/businesses/[id]/view/route.ts
Normal file
32
app/api/businesses/[id]/view/route.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import prisma from '../../../../../lib/prisma';
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const { id } = params;
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({ error: 'ID requis' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Attempt to increment in DB
|
||||
// Note: We don't care about mock businesses here as they are static
|
||||
const business = await prisma.business.update({
|
||||
where: { id },
|
||||
data: {
|
||||
viewCount: {
|
||||
increment: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, viewCount: business.viewCount });
|
||||
} catch (error) {
|
||||
// If business not found in DB (might be mock), just return success but no update
|
||||
console.error('Error incrementing view count:', error);
|
||||
return NextResponse.json({ success: false, error: 'Business not found or error' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
27
app/api/businesses/me/route.ts
Normal file
27
app/api/businesses/me/route.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import prisma from '../../../../lib/prisma'
|
||||
|
||||
// GET /api/businesses/me — Fetch business for the current user
|
||||
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 business = await prisma.business.findFirst({
|
||||
where: { ownerId: userId },
|
||||
include: { offers: true }
|
||||
})
|
||||
|
||||
if (!business) {
|
||||
return NextResponse.json(null) // Return null if no business yet
|
||||
}
|
||||
|
||||
return NextResponse.json(business)
|
||||
} catch (error) {
|
||||
console.error('GET /api/businesses/me error:', error)
|
||||
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,128 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import prisma from '../../../lib/prisma'
|
||||
import { MOCK_BUSINESSES } from '../../../lib/mockData'
|
||||
|
||||
// GET /api/businesses — List all businesses
|
||||
// GET /api/businesses — List all businesses (Mock + DB)
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const category = searchParams.get('category')
|
||||
const q = searchParams.get('q')
|
||||
const q = searchParams.get('q')?.toLowerCase()
|
||||
const featured = searchParams.get('featured')
|
||||
|
||||
const where: Record<string, unknown> = {}
|
||||
// 1. Fetch from Database
|
||||
const where: any = {}
|
||||
if (category && category !== 'All') where.category = category
|
||||
if (featured === 'true') where.isFeatured = true
|
||||
if (q) {
|
||||
where.OR = [
|
||||
{ name: { contains: q, mode: 'insensitive' } },
|
||||
{ description: { contains: q, mode: 'insensitive' } },
|
||||
{ tags: { hasSome: [q] } },
|
||||
// Simplified tags check
|
||||
{ tags: { has: q } }
|
||||
]
|
||||
}
|
||||
|
||||
const businesses = await prisma.business.findMany({
|
||||
const dbBusinesses = await prisma.business.findMany({
|
||||
where,
|
||||
include: { owner: true, offers: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
return NextResponse.json(businesses)
|
||||
// 2. Filter Mock Businesses
|
||||
const filteredMocks = MOCK_BUSINESSES.filter(biz => {
|
||||
// Category filter
|
||||
if (category && category !== 'All' && biz.category !== category) return false;
|
||||
|
||||
// Featured filter
|
||||
if (featured === 'true' && !biz.isFeatured) return false;
|
||||
|
||||
// Search query filter
|
||||
if (q) {
|
||||
const matches =
|
||||
biz.name.toLowerCase().includes(q) ||
|
||||
biz.description.toLowerCase().includes(q) ||
|
||||
biz.tags.some(t => t.toLowerCase().includes(q));
|
||||
if (!matches) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// 3. Combine and return
|
||||
// Prioritize DB entries over mocks to show user-created data first
|
||||
const combined = [...dbBusinesses, ...filteredMocks];
|
||||
|
||||
return NextResponse.json(combined)
|
||||
} catch (error) {
|
||||
console.error('GET /api/businesses error:', error)
|
||||
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/businesses — Create a business
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const business = await prisma.business.create({
|
||||
data: body,
|
||||
include: { owner: true },
|
||||
})
|
||||
return NextResponse.json(business, { status: 201 })
|
||||
const headerUserId = request.headers.get('x-user-id')
|
||||
const { ownerId: bodyOwnerId, ...data } = body
|
||||
|
||||
const ownerId = bodyOwnerId || headerUserId
|
||||
|
||||
if (!ownerId) {
|
||||
return NextResponse.json({ error: 'ownerId est requis (via body ou x-user-id header)' }, { status: 400 })
|
||||
}
|
||||
|
||||
// 1. Sanitize data - ONLY pass fields that exist in the Prisma Business model
|
||||
// We must exclude 'id', 'offers', 'owner', 'createdAt', 'updatedAt' from the data object
|
||||
const cleanData: any = {
|
||||
name: data.name || "Nouvelle Entreprise",
|
||||
category: data.category || "Autre",
|
||||
location: data.location || "Ma Ville",
|
||||
description: data.description || "",
|
||||
logoUrl: data.logoUrl || "https://picsum.photos/200/200?random=default",
|
||||
videoUrl: data.videoUrl || null,
|
||||
contactEmail: data.contactEmail || "",
|
||||
contactPhone: data.contactPhone || null,
|
||||
socialLinks: data.socialLinks || {},
|
||||
verified: data.verified ?? false,
|
||||
viewCount: data.viewCount ?? 0,
|
||||
rating: data.rating ?? 0,
|
||||
tags: Array.isArray(data.tags) ? data.tags : [],
|
||||
isFeatured: data.isFeatured ?? false,
|
||||
founderName: data.founderName || null,
|
||||
founderImageUrl: data.founderImageUrl || null,
|
||||
keyMetric: data.keyMetric || null,
|
||||
}
|
||||
|
||||
let business;
|
||||
const existing = await prisma.business.findFirst({
|
||||
where: { ownerId: ownerId }
|
||||
});
|
||||
|
||||
console.log('Upserting business for ownerId:', ownerId, 'Existing:', !!existing);
|
||||
|
||||
if (existing) {
|
||||
// When updating, we use the sanitized cleanData and the existing record's id
|
||||
business = await prisma.business.update({
|
||||
where: { id: existing.id },
|
||||
data: cleanData,
|
||||
include: { owner: true }
|
||||
});
|
||||
} else {
|
||||
// When creating, we add the ownerId to the sanitized cleanData
|
||||
business = await prisma.business.create({
|
||||
data: {
|
||||
...cleanData,
|
||||
ownerId: ownerId
|
||||
},
|
||||
include: { owner: true }
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Business saved successfully:', business.id);
|
||||
return NextResponse.json(business, { status: 200 })
|
||||
} catch (error) {
|
||||
console.error('POST /api/businesses error:', error)
|
||||
return NextResponse.json({ error: 'Erreur lors de la création' }, { status: 500 })
|
||||
return NextResponse.json({ error: 'Erreur lors de l’enregistrement' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user