From 0b5b2c7ae6d5f54cfde39624b9f1a018228b3dd7 Mon Sep 17 00:00:00 2001 From: bigtux Date: Sat, 7 Feb 2026 00:25:08 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20Fase=203=20-=20Sistema=20de=20v=C3=ADde?= =?UTF-8?q?o=20chamadas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Integração Daily.co para videochamadas - API para criar/gerenciar salas de vídeo - API de agendamentos (CRUD) - Página de sessão com controles de vídeo/áudio - Página de agenda com calendário visual - Lista de próximos agendamentos --- package-lock.json | 125 +++++++++++ package.json | 1 + src/app/api/appointments/route.ts | 109 +++++++++ src/app/api/daily/room/route.ts | 93 ++++++++ src/app/dashboard/agenda/page.tsx | 297 +++++++++++++++++++++++++ src/app/dashboard/sessao/[id]/page.tsx | 194 ++++++++++++++++ src/lib/daily.ts | 130 +++++++++++ 7 files changed, 949 insertions(+) create mode 100644 src/app/api/appointments/route.ts create mode 100644 src/app/api/daily/room/route.ts create mode 100644 src/app/dashboard/agenda/page.tsx create mode 100644 src/app/dashboard/sessao/[id]/page.tsx create mode 100644 src/lib/daily.ts diff --git a/package-lock.json b/package-lock.json index c344dd1..2e8915c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@auth/prisma-adapter": "^2.11.1", + "@daily-co/daily-js": "^0.87.0", "@prisma/client": "^6.19.2", "@stripe/stripe-js": "^8.7.0", "bcrypt": "^6.0.0", @@ -281,6 +282,15 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -329,6 +339,22 @@ "node": ">=6.9.0" } }, + "node_modules/@daily-co/daily-js": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@daily-co/daily-js/-/daily-js-0.87.0.tgz", + "integrity": "sha512-hWHdBDvJwDeg8unz+XG9hD7xamuFi5Jmsk89ASATKL4fdTDHplpxi4PG9aaPXzBZYYLTjuxUje1K7B5uUR+gzw==", + "license": "BSD-2-Clause", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@sentry/browser": "^8.33.1", + "bowser": "^2.8.1", + "dequal": "^2.0.3", + "events": "^3.1.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", @@ -1374,6 +1400,81 @@ "dev": true, "license": "MIT" }, + "node_modules/@sentry-internal/browser-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz", + "integrity": "sha512-ROgqtQfpH/82AQIpESPqPQe0UyWywKJsmVIqi3c5Fh+zkds5LUxnssTj3yNd1x+kxaPDVB023jAP+3ibNgeNDw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.55.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.55.0.tgz", + "integrity": "sha512-cP3BD/Q6pquVQ+YL+rwCnorKuTXiS9KXW8HNKu4nmmBAyf7urjs+F6Hr1k9MXP5yQ8W3yK7jRWd09Yu6DHWOiw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.55.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.55.0.tgz", + "integrity": "sha512-roCDEGkORwolxBn8xAKedybY+Jlefq3xYmgN2fr3BTnsXjSYOPC7D1/mYqINBat99nDtvgFvNfRcZPiwwZ1hSw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.55.0", + "@sentry/core": "8.55.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.55.0.tgz", + "integrity": "sha512-nIkfgRWk1091zHdu4NbocQsxZF1rv1f7bbp3tTIlZYbrH62XVZosx5iHAuZG0Zc48AETLE7K4AX9VGjvQj8i9w==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "8.55.0", + "@sentry/core": "8.55.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/browser": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.55.0.tgz", + "integrity": "sha512-1A31mCEWCjaMxJt6qGUK+aDnLDcK6AwLAZnqpSchNysGni1pSn1RWSmk9TBF8qyTds5FH8B31H480uxMPUJ7Cw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.55.0", + "@sentry-internal/feedback": "8.55.0", + "@sentry-internal/replay": "8.55.0", + "@sentry-internal/replay-canvas": "8.55.0", + "@sentry/core": "8.55.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/core": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.55.0.tgz", + "integrity": "sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -2602,6 +2703,12 @@ "node": ">= 18" } }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3033,6 +3140,15 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", @@ -3760,6 +3876,15 @@ "node": ">=0.10.0" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", diff --git a/package.json b/package.json index 17d3916..5f5b10f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@auth/prisma-adapter": "^2.11.1", + "@daily-co/daily-js": "^0.87.0", "@prisma/client": "^6.19.2", "@stripe/stripe-js": "^8.7.0", "bcrypt": "^6.0.0", diff --git a/src/app/api/appointments/route.ts b/src/app/api/appointments/route.ts new file mode 100644 index 0000000..326163f --- /dev/null +++ b/src/app/api/appointments/route.ts @@ -0,0 +1,109 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@/lib/auth'; +import { prisma } from '@/lib/prisma'; + +// Listar agendamentos +export async function GET(request: NextRequest) { + try { + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ error: 'Não autenticado' }, { status: 401 }); + } + + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + include: { children: true }, + }); + + if (!user) { + return NextResponse.json({ error: 'Usuário não encontrado' }, { status: 404 }); + } + + let appointments; + + if (user.role === 'therapist') { + // Terapeuta vê seus agendamentos + appointments = await prisma.appointment.findMany({ + where: { therapistId: user.id }, + include: { + child: { include: { parent: true } }, + therapist: true, + }, + orderBy: { scheduledAt: 'asc' }, + }); + } else { + // Pai vê agendamentos dos filhos + const childIds = user.children.map(c => c.id); + appointments = await prisma.appointment.findMany({ + where: { childId: { in: childIds } }, + include: { + child: true, + therapist: true, + }, + orderBy: { scheduledAt: 'asc' }, + }); + } + + return NextResponse.json(appointments); + } catch (error) { + console.error('Erro ao listar agendamentos:', error); + return NextResponse.json( + { error: 'Erro ao buscar agendamentos' }, + { status: 500 } + ); + } +} + +// Criar agendamento +export async function POST(request: NextRequest) { + try { + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ error: 'Não autenticado' }, { status: 401 }); + } + + // Verificar se é terapeuta + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + }); + + if (!user || (user.role !== 'therapist' && user.role !== 'admin')) { + return NextResponse.json({ error: 'Sem permissão' }, { status: 403 }); + } + + const body = await request.json(); + const { childId, scheduledAt, duration = 50, notes } = body; + + if (!childId || !scheduledAt) { + return NextResponse.json( + { error: 'childId e scheduledAt são obrigatórios' }, + { status: 400 } + ); + } + + const appointment = await prisma.appointment.create({ + data: { + childId, + therapistId: user.id, + scheduledAt: new Date(scheduledAt), + duration, + notes, + status: 'scheduled', + }, + include: { + child: true, + therapist: true, + }, + }); + + return NextResponse.json(appointment); + } catch (error) { + console.error('Erro ao criar agendamento:', error); + return NextResponse.json( + { error: 'Erro ao criar agendamento' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/daily/room/route.ts b/src/app/api/daily/room/route.ts new file mode 100644 index 0000000..089c89c --- /dev/null +++ b/src/app/api/daily/room/route.ts @@ -0,0 +1,93 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@/lib/auth'; +import { prisma } from '@/lib/prisma'; +import { createRoom, createMeetingToken, getRoom } from '@/lib/daily'; + +// Criar sala para um agendamento +export async function POST(request: NextRequest) { + try { + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ error: 'Não autenticado' }, { status: 401 }); + } + + const body = await request.json(); + const { appointmentId } = body; + + if (!appointmentId) { + return NextResponse.json({ error: 'appointmentId é obrigatório' }, { status: 400 }); + } + + // Buscar agendamento + const appointment = await prisma.appointment.findUnique({ + where: { id: appointmentId }, + include: { + child: { include: { parent: true } }, + therapist: true, + }, + }); + + if (!appointment) { + return NextResponse.json({ error: 'Agendamento não encontrado' }, { status: 404 }); + } + + // Verificar se o usuário é o pai ou terapeuta + const isParent = appointment.child.parentId === session.user.id; + const isTherapist = appointment.therapistId === session.user.id; + + if (!isParent && !isTherapist) { + return NextResponse.json({ error: 'Sem permissão' }, { status: 403 }); + } + + // Se já tem sala, retornar + if (appointment.roomUrl && appointment.roomName) { + const existingRoom = await getRoom(appointment.roomName); + if (existingRoom) { + const token = await createMeetingToken(appointment.roomName, { + userName: session.user.name || 'Participante', + isOwner: isTherapist, + }); + + return NextResponse.json({ + roomUrl: `${appointment.roomUrl}?t=${token.token}`, + roomName: appointment.roomName, + }); + } + } + + // Criar nova sala + const roomName = `iris-${appointmentId}`; + const room = await createRoom({ + name: roomName, + expiryMinutes: 90, // Sessão de 50min + buffer + maxParticipants: 4, + }); + + // Atualizar agendamento + await prisma.appointment.update({ + where: { id: appointmentId }, + data: { + roomUrl: room.url, + roomName: room.name, + }, + }); + + // Criar token + const token = await createMeetingToken(room.name, { + userName: session.user.name || 'Participante', + isOwner: isTherapist, + }); + + return NextResponse.json({ + roomUrl: `${room.url}?t=${token.token}`, + roomName: room.name, + }); + } catch (error) { + console.error('Erro ao criar sala:', error); + return NextResponse.json( + { error: 'Erro ao criar sala de vídeo' }, + { status: 500 } + ); + } +} diff --git a/src/app/dashboard/agenda/page.tsx b/src/app/dashboard/agenda/page.tsx new file mode 100644 index 0000000..691462d --- /dev/null +++ b/src/app/dashboard/agenda/page.tsx @@ -0,0 +1,297 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { useSession } from 'next-auth/react'; +import { + Sparkles, ArrowLeft, Calendar, Clock, Video, User, + ChevronLeft, ChevronRight, Plus, Loader2 +} from 'lucide-react'; + +interface Appointment { + id: string; + scheduledAt: string; + duration: number; + status: string; + roomUrl: string | null; + child: { + id: string; + name: string; + }; + therapist: { + id: string; + name: string; + }; +} + +export default function AgendaPage() { + const { data: session } = useSession(); + const [appointments, setAppointments] = useState([]); + const [loading, setLoading] = useState(true); + const [currentDate, setCurrentDate] = useState(new Date()); + + useEffect(() => { + fetchAppointments(); + }, []); + + const fetchAppointments = async () => { + try { + const response = await fetch('/api/appointments'); + if (response.ok) { + const data = await response.json(); + setAppointments(data); + } + } catch (error) { + console.error('Erro ao buscar agendamentos:', error); + } finally { + setLoading(false); + } + }; + + const getDaysInMonth = (date: Date) => { + const year = date.getFullYear(); + const month = date.getMonth(); + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const daysInMonth = lastDay.getDate(); + const startingDay = firstDay.getDay(); + + const days = []; + + // Dias do mês anterior + for (let i = 0; i < startingDay; i++) { + const prevDate = new Date(year, month, -startingDay + i + 1); + days.push({ date: prevDate, isCurrentMonth: false }); + } + + // Dias do mês atual + for (let i = 1; i <= daysInMonth; i++) { + days.push({ date: new Date(year, month, i), isCurrentMonth: true }); + } + + // Dias do próximo mês + const remainingDays = 42 - days.length; + for (let i = 1; i <= remainingDays; i++) { + days.push({ date: new Date(year, month + 1, i), isCurrentMonth: false }); + } + + return days; + }; + + const getAppointmentsForDate = (date: Date) => { + return appointments.filter(apt => { + const aptDate = new Date(apt.scheduledAt); + return aptDate.toDateString() === date.toDateString(); + }); + }; + + const formatTime = (dateStr: string) => { + return new Date(dateStr).toLocaleTimeString('pt-BR', { + hour: '2-digit', + minute: '2-digit', + }); + }; + + const isToday = (date: Date) => { + const today = new Date(); + return date.toDateString() === today.toDateString(); + }; + + const isFuture = (dateStr: string) => { + return new Date(dateStr) > new Date(); + }; + + const prevMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1)); + }; + + const nextMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1)); + }; + + const monthYear = currentDate.toLocaleDateString('pt-BR', { + month: 'long', + year: 'numeric', + }); + + const days = getDaysInMonth(currentDate); + const weekDays = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb']; + + // Próximos agendamentos + const upcomingAppointments = appointments + .filter(apt => isFuture(apt.scheduledAt)) + .slice(0, 5); + + return ( +
+ {/* Header */} +
+
+ + + +
+
+ +
+
+

Agenda

+

Seus agendamentos

+
+
+
+
+ +
+
+ {/* Calendar */} +
+
+
+

+ {monthYear} +

+
+ + +
+
+ + {/* Week days header */} +
+ {weekDays.map(day => ( +
+ {day} +
+ ))} +
+ + {/* Calendar grid */} +
+ {days.map((day, i) => { + const dayAppointments = getAppointmentsForDate(day.date); + const hasAppointments = dayAppointments.length > 0; + + return ( +
+
+ + {day.date.getDate()} + + {hasAppointments && ( +
+ {dayAppointments.slice(0, 2).map(apt => ( +
+ {formatTime(apt.scheduledAt)} +
+ ))} + {dayAppointments.length > 2 && ( +
+ +{dayAppointments.length - 2} +
+ )} +
+ )} +
+
+ ); + })} +
+
+
+ + {/* Upcoming appointments */} +
+
+

+ Próximas Sessões +

+ + {loading ? ( +
+ +
+ ) : upcomingAppointments.length === 0 ? ( +
+ +

Nenhuma sessão agendada

+
+ ) : ( +
+ {upcomingAppointments.map(apt => ( +
+
+
+

+ {apt.child.name} +

+

+ com {apt.therapist.name} +

+
+ + {apt.status === 'scheduled' ? 'Agendado' : apt.status} + +
+ +
+ + + {new Date(apt.scheduledAt).toLocaleDateString('pt-BR')} + + + + {formatTime(apt.scheduledAt)} + +
+ + +
+ ))} +
+ )} +
+
+
+
+
+ ); +} diff --git a/src/app/dashboard/sessao/[id]/page.tsx b/src/app/dashboard/sessao/[id]/page.tsx new file mode 100644 index 0000000..af43f33 --- /dev/null +++ b/src/app/dashboard/sessao/[id]/page.tsx @@ -0,0 +1,194 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { + Sparkles, ArrowLeft, Video, VideoOff, Mic, MicOff, + Phone, Settings, MessageCircle, Users, Loader2 +} from 'lucide-react'; + +export default function SessaoPage() { + const params = useParams(); + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [roomUrl, setRoomUrl] = useState(null); + const [callFrame, setCallFrame] = useState(null); + const [videoEnabled, setVideoEnabled] = useState(true); + const [audioEnabled, setAudioEnabled] = useState(true); + + const appointmentId = params.id as string; + + const initializeCall = useCallback(async () => { + try { + setLoading(true); + + // Buscar/criar sala + const response = await fetch('/api/daily/room', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ appointmentId }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Erro ao conectar'); + } + + const { roomUrl } = await response.json(); + setRoomUrl(roomUrl); + + // Carregar Daily.co iframe + const DailyIframe = (await import('@daily-co/daily-js')).default; + + const frame = DailyIframe.createFrame( + document.getElementById('call-container')!, + { + iframeStyle: { + width: '100%', + height: '100%', + border: '0', + borderRadius: '12px', + }, + showLeaveButton: true, + showFullscreenButton: true, + } + ); + + await frame.join({ url: roomUrl }); + setCallFrame(frame); + + frame.on('left-meeting', () => { + router.push('/dashboard'); + }); + + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }, [appointmentId, router]); + + useEffect(() => { + initializeCall(); + + return () => { + if (callFrame) { + callFrame.destroy(); + } + }; + }, [initializeCall]); + + const toggleVideo = () => { + if (callFrame) { + callFrame.setLocalVideo(!videoEnabled); + setVideoEnabled(!videoEnabled); + } + }; + + const toggleAudio = () => { + if (callFrame) { + callFrame.setLocalAudio(!audioEnabled); + setAudioEnabled(!audioEnabled); + } + }; + + const endCall = () => { + if (callFrame) { + callFrame.leave(); + } + router.push('/dashboard'); + }; + + if (loading) { + return ( +
+
+ +

Conectando à sessão...

+
+
+ ); + } + + if (error) { + return ( +
+
+
+
+

Erro ao conectar

+

{error}

+ + + Voltar ao Dashboard + +
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+
+ +
+ Sessão de Terapia +
+
+ + Conectado +
+
+
+ + {/* Video Container */} +
+
+
+ + {/* Controls */} +
+
+ + + + + +
+
+
+ ); +} diff --git a/src/lib/daily.ts b/src/lib/daily.ts new file mode 100644 index 0000000..4b5efd3 --- /dev/null +++ b/src/lib/daily.ts @@ -0,0 +1,130 @@ +const DAILY_API_KEY = process.env.DAILY_API_KEY; +const DAILY_API_URL = 'https://api.daily.co/v1'; + +interface DailyRoom { + id: string; + name: string; + url: string; + created_at: string; + config: { + exp?: number; + nbf?: number; + max_participants?: number; + enable_chat?: boolean; + enable_knocking?: boolean; + start_video_off?: boolean; + start_audio_off?: boolean; + }; +} + +interface CreateRoomOptions { + name?: string; + expiryMinutes?: number; + maxParticipants?: number; +} + +export async function createRoom(options: CreateRoomOptions = {}): Promise { + const { name, expiryMinutes = 60, maxParticipants = 4 } = options; + + const roomName = name || `sessao-${Date.now()}`; + const exp = Math.floor(Date.now() / 1000) + expiryMinutes * 60; + + const response = await fetch(`${DAILY_API_URL}/rooms`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${DAILY_API_KEY}`, + }, + body: JSON.stringify({ + name: roomName, + properties: { + exp, + max_participants: maxParticipants, + enable_chat: true, + enable_knocking: true, + start_video_off: false, + start_audio_off: false, + enable_screenshare: true, + enable_recording: 'cloud', + eject_at_room_exp: true, + }, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Erro ao criar sala'); + } + + return response.json(); +} + +export async function getRoom(roomName: string): Promise { + const response = await fetch(`${DAILY_API_URL}/rooms/${roomName}`, { + headers: { + Authorization: `Bearer ${DAILY_API_KEY}`, + }, + }); + + if (!response.ok) { + if (response.status === 404) return null; + throw new Error('Erro ao buscar sala'); + } + + return response.json(); +} + +export async function deleteRoom(roomName: string): Promise { + const response = await fetch(`${DAILY_API_URL}/rooms/${roomName}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${DAILY_API_KEY}`, + }, + }); + + if (!response.ok && response.status !== 404) { + throw new Error('Erro ao deletar sala'); + } +} + +interface MeetingToken { + token: string; +} + +export async function createMeetingToken( + roomName: string, + options: { + userName: string; + isOwner?: boolean; + expiryMinutes?: number; + } +): Promise { + const { userName, isOwner = false, expiryMinutes = 60 } = options; + const exp = Math.floor(Date.now() / 1000) + expiryMinutes * 60; + + const response = await fetch(`${DAILY_API_URL}/meeting-tokens`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${DAILY_API_KEY}`, + }, + body: JSON.stringify({ + properties: { + room_name: roomName, + user_name: userName, + is_owner: isOwner, + exp, + enable_screenshare: true, + start_video_off: false, + start_audio_off: false, + }, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Erro ao criar token'); + } + + return response.json(); +}