gestion des messages page 404

This commit is contained in:
2026-05-13 21:19:15 +02:00
parent eb43d65280
commit ddb37e057d
6 changed files with 89 additions and 6 deletions

View File

@@ -29,6 +29,7 @@ export async function getSiteSettings() {
homeBlogIds: [], homeBlogIds: [],
homeBlogCategories: [], homeBlogCategories: [],
homeCategories: ["Technologie & IT", "Agriculture & Agrobusiness", "Mode & Textile", "Cosmétique & Beauté"], homeCategories: ["Technologie & IT", "Agriculture & Agrobusiness", "Mode & Textile", "Cosmétique & Beauté"],
notFoundQuotes: ["L'échec est une étape vers la réussite, mais une erreur 404 est juste une impasse."],
resetPasswordSubject: "Réinitialisation de votre mot de passe - Afrohub", resetPasswordSubject: "Réinitialisation de votre mot de passe - Afrohub",
resetPasswordTemplate: `<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;"> resetPasswordTemplate: `<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;">
<h2 style="color: #0d9488; text-align: center;">{siteName}</h2> <h2 style="color: #0d9488; text-align: center;">{siteName}</h2>

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import React, { useState, useEffect, useTransition } from 'react'; import React, { useState, useEffect, useTransition } from 'react';
import { Save, Globe, Mail, Phone, MapPin, Share2, Link as LinkIcon, Loader2, Newspaper, Briefcase } from 'lucide-react'; import { Save, Globe, Mail, Phone, MapPin, Share2, Link as LinkIcon, Loader2, Newspaper, Briefcase, Plus, Trash2 } from 'lucide-react';
import { getSiteSettings, updateSiteSettings } from '@/app/actions/settings'; import { getSiteSettings, updateSiteSettings } from '@/app/actions/settings';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { getBlogPosts } from '@/app/actions/actualites'; import { getBlogPosts } from '@/app/actions/actualites';
@@ -46,6 +46,7 @@ export default function SettingsPage({
const [selectedPostIds, setSelectedPostIds] = useState<string[]>([]); const [selectedPostIds, setSelectedPostIds] = useState<string[]>([]);
const [selectedHomeCategories, setSelectedHomeCategories] = useState<string[]>([]); const [selectedHomeCategories, setSelectedHomeCategories] = useState<string[]>([]);
const [selectedBlogCategories, setSelectedBlogCategories] = useState<string[]>([]); const [selectedBlogCategories, setSelectedBlogCategories] = useState<string[]>([]);
const [notFoundQuotes, setNotFoundQuotes] = useState<string[]>([]);
const [resetPasswordTemplate, setResetPasswordTemplate] = useState(''); const [resetPasswordTemplate, setResetPasswordTemplate] = useState('');
const [verifyEmailTemplate, setVerifyEmailTemplate] = useState(''); const [verifyEmailTemplate, setVerifyEmailTemplate] = useState('');
@@ -65,6 +66,7 @@ export default function SettingsPage({
setSelectedPostIds(settingsData?.homeBlogIds || []); setSelectedPostIds(settingsData?.homeBlogIds || []);
setSelectedHomeCategories(settingsData?.homeCategories || []); setSelectedHomeCategories(settingsData?.homeCategories || []);
setSelectedBlogCategories(settingsData?.homeBlogCategories || []); setSelectedBlogCategories(settingsData?.homeBlogCategories || []);
setNotFoundQuotes(settingsData?.notFoundQuotes || ["L'échec est une étape vers la réussite, mais une erreur 404 est juste une impasse."]);
setResetPasswordTemplate(settingsData?.resetPasswordTemplate || ''); setResetPasswordTemplate(settingsData?.resetPasswordTemplate || '');
setVerifyEmailTemplate(settingsData?.verifyEmailTemplate || ''); setVerifyEmailTemplate(settingsData?.verifyEmailTemplate || '');
} catch (error) { } catch (error) {
@@ -99,6 +101,7 @@ export default function SettingsPage({
homeBlogIds: selectedPostIds, homeBlogIds: selectedPostIds,
homeBlogCategories: selectedBlogCategories, homeBlogCategories: selectedBlogCategories,
homeCategories: selectedHomeCategories, homeCategories: selectedHomeCategories,
notFoundQuotes: notFoundQuotes.filter(q => q.trim() !== ''),
resetPasswordSubject: formData.get('resetPasswordSubject'), resetPasswordSubject: formData.get('resetPasswordSubject'),
resetPasswordTemplate: resetPasswordTemplate, resetPasswordTemplate: resetPasswordTemplate,
verifyEmailSubject: formData.get('verifyEmailSubject'), verifyEmailSubject: formData.get('verifyEmailSubject'),
@@ -133,6 +136,22 @@ export default function SettingsPage({
); );
}; };
const handleAddQuote = () => {
setNotFoundQuotes(prev => [...prev, '']);
};
const handleUpdateQuote = (index: number, value: string) => {
setNotFoundQuotes(prev => {
const updated = [...prev];
updated[index] = value;
return updated;
});
};
const handleRemoveQuote = (index: number) => {
setNotFoundQuotes(prev => prev.filter((_, i) => i !== index));
};
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center p-20"> <div className="flex items-center justify-center p-20">
@@ -205,6 +224,57 @@ export default function SettingsPage({
</div> </div>
</div> </div>
{/* Section Page 404 - Phrases */}
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden">
<div className="p-4 bg-slate-800/50 border-b border-slate-700 flex items-center justify-between">
<div className="flex items-center gap-2">
<Globe className="w-5 h-5 text-rose-400" />
<h2 className="font-semibold text-white">Phrases de la Page 404</h2>
</div>
<span className="text-xs bg-rose-500/10 text-rose-400 border border-rose-500/20 px-2.5 py-1 rounded-full font-bold">
{notFoundQuotes.length} configurée{notFoundQuotes.length > 1 ? 's' : ''}
</span>
</div>
<div className="p-6 space-y-4">
<p className="text-sm text-slate-400">
Configurez ici les différentes phrases ou citations qui s'afficheront de manière aléatoire sur la page d'erreur 404.
</p>
<div className="space-y-3">
{notFoundQuotes.map((quote, index) => (
<div key={index} className="flex items-center gap-2">
<input
type="text"
value={quote}
onChange={(e) => handleUpdateQuote(index, e.target.value)}
placeholder="Saisissez une citation..."
className="flex-grow bg-slate-950 border border-slate-700 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500 text-sm"
/>
{notFoundQuotes.length > 1 && (
<button
type="button"
onClick={() => handleRemoveQuote(index)}
className="p-3 bg-slate-950 border border-slate-700 hover:border-rose-500/50 text-slate-400 hover:text-rose-400 rounded-xl transition-all"
title="Supprimer cette phrase"
>
<Trash2 className="w-4 h-4" />
</button>
)}
</div>
))}
</div>
<button
type="button"
onClick={handleAddQuote}
className="inline-flex items-center gap-2 px-4 py-2.5 bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 rounded-xl text-sm font-bold transition-all"
>
<Plus className="w-4 h-4" />
Ajouter une phrase
</button>
</div>
</div>
{/* Section Page d'Accueil - Secteurs */} {/* Section Page d'Accueil - Secteurs */}
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden"> <div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden">
<div className="p-4 bg-slate-800/50 border-b border-slate-700 flex items-center gap-2"> <div className="p-4 bg-slate-800/50 border-b border-slate-700 flex items-center gap-2">

View File

@@ -1,8 +1,17 @@
import React from 'react'; import React from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { Home, ArrowLeft, Search } from 'lucide-react'; import { Home, Search } from 'lucide-react';
import { getSiteSettings } from '../lib/settings';
export default async function NotFound() {
const settings = await getSiteSettings();
const quotes = (settings as any)?.notFoundQuotes && (settings as any).notFoundQuotes.length > 0
? (settings as any).notFoundQuotes
: ["L'échec est une étape vers la réussite, mais une erreur 404 est juste une impasse."];
// Sélection d'une phrase aléatoire
const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
export default function NotFound() {
return ( return (
<div className="min-h-screen bg-white flex items-center justify-center px-4 sm:px-6 lg:px-8 py-24"> <div className="min-h-screen bg-white flex items-center justify-center px-4 sm:px-6 lg:px-8 py-24">
<div className="max-w-md w-full text-center"> <div className="max-w-md w-full text-center">
@@ -41,10 +50,11 @@ export default function NotFound() {
<div className="mt-12 pt-12 border-t border-gray-100"> <div className="mt-12 pt-12 border-t border-gray-100">
<p className="text-sm text-gray-400 italic"> <p className="text-sm text-gray-400 italic">
"L'échec est une étape vers la réussite, mais une erreur 404 est juste une impasse." "{randomQuote}"
</p> </p>
</div> </div>
</div> </div>
</div> </div>
); );
} }

View File

@@ -19,7 +19,8 @@ export async function getSiteSettings() {
homeBlogSelection: "latest", homeBlogSelection: "latest",
homeBlogIds: [], homeBlogIds: [],
homeBlogCategories: [], homeBlogCategories: [],
homeCategories: ["Technologie & IT", "Agriculture & Agrobusiness", "Mode & Textile", "Cosmétique & Beauté"] homeCategories: ["Technologie & IT", "Agriculture & Agrobusiness", "Mode & Textile", "Cosmétique & Beauté"],
notFoundQuotes: ["L'échec est une étape vers la réussite, mais une erreur 404 est juste une impasse."]
}; };
try { try {

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -332,6 +332,7 @@ model SiteSetting {
deleteAccountTemplate String @default("<div style=\"font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;\"><h2 style=\"color: #dc2626; text-align: center;\">{siteName}</h2><p>Bonjour {name},</p><p>Votre demande de suppression de compte a bien été prise en compte. Votre compte sera définitivement supprimé le <strong>{deletionDate}</strong>.</p><p>Si vous changez d'avis, vous pouvez réactiver votre compte avant cette date en cliquant sur le bouton ci-dessous :</p><div style=\"text-align: center; margin: 30px 0;\"><a href=\"{reactivateUrl}\" style=\"background-color: #0d9488; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; font-weight: bold;\">Réactiver mon compte</a></div><p>Passé ce délai, toutes vos données seront définitivement effacées.</p><hr style=\"border: 0; border-top: 1px solid #e5e7eb; margin: 20px 0;\"><p style=\"font-size: 12px; color: #6b7280; text-align: center;\">&copy; 2025 {siteName}. Tous droits réservés.</p></div>") deleteAccountTemplate String @default("<div style=\"font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;\"><h2 style=\"color: #dc2626; text-align: center;\">{siteName}</h2><p>Bonjour {name},</p><p>Votre demande de suppression de compte a bien été prise en compte. Votre compte sera définitivement supprimé le <strong>{deletionDate}</strong>.</p><p>Si vous changez d'avis, vous pouvez réactiver votre compte avant cette date en cliquant sur le bouton ci-dessous :</p><div style=\"text-align: center; margin: 30px 0;\"><a href=\"{reactivateUrl}\" style=\"background-color: #0d9488; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; font-weight: bold;\">Réactiver mon compte</a></div><p>Passé ce délai, toutes vos données seront définitivement effacées.</p><hr style=\"border: 0; border-top: 1px solid #e5e7eb; margin: 20px 0;\"><p style=\"font-size: 12px; color: #6b7280; text-align: center;\">&copy; 2025 {siteName}. Tous droits réservés.</p></div>")
reactivateAccountSubject String @default("Réactivation de votre compte - Afrohub") reactivateAccountSubject String @default("Réactivation de votre compte - Afrohub")
reactivateAccountTemplate String @default("<div style=\"font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;\"><h2 style=\"color: #0d9488; text-align: center;\">{siteName}</h2><p>Bonjour {name},</p><p>Nous avons bien reçu votre demande de réactivation de compte.</p><p><strong>Raison invoquée :</strong><br>{reason}</p><p>Votre compte est désormais actif. Vous pouvez vous connecter à nouveau.</p><hr style=\"border: 0; border-top: 1px solid #e5e7eb; margin: 20px 0;\"><p style=\"font-size: 12px; color: #6b7280; text-align: center;\">&copy; 2025 {siteName}. Tous droits réservés.</p></div>") reactivateAccountTemplate String @default("<div style=\"font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;\"><h2 style=\"color: #0d9488; text-align: center;\">{siteName}</h2><p>Bonjour {name},</p><p>Nous avons bien reçu votre demande de réactivation de compte.</p><p><strong>Raison invoquée :</strong><br>{reason}</p><p>Votre compte est désormais actif. Vous pouvez vous connecter à nouveau.</p><hr style=\"border: 0; border-top: 1px solid #e5e7eb; margin: 20px 0;\"><p style=\"font-size: 12px; color: #6b7280; text-align: center;\">&copy; 2025 {siteName}. Tous droits réservés.</p></div>")
notFoundQuotes String[] @default(["L'échec est une étape vers la réussite, mais une erreur 404 est juste une impasse."])
} }
model OneShotPlan { model OneShotPlan {