ultisuite-client/components/landing/product/product-showcases.tsx
R3D347HR4Y efaaf16f60
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: update metadata and layout for new product pages
- Refactored metadata for contacts, administration, and Ulticards pages to utilize dynamic app names and descriptions.
- Introduced new product pages for Ultiai, Ultical, Ulticards, Ultidrive, Ultimail, and Ultimeet with appropriate metadata.
- Enhanced layout components to ensure consistent styling and functionality across new product sections.
- Updated various components to replace hardcoded labels with dynamic references to improve maintainability and consistency.
2026-06-19 22:11:42 +02:00

257 lines
8.2 KiB
TypeScript

"use client"
import dynamic from "next/dynamic"
import type { ComponentType } from "react"
import { Icon } from "@iconify/react"
import { LandingReveal } from "@/components/landing/landing-reveal"
import { ProductSectionHeading } from "@/components/landing/product/product-section-heading"
import type { ProductShowcase, ProductShowcaseDemo } from "@/components/landing/product/product-data"
import { cn } from "@/lib/utils"
function DemoLoading() {
return (
<div className="flex min-h-[22rem] items-center justify-center text-sm text-[var(--landing-muted)] sm:min-h-[26rem]">
Chargement de la démo
</div>
)
}
// Code splitting : chaque démo est chargée à la demande, côté client uniquement,
// pour qu'une page produit n'embarque que ses propres aperçus (et leurs deps lourdes).
// `next/dynamic` exige un objet littéral pour les options (pas une variable partagée).
const DEMO_COMPONENTS: Record<ProductShowcaseDemo, ComponentType> = {
"ultimail-inbox": dynamic(
() =>
import("@/components/landing/product/product-demos/ultimail-inbox-demo").then(
(m) => m.UltimailInboxDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultimail-compose": dynamic(
() =>
import("@/components/landing/product/product-demos/ultimail-compose-demo").then(
(m) => m.UltimailComposeDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultimail-automation": dynamic(
() =>
import("@/components/landing/product/product-demos/ultimail-automation-demo").then(
(m) => m.UltimailAutomationDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultidrive-browser": dynamic(
() =>
import("@/components/landing/product/product-demos/ultidrive-browser-demo").then(
(m) => m.UltidriveBrowserDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultidrive-docs": dynamic(
() =>
import("@/components/landing/product/product-demos/ultidrive-docs-demo").then(
(m) => m.UltidriveDocsDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultidrive-share": dynamic(
() =>
import("@/components/landing/product/product-demos/ultidrive-share-demo").then(
(m) => m.UltidriveShareDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultical-agenda": dynamic(
() =>
import("@/components/landing/product/product-demos/ultical-agenda-demo").then(
(m) => m.UlticalAgendaDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultical-invitations": dynamic(
() =>
import("@/components/landing/product/product-demos/ultical-invitation-demo").then(
(m) => m.UlticalInvitationDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultical-scheduling": dynamic(
() =>
import("@/components/landing/product/product-demos/ultical-scheduling-demo").then(
(m) => m.UlticalSchedulingDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultiai-chat": dynamic(
() =>
import("@/components/landing/product/product-demos/ultiai-chat-demo").then(
(m) => m.UltiaiChatDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultiai-tools": dynamic(
() =>
import("@/components/landing/product/product-demos/ultiai-tools-demo").then(
(m) => m.UltiaiToolsDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultiai-triage": dynamic(
() =>
import("@/components/landing/product/product-demos/ultiai-triage-demo").then(
(m) => m.UltiaiTriageDemo
),
{ loading: DemoLoading, ssr: false }
),
"ulticards-directory": dynamic(
() =>
import("@/components/landing/product/product-demos/ulticards-directory-demo").then(
(m) => m.UlticardsDirectoryDemo
),
{ loading: DemoLoading, ssr: false }
),
"ulticards-discovery": dynamic(
() =>
import("@/components/landing/product/product-demos/ulticards-discovery-demo").then(
(m) => m.UlticardsDiscoveryDemo
),
{ loading: DemoLoading, ssr: false }
),
"ulticards-merge": dynamic(
() =>
import("@/components/landing/product/product-demos/ulticards-merge-demo").then(
(m) => m.UlticardsMergeDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultimeet-room": dynamic(
() =>
import("@/components/landing/product/product-demos/ultimeet-room-demo").then(
(m) => m.UltimeetRoomDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultimeet-lobby": dynamic(
() =>
import("@/components/landing/product/product-demos/ultimeet-lobby-demo").then(
(m) => m.UltimeetLobbyDemo
),
{ loading: DemoLoading, ssr: false }
),
"ultimeet-collab": dynamic(
() =>
import("@/components/landing/product/product-demos/ultimeet-collab-demo").then(
(m) => m.UltimeetCollabDemo
),
{ loading: DemoLoading, ssr: false }
),
"admin-migration": dynamic(
() =>
import("@/components/landing/product/product-demos/admin-migration-demo").then(
(m) => m.AdminMigrationDemo
),
{ loading: DemoLoading, ssr: false }
),
"admin-identity": dynamic(
() =>
import("@/components/landing/product/product-demos/admin-identity-demo").then(
(m) => m.AdminIdentityDemo
),
{ loading: DemoLoading, ssr: false }
),
"admin-users": dynamic(
() =>
import("@/components/landing/product/product-demos/admin-users-demo").then(
(m) => m.AdminUsersDemo
),
{ loading: DemoLoading, ssr: false }
),
"admin-quotas": dynamic(
() =>
import("@/components/landing/product/product-demos/admin-quotas-demo").then(
(m) => m.AdminQuotasDemo
),
{ loading: DemoLoading, ssr: false }
),
"admin-policies": dynamic(
() =>
import("@/components/landing/product/product-demos/admin-policies-demo").then(
(m) => m.AdminPoliciesDemo
),
{ loading: DemoLoading, ssr: false }
),
}
export function ProductShowcases({
showcases,
accent,
}: {
showcases: ProductShowcase[]
accent: string
}) {
return (
<>
{showcases.map((showcase, index) => {
const Demo = DEMO_COMPONENTS[showcase.demo]
const reverse = showcase.reverse ?? index % 2 === 1
return (
<section
key={showcase.id}
id={showcase.id}
className="scroll-mt-20 px-4 py-16 sm:px-6 sm:py-20"
>
<div className="mx-auto flex w-full max-w-6xl flex-col gap-10 lg:gap-12">
<ProductSectionHeading
eyebrow={showcase.eyebrow}
title={showcase.title}
description={showcase.description}
accent={accent}
/>
<div
className={cn(
"grid grid-cols-1 items-center gap-8 lg:grid-cols-2 lg:gap-10",
reverse && "lg:[&>*:first-child]:order-2 lg:[&>*:last-child]:order-1"
)}
>
<LandingReveal delay={0.05}>
<ul className="flex flex-col gap-3">
{showcase.features.map((feature) => (
<li
key={feature.title}
className="landing-glass flex gap-3 rounded-xl p-4"
>
<span
className="flex size-10 shrink-0 items-center justify-center rounded-lg"
style={{
backgroundColor: `${accent}14`,
color: accent,
}}
>
<Icon icon={feature.icon} className="size-5" aria-hidden />
</span>
<div className="min-w-0">
<h3 className="font-semibold tracking-tight">{feature.title}</h3>
<p className="mt-0.5 text-sm text-[var(--landing-muted)]">
{feature.description}
</p>
</div>
</li>
))}
</ul>
</LandingReveal>
<LandingReveal delay={0.12}>
<Demo />
</LandingReveal>
</div>
</div>
</section>
)
})}
</>
)
}