Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Added new layout and page components for forgot password and signup functionalities, enhancing user experience. - Integrated authentication flow handling for password recovery and account creation, utilizing dynamic metadata. - Updated login form to include links for forgot password and signup, improving navigation between authentication states. - Refactored CSS styles for login components to ensure consistent design across different authentication pages.
381 lines
14 KiB
TypeScript
381 lines
14 KiB
TypeScript
"use client"
|
|
|
|
import { ULTICARDS_APP_NAME } from "@/lib/suite/page-metadata"
|
|
import Link from "next/link"
|
|
import { Icon } from "@iconify/react"
|
|
import { LandingReveal } from "@/components/landing/landing-reveal"
|
|
import {
|
|
LANDING_APPS,
|
|
LANDING_FEATURES,
|
|
LANDING_INTEGRATIONS,
|
|
landingAppGlowVars,
|
|
} from "@/components/landing/landing-data"
|
|
import { useChromeIdentity } from "@/lib/hooks/use-chrome-identity"
|
|
import { getSignupUrl } from "@/lib/auth/oidc-config"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
function SectionHeading({
|
|
eyebrow,
|
|
title,
|
|
description,
|
|
}: {
|
|
eyebrow: string
|
|
title: React.ReactNode
|
|
description?: string
|
|
}) {
|
|
return (
|
|
<LandingReveal className="mx-auto flex max-w-2xl flex-col items-center gap-4 text-center">
|
|
<span className="rounded-full bg-[var(--landing-chip)] px-3.5 py-1 text-xs font-semibold uppercase tracking-wider text-[var(--landing-chip-fg)]">
|
|
{eyebrow}
|
|
</span>
|
|
<h2 className="text-balance text-3xl font-bold tracking-tight sm:text-4xl">
|
|
{title}
|
|
</h2>
|
|
{description ? (
|
|
<p className="text-balance text-base leading-relaxed text-[var(--landing-muted)]">
|
|
{description}
|
|
</p>
|
|
) : null}
|
|
</LandingReveal>
|
|
)
|
|
}
|
|
|
|
/* ---------- Applications ---------- */
|
|
|
|
export function LandingAppsSection() {
|
|
return (
|
|
<section id="applications" className="scroll-mt-20 px-4 py-20 sm:px-6">
|
|
<div className="mx-auto flex w-full max-w-6xl flex-col gap-12">
|
|
<SectionHeading
|
|
eyebrow="Applications"
|
|
title={
|
|
<>
|
|
Une suite <span className="landing-gradient-text">connectée</span>,
|
|
pas une collection d'outils
|
|
</>
|
|
}
|
|
description="Chaque application partage la même identité, les mêmes contacts et le même stockage. Ouvrez, l'écosystème suit."
|
|
/>
|
|
|
|
<ul className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
{LANDING_APPS.map((app, index) => {
|
|
const card = (
|
|
<div
|
|
className={cn(
|
|
"landing-glass landing-halo-card flex h-full flex-col gap-3 rounded-2xl p-5",
|
|
app.soon && "opacity-75"
|
|
)}
|
|
style={landingAppGlowVars(app.gradient) as React.CSSProperties}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<span
|
|
className="flex size-11 items-center justify-center rounded-xl"
|
|
style={{ backgroundColor: `${app.accent}1a` }}
|
|
>
|
|
{app.iconDark ? (
|
|
<>
|
|
<img
|
|
src={app.icon}
|
|
alt=""
|
|
className="size-7 object-contain dark:hidden"
|
|
draggable={false}
|
|
aria-hidden
|
|
/>
|
|
<img
|
|
src={app.iconDark}
|
|
alt=""
|
|
className="hidden size-7 object-contain dark:block"
|
|
draggable={false}
|
|
aria-hidden
|
|
/>
|
|
</>
|
|
) : (
|
|
<img
|
|
src={app.icon}
|
|
alt=""
|
|
className="size-7 object-contain"
|
|
draggable={false}
|
|
aria-hidden
|
|
/>
|
|
)}
|
|
</span>
|
|
{app.soon ? (
|
|
<span className="rounded-full border border-[var(--landing-line)] px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--landing-muted)]">
|
|
Bientôt
|
|
</span>
|
|
) : (
|
|
<Icon
|
|
icon="mdi:arrow-top-right"
|
|
className="size-4 text-[var(--landing-muted)] transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
|
|
aria-hidden
|
|
/>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-wider text-[var(--landing-muted)]">
|
|
{app.tagline}
|
|
</p>
|
|
<h3 className="text-lg font-semibold tracking-tight">
|
|
{app.name}
|
|
</h3>
|
|
</div>
|
|
<p className="text-sm leading-relaxed text-[var(--landing-muted)]">
|
|
{app.description}
|
|
</p>
|
|
</div>
|
|
)
|
|
|
|
return (
|
|
<LandingReveal as="li" key={app.name} delay={(index % 4) * 0.07}>
|
|
{app.href && !app.soon ? (
|
|
<Link href={app.productHref ?? app.href} className="group block h-full rounded-2xl outline-none focus-visible:ring-2 focus-visible:ring-ring/50">
|
|
{card}
|
|
</Link>
|
|
) : (
|
|
card
|
|
)}
|
|
</LandingReveal>
|
|
)
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
/* ---------- Fonctionnalités (bento) ---------- */
|
|
|
|
export function LandingFeaturesSection() {
|
|
return (
|
|
<section id="fonctionnalites" className="scroll-mt-20 px-4 py-20 sm:px-6">
|
|
<div className="mx-auto flex w-full max-w-6xl flex-col gap-12">
|
|
<SectionHeading
|
|
eyebrow="Fonctionnalités"
|
|
title={
|
|
<>
|
|
Conçue pour remplacer,
|
|
<br className="hidden sm:block" /> pensée pour{" "}
|
|
<span className="landing-gradient-text">durer</span>
|
|
</>
|
|
}
|
|
description="Tout ce que vous attendez d'une suite moderne — sans céder vos données pour l'obtenir."
|
|
/>
|
|
|
|
<ul className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
{LANDING_FEATURES.map((feature, index) => (
|
|
<LandingReveal
|
|
as="li"
|
|
key={feature.title}
|
|
delay={(index % 4) * 0.07}
|
|
className={cn(feature.wide && "sm:col-span-2")}
|
|
>
|
|
<div className="landing-glass landing-halo-card flex h-full flex-col gap-3 rounded-2xl p-6">
|
|
<span className="flex size-11 items-center justify-center rounded-xl bg-[var(--landing-chip)] text-[var(--landing-chip-fg)]">
|
|
<Icon icon={feature.icon} className="size-6" aria-hidden />
|
|
</span>
|
|
<h3 className="text-lg font-semibold tracking-tight">
|
|
{feature.title}
|
|
</h3>
|
|
<p className="text-sm leading-relaxed text-[var(--landing-muted)]">
|
|
{feature.description}
|
|
</p>
|
|
</div>
|
|
</LandingReveal>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
/* ---------- Souveraineté + stats ---------- */
|
|
|
|
const STATS = [
|
|
{ value: "100 %", label: "open source, auditable" },
|
|
{ value: "0", label: "tracker, télémétrie ou pub" },
|
|
{ value: "1", label: "identité SSO pour toute la suite" },
|
|
{ value: "∞", label: "contrôle : c'est votre serveur" },
|
|
]
|
|
|
|
export function LandingSovereigntySection() {
|
|
return (
|
|
<section id="souverainete" className="scroll-mt-20 px-4 py-20 sm:px-6">
|
|
<div className="mx-auto w-full max-w-6xl">
|
|
<LandingReveal>
|
|
<div className="landing-glass-strong relative overflow-hidden rounded-3xl px-6 py-12 sm:px-12 sm:py-16">
|
|
<div
|
|
className="pointer-events-none absolute inset-0 opacity-60"
|
|
style={{
|
|
background:
|
|
"radial-gradient(60% 90% at 15% 0%, color-mix(in oklab, var(--landing-glow-a) 22%, transparent), transparent 70%), radial-gradient(50% 80% at 90% 100%, color-mix(in oklab, var(--landing-glow-b) 18%, transparent), transparent 70%)",
|
|
}}
|
|
aria-hidden
|
|
/>
|
|
<div className="relative flex flex-col gap-10">
|
|
<div className="flex max-w-2xl flex-col gap-4">
|
|
<span className="inline-flex w-fit items-center gap-2 rounded-full bg-[var(--landing-chip)] px-3.5 py-1 text-xs font-semibold uppercase tracking-wider text-[var(--landing-chip-fg)]">
|
|
<Icon icon="mdi:shield-check-outline" className="size-4" aria-hidden />
|
|
Souveraineté
|
|
</span>
|
|
<h2 className="text-balance text-3xl font-bold tracking-tight sm:text-4xl">
|
|
Vos données ne quittent jamais{" "}
|
|
<span className="landing-gradient-text">votre territoire</span>
|
|
</h2>
|
|
<p className="text-base leading-relaxed text-[var(--landing-muted)]">
|
|
Déployée chez vous ou chez l'hébergeur de votre choix, la
|
|
suite Ulti garde mails, fichiers et identités sous votre
|
|
juridiction. Migration progressive : rattachez vos comptes
|
|
existants et avancez à votre rythme.
|
|
</p>
|
|
</div>
|
|
|
|
<dl className="grid grid-cols-2 gap-6 lg:grid-cols-4">
|
|
{STATS.map((stat, index) => (
|
|
<LandingReveal key={stat.label} delay={index * 0.08}>
|
|
<div className="flex flex-col gap-1 border-l-2 border-[var(--landing-glow-a)] pl-4">
|
|
<dt className="order-2 text-sm text-[var(--landing-muted)]">
|
|
{stat.label}
|
|
</dt>
|
|
<dd className="order-1 text-3xl font-bold tracking-tight sm:text-4xl">
|
|
{stat.value}
|
|
</dd>
|
|
</div>
|
|
</LandingReveal>
|
|
))}
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</LandingReveal>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
/* ---------- Intégrations (marquee) ---------- */
|
|
|
|
function IntegrationsTrack() {
|
|
return (
|
|
<div className="landing-marquee__track" aria-hidden>
|
|
{LANDING_INTEGRATIONS.map((item) => (
|
|
<span
|
|
key={item.label}
|
|
className="flex shrink-0 items-center gap-2 text-sm font-medium text-[var(--landing-muted)]"
|
|
>
|
|
<Icon icon={item.icon} className="size-5" aria-hidden />
|
|
{item.label}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function LandingIntegrationsSection() {
|
|
return (
|
|
<section className="px-0 pt-4 pb-10">
|
|
<LandingReveal className="mx-auto flex w-full max-w-6xl flex-col gap-6">
|
|
<p className="text-center text-xs font-semibold uppercase tracking-widest text-[var(--landing-muted)]">
|
|
S'intègre avec vos standards ouverts
|
|
</p>
|
|
<div
|
|
className="landing-marquee"
|
|
role="list"
|
|
aria-label={LANDING_INTEGRATIONS.map((i) => i.label).join(", ")}
|
|
>
|
|
<IntegrationsTrack />
|
|
<IntegrationsTrack />
|
|
</div>
|
|
</LandingReveal>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
/* ---------- CTA final + footer ---------- */
|
|
|
|
export function LandingFooter() {
|
|
const identity = useChromeIdentity()
|
|
|
|
return (
|
|
<footer className="px-4 pb-10 pt-20 sm:px-6">
|
|
<div className="mx-auto flex w-full max-w-6xl flex-col gap-14">
|
|
<LandingReveal className="flex flex-col items-center gap-6 text-center">
|
|
<h2 className="text-balance text-3xl font-bold tracking-tight sm:text-5xl">
|
|
Prêt à reprendre{" "}
|
|
<span className="landing-gradient-text">le contrôle</span> ?
|
|
</h2>
|
|
<p className="max-w-xl text-balance text-base text-[var(--landing-muted)]">
|
|
{identity
|
|
? "Votre suite est déjà prête. Ouvrez une application et continuez là où vous en étiez."
|
|
: "Créez votre compte et découvrez une suite complète qui travaille pour vous — pas l'inverse."}
|
|
</p>
|
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
|
{identity ? (
|
|
<>
|
|
<Link
|
|
href="/mail/inbox"
|
|
className="landing-cta landing-cta--primary h-12 px-7 text-base"
|
|
>
|
|
Ouvrir Ultimail
|
|
<Icon icon="mdi:arrow-right" className="size-5" aria-hidden />
|
|
</Link>
|
|
<Link
|
|
href="/chat"
|
|
className="landing-cta landing-cta--ghost h-12 px-7 text-base"
|
|
>
|
|
Parler à UltiAI
|
|
</Link>
|
|
</>
|
|
) : (
|
|
<>
|
|
<a
|
|
href={getSignupUrl()}
|
|
className="landing-cta landing-cta--primary h-12 px-7 text-base"
|
|
>
|
|
Créer un compte
|
|
<Icon icon="mdi:arrow-right" className="size-5" aria-hidden />
|
|
</a>
|
|
<Link
|
|
href="/login"
|
|
className="landing-cta landing-cta--ghost h-12 px-7 text-base"
|
|
>
|
|
Se connecter
|
|
</Link>
|
|
</>
|
|
)}
|
|
</div>
|
|
</LandingReveal>
|
|
|
|
<div className="flex flex-col items-center justify-between gap-4 border-t border-[var(--landing-line)] pt-8 text-sm text-[var(--landing-muted)] sm:flex-row">
|
|
<div className="flex items-center gap-2.5">
|
|
<img
|
|
src="/ultisuite-mark.svg"
|
|
alt=""
|
|
className="h-6 w-6 object-contain"
|
|
draggable={false}
|
|
aria-hidden
|
|
/>
|
|
<span className="font-semibold text-[var(--landing-fg)]">
|
|
UltiSuite
|
|
</span>
|
|
<span aria-hidden>·</span>
|
|
<span>Suite collaborative souveraine et open source</span>
|
|
</div>
|
|
<nav className="flex items-center gap-4" aria-label="Liens">
|
|
<Link href="/mail" className="transition-colors hover:text-[var(--landing-fg)]">
|
|
Ultimail
|
|
</Link>
|
|
<Link href="/drive" className="transition-colors hover:text-[var(--landing-fg)]">
|
|
UltiDrive
|
|
</Link>
|
|
<Link href="/contacts" className="transition-colors hover:text-[var(--landing-fg)]">
|
|
{ULTICARDS_APP_NAME}
|
|
</Link>
|
|
<Link href="/chat" className="transition-colors hover:text-[var(--landing-fg)]">
|
|
UltiAI
|
|
</Link>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
)
|
|
}
|