migration correction et maj
Some checks failed
Build and Push App / build (push) Failing after 11m56s
Some checks failed
Build and Push App / build (push) Failing after 11m56s
This commit is contained in:
@@ -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" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
120
admin/src/components/BusinessSeoModal.tsx
Normal file
120
admin/src/components/BusinessSeoModal.tsx
Normal file
@@ -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 (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-slate-950/80 backdrop-blur-sm" onClick={onClose} />
|
||||
|
||||
<div className="relative bg-slate-900 border border-slate-800 rounded-2xl w-full max-w-lg shadow-2xl animate-in zoom-in duration-200">
|
||||
<div className="p-6 border-b border-slate-800 flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-500">
|
||||
<Globe className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-white">SEO & Partage</h3>
|
||||
<p className="text-xs text-slate-400">Personnaliser l'aperçu WhatsApp pour {business.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-2 hover:bg-slate-800 rounded-lg text-slate-400 hover:text-white transition-colors">
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Meta Titre (WhatsApp/Google)</label>
|
||||
<input
|
||||
value={metaTitle}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<p className="text-[10px] text-slate-500 italic">Recommandé : Moins de 60 caractères.</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Meta Description</label>
|
||||
<textarea
|
||||
value={metaDescription}
|
||||
onChange={(e) => setMetaDescription(e.target.value)}
|
||||
rows={4}
|
||||
placeholder="Décrivez l'entreprise en quelques mots pour attirer les clics..."
|
||||
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 resize-none"
|
||||
/>
|
||||
<p className="text-[10px] text-slate-500 italic">Recommandé : Entre 140 et 160 caractères.</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-indigo-500/5 border border-indigo-500/10 rounded-xl p-4">
|
||||
<h4 className="text-[10px] font-bold text-indigo-400 uppercase tracking-widest mb-2">Aperçu du partage</h4>
|
||||
<div className="bg-slate-950 rounded-lg p-3 border border-slate-800">
|
||||
<div className="text-sm font-bold text-indigo-400 line-clamp-1">{metaTitle || business.name}</div>
|
||||
<div className="text-xs text-slate-500 line-clamp-2 mt-1">{metaDescription || "Découvrez cette entreprise sur Afroprenariat..."}</div>
|
||||
<div className="text-[10px] text-slate-600 mt-2">afroprenariat.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex-1 py-3 rounded-xl border border-slate-800 text-slate-400 font-bold hover:bg-slate-800 transition-all"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="flex-[2] py-3 rounded-xl bg-indigo-600 text-white font-bold hover:bg-indigo-500 transition-all flex items-center justify-center gap-2 shadow-lg shadow-indigo-600/20"
|
||||
>
|
||||
{isPending ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<Save className="w-5 h-5" />
|
||||
)}
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { ShieldAlert, ShieldCheck, ShoppingBag, User } from 'lucide-react';
|
||||
import { ShieldAlert, ShieldCheck, ShoppingBag, User, Globe } from 'lucide-react';
|
||||
import { suspendUser, unsuspendUser, suspendBusiness, unsuspendBusiness } from '@/app/actions/suspension';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import SuspensionModal from './SuspensionModal';
|
||||
import BusinessSeoModal from './BusinessSeoModal';
|
||||
|
||||
interface EntrepreneurActionsProps {
|
||||
businessId: string;
|
||||
@@ -13,6 +14,8 @@ interface EntrepreneurActionsProps {
|
||||
userName: string;
|
||||
isBusinessSuspended: boolean;
|
||||
isUserSuspended: boolean;
|
||||
metaTitle?: string | null;
|
||||
metaDescription?: string | null;
|
||||
}
|
||||
|
||||
export default function EntrepreneurActions({
|
||||
@@ -21,9 +24,12 @@ export default function EntrepreneurActions({
|
||||
userId,
|
||||
userName,
|
||||
isBusinessSuspended,
|
||||
isUserSuspended
|
||||
isUserSuspended,
|
||||
metaTitle,
|
||||
metaDescription
|
||||
}: EntrepreneurActionsProps) {
|
||||
const [modalType, setModalType] = useState<'USER' | 'BUSINESS' | null>(null);
|
||||
const [seoModalOpen, setSeoModalOpen] = useState(false);
|
||||
|
||||
const handleConfirm = async (reason: string) => {
|
||||
if (modalType === 'USER') {
|
||||
@@ -41,6 +47,16 @@ export default function EntrepreneurActions({
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2 justify-end">
|
||||
{/* SEO Button */}
|
||||
<button
|
||||
onClick={() => setSeoModalOpen(true)}
|
||||
className="btn-action bg-indigo-500/10 text-indigo-500 hover:bg-indigo-500 hover:text-white"
|
||||
title="Modifier le SEO / Partage WhatsApp"
|
||||
>
|
||||
<Globe className="w-4 h-4" />
|
||||
<span className="text-[10px] uppercase font-bold">SEO</span>
|
||||
</button>
|
||||
|
||||
{/* User Suspension */}
|
||||
{isUserSuspended ? (
|
||||
<button
|
||||
@@ -93,6 +109,17 @@ export default function EntrepreneurActions({
|
||||
? `Voulez-vous suspendre TOUT le compte de ${userName} ? Sa boutique sera également masquée.`
|
||||
: `Voulez-vous masquer la boutique "${businessName}" ? L'entrepreneur gardera l'accès à son compte.`}
|
||||
/>
|
||||
|
||||
<BusinessSeoModal
|
||||
isOpen={seoModalOpen}
|
||||
onClose={() => setSeoModalOpen(false)}
|
||||
business={{
|
||||
id: businessId,
|
||||
name: businessName,
|
||||
metaTitle,
|
||||
metaDescription
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user