diff --git a/apps/frontend/DESIGN_SYSTEM.md b/apps/frontend/DESIGN_SYSTEM.md index d4675d6..69f00e1 100644 --- a/apps/frontend/DESIGN_SYSTEM.md +++ b/apps/frontend/DESIGN_SYSTEM.md @@ -1,5 +1,23 @@ # Xpeditis Design System +> ## ⚠️ Ce document n'est pas la source de vérité +> +> La source de vérité du design system est **`apps/frontend/tailwind.config.ts`**, complétée par les variables CSS de `apps/frontend/app/globals.css`. +> +> Pour travailler sur l'UI, référez-vous à : +> +> | Document | Contenu | +> |---|---| +> | [`docs/design-system-audit.md`](../../docs/design-system-audit.md) | **Charte verrouillée** — valeurs réelles relevées dans le code, 24 sections | +> | [`docs/ui-audit.md`](../../docs/ui-audit.md) | Cartographie des pages, problèmes identifiés, duplications | +> | [`docs/ui-architecture.md`](../../docs/ui-architecture.md) | Architecture UI cible, plan de refonte, indicateurs, règles de contraste | +> +> Le présent fichier est conservé pour sa description narrative de l'identité de marque. **Les valeurs ci-dessous sont exactes**, mais les exemples d'usage qu'il contient précèdent la refonte UI et ne reflètent plus les composants en place. +> +> Deux points en particulier ont évolué depuis sa rédaction — voir `docs/ui-architecture.md` §17 : +> - Le turquoise `#34CCCD` **ne doit pas porter de texte sur fond clair** (2,05:1, échec AA). Il est réservé aux fonds, bordures et anneaux de focus. +> - Sur un aplat turquoise, le texte est **navy** (8,49:1, AAA) et non blanc. + ## 📐 Charte Graphique Ce document définit la charte graphique officielle de Xpeditis pour assurer la cohérence visuelle de l'application. diff --git a/apps/frontend/app/[locale]/about/page.tsx b/apps/frontend/app/[locale]/about/page.tsx index a49585e..d50ae0d 100644 --- a/apps/frontend/app/[locale]/about/page.tsx +++ b/apps/frontend/app/[locale]/about/page.tsx @@ -24,7 +24,7 @@ type TimelineKey = '2023' | '2024' | '2025' | '2026'; type StatKey = 'clients' | 'carriers' | 'countries' | 'bookings'; const VALUES: { key: ValueKey; icon: LucideIcon; color: string }[] = [ - { key: 'excellence', icon: Target, color: 'from-blue-500 to-cyan-500' }, + { key: 'excellence', icon: Target, color: 'from-brand-turquoise to-cyan-500' }, { key: 'transparency', icon: Heart, color: 'from-pink-500 to-rose-500' }, { key: 'collaboration', icon: Users, color: 'from-purple-500 to-indigo-500' }, { key: 'innovation', icon: TrendingUp, color: 'from-orange-500 to-amber-500' }, @@ -158,7 +158,7 @@ export default function AboutPage() {

{t('mission.title')}

-

{t('mission.body')}

+

{t('mission.body')}

{t('vision.title')}

-

{t('vision.body')}

+

{t('vision.body')}

{/* Stats Section */} -
+
{stat.value} -
{t(`stats.${stat.key}`)}
+
{t(`stats.${stat.key}`)}
))} @@ -213,7 +213,7 @@ export default function AboutPage() {

{t('valuesTitle')}

-

{t('valuesSubtitle')}

+

{t('valuesSubtitle')}

{t(`values.${value.key}.title`)} -

{t(`values.${value.key}.description`)}

+

{t(`values.${value.key}.description`)}

); })} @@ -248,7 +248,7 @@ export default function AboutPage() {
{/* Timeline Section */} -
+
{t('timelineTitle')} -

{t('timelineSubtitle')}

+

{t('timelineSubtitle')}

@@ -286,7 +286,7 @@ export default function AboutPage() {
-
+
@@ -296,7 +296,7 @@ export default function AboutPage() {

{t(`timeline.${year}.title`)}

-

{t(`timeline.${year}.description`)}

+

{t(`timeline.${year}.description`)}

@@ -336,7 +336,7 @@ export default function AboutPage() {

{t('teamTitle')}

-

{t('teamSubtitle')}

+

{t('teamSubtitle')}

@@ -370,7 +370,7 @@ export default function AboutPage() {

{t(`team.${member.key}.role`)}

-

{t(`team.${member.key}.bio`)}

+

{t(`team.${member.key}.bio`)}

))} @@ -399,7 +399,7 @@ export default function AboutPage() { {t('cta.viewCareers')} diff --git a/apps/frontend/app/[locale]/admin/blog/page.tsx b/apps/frontend/app/[locale]/admin/blog/page.tsx index 7973ffc..912eeb0 100644 --- a/apps/frontend/app/[locale]/admin/blog/page.tsx +++ b/apps/frontend/app/[locale]/admin/blog/page.tsx @@ -38,6 +38,7 @@ import { Sparkles, } from 'lucide-react'; import { Link } from '@/i18n/navigation'; +import { useToast } from '@/components/ui/toast'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000'; @@ -67,9 +68,9 @@ const STATUS_LABELS: Record = { const STATUS_COLORS: Record = { draft: 'bg-yellow-100 text-yellow-800', - scheduled: 'bg-blue-100 text-blue-800', + scheduled: 'bg-brand-turquoise/10 text-brand-navy', published: 'bg-green-100 text-green-800', - archived: 'bg-gray-100 text-gray-600', + archived: 'bg-neutral-100 text-neutral-600', }; interface FormData extends CreateBlogPostRequest { @@ -134,18 +135,19 @@ function SeoPreview({ const displayDesc = metaDescription || "Description de l'article..."; const displayUrl = `xpeditis.com/blog/${slug || 'votre-slug'}`; return ( -
-

+

+

Aperçu Google

-

{displayTitle}

+

{displayTitle}

{displayUrl}

-

{displayDesc}

+

{displayDesc}

); } export default function AdminBlogPage() { + const { toast } = useToast(); const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -254,7 +256,7 @@ export default function AdminBlogPage() { await fetchPosts(); closeModal(); } catch (err: any) { - alert(err.message || 'Erreur lors de la création'); + toast.error(err.message || 'Erreur lors de la création'); } finally { setSaving(false); } @@ -272,7 +274,7 @@ export default function AdminBlogPage() { await fetchPosts(); closeModal(); } catch (err: any) { - alert(err.message || 'Erreur lors de la mise à jour'); + toast.error(err.message || 'Erreur lors de la mise à jour'); } finally { setSaving(false); } @@ -286,7 +288,7 @@ export default function AdminBlogPage() { setShowDeleteConfirm(false); setSelectedPost(null); } catch (err: any) { - alert(err.message || 'Erreur lors de la suppression'); + toast.error(err.message || 'Erreur lors de la suppression'); } }; @@ -296,7 +298,7 @@ export default function AdminBlogPage() { await updateBlogPost(post.id, { status: nextStatus }); await fetchPosts(); } catch (err: any) { - alert(err.message || 'Erreur lors du changement de statut'); + toast.error(err.message || 'Erreur lors du changement de statut'); } }; @@ -305,17 +307,17 @@ export default function AdminBlogPage() { await updateBlogPost(post.id, { isFeatured: !post.isFeatured }); await fetchPosts(); } catch (err: any) { - alert(err.message || 'Erreur lors du changement'); + toast.error(err.message || 'Erreur lors du changement'); } }; const uploadCoverFile = async (file: File) => { if (!file.type.startsWith('image/')) { - alert('Veuillez sélectionner une image'); + toast.error('Veuillez sélectionner une image'); return; } if (file.size > 5 * 1024 * 1024) { - alert('Image trop volumineuse (max 5 Mo)'); + toast.error('Image trop volumineuse (max 5 Mo)'); return; } @@ -331,7 +333,7 @@ export default function AdminBlogPage() { const coverUrl = result.url.startsWith('http') ? result.url : `${API_BASE_URL}${result.url}`; setFormData(prev => ({ ...prev, coverImageUrl: coverUrl })); } catch (err: any) { - alert(err.message || "Erreur lors de l'upload"); + toast.error(err.message || "Erreur lors de l'upload"); } finally { setUploadingCover(false); if (coverInputRef.current) coverInputRef.current.value = ''; @@ -375,7 +377,7 @@ export default function AdminBlogPage() { await duplicateBlogPost(post.id); await fetchPosts(); } catch (err: any) { - alert(err.message || 'Erreur lors de la duplication'); + toast.error(err.message || 'Erreur lors de la duplication'); } finally { setActioningId(null); } @@ -387,7 +389,7 @@ export default function AdminBlogPage() { await restoreBlogPost(post.id); await fetchPosts(); } catch (err: any) { - alert(err.message || 'Erreur lors de la restauration'); + toast.error(err.message || 'Erreur lors de la restauration'); } finally { setActioningId(null); } @@ -401,7 +403,7 @@ export default function AdminBlogPage() { setShowDeleteConfirm(false); setSelectedPost(null); } catch (err: any) { - alert(err.message || 'Erreur lors de la suppression définitive'); + toast.error(err.message || 'Erreur lors de la suppression définitive'); } }; @@ -502,13 +504,13 @@ export default function AdminBlogPage() { const metaDescLen = formData.metaDescription.length; const metaTitleColor = metaTitleLen === 0 - ? 'text-gray-400' + ? 'text-neutral-400' : metaTitleLen <= 60 ? 'text-green-600' : 'text-red-500'; const metaDescColor = metaDescLen === 0 - ? 'text-gray-400' + ? 'text-neutral-400' : metaDescLen <= 160 ? 'text-green-600' : 'text-red-500'; @@ -524,7 +526,7 @@ export default function AdminBlogPage() { actions={
{loading ? ( -
Chargement des articles...
+
Chargement des articles...
) : ( -
- - +
+
+ - - - - - - - + {visiblePosts.length === 0 ? ( - ) : ( visiblePosts.map(post => ( - + - - - +
+ Article + Catégorie + Statut + Auteur + Date + Actions
+ {viewFilter === 'trash' ? 'La corbeille est vide.' : 'Aucun article. Créez votre premier article !'} @@ -602,7 +604,7 @@ export default function AdminBlogPage() {
{post.coverImageUrl && ( @@ -617,15 +619,15 @@ export default function AdminBlogPage() { {post.isFeatured && ( )} - + {post.title}
-
{post.slug}
+
{post.slug}
+ {CATEGORIES.find(c => c.value === post.category)?.label ?? post.category} @@ -636,7 +638,7 @@ export default function AdminBlogPage() { {STATUS_LABELS[post.status]} {post.status === 'scheduled' && post.publishedAt && ( - + {new Date(post.publishedAt).toLocaleString('fr-FR', { day: '2-digit', @@ -649,8 +651,8 @@ export default function AdminBlogPage() { )} {post.authorName} + {post.authorName} {post.publishedAt ? new Date(post.publishedAt).toLocaleDateString('fr-FR') : new Date(post.createdAt).toLocaleDateString('fr-FR')} @@ -662,7 +664,7 @@ export default function AdminBlogPage() { -

+

{showCreateModal ? 'Nouvel article' : `Modifier — ${selectedPost?.title}`}

@@ -780,7 +782,7 @@ export default function AdminBlogPage() {