Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
182 lines
6.0 KiB
TypeScript
182 lines
6.0 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { Loader2, Video } from "lucide-react"
|
|
import { toast } from "sonner"
|
|
import { MeetHeader } from "@/components/meet/meet-header"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import {
|
|
useCreateMeetRoom,
|
|
useMeetConfig,
|
|
useMeetRoomToken,
|
|
} from "@/lib/api/hooks/use-meet-queries"
|
|
import { meetRoomPath } from "@/lib/meet/meet-url"
|
|
|
|
export function MeetLobby() {
|
|
const router = useRouter()
|
|
const { data: config, isLoading } = useMeetConfig()
|
|
const createRoom = useCreateMeetRoom()
|
|
const roomToken = useMeetRoomToken()
|
|
const [roomName, setRoomName] = useState("")
|
|
const [joinRoom, setJoinRoom] = useState("")
|
|
const pending = createRoom.isPending || roomToken.isPending
|
|
|
|
const startInstantMeeting = async () => {
|
|
try {
|
|
const token = await createRoom.mutateAsync(undefined)
|
|
router.push(meetRoomPath(token.room, token.token))
|
|
} catch {
|
|
toast.error("Impossible de créer la réunion")
|
|
}
|
|
}
|
|
|
|
const startNamedMeeting = async () => {
|
|
const name = roomName.trim()
|
|
if (!name) {
|
|
toast.message("Indiquez un nom de salle")
|
|
return
|
|
}
|
|
try {
|
|
const token = await createRoom.mutateAsync({ name })
|
|
router.push(meetRoomPath(token.room, token.token))
|
|
} catch {
|
|
toast.error("Impossible de créer la réunion")
|
|
}
|
|
}
|
|
|
|
const joinExistingRoom = async () => {
|
|
const room = joinRoom.trim()
|
|
if (!room) {
|
|
toast.message("Indiquez le code ou le nom de la salle")
|
|
return
|
|
}
|
|
try {
|
|
const token = await roomToken.mutateAsync(room)
|
|
router.push(meetRoomPath(token.room, token.token))
|
|
} catch {
|
|
toast.error("Impossible de rejoindre la salle")
|
|
}
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-dvh flex-col">
|
|
<MeetHeader />
|
|
<div className="flex flex-1 items-center justify-center text-sm text-muted-foreground">
|
|
<Loader2 className="mr-2 size-4 animate-spin" aria-hidden />
|
|
Chargement UltiMeet…
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!config?.enabled) {
|
|
return (
|
|
<div className="flex h-dvh flex-col">
|
|
<MeetHeader />
|
|
<div className="flex flex-1 flex-col items-center justify-center gap-3 px-6 text-center">
|
|
<Video className="size-10 text-muted-foreground" aria-hidden />
|
|
<p className="max-w-md text-sm text-muted-foreground">
|
|
UltiMeet n'est pas activé sur cette instance. Activez Jitsi dans
|
|
l'administration ou contactez votre administrateur.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-dvh flex-col">
|
|
<MeetHeader />
|
|
<main className="mx-auto flex w-full max-w-lg flex-1 flex-col justify-center gap-8 px-4 py-8">
|
|
<div className="space-y-2 text-center">
|
|
<h1 className="text-2xl font-medium tracking-tight">Visioconférence sécurisée</h1>
|
|
<p className="text-sm leading-relaxed text-muted-foreground">
|
|
Réunions chiffrées hébergées sur votre infrastructure, intégrées à
|
|
l'Agenda et à la suite Ulti.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4 rounded-2xl border border-border/70 bg-card p-5 shadow-sm">
|
|
<Button
|
|
type="button"
|
|
className="h-11 w-full rounded-full text-base"
|
|
disabled={pending}
|
|
onClick={() => void startInstantMeeting()}
|
|
>
|
|
{createRoom.isPending ? (
|
|
<Loader2 className="mr-2 size-4 animate-spin" aria-hidden />
|
|
) : (
|
|
<Video className="mr-2 size-4" aria-hidden />
|
|
)}
|
|
Nouvelle réunion instantanée
|
|
</Button>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="meet-room-name">Réunion planifiée (nom de salle)</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
id="meet-room-name"
|
|
value={roomName}
|
|
onChange={(e) => setRoomName(e.target.value)}
|
|
placeholder="equipe-produit"
|
|
autoComplete="off"
|
|
disabled={pending}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") void startNamedMeeting()
|
|
}}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
className="shrink-0 rounded-full"
|
|
disabled={pending}
|
|
onClick={() => void startNamedMeeting()}
|
|
>
|
|
Créer
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t border-border/60 pt-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="meet-join-room">Rejoindre avec un code</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
id="meet-join-room"
|
|
value={joinRoom}
|
|
onChange={(e) => setJoinRoom(e.target.value)}
|
|
placeholder="abc12345"
|
|
autoComplete="off"
|
|
disabled={pending}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") void joinExistingRoom()
|
|
}}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
className="shrink-0 rounded-full"
|
|
disabled={pending}
|
|
onClick={() => void joinExistingRoom()}
|
|
>
|
|
Rejoindre
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-center text-xs text-muted-foreground">
|
|
{config.transcription_enabled
|
|
? `Transcription ${config.transcription_mode === "queued" ? "différée" : "en direct"} disponible.`
|
|
: "Les liens Agenda ouvrent directement la salle correspondante."}
|
|
</p>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|