Files
calendar/frontend(experimental)/newchat/components/room-dialogs.tsx
T
ben bf601846db new frontend
ik ben zo cool
2026-08-19 09:33:48 +02:00

225 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import type React from "react"
import { useState } from "react"
import { Loader2, Search } from "lucide-react"
import { GlassModal, fieldClass, labelClass } from "./glass-modal"
import * as api from "@/lib/api"
import { ApiError } from "@/lib/api"
function randomCode() {
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join("")
}
interface CreateRoomDialogProps {
onClose: () => void
onCreated: (roomId: number) => void
}
export function CreateRoomDialog({ onClose, onCreated }: CreateRoomDialogProps) {
const [name, setName] = useState("")
const [code, setCode] = useState(randomCode())
const [error, setError] = useState<string | null>(null)
const [submitting, setSubmitting] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
if (!/^[A-Za-z0-9]{6,32}$/.test(code)) {
setError("Invite code must be 632 letters or numbers.")
return
}
setSubmitting(true)
try {
const res = await api.createRoom(name.trim(), code)
onCreated(res.room_id)
onClose()
} catch (err) {
setError(err instanceof ApiError ? err.message : "Could not create the room.")
} finally {
setSubmitting(false)
}
}
return (
<GlassModal title="Create a room" onClose={onClose}>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div>
<label className={labelClass} htmlFor="room-name">
Room name
</label>
<input
id="room-name"
required
value={name}
onChange={(e) => setName(e.target.value)}
className={fieldClass}
placeholder="e.g. Design Team"
/>
</div>
<div>
<label className={labelClass} htmlFor="room-code">
Invite code
</label>
<div className="flex gap-2">
<input
id="room-code"
required
value={code}
onChange={(e) => setCode(e.target.value)}
className={fieldClass}
placeholder="632 letters/numbers"
/>
<button
type="button"
onClick={() => setCode(randomCode())}
className="shrink-0 rounded-xl border border-white/20 bg-white/10 px-3 text-sm transition-colors hover:bg-white/20"
>
Random
</button>
</div>
<p className="mt-1 text-xs text-white/60">Share this code so others can join.</p>
</div>
{error && (
<div className="rounded-xl border border-red-400/40 bg-red-500/20 px-4 py-2.5 text-sm">
{error}
</div>
)}
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="rounded-xl border border-white/20 bg-white/10 px-4 py-2.5 text-sm font-medium transition-colors hover:bg-white/20"
>
Cancel
</button>
<button
type="submit"
disabled={submitting}
className="flex items-center gap-2 rounded-xl bg-blue-500 px-5 py-2.5 text-sm font-medium transition-colors hover:bg-blue-600 disabled:opacity-70"
>
{submitting && <Loader2 className="h-4 w-4 animate-spin" />}
Create
</button>
</div>
</form>
</GlassModal>
)
}
interface JoinRoomDialogProps {
onClose: () => void
onJoined: (roomId: number) => void
}
export function JoinRoomDialog({ onClose, onJoined }: JoinRoomDialogProps) {
const [code, setCode] = useState("")
const [preview, setPreview] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
const [searching, setSearching] = useState(false)
const [submitting, setSubmitting] = useState(false)
const handleSearch = async () => {
setError(null)
setPreview(null)
if (!code.trim()) return
setSearching(true)
try {
const res = await api.searchRoom(code.trim())
if (res.status === "found" && res.room_name) {
setPreview(res.room_name)
} else {
setError("No room found with that invite code.")
}
} catch (err) {
setError(err instanceof ApiError ? err.message : "Search failed.")
} finally {
setSearching(false)
}
}
const handleJoin = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
setSubmitting(true)
try {
const res = await api.joinRoom(code.trim())
if (res.status === "joined" && res.room_id) {
onJoined(res.room_id)
onClose()
} else {
setError("Could not join — the room was not found or you're already a member.")
}
} catch (err) {
setError(err instanceof ApiError ? err.message : "Could not join the room.")
} finally {
setSubmitting(false)
}
}
return (
<GlassModal title="Join a room" onClose={onClose}>
<form onSubmit={handleJoin} className="flex flex-col gap-4">
<div>
<label className={labelClass} htmlFor="join-code">
Invite code
</label>
<div className="flex gap-2">
<input
id="join-code"
required
value={code}
onChange={(e) => {
setCode(e.target.value)
setPreview(null)
}}
className={fieldClass}
placeholder="Enter code"
/>
<button
type="button"
onClick={handleSearch}
disabled={searching}
className="flex shrink-0 items-center gap-1.5 rounded-xl border border-white/20 bg-white/10 px-3 text-sm transition-colors hover:bg-white/20 disabled:opacity-70"
>
{searching ? <Loader2 className="h-4 w-4 animate-spin" /> : <Search className="h-4 w-4" />}
</button>
</div>
</div>
{preview && (
<div className="rounded-xl border border-emerald-400/40 bg-emerald-500/20 px-4 py-2.5 text-sm">
Found room: <strong>{preview}</strong>
</div>
)}
{error && (
<div className="rounded-xl border border-red-400/40 bg-red-500/20 px-4 py-2.5 text-sm">
{error}
</div>
)}
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="rounded-xl border border-white/20 bg-white/10 px-4 py-2.5 text-sm font-medium transition-colors hover:bg-white/20"
>
Cancel
</button>
<button
type="submit"
disabled={submitting}
className="flex items-center gap-2 rounded-xl bg-blue-500 px-5 py-2.5 text-sm font-medium transition-colors hover:bg-blue-600 disabled:opacity-70"
>
{submitting && <Loader2 className="h-4 w-4 animate-spin" />}
Join
</button>
</div>
</form>
</GlassModal>
)
}