diff --git a/surfsense_web/components/layout/index.ts b/surfsense_web/components/layout/index.ts index 4fe2975c1..3a79ccde7 100644 --- a/surfsense_web/components/layout/index.ts +++ b/surfsense_web/components/layout/index.ts @@ -12,6 +12,7 @@ export type { } from "./types/layout.types"; export { ChatListItem, + CreateSearchSpaceDialog, Header, IconRail, LayoutShell, diff --git a/surfsense_web/components/layout/ui/dialogs/CreateSearchSpaceDialog.tsx b/surfsense_web/components/layout/ui/dialogs/CreateSearchSpaceDialog.tsx new file mode 100644 index 000000000..94646d739 --- /dev/null +++ b/surfsense_web/components/layout/ui/dialogs/CreateSearchSpaceDialog.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useAtomValue } from "jotai"; +import { Loader2, Plus, Search } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { createSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { trackSearchSpaceCreated } from "@/lib/posthog/events"; + +const formSchema = z.object({ + name: z.string().min(1, "Name is required"), + description: z.string().optional(), +}); + +type FormValues = z.infer; + +interface CreateSearchSpaceDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpaceDialogProps) { + const t = useTranslations("searchSpace"); + const tCommon = useTranslations("common"); + const router = useRouter(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const { mutateAsync: createSearchSpace } = useAtomValue(createSearchSpaceMutationAtom); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + description: "", + }, + }); + + const handleSubmit = async (values: FormValues) => { + setIsSubmitting(true); + try { + const result = await createSearchSpace({ + name: values.name, + description: values.description || "", + }); + + // Track search space creation + trackSearchSpaceCreated(result.id, values.name); + + // Reset form and close dialog + form.reset(); + onOpenChange(false); + + // Redirect to the newly created search space's onboarding + router.push(`/dashboard/${result.id}/onboard`); + } catch (error) { + console.error("Failed to create search space:", error); + } finally { + setIsSubmitting(false); + } + }; + + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen) { + form.reset(); + } + onOpenChange(newOpen); + }; + + return ( + + + +
+
+ +
+
+ {t("create_title")} + {t("create_description")} +
+
+
+ +
+ + ( + + {t("name_label")} + + + + + + )} + /> + + ( + + + {t("description_label")}{" "} + + ({tCommon("optional")}) + + + + + + + + )} + /> + + + + + + + +
+
+ ); +} + diff --git a/surfsense_web/components/layout/ui/dialogs/index.ts b/surfsense_web/components/layout/ui/dialogs/index.ts new file mode 100644 index 000000000..28f3b387d --- /dev/null +++ b/surfsense_web/components/layout/ui/dialogs/index.ts @@ -0,0 +1,2 @@ +export { CreateSearchSpaceDialog } from "./CreateSearchSpaceDialog"; + diff --git a/surfsense_web/components/layout/ui/index.ts b/surfsense_web/components/layout/ui/index.ts index 31e288561..1c3ddb2ca 100644 --- a/surfsense_web/components/layout/ui/index.ts +++ b/surfsense_web/components/layout/ui/index.ts @@ -1,3 +1,4 @@ +export { CreateSearchSpaceDialog } from "./dialogs"; export { Header } from "./header"; export { IconRail, NavIcon, SearchSpaceAvatar } from "./icon-rail"; export { LayoutShell } from "./shell"; diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index 8b0164211..52991793a 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -77,6 +77,16 @@ "creating_account_btn": "Creating account...", "redirecting_login": "Redirecting to login page..." }, + "searchSpace": { + "create_title": "Create Search Space", + "create_description": "Create a new search space to organize your knowledge", + "name_label": "Name", + "name_placeholder": "Enter search space name", + "description_label": "Description", + "description_placeholder": "What is this search space for?", + "create_button": "Create", + "creating": "Creating..." + }, "dashboard": { "title": "Dashboard", "search_spaces": "Search Spaces", diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json index ee04baad5..ea3af36e3 100644 --- a/surfsense_web/messages/zh.json +++ b/surfsense_web/messages/zh.json @@ -77,6 +77,16 @@ "creating_account_btn": "创建中...", "redirecting_login": "正在跳转到登录页面..." }, + "searchSpace": { + "create_title": "创建搜索空间", + "create_description": "创建一个新的搜索空间来组织您的知识", + "name_label": "名称", + "name_placeholder": "输入搜索空间名称", + "description_label": "描述", + "description_placeholder": "这个搜索空间是做什么的?", + "create_button": "创建", + "creating": "创建中..." + }, "dashboard": { "title": "仪表盘", "search_spaces": "搜索空间",