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.
161 lines
6.1 KiB
TypeScript
161 lines
6.1 KiB
TypeScript
"use client"
|
|
|
|
import { Icon } from "@iconify/react"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const ACCENT = "#34A853"
|
|
|
|
type Participant = {
|
|
name: string
|
|
color: string
|
|
muted: boolean
|
|
cameraOff?: boolean
|
|
speaking?: boolean
|
|
}
|
|
|
|
const PARTICIPANTS: Participant[] = [
|
|
{ name: "Léa Fontaine", color: "#34A853", muted: false, speaking: true },
|
|
{ name: "Vincent Morel", color: "#4285F4", muted: true },
|
|
{ name: "Thomas Giraud", color: "#EA4335", muted: false, cameraOff: true },
|
|
{ name: "Camille Visiteur", color: "#FBBC04", muted: true },
|
|
{ name: "Sofia Rossi", color: "#A142F4", muted: false },
|
|
{ name: "Karim Benali", color: "#00ACC1", muted: true, cameraOff: true },
|
|
]
|
|
|
|
function initials(name: string) {
|
|
return name
|
|
.split(" ")
|
|
.map((part) => part.charAt(0))
|
|
.slice(0, 2)
|
|
.join("")
|
|
}
|
|
|
|
function ParticipantTile({ participant }: { participant: Participant }) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"relative flex items-center justify-center overflow-hidden rounded-xl bg-[var(--landing-chip)]/50",
|
|
participant.speaking && "ring-2"
|
|
)}
|
|
style={
|
|
participant.speaking
|
|
? ({ boxShadow: `inset 0 0 0 2px ${ACCENT}` } as React.CSSProperties)
|
|
: undefined
|
|
}
|
|
>
|
|
{participant.cameraOff ? (
|
|
<span
|
|
className="flex size-12 items-center justify-center rounded-full text-base font-semibold text-white"
|
|
style={{ backgroundColor: participant.color }}
|
|
aria-hidden
|
|
>
|
|
{initials(participant.name)}
|
|
</span>
|
|
) : (
|
|
<div
|
|
className="absolute inset-0"
|
|
style={{
|
|
background: `radial-gradient(circle at 50% 35%, ${participant.color}33, transparent 70%)`,
|
|
}}
|
|
aria-hidden
|
|
>
|
|
<span
|
|
className="absolute left-1/2 top-[38%] flex size-12 -translate-x-1/2 items-center justify-center rounded-full text-base font-semibold text-white"
|
|
style={{ backgroundColor: participant.color }}
|
|
>
|
|
{initials(participant.name)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="absolute bottom-1.5 left-1.5 flex items-center gap-1 rounded-md bg-black/55 px-1.5 py-0.5 text-[11px] font-medium text-white">
|
|
<Icon
|
|
icon={participant.muted ? "mdi:microphone-off" : "mdi:microphone"}
|
|
className={cn("size-3", participant.muted && "text-[#EA4335]")}
|
|
aria-hidden
|
|
/>
|
|
<span className="max-w-[7rem] truncate">{participant.name}</span>
|
|
</div>
|
|
|
|
{participant.speaking ? (
|
|
<span
|
|
className="absolute right-1.5 top-1.5 flex size-5 items-center justify-center rounded-full"
|
|
style={{ backgroundColor: ACCENT }}
|
|
aria-hidden
|
|
>
|
|
<Icon icon="mdi:volume-high" className="size-3 text-white" />
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const CONTROLS: { icon: string; label: string; primary?: boolean }[] = [
|
|
{ icon: "mdi:microphone", label: "Micro" },
|
|
{ icon: "mdi:video", label: "Caméra" },
|
|
{ icon: "mdi:monitor-share", label: "Partager", primary: true },
|
|
{ icon: "mdi:hand-back-left-outline", label: "Main" },
|
|
{ icon: "mdi:message-outline", label: "Chat" },
|
|
]
|
|
|
|
/** Aperçu statique d'une salle de réunion UltiMeet — grille de participants et contrôles. */
|
|
export function UltimeetRoomDemo() {
|
|
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="flex items-center gap-2.5 border-b border-[var(--landing-line)] px-4 py-3">
|
|
<img src="/ultimeet-mark.svg" alt="" className="size-5 object-contain" aria-hidden />
|
|
<span className="text-sm font-semibold tracking-tight">Atelier Nord — point hebdo</span>
|
|
<span className="ml-auto flex items-center gap-1 text-[11px] font-medium text-[var(--landing-muted)]">
|
|
<Icon icon="mdi:lock-check" className="size-3.5" style={{ color: ACCENT }} aria-hidden />
|
|
Chiffré
|
|
</span>
|
|
<span className="flex items-center gap-1 text-[11px] font-medium tabular-nums text-[var(--landing-muted)]">
|
|
<Icon icon="mdi:clock-outline" className="size-3.5" aria-hidden />
|
|
12:47
|
|
</span>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-2 bg-[var(--landing-bg)] p-3 [grid-auto-rows:9rem] sm:grid-cols-3 sm:[grid-auto-rows:9.5rem]">
|
|
{PARTICIPANTS.map((participant) => (
|
|
<ParticipantTile key={participant.name} participant={participant} />
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex items-center justify-center gap-2 border-t border-[var(--landing-line)] bg-[var(--landing-chip)]/30 px-4 py-3">
|
|
{CONTROLS.map((control) => (
|
|
<button
|
|
key={control.label}
|
|
type="button"
|
|
tabIndex={-1}
|
|
aria-label={control.label}
|
|
className={cn(
|
|
"flex size-10 items-center justify-center rounded-full border transition-colors",
|
|
control.primary
|
|
? "border-transparent text-white"
|
|
: "border-[var(--landing-line)] text-[var(--landing-fg)] hover:bg-[var(--landing-chip)]"
|
|
)}
|
|
style={control.primary ? { backgroundColor: ACCENT } : undefined}
|
|
>
|
|
<Icon icon={control.icon} className="size-5" aria-hidden />
|
|
</button>
|
|
))}
|
|
<button
|
|
type="button"
|
|
tabIndex={-1}
|
|
aria-label="Quitter"
|
|
className="ml-1 flex h-10 items-center gap-1.5 rounded-full bg-[#EA4335] px-4 text-sm font-semibold text-white"
|
|
>
|
|
<Icon icon="mdi:phone-hangup" className="size-5" aria-hidden />
|
|
<span className="hidden sm:inline">Quitter</span>
|
|
</button>
|
|
</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 />
|
|
Aperçu statique — visio WebRTC chiffrée, rien n'est diffusé.
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|