"use client" import * as React from "react" import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { z } from "zod/v4" import { zodResolver } from "@hookform/resolvers/zod" import { Hash, Volume2, Megaphone, Lock, FolderOpen } from "lucide-react" import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { Switch } from "@/components/ui/switch" import { cn } from "@/lib/utils" import { createChannel } from "@/app/actions/conversations" import { listCategories } from "@/app/actions/channel-categories" const channelSchema = z.object({ name: z .string() .min(2, "Name must be at least 2 characters") .max(50, "Name must be less than 50 characters") .regex( /^[a-z0-9-]+$/, "Lowercase letters, numbers, and hyphens only" ), type: z.enum(["text", "voice", "announcement"]), categoryId: z.string().nullable(), isPrivate: z.boolean(), }) type ChannelFormData = z.infer const channelTypes = [ { value: "text", label: "Text", icon: Hash, description: "Send messages, images, GIFs, and files", disabled: false, }, { value: "voice", label: "Voice", icon: Volume2, description: "Hang out together with voice, video, and screen share", disabled: true, }, { value: "announcement", label: "Announcement", icon: Megaphone, description: "Important updates that only admins can post", disabled: false, }, ] as const type CategoryData = { readonly id: string readonly name: string readonly position: number readonly channelCount: number } type CreateChannelDialogProps = { readonly open: boolean readonly onOpenChange: (open: boolean) => void } export function CreateChannelDialog({ open, onOpenChange, }: CreateChannelDialogProps) { const router = useRouter() const [isSubmitting, setIsSubmitting] = React.useState(false) const [categories, setCategories] = React.useState([]) const [loadingCategories, setLoadingCategories] = React.useState(true) React.useEffect(() => { async function loadCategories() { if (open) { const result = await listCategories() if (result.success && result.data) { setCategories( result.data.map((cat) => ({ id: cat.id, name: cat.name, position: cat.position, channelCount: cat.channelCount, })) ) } setLoadingCategories(false) } } loadCategories() }, [open]) const form = useForm({ resolver: zodResolver(channelSchema), defaultValues: { name: "", type: "text", categoryId: null, isPrivate: false, }, }) const onSubmit = async (data: ChannelFormData) => { setIsSubmitting(true) const result = await createChannel({ name: data.name, type: data.type, categoryId: data.categoryId, isPrivate: data.isPrivate, }) if (result.success && result.data) { form.reset() onOpenChange(false) router.push(`/dashboard/conversations/${result.data.channelId}`) router.refresh() } else { form.setError("root", { message: result.error ?? "Failed to create channel", }) } setIsSubmitting(false) } return ( Create Channel
{/* channel type - vertical radio cards */} ( Channel Type {channelTypes.map((ct) => { const Icon = ct.icon const selected = field.value === ct.value return ( ) })} )} /> {/* channel name with # prefix */} ( Channel Name
field.onChange( e.target.value .toLowerCase() .replace(/[^a-z0-9-]/g, "-") ) } />
)} /> {/* category selector */} ( Category )} /> {/* private toggle */} (
Private Channel

Only selected members and roles will be able to view this channel.

)} /> {form.formState.errors.root && (

{form.formState.errors.root.message}

)}
) }