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.
136 lines
5.3 KiB
TypeScript
136 lines
5.3 KiB
TypeScript
"use client"
|
|
|
|
import { Icon } from "@iconify/react"
|
|
import { addDays, format, startOfDay } from "date-fns"
|
|
import { fr } from "date-fns/locale"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const ACCENT = "#FBBC04"
|
|
|
|
const SLOTS = ["09:00", "09:30", "10:00", "11:30", "14:00", "15:30"] as const
|
|
const SELECTED_SLOT = "10:00"
|
|
|
|
function buildDays() {
|
|
const base = startOfDay(new Date())
|
|
return Array.from({ length: 5 }, (_, index) => {
|
|
const date = addDays(base, index + 1)
|
|
return {
|
|
key: format(date, "yyyy-MM-dd"),
|
|
weekday: format(date, "EEE", { locale: fr }),
|
|
day: format(date, "d", { locale: fr }),
|
|
busy: index === 2,
|
|
}
|
|
})
|
|
}
|
|
|
|
/** Page de réservation type Calendly — aperçu statique sur les disponibilités réelles. */
|
|
export function UlticalSchedulingDemo() {
|
|
const days = buildDays()
|
|
const selectedDay = days.find((day) => !day.busy) ?? days[0]!
|
|
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
<div className="landing-glass-strong overflow-hidden rounded-2xl shadow-[0_32px_70px_-36px_rgba(30,40,90,0.45)]">
|
|
<div className="grid grid-cols-1 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]">
|
|
<div className="flex flex-col gap-4 border-b border-[var(--landing-line)] p-5 sm:border-b-0 sm:border-r">
|
|
<div className="flex items-center gap-2 text-xs font-medium text-[var(--landing-muted)]">
|
|
<img
|
|
src="/agenda-mark.svg"
|
|
alt=""
|
|
className="size-5 object-contain"
|
|
aria-hidden
|
|
/>
|
|
Camille Visiteur
|
|
</div>
|
|
<h3 className="text-lg font-semibold tracking-tight">
|
|
Rendez-vous découverte
|
|
</h3>
|
|
<ul className="flex flex-col gap-2.5 text-sm text-[var(--landing-muted)]">
|
|
<li className="flex items-center gap-2.5">
|
|
<Icon icon="mdi:clock-outline" className="size-4 shrink-0" aria-hidden />
|
|
30 minutes
|
|
</li>
|
|
<li className="flex items-center gap-2.5">
|
|
<Icon icon="mdi:video-outline" className="size-4 shrink-0" aria-hidden />
|
|
Visio UltiMeet (lien généré)
|
|
</li>
|
|
<li className="flex items-center gap-2.5">
|
|
<Icon icon="mdi:earth" className="size-4 shrink-0" aria-hidden />
|
|
Europe/Paris (GMT+2)
|
|
</li>
|
|
</ul>
|
|
<p className="mt-auto text-xs leading-relaxed text-[var(--landing-muted)]">
|
|
Les créneaux occupés sont masqués automatiquement d'après votre
|
|
agenda CalDAV — aucune double réservation.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-4 p-5">
|
|
<p className="text-sm font-medium text-[var(--landing-fg)]">
|
|
Choisissez un créneau
|
|
</p>
|
|
|
|
<div className="grid grid-cols-5 gap-1.5">
|
|
{days.map((day) => {
|
|
const active = day.key === selectedDay.key
|
|
return (
|
|
<div
|
|
key={day.key}
|
|
className={cn(
|
|
"flex flex-col items-center gap-0.5 rounded-lg border px-1 py-2 text-center transition-colors",
|
|
active
|
|
? "border-transparent text-[#202124]"
|
|
: "border-[var(--landing-line)] text-[var(--landing-fg)]",
|
|
day.busy && "opacity-40"
|
|
)}
|
|
style={active ? { backgroundColor: ACCENT } : undefined}
|
|
>
|
|
<span className="text-[10px] uppercase">{day.weekday}</span>
|
|
<span className="text-sm font-semibold tabular-nums">{day.day}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-2">
|
|
{SLOTS.map((slot) => {
|
|
const selected = slot === SELECTED_SLOT
|
|
return (
|
|
<button
|
|
key={slot}
|
|
type="button"
|
|
tabIndex={-1}
|
|
className={cn(
|
|
"h-9 rounded-lg border text-sm font-medium tabular-nums transition-colors",
|
|
selected
|
|
? "border-transparent text-[#202124]"
|
|
: "border-[var(--landing-line)] text-[var(--landing-fg)] hover:bg-[var(--landing-chip)]"
|
|
)}
|
|
style={selected ? { backgroundColor: ACCENT } : undefined}
|
|
>
|
|
{slot}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
tabIndex={-1}
|
|
className="mt-1 flex h-10 items-center justify-center gap-2 rounded-full text-sm font-semibold text-[#202124]"
|
|
style={{ backgroundColor: ACCENT }}
|
|
>
|
|
Confirmer le rendez-vous
|
|
<Icon icon="mdi:arrow-right" className="size-4" aria-hidden />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="flex items-start gap-2 text-sm text-[var(--landing-muted)]">
|
|
<Icon icon="mdi:incognito" className="mt-0.5 size-4 shrink-0" aria-hidden />
|
|
Page de réservation publique — disponibilités free/busy, rien n'est réservé.
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|