Skip to content

Commit dcb0631

Browse files
Merge pull request #15 from ARYPROGRAMMER/develop/home
feat: snippet sharing linked via convex
2 parents d9ea2a9 + 3262202 commit dcb0631

File tree

5 files changed

+127
-0
lines changed

5 files changed

+127
-0
lines changed

convex/_generated/api.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from "convex/server";
1616
import type * as codeExecutions from "../codeExecutions.js";
1717
import type * as http from "../http.js";
18+
import type * as snippets from "../snippets.js";
1819
import type * as users from "../users.js";
1920

2021
/**
@@ -28,6 +29,7 @@ import type * as users from "../users.js";
2829
declare const fullApi: ApiFromModules<{
2930
codeExecutions: typeof codeExecutions;
3031
http: typeof http;
32+
snippets: typeof snippets;
3133
users: typeof users;
3234
}>;
3335
export declare const api: FilterApi<

convex/snippets.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { v } from "convex/values";
2+
import { mutation } from "./_generated/server";
3+
4+
export const createSnippet = mutation({
5+
args: {
6+
title: v.string(),
7+
language: v.string(),
8+
code: v.string(),
9+
},
10+
handler: async (ctx, args) => {
11+
const identity = await ctx.auth.getUserIdentity();
12+
if (!identity) throw new Error("Unauthenticated");
13+
14+
const user = await ctx.db
15+
.query("users")
16+
.withIndex("by_user_id")
17+
.filter((q) => q.eq(q.field("userId"), identity.subject))
18+
.first();
19+
20+
if (!user) throw new Error("User not found");
21+
22+
const snippetId = await ctx.db.insert("snippets", {
23+
userId: identity.subject,
24+
userName: user.name,
25+
title: args.title,
26+
language: args.language,
27+
code: args.code,
28+
});
29+
30+
return snippetId;
31+
},
32+
});

src/app/(home)/_components/EditorPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Editor } from "@monaco-editor/react";
1010
import { useClerk } from "@clerk/nextjs";
1111
import { EditorPanelSkeleton, EditorViewSkeleton } from "./EditorPanelLoading";
1212
import useMounted from "@/hooks/useMounted";
13+
import ShareSnippetDialog from "./ShareSnippetDialog";
1314

1415
function EditorPanel() {
1516
const clerk = useClerk();
@@ -152,6 +153,7 @@ function EditorPanel() {
152153
{!clerk.loaded && <EditorPanelSkeleton />}
153154
</div>
154155
</div>
156+
{isShareDialogOpen && <ShareSnippetDialog onClose={() => setIsShareDialogOpen(false)} />}
155157
</div>
156158
);
157159
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { useCodeEditorState } from "@/store/useCodeEditorStore";
2+
import { useMutation } from "convex/react";
3+
import React from "react";
4+
import { api } from "../../../../convex/_generated/api";
5+
import { X } from "lucide-react";
6+
import toast from "react-hot-toast";
7+
8+
function ShareSnippetDialog({ onClose }: { onClose: () => void }) {
9+
const [title, setTitle] = React.useState("");
10+
const [isSharing, setIsSharing] = React.useState(false);
11+
const { language, getCode } = useCodeEditorState();
12+
const createSnippet = useMutation(api.snippets.createSnippet);
13+
14+
const handleShare = async (e: React.FormEvent)=>{
15+
e.preventDefault();
16+
17+
setIsSharing(true);
18+
19+
try{
20+
const code = getCode();
21+
await createSnippet({ title, language, code });
22+
onClose();
23+
setTitle("");
24+
toast.success("Snippet shared successfully");
25+
26+
}
27+
catch(error){
28+
console.error(error);
29+
toast.error("Failed to share snippet");
30+
}
31+
32+
finally{
33+
setIsSharing(false);
34+
}
35+
36+
37+
}
38+
39+
return (
40+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
41+
<div className="bg-[#1e1e2e] rounded-lg p-6 w-full max-w-md">
42+
<div className="flex items-center justify-between mb-4">
43+
<h2 className="text-xl font-semibold text-white">Share Snippet</h2>
44+
<button onClick={onClose} className="text-gray-400 hover:text-gray-300">
45+
<X className="w-5 h-5" />
46+
</button>
47+
</div>
48+
49+
<form onSubmit={handleShare}>
50+
<div className="mb-4">
51+
<label htmlFor="title" className="block text-sm font-medium text-gray-400 mb-2">
52+
Title
53+
</label>
54+
<input
55+
type="text"
56+
id="title"
57+
value={title}
58+
onChange={(e) => setTitle(e.target.value)}
59+
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"
60+
placeholder="Enter snippet title"
61+
required
62+
/>
63+
</div>
64+
65+
<div className="flex justify-end gap-3">
66+
<button
67+
type="button"
68+
onClick={onClose}
69+
className="px-4 py-2 text-gray-400 hover:text-gray-300"
70+
>
71+
Cancel
72+
</button>
73+
<button
74+
type="submit"
75+
disabled={isSharing}
76+
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600
77+
disabled:opacity-50"
78+
>
79+
{isSharing ? "Sharing..." : "Share"}
80+
</button>
81+
</div>
82+
</form>
83+
</div>
84+
</div>
85+
86+
)
87+
}
88+
89+
export default ShareSnippetDialog;

src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ClerkProvider } from "@clerk/nextjs";
55
import ConvexClientProvider from "@/components/providers/ConvexClientProvider";
66
import { BuyMeCoffeeButton } from "@/components/ui/BuyMeCoffee";
77
import Footer from "@/components/ui/Footer";
8+
import { Toaster } from "react-hot-toast";
89

910
const geistSans = localFont({
1011
src: "./fonts/GeistVF.woff",
@@ -36,6 +37,7 @@ export default function RootLayout({
3637
<ConvexClientProvider>{children}</ConvexClientProvider>
3738
<BuyMeCoffeeButton />
3839
<Footer />
40+
<Toaster />
3941
</body>
4042
</html>
4143
</ClerkProvider>

0 commit comments

Comments
 (0)