ultisuite-client/components/landing/product/product-page-shell.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

209 lines
7.5 KiB
TypeScript

"use client"
import { useRef, useState } from "react"
import Link from "next/link"
import { Icon } from "@iconify/react"
import { ProductCrossPlatformSection } from "@/components/landing/product/product-cross-platform-section"
import { ProductCta } from "@/components/landing/product/product-cta"
import { ProductFeatureGrid } from "@/components/landing/product/product-feature-grid"
import { ProductInteropSection } from "@/components/landing/product/product-interop-section"
import { ProductShowcases } from "@/components/landing/product/product-showcases"
import { ProductHero } from "@/components/landing/product/product-hero"
import { ProductHighlights } from "@/components/landing/product/product-highlights"
import { ProductIntegrations } from "@/components/landing/product/product-integrations"
import type { ProductPageData } from "@/components/landing/product/product-data"
import { useChromeIdentity } from "@/lib/hooks/use-chrome-identity"
import { getAuthentikEnrollmentUrl } from "@/lib/auth/oidc-config"
import { cn } from "@/lib/utils"
function ProductHeader({
data,
scrolled,
}: {
data: ProductPageData
scrolled: boolean
}) {
const identity = useChromeIdentity()
return (
<header
className={cn(
"sticky top-0 z-40 transition-[background-color,border-color,box-shadow,backdrop-filter] duration-300",
scrolled
? "landing-glass-strong shadow-[0_8px_30px_-18px_rgba(0,0,0,0.35)]"
: "border-b border-transparent"
)}
>
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between gap-4 px-4 sm:px-6">
<div className="flex min-w-0 items-center gap-3">
<Link
href="/"
className="flex shrink-0 items-center gap-1.5 rounded-full px-2.5 py-1.5 text-sm font-medium text-[var(--landing-muted)] transition-colors hover:bg-[var(--landing-chip)] hover:text-[var(--landing-fg)]"
>
<Icon icon="mdi:arrow-left" className="size-4" aria-hidden />
<span className="hidden sm:inline">UltiSuite</span>
</Link>
<span className="text-[var(--landing-line)]" aria-hidden>
/
</span>
<Link
href="/"
className="flex min-w-0 items-center gap-2 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
>
<img
src={data.icon}
alt=""
className="size-7 shrink-0 object-contain"
draggable={false}
aria-hidden
/>
<span className="truncate text-lg font-semibold tracking-tight">
{data.name}
</span>
</Link>
</div>
<div className="flex shrink-0 items-center gap-2">
{identity ? (
<Link
href={data.ctas.primary.href}
className="landing-cta landing-cta--primary h-9 px-4 text-sm"
style={
{
"--landing-glow-a": data.accent,
"--landing-glow-b": data.accent,
"--landing-glow-c": data.accent,
} as React.CSSProperties
}
>
{data.ctas.primary.label}
<Icon icon="mdi:arrow-right" className="size-4" aria-hidden />
</Link>
) : (
<>
<a
href={getAuthentikEnrollmentUrl()}
className="landing-cta landing-cta--ghost hidden h-9 px-4 text-sm sm:inline-flex"
>
Créer un compte
</a>
<Link
href="/login"
className="landing-cta landing-cta--primary h-9 px-4 text-sm"
style={
{
"--landing-glow-a": data.accent,
"--landing-glow-b": data.accent,
"--landing-glow-c": data.accent,
} as React.CSSProperties
}
>
Se connecter
</Link>
</>
)}
</div>
</div>
</header>
)
}
function ProductFooter({ data }: { data: ProductPageData }) {
return (
<footer className="border-t border-[var(--landing-line)] px-4 py-8 sm:px-6">
<div className="mx-auto flex w-full max-w-6xl flex-col items-center justify-between gap-4 text-sm text-[var(--landing-muted)] sm:flex-row">
<div className="flex items-center gap-2.5">
<img
src={data.icon}
alt=""
className="h-5 w-5 object-contain"
draggable={false}
aria-hidden
/>
<span className="font-semibold text-[var(--landing-fg)]">
{data.name}
</span>
<span aria-hidden>·</span>
<span>{data.tagline} UltiSuite</span>
</div>
<nav className="flex items-center gap-4" aria-label="Liens">
<Link href="/" className="transition-colors hover:text-[var(--landing-fg)]">
Accueil
</Link>
<Link
href={data.ctas.primary.href}
className="transition-colors hover:text-[var(--landing-fg)]"
>
Ouvrir l'app
</Link>
{data.ctas.secondary ? (
<Link
href={data.ctas.secondary.href}
className="transition-colors hover:text-[var(--landing-fg)]"
>
Démo
</Link>
) : null}
</nav>
</div>
</footer>
)
}
export function ProductPageShell({ data }: { data: ProductPageData }) {
const scrollRef = useRef<HTMLDivElement>(null)
const [scrolled, setScrolled] = useState(false)
const accentStyle = {
"--landing-glow-a": data.accent,
"--landing-glow-b": data.accent,
"--landing-glow-c": data.accent,
"--landing-chip": `${data.accent}1a`,
"--landing-chip-fg": data.accent,
} as React.CSSProperties
return (
<div
ref={scrollRef}
className="landing-root relative h-dvh overflow-y-auto overflow-x-hidden"
style={accentStyle}
onScroll={() => {
const top = scrollRef.current?.scrollTop ?? 0
setScrolled((prev) => (top > 8 ? true : top <= 2 ? false : prev))
}}
>
<div className="landing-backdrop" aria-hidden>
<div className="landing-orb landing-orb--a" />
<div className="landing-orb landing-orb--b" />
<div className="landing-orb landing-orb--c" />
</div>
<div className="relative z-10 flex min-h-full flex-col">
<ProductHeader data={data} scrolled={scrolled} />
<main className="flex-1">
<ProductHero data={data} />
<ProductHighlights section={data.highlightsSection} accent={data.accent} />
{data.showcases.length > 0 ? (
<ProductShowcases showcases={data.showcases} accent={data.accent} />
) : data.featureGroups ? (
<ProductFeatureGrid groups={data.featureGroups} accent={data.accent} />
) : null}
{data.crossPlatformSection ? (
<ProductCrossPlatformSection
section={data.crossPlatformSection}
accent={data.accent}
demoApp={data.crossPlatformDemo}
/>
) : null}
{data.interopSection ? (
<ProductInteropSection section={data.interopSection} accent={data.accent} />
) : null}
<ProductIntegrations integrations={data.integrations} accent={data.accent} />
<ProductCta section={data.ctaSection} accent={data.accent} />
</main>
<ProductFooter data={data} />
</div>
</div>
)
}