From 32622027308a433b872574ece4ce9df8e0081a77 Mon Sep 17 00:00:00 2001 From: ARYPROGRAMMER Date: Fri, 20 Dec 2024 17:56:07 +0530 Subject: [PATCH] feat: snippet sharing linked via convex Signed-off-by: ARYPROGRAMMER --- convex/_generated/api.d.ts | 2 + convex/snippets.ts | 32 +++++++ src/app/(home)/_components/EditorPanel.tsx | 2 + .../(home)/_components/ShareSnippetDialog.tsx | 89 +++++++++++++++++++ src/app/layout.tsx | 2 + 5 files changed, 127 insertions(+) create mode 100644 convex/snippets.ts create mode 100644 src/app/(home)/_components/ShareSnippetDialog.tsx diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 5c88e74..1f35d6b 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -15,6 +15,7 @@ import type { } from "convex/server"; import type * as codeExecutions from "../codeExecutions.js"; import type * as http from "../http.js"; +import type * as snippets from "../snippets.js"; import type * as users from "../users.js"; /** @@ -28,6 +29,7 @@ import type * as users from "../users.js"; declare const fullApi: ApiFromModules<{ codeExecutions: typeof codeExecutions; http: typeof http; + snippets: typeof snippets; users: typeof users; }>; export declare const api: FilterApi< diff --git a/convex/snippets.ts b/convex/snippets.ts new file mode 100644 index 0000000..e33112c --- /dev/null +++ b/convex/snippets.ts @@ -0,0 +1,32 @@ +import { v } from "convex/values"; +import { mutation } from "./_generated/server"; + +export const createSnippet = mutation({ + args: { + title: v.string(), + language: v.string(), + code: v.string(), + }, + handler: async (ctx, args) => { + const identity = await ctx.auth.getUserIdentity(); + if (!identity) throw new Error("Unauthenticated"); + + const user = await ctx.db + .query("users") + .withIndex("by_user_id") + .filter((q) => q.eq(q.field("userId"), identity.subject)) + .first(); + + if (!user) throw new Error("User not found"); + + const snippetId = await ctx.db.insert("snippets", { + userId: identity.subject, + userName: user.name, + title: args.title, + language: args.language, + code: args.code, + }); + + return snippetId; + }, +}); diff --git a/src/app/(home)/_components/EditorPanel.tsx b/src/app/(home)/_components/EditorPanel.tsx index db3b16e..f9e6c45 100644 --- a/src/app/(home)/_components/EditorPanel.tsx +++ b/src/app/(home)/_components/EditorPanel.tsx @@ -10,6 +10,7 @@ import { Editor } from "@monaco-editor/react"; import { useClerk } from "@clerk/nextjs"; import { EditorPanelSkeleton, EditorViewSkeleton } from "./EditorPanelLoading"; import useMounted from "@/hooks/useMounted"; +import ShareSnippetDialog from "./ShareSnippetDialog"; function EditorPanel() { const clerk = useClerk(); @@ -152,6 +153,7 @@ function EditorPanel() { {!clerk.loaded && } + {isShareDialogOpen && setIsShareDialogOpen(false)} />} ); } diff --git a/src/app/(home)/_components/ShareSnippetDialog.tsx b/src/app/(home)/_components/ShareSnippetDialog.tsx new file mode 100644 index 0000000..87c0131 --- /dev/null +++ b/src/app/(home)/_components/ShareSnippetDialog.tsx @@ -0,0 +1,89 @@ +import { useCodeEditorState } from "@/store/useCodeEditorStore"; +import { useMutation } from "convex/react"; +import React from "react"; +import { api } from "../../../../convex/_generated/api"; +import { X } from "lucide-react"; +import toast from "react-hot-toast"; + +function ShareSnippetDialog({ onClose }: { onClose: () => void }) { + const [title, setTitle] = React.useState(""); + const [isSharing, setIsSharing] = React.useState(false); + const { language, getCode } = useCodeEditorState(); + const createSnippet = useMutation(api.snippets.createSnippet); + + const handleShare = async (e: React.FormEvent)=>{ + e.preventDefault(); + + setIsSharing(true); + + try{ + const code = getCode(); + await createSnippet({ title, language, code }); + onClose(); + setTitle(""); + toast.success("Snippet shared successfully"); + + } + catch(error){ + console.error(error); + toast.error("Failed to share snippet"); + } + + finally{ + setIsSharing(false); + } + + + } + + return ( +
+
+
+

Share Snippet

+ +
+ +
+
+ + setTitle(e.target.value)} + className="w-full px-3 py-2 bg-[#181825] border border-[#313244] rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="Enter snippet title" + required + /> +
+ +
+ + +
+
+
+
+ + ) +} + +export default ShareSnippetDialog; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2998baf..af8d84a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import { ClerkProvider } from "@clerk/nextjs"; import ConvexClientProvider from "@/components/providers/ConvexClientProvider"; import { BuyMeCoffeeButton } from "@/components/ui/BuyMeCoffee"; import Footer from "@/components/ui/Footer"; +import { Toaster } from "react-hot-toast"; const geistSans = localFont({ src: "./fonts/GeistVF.woff", @@ -36,6 +37,7 @@ export default function RootLayout({ {children}