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:
2026-04-11 22:20:41 +02:00
parent d88ba7c53c
commit b227657f19
100 changed files with 14142 additions and 157 deletions

View 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 });
}
}

View 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 })
}
}

View File

@@ -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 lenregistrement' }, { status: 500 })
}
}