diff --git a/vault/app/snippets/new/page.tsx b/vault/app/snippets/new/page.tsx new file mode 100644 index 0000000..68fc43c --- /dev/null +++ b/vault/app/snippets/new/page.tsx @@ -0,0 +1,213 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { motion } from "framer-motion"; +import { Save, Code2, Type } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { CodeEditor } from "@/components/code-editor"; +import { PrivacyToggle } from "@/components/privacy-toggle"; +import { LanguageSelector } from "@/components/language-selector"; +import { ExpirationSelector } from "@/components/expiration-selector"; +import { TagInput } from "@/components/tag-input"; +import { api } from "@/lib/api-client"; +import { PrivacyType, Expiration } from "@/lib/types"; +import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + +type SnippetMode = "code" | "text"; + +export default function NewSnippetPage() { + const router = useRouter(); + const [mode, setMode] = useState("code"); + const [title, setTitle] = useState(""); + const [code, setCode] = useState(""); + const [language, setLanguage] = useState("javascript"); + const [privacy, setPrivacy] = useState("secret"); + const [password, setPassword] = useState(""); + const [expiration, setExpiration] = useState("never"); + const [tags, setTags] = useState([]); + const [checkingAuth, setCheckingAuth] = useState(true); + const [saving, setSaving] = useState(false); + + useEffect(() => { + api.me() + .then(({ user }) => { + if (!user) router.push("/login"); + }) + .catch(() => router.push("/login")) + .finally(() => setCheckingAuth(false)); + }, [router]); + + const handleSave = async () => { + if (saving) return; + if (!title.trim()) { + toast.error("Please add a title"); + return; + } + if (!code.trim()) { + toast.error(mode === "code" ? "Please add some code" : "Please add some text"); + return; + } + if (privacy === "password" && !password.trim()) { + toast.error("Please set a password"); + return; + } + + try { + setSaving(true); + const { snippet } = await api.createSnippet({ + title: title.trim(), + code, + language: mode === "text" ? "plaintext" : language, + privacy, + password: privacy === "password" ? password : undefined, + expiration, + tags, + }); + + toast.success("Snippet saved!"); + router.push(`/snippets/${snippet.id}`); + } catch (error) { + toast.error(error instanceof Error ? error.message : "Failed to save snippet"); + if (error instanceof Error && error.message === "Authentication required") router.push("/login"); + } finally { + setSaving(false); + } + }; + + if (checkingAuth) { + return ( +
+
+
+
+
+
+
+ ); + } + + return ( +
+ +
+
+

New snippet

+

+ Write your {mode === "code" ? "code" : "text"}, set privacy, and share. +

+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + setTitle(e.target.value)} + className="h-11 bg-muted/50 border-border/50" + /> +
+ + {mode === "code" && ( +
+
+ + +
+
+ + +
+
+ )} + + {mode === "text" && ( +
+ + +
+ )} + +
+ + +
+ +
+ + +
+ + {mode === "code" ? ( +
+ + +
+ ) : ( +
+ +