diff --git a/admin/prisma/migrations/0_init/migration.sql b/admin/prisma/migrations/0_init/migration.sql new file mode 100644 index 0000000..d14cf34 Binary files /dev/null and b/admin/prisma/migrations/0_init/migration.sql differ diff --git a/admin/prisma/migrations/20260427214502_init/migration.sql b/admin/prisma/migrations/20260427214502_init/migration.sql new file mode 100644 index 0000000..220c22c --- /dev/null +++ b/admin/prisma/migrations/20260427214502_init/migration.sql @@ -0,0 +1,459 @@ +-- CreateEnum +CREATE TYPE "RatingStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); + +-- CreateEnum +CREATE TYPE "ReportStatus" AS ENUM ('PENDING', 'RESOLVED', 'DISMISSED'); + +-- CreateEnum +CREATE TYPE "UserRole" AS ENUM ('VISITOR', 'ENTREPRENEUR', 'ADMIN'); + +-- CreateEnum +CREATE TYPE "Plan" AS ENUM ('STARTER', 'BOOSTER', 'EMPIRE'); + +-- CreateEnum +CREATE TYPE "OfferType" AS ENUM ('PRODUCT', 'SERVICE'); + +-- CreateEnum +CREATE TYPE "InterviewType" AS ENUM ('VIDEO', 'ARTICLE'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "role" "UserRole" NOT NULL DEFAULT 'VISITOR', + "avatar" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "bio" TEXT, + "location" TEXT, + "phone" TEXT, + "isSuspended" BOOLEAN NOT NULL DEFAULT false, + "suspensionReason" TEXT, + "countryId" TEXT, + "resetToken" TEXT, + "resetTokenExpiry" TIMESTAMP(3), + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "verificationToken" TEXT, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Business" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "category" TEXT NOT NULL, + "location" TEXT NOT NULL, + "description" TEXT NOT NULL, + "logoUrl" TEXT NOT NULL, + "videoUrl" TEXT, + "contactEmail" TEXT NOT NULL, + "contactPhone" TEXT, + "socialLinks" JSONB, + "verified" BOOLEAN NOT NULL DEFAULT false, + "viewCount" INTEGER NOT NULL DEFAULT 0, + "rating" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "ratingCount" INTEGER NOT NULL DEFAULT 0, + "tags" TEXT[], + "isFeatured" BOOLEAN NOT NULL DEFAULT false, + "founderName" TEXT, + "founderImageUrl" TEXT, + "keyMetric" TEXT, + "ownerId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "slug" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT false, + "showEmail" BOOLEAN NOT NULL DEFAULT true, + "showPhone" BOOLEAN NOT NULL DEFAULT true, + "showSocials" BOOLEAN NOT NULL DEFAULT true, + "websiteUrl" TEXT, + "isSuspended" BOOLEAN NOT NULL DEFAULT false, + "suspensionReason" TEXT, + "plan" "Plan" NOT NULL DEFAULT 'STARTER', + "city" TEXT, + "countryId" TEXT, + "coverUrl" TEXT, + "coverPosition" TEXT DEFAULT '50% 50%', + "coverZoom" DOUBLE PRECISION DEFAULT 1.0, + "suggestedCategory" TEXT, + "categoryId" TEXT, + + CONSTRAINT "Business_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BusinessCategory" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "icon" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "BusinessCategory_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Country" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "code" TEXT NOT NULL, + "flag" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "description" TEXT, + + CONSTRAINT "Country_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PricingPlan" ( + "id" TEXT NOT NULL, + "tier" "Plan" NOT NULL, + "name" TEXT NOT NULL, + "priceXOF" TEXT NOT NULL, + "priceEUR" TEXT NOT NULL, + "description" TEXT NOT NULL, + "features" TEXT[], + "offerLimit" INTEGER NOT NULL DEFAULT 1, + "recommended" BOOLEAN NOT NULL DEFAULT false, + "color" TEXT NOT NULL DEFAULT 'gray', + "updatedAt" TIMESTAMP(3) NOT NULL, + "yearlyPriceEUR" TEXT, + "yearlyPriceXOF" TEXT, + + CONSTRAINT "PricingPlan_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Offer" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "type" "OfferType" NOT NULL, + "price" DOUBLE PRECISION NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'XOF', + "description" TEXT, + "imageUrl" TEXT NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "businessId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Offer_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BlogPost" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "excerpt" TEXT NOT NULL, + "content" TEXT NOT NULL, + "author" TEXT NOT NULL, + "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "imageUrl" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "metaDescription" TEXT, + "metaTitle" TEXT, + "slug" TEXT, + "tags" TEXT[] DEFAULT ARRAY[]::TEXT[], + + CONSTRAINT "BlogPost_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Interview" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "guestName" TEXT NOT NULL, + "companyName" TEXT NOT NULL, + "role" TEXT NOT NULL, + "type" "InterviewType" NOT NULL, + "thumbnailUrl" TEXT NOT NULL, + "videoUrl" TEXT, + "content" TEXT, + "excerpt" TEXT NOT NULL, + "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "duration" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "metaDescription" TEXT, + "metaTitle" TEXT, + "slug" TEXT, + "tags" TEXT[] DEFAULT ARRAY[]::TEXT[], + + CONSTRAINT "Interview_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Comment" ( + "id" TEXT NOT NULL, + "content" TEXT NOT NULL, + "authorId" TEXT NOT NULL, + "businessId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AnalyticsEvent" ( + "id" TEXT NOT NULL, + "type" TEXT NOT NULL, + "path" TEXT NOT NULL, + "label" TEXT, + "value" DOUBLE PRECISION, + "metadata" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "browser" TEXT, + "city" TEXT, + "country" TEXT, + "device" TEXT, + "ip" TEXT, + "os" TEXT, + "referrer" TEXT, + "userId" TEXT, + + CONSTRAINT "AnalyticsEvent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Rating" ( + "id" TEXT NOT NULL, + "value" INTEGER NOT NULL, + "userId" TEXT NOT NULL, + "businessId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "comment" TEXT, + "reply" TEXT, + "replyAt" TIMESTAMP(3), + "status" "RatingStatus" NOT NULL DEFAULT 'PENDING', + + CONSTRAINT "Rating_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Conversation" ( + "id" TEXT NOT NULL, + "businessId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Conversation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ConversationParticipant" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "conversationId" TEXT NOT NULL, + "isArchived" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "ConversationParticipant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Message" ( + "id" TEXT NOT NULL, + "content" TEXT NOT NULL, + "senderId" TEXT NOT NULL, + "conversationId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "read" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "Message_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MessageReport" ( + "id" TEXT NOT NULL, + "reason" TEXT, + "messageId" TEXT NOT NULL, + "reporterId" TEXT NOT NULL, + "status" "ReportStatus" NOT NULL DEFAULT 'PENDING', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "MessageReport_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SiteSetting" ( + "id" TEXT NOT NULL DEFAULT 'singleton', + "siteName" TEXT NOT NULL DEFAULT 'Afrohub', + "siteSlogan" TEXT NOT NULL DEFAULT 'La plateforme de référence pour l''entrepreneuriat africain.', + "contactEmail" TEXT NOT NULL DEFAULT 'support@afrohub.com', + "contactPhone" TEXT DEFAULT '+225 00 00 00 00 00', + "address" TEXT DEFAULT 'Abidjan, Côte d''Ivoire', + "facebookUrl" TEXT, + "twitterUrl" TEXT, + "instagramUrl" TEXT, + "linkedinUrl" TEXT, + "footerText" TEXT DEFAULT '© 2025 Afrohub. Tous droits réservés.', + "updatedAt" TIMESTAMP(3) NOT NULL, + "homeBlogCount" INTEGER NOT NULL DEFAULT 3, + "homeBlogShow" BOOLEAN NOT NULL DEFAULT true, + "homeBlogSubtitle" TEXT NOT NULL DEFAULT 'Toutes les actualités de l''entrepreneuriat africain.', + "homeBlogTitle" TEXT NOT NULL DEFAULT 'Derniers Articles', + "homeBlogCategories" TEXT[] DEFAULT ARRAY[]::TEXT[], + "homeBlogIds" TEXT[] DEFAULT ARRAY[]::TEXT[], + "homeBlogSelection" TEXT NOT NULL DEFAULT 'latest', + "homeCategories" TEXT[] DEFAULT ARRAY['Technologie & IT', 'Agriculture & Agrobusiness', 'Mode & Textile', 'Cosmétique & Beauté']::TEXT[], + "resetPasswordSubject" TEXT NOT NULL DEFAULT 'Réinitialisation de votre mot de passe - Afrohub', + "resetPasswordTemplate" TEXT NOT NULL DEFAULT '

{siteName}

Bonjour {name},

Vous avez demandé la réinitialisation de votre mot de passe. Cliquez sur le bouton ci-dessous pour choisir un nouveau mot de passe :

Réinitialiser mon mot de passe

Ce lien expirera dans 1 heure. Si vous n''avez pas demandé cette réinitialisation, vous pouvez ignorer cet email.


© 2025 {siteName}. Tous droits réservés.

', + "verifyEmailSubject" TEXT NOT NULL DEFAULT 'Vérification de votre compte - Afrohub', + "verifyEmailTemplate" TEXT NOT NULL DEFAULT '

{siteName}

Bonjour {name},

Merci de vous être inscrit sur {siteName}. Pour finaliser la création de votre compte, merci de vérifier votre adresse email en cliquant sur le bouton ci-dessous :

Vérifier mon compte

Si vous n''avez pas créé de compte sur notre plateforme, vous pouvez ignorer cet email.


© 2025 {siteName}. Tous droits réservés.

', + + CONSTRAINT "SiteSetting_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LegalDocument" ( + "id" TEXT NOT NULL, + "type" TEXT NOT NULL, + "title" TEXT NOT NULL, + "content" TEXT NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LegalDocument_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Event" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "location" TEXT NOT NULL, + "thumbnailUrl" TEXT NOT NULL, + "link" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "metaDescription" TEXT, + "metaTitle" TEXT, + "slug" TEXT, + "tags" TEXT[] DEFAULT ARRAY[]::TEXT[], + + CONSTRAINT "Event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Favorite" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "businessId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Favorite_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_resetToken_key" ON "User"("resetToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_verificationToken_key" ON "User"("verificationToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "Business_ownerId_key" ON "Business"("ownerId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Business_slug_key" ON "Business"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "BusinessCategory_name_key" ON "BusinessCategory"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "BusinessCategory_slug_key" ON "BusinessCategory"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "Country_name_key" ON "Country"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Country_code_key" ON "Country"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "PricingPlan_tier_key" ON "PricingPlan"("tier"); + +-- CreateIndex +CREATE UNIQUE INDEX "BlogPost_slug_key" ON "BlogPost"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "Interview_slug_key" ON "Interview"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "Rating_userId_businessId_key" ON "Rating"("userId", "businessId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ConversationParticipant_userId_conversationId_key" ON "ConversationParticipant"("userId", "conversationId"); + +-- CreateIndex +CREATE UNIQUE INDEX "LegalDocument_type_key" ON "LegalDocument"("type"); + +-- CreateIndex +CREATE UNIQUE INDEX "Event_slug_key" ON "Event"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "Favorite_userId_businessId_key" ON "Favorite"("userId", "businessId"); + +-- AddForeignKey +ALTER TABLE "User" ADD CONSTRAINT "User_countryId_fkey" FOREIGN KEY ("countryId") REFERENCES "Country"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Business" ADD CONSTRAINT "Business_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "BusinessCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Business" ADD CONSTRAINT "Business_countryId_fkey" FOREIGN KEY ("countryId") REFERENCES "Country"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Business" ADD CONSTRAINT "Business_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Offer" ADD CONSTRAINT "Offer_businessId_fkey" FOREIGN KEY ("businessId") REFERENCES "Business"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_businessId_fkey" FOREIGN KEY ("businessId") REFERENCES "Business"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Rating" ADD CONSTRAINT "Rating_businessId_fkey" FOREIGN KEY ("businessId") REFERENCES "Business"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Rating" ADD CONSTRAINT "Rating_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Conversation" ADD CONSTRAINT "Conversation_businessId_fkey" FOREIGN KEY ("businessId") REFERENCES "Business"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ConversationParticipant" ADD CONSTRAINT "ConversationParticipant_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ConversationParticipant" ADD CONSTRAINT "ConversationParticipant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Message" ADD CONSTRAINT "Message_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Message" ADD CONSTRAINT "Message_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MessageReport" ADD CONSTRAINT "MessageReport_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MessageReport" ADD CONSTRAINT "MessageReport_reporterId_fkey" FOREIGN KEY ("reporterId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Favorite" ADD CONSTRAINT "Favorite_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Favorite" ADD CONSTRAINT "Favorite_businessId_fkey" FOREIGN KEY ("businessId") REFERENCES "Business"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/admin/prisma/migrations/20260503171918_add_published_at/migration.sql b/admin/prisma/migrations/20260503171918_add_published_at/migration.sql new file mode 100644 index 0000000..5c2a930 --- /dev/null +++ b/admin/prisma/migrations/20260503171918_add_published_at/migration.sql @@ -0,0 +1,8 @@ +-- AlterTable +ALTER TABLE "BlogPost" ADD COLUMN "publishedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; + +-- AlterTable +ALTER TABLE "Event" ADD COLUMN "publishedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; + +-- AlterTable +ALTER TABLE "Interview" ADD COLUMN "publishedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/admin/prisma/migrations/20260503172958_add_content_status/migration.sql b/admin/prisma/migrations/20260503172958_add_content_status/migration.sql new file mode 100644 index 0000000..f70ae9d --- /dev/null +++ b/admin/prisma/migrations/20260503172958_add_content_status/migration.sql @@ -0,0 +1,11 @@ +-- CreateEnum +CREATE TYPE "ContentStatus" AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED'); + +-- AlterTable +ALTER TABLE "BlogPost" ADD COLUMN "status" "ContentStatus" NOT NULL DEFAULT 'PUBLISHED'; + +-- AlterTable +ALTER TABLE "Event" ADD COLUMN "status" "ContentStatus" NOT NULL DEFAULT 'PUBLISHED'; + +-- AlterTable +ALTER TABLE "Interview" ADD COLUMN "status" "ContentStatus" NOT NULL DEFAULT 'PUBLISHED'; diff --git a/admin/prisma/migrations/migration_lock.toml b/admin/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/admin/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/admin/prisma/schema.prisma b/admin/prisma/schema.prisma index ca723e3..ce93b4f 100644 --- a/admin/prisma/schema.prisma +++ b/admin/prisma/schema.prisma @@ -68,6 +68,8 @@ model Business { websiteUrl String? isSuspended Boolean @default(false) suspensionReason String? + metaTitle String? + metaDescription String? plan Plan @default(STARTER) city String? countryId String? diff --git a/admin/src/app/actions/business.ts b/admin/src/app/actions/business.ts index 3ff72d6..7469b6d 100644 --- a/admin/src/app/actions/business.ts +++ b/admin/src/app/actions/business.ts @@ -79,3 +79,23 @@ export async function getPendingVerificationCount() { return 0; } } +export async function updateBusinessSeo(id: string, data: { + metaTitle: string; + metaDescription: string; +}) { + try { + await prisma.business.update({ + where: { id }, + data: { + metaTitle: data.metaTitle, + metaDescription: data.metaDescription, + }, + }); + revalidatePath("/users"); + revalidatePath(`/annuaire/${id}`); + return { success: true }; + } catch (error) { + console.error("Failed to update business SEO:", error); + return { success: false, error: "Erreur lors de la mise à jour SEO" }; + } +} diff --git a/admin/src/app/users/page.tsx b/admin/src/app/users/page.tsx index ad40879..5830950 100644 --- a/admin/src/app/users/page.tsx +++ b/admin/src/app/users/page.tsx @@ -180,6 +180,8 @@ export default async function UsersPage({ userName={business.owner.name} isBusinessSuspended={!!business.isSuspended} isUserSuspended={!!business.owner?.isSuspended} + metaTitle={business.metaTitle} + metaDescription={business.metaDescription} /> diff --git a/admin/src/components/BusinessSeoModal.tsx b/admin/src/components/BusinessSeoModal.tsx new file mode 100644 index 0000000..6dcde76 --- /dev/null +++ b/admin/src/components/BusinessSeoModal.tsx @@ -0,0 +1,120 @@ +"use client"; + +import React, { useState, useTransition } from 'react'; +import { X, Save, Loader2, Globe } from 'lucide-react'; +import { updateBusinessSeo } from '@/app/actions/business'; +import { toast } from 'react-hot-toast'; + +interface BusinessSeoModalProps { + isOpen: boolean; + onClose: () => void; + business: { + id: string; + name: string; + metaTitle?: string | null; + metaDescription?: string | null; + }; +} + +export default function BusinessSeoModal({ isOpen, onClose, business }: BusinessSeoModalProps) { + const [isPending, startTransition] = useTransition(); + const [metaTitle, setMetaTitle] = useState(business.metaTitle || ''); + const [metaDescription, setMetaDescription] = useState(business.metaDescription || ''); + + if (!isOpen) return null; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + startTransition(async () => { + const result = await updateBusinessSeo(business.id, { + metaTitle, + metaDescription + }); + if (result.success) { + toast.success("Configuration SEO mise à jour"); + onClose(); + } else { + toast.error(result.error || "Erreur lors de la mise à jour"); + } + }); + }; + + return ( +
+
+ +
+
+
+
+ +
+
+

SEO & Partage

+

Personnaliser l'aperçu WhatsApp pour {business.name}

+
+
+ +
+ +
+
+ + setMetaTitle(e.target.value)} + placeholder={`Ex: ${business.name} - Expert en ...`} + className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500 transition-all" + /> +

Recommandé : Moins de 60 caractères.

+
+ +
+ +