Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- 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.
257 lines
8.2 KiB
TypeScript
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>
|
|
)
|
|
})}
|
|
</>
|
|
)
|
|
}
|