"use client"; import { useState, useEffect, type SyntheticEvent } from "react"; import type { ReactElement } from "react"; import type { Domain } from "@mosaic/shared"; import { DomainList } from "@/components/domains/DomainList"; import { fetchDomains, createDomain, deleteDomain } from "@/lib/api/domains"; import type { CreateDomainDto } from "@/lib/api/domains"; import { useWorkspaceId } from "@/lib/hooks"; /* --------------------------------------------------------------------------- Slug generation helper --------------------------------------------------------------------------- */ function generateSlug(name: string): string { return name .toLowerCase() .trim() .replace(/[^a-z0-9\s-]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-") .slice(0, 100); } /* --------------------------------------------------------------------------- Create Domain Dialog --------------------------------------------------------------------------- */ interface CreateDomainDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onSubmit: (data: CreateDomainDto) => Promise; isSubmitting: boolean; } function CreateDomainDialog({ open, onOpenChange, onSubmit, isSubmitting, }: CreateDomainDialogProps): ReactElement | null { const [name, setName] = useState(""); const [slug, setSlug] = useState(""); const [slugTouched, setSlugTouched] = useState(false); const [description, setDescription] = useState(""); const [formError, setFormError] = useState(null); function resetForm(): void { setName(""); setSlug(""); setSlugTouched(false); setDescription(""); setFormError(null); } function handleNameChange(value: string): void { setName(value); if (!slugTouched) { setSlug(generateSlug(value)); } } function handleSlugChange(value: string): void { setSlugTouched(true); setSlug(value.toLowerCase().replace(/[^a-z0-9-]/g, "")); } async function handleSubmit(e: SyntheticEvent): Promise { e.preventDefault(); setFormError(null); const trimmedName = name.trim(); if (!trimmedName) { setFormError("Domain name is required."); return; } const trimmedSlug = slug.trim(); if (!trimmedSlug) { setFormError("Slug is required."); return; } if (!/^[a-z0-9-]+$/.test(trimmedSlug)) { setFormError("Slug must contain only lowercase letters, numbers, and hyphens."); return; } try { const payload: CreateDomainDto = { name: trimmedName, slug: trimmedSlug }; const trimmedDesc = description.trim(); if (trimmedDesc) { payload.description = trimmedDesc; } await onSubmit(payload); resetForm(); } catch (err: unknown) { setFormError(err instanceof Error ? err.message : "Failed to create domain."); } } if (!open) return null; return (
{/* Backdrop */}
{ if (!isSubmitting) { resetForm(); onOpenChange(false); } }} /> {/* Dialog */}

New Domain

Domains help you organize tasks, projects, and events by life area.

{ void handleSubmit(e); }} > {/* Name */}
{ handleNameChange(e.target.value); }} placeholder="e.g. Personal Finance" maxLength={255} autoFocus style={{ width: "100%", padding: "8px 12px", background: "var(--bg, #f9fafb)", border: "1px solid var(--border, #d1d5db)", borderRadius: "6px", color: "var(--text, #111)", fontSize: "0.9rem", outline: "none", boxSizing: "border-box", }} />
{/* Slug */}
{ handleSlugChange(e.target.value); }} placeholder="e.g. personal-finance" maxLength={100} style={{ width: "100%", padding: "8px 12px", background: "var(--bg, #f9fafb)", border: "1px solid var(--border, #d1d5db)", borderRadius: "6px", color: "var(--text, #111)", fontSize: "0.9rem", outline: "none", boxSizing: "border-box", fontFamily: "var(--mono, monospace)", }} />

Lowercase letters, numbers, and hyphens only.

{/* Description */}