diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts
index 1f35d6b..8f885ce 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 lemonSqueezy from "../lemonSqueezy.js";
import type * as snippets from "../snippets.js";
import type * as users from "../users.js";
@@ -29,6 +30,7 @@ import type * as users from "../users.js";
declare const fullApi: ApiFromModules<{
codeExecutions: typeof codeExecutions;
http: typeof http;
+ lemonSqueezy: typeof lemonSqueezy;
snippets: typeof snippets;
users: typeof users;
}>;
diff --git a/convex/http.ts b/convex/http.ts
index 3ee81ca..36d759d 100644
--- a/convex/http.ts
+++ b/convex/http.ts
@@ -5,6 +5,51 @@ import { WebhookEvent } from "@clerk/nextjs/server";
import { api, internal } from "./_generated/api";
const http = httpRouter();
+http.route({
+ path: "/lemon-squeezy-webhook",
+ method: "POST",
+ handler: httpAction(async (ctx, req) => {
+ const payloadString = await req.json();
+ const signature = req.headers.get("X-Signature");
+ if (!signature) {
+ return new Response("Missing signature", { status: 400 });
+ }
+ try {
+ const payload = await ctx.runAction(internal.lemonSqueezy.verifyWebhook, {
+ payload: payloadString,
+ signature,
+ });
+
+ if (payload.meta.event_name === "order.created") {
+ const { data } = payload;
+ const {success} = await ctx.runMutation(api.users.upgradePro,{
+ email: data.attributes.use_email,
+ lemonSqueezyCustomerId: data.attributes.customer_id.toString(),
+ lemonSqueezyOrderId: data.id,
+ amount: data.attributes.total,
+ });
+
+ if (success) {
+
+
+
+
+
+
+
+
+ }
+ }
+
+ return new Response("Webhook processed successfully", { status: 200 });
+
+ } catch (e) {
+ console.log("Webhook error:", e);
+ return new Response("Error processing webhook", { status: 500 });
+ }
+ }),
+});
+
http.route({
path: "/clerk-webhook",
method: "POST",
@@ -59,9 +104,7 @@ http.route({
}
}
return new Response("Webhook processed successfully", { status: 200 });
-
}),
});
-
export default http;
diff --git a/convex/lemonSqueezy.ts b/convex/lemonSqueezy.ts
new file mode 100644
index 0000000..f1337d7
--- /dev/null
+++ b/convex/lemonSqueezy.ts
@@ -0,0 +1,28 @@
+"use node";
+import { v } from "convex/values";
+import { internalAction } from "./_generated/server";
+import { createHmac } from "crypto";
+
+const webhookSecret = process.env.NEXT_PUBLIC_CLERK_WEBHOOK_SECRET!;
+
+function verifySignature(payload: string, signature: string) {
+ const hmac = createHmac("sha256", webhookSecret);
+ const computedSignature = hmac.update(payload).digest("hex");
+ return computedSignature === signature;
+}
+
+export const verifyWebhook = internalAction({
+ args: {
+ payload: v.string(),
+ signature: v.string(),
+ },
+ handler: async (ctx, args) => {
+ const isValid = verifySignature(args.payload, args.signature);
+
+ if (!isValid) {
+ throw new Error("Invalid signature");
+ }
+
+ return JSON.parse(args.payload);
+ },
+});
diff --git a/convex/users.ts b/convex/users.ts
index 90f0881..af1e914 100644
--- a/convex/users.ts
+++ b/convex/users.ts
@@ -39,4 +39,31 @@ export const getUser = query({
if (!user)return null;
return user;
}
-})
\ No newline at end of file
+})
+
+
+export const upgradePro = mutation({
+ args: {
+ email: v.string(),
+ lemonSqueezyCustomerId: v.string(),
+ lemonSqueezyOrderId: v.string(),
+ amount: v.number(),
+ },
+ handler: async (ctx, args) => {
+ const user = await ctx.db
+ .query("users")
+ .filter((q) => q.eq(q.field("email"), args.email))
+ .first();
+
+ if (!user) throw new Error("User not found");
+
+ await ctx.db.patch(user._id, {
+ isPro: true,
+ proSince: Date.now(),
+ lemonSqueezyCustomerId: args.lemonSqueezyCustomerId,
+ lemonSqueezyOrderId: args.lemonSqueezyOrderId,
+ });
+
+ return { success: true };
+ },
+ });
\ No newline at end of file
diff --git a/src/app/(home)/_components/HeaderProfileBtn.tsx b/src/app/(home)/_components/HeaderProfileBtn.tsx
index f0efc71..61d6221 100644
--- a/src/app/(home)/_components/HeaderProfileBtn.tsx
+++ b/src/app/(home)/_components/HeaderProfileBtn.tsx
@@ -1,5 +1,6 @@
"use client";
-import { SignedOut, SignInButton, UserButton } from "@clerk/nextjs";
+import LoginButton from "@/components/ui/LoginButton";
+import { SignedOut, UserButton } from "@clerk/nextjs";
import { User } from "lucide-react";
function HeaderProfileBtn() {
@@ -16,7 +17,7 @@ function HeaderProfileBtn() {
-
+
>
);
diff --git a/src/app/pricing/_components/BeingProPlan.tsx b/src/app/pricing/_components/BeingProPlan.tsx
new file mode 100644
index 0000000..ae3b491
--- /dev/null
+++ b/src/app/pricing/_components/BeingProPlan.tsx
@@ -0,0 +1,45 @@
+import NavigationHeader from "@/components/ui/NavigationHeader";
+import { ArrowRight, Command, Star } from "lucide-react";
+import Link from "next/link";
+
+function BeingProPlan() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pro Plan Active
+
+
+ Experience the full power of professional development
+
+
+
+
+
Open Editor
+
+
+
+
+
+
+
+ );
+}
+export default BeingProPlan;
diff --git a/src/app/pricing/_components/Category.tsx b/src/app/pricing/_components/Category.tsx
new file mode 100644
index 0000000..a383f3a
--- /dev/null
+++ b/src/app/pricing/_components/Category.tsx
@@ -0,0 +1,8 @@
+const FeatureCategory = ({ children, label }: { children: React.ReactNode; label: string }) => (
+
+ );
+
+ export default FeatureCategory;
\ No newline at end of file
diff --git a/src/app/pricing/_components/Item.tsx b/src/app/pricing/_components/Item.tsx
new file mode 100644
index 0000000..a05795b
--- /dev/null
+++ b/src/app/pricing/_components/Item.tsx
@@ -0,0 +1,12 @@
+import { Check } from "lucide-react";
+
+const FeatureItem = ({ children }: { children: React.ReactNode }) => (
+
+);
+
+export default FeatureItem;
\ No newline at end of file
diff --git a/src/app/pricing/_components/Upgrade.tsx b/src/app/pricing/_components/Upgrade.tsx
new file mode 100644
index 0000000..b0f8332
--- /dev/null
+++ b/src/app/pricing/_components/Upgrade.tsx
@@ -0,0 +1,19 @@
+import { Zap } from "lucide-react";
+import Link from "next/link";
+
+export default function UpgradeButton() {
+ const CHEKOUT_URL =
+ "https://arya-opensource.lemonsqueezy.com/buy/c4c9a31d-3c39-4678-aa64-70fc57205d60";
+
+ return (
+
+
+ Upgrade to Pro
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/pricing/_constants/index.ts b/src/app/pricing/_constants/index.ts
new file mode 100644
index 0000000..2e98552
--- /dev/null
+++ b/src/app/pricing/_constants/index.ts
@@ -0,0 +1,45 @@
+import { Boxes, Globe, RefreshCcw, Shield } from "lucide-react";
+
+export const ENTERPRISE_FEATURES = [
+ {
+ icon: Globe,
+ label: "Global Infrastructure",
+ desc: "Lightning-fast execution across worldwide edge nodes",
+ },
+ {
+ icon: Shield,
+ label: "Enterprise Security",
+ desc: "Bank-grade encryption and security protocols",
+ },
+ {
+ icon: RefreshCcw,
+ label: "Real-time Sync",
+ desc: "Instant synchronization across all devices",
+ },
+ {
+ icon: Boxes,
+ label: "Unlimited Storage",
+ desc: "Store unlimited snippets and projects",
+ },
+];
+
+export const FEATURES = {
+ development: [
+ "Advanced AI",
+ "Custom theme builder",
+ "Integrated debugging tools",
+ "Multi-language support",
+ ],
+ collaboration: [
+ "Real-time pair programming",
+ "Team workspaces",
+ "Version control integration",
+ "Code review tools",
+ ],
+ deployment: [
+ "One-click deployment",
+ "CI/CD integration",
+ "Container support",
+ "Custom domain mapping",
+ ],
+};
\ No newline at end of file
diff --git a/src/app/pricing/page.tsx b/src/app/pricing/page.tsx
new file mode 100644
index 0000000..06cf7ae
--- /dev/null
+++ b/src/app/pricing/page.tsx
@@ -0,0 +1,150 @@
+import { currentUser } from "@clerk/nextjs/server";
+import { ConvexHttpClient } from "convex/browser";
+import React from "react";
+import { api } from "../../../convex/_generated/api";
+import BeingProPlan from "./_components/BeingProPlan";
+import { Star } from "lucide-react";
+import { ENTERPRISE_FEATURES, FEATURES } from "./_constants";
+import NavigationHeader from "@/components/ui/NavigationHeader";
+import { SignedIn, SignedOut } from "@clerk/nextjs";
+import UpgradeButton from "./_components/Upgrade";
+import FeatureItem from "./_components/Item";
+import FeatureCategory from "./_components/Category";
+import LoginButton from "@/components/ui/LoginButton";
+
+async function PricingPage() {
+ const user = await currentUser();
+ const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
+
+ const convexUser = await convex.query(api.users.getUser, {
+ userId: user?.id || "",
+ });
+
+ if (convexUser?.isPro) return ;
+
+ return (
+
+
+
+
+ {/* main content */}
+
+
+
+ {/* Hero */}
+
+
+
+
+ Elevate Your
+ Development Experience
+
+
+
+ Join the next generation of developers with our professional suite of tools
+
+
+
+ {/* Enterprise Features */}
+
+ {ENTERPRISE_FEATURES.map((feature) => (
+
+
+
+
+
+
+
{feature.label}
+
{feature.desc}
+
+
+ ))}
+
+
+ {/* Pricing Card */}
+
+
+
+
+
+
+
+
+ {/* header */}
+
+
+
+
+
Lifetime Pro Access
+
+ ₹
+
+ 799
+
+ one-time
+
+
Unlock the full potential of CodeX
+
+
+ {/* Features grid */}
+
+
+ {FEATURES.development.map((feature, idx) => (
+ {feature}
+ ))}
+
+
+
+ {FEATURES.collaboration.map((feature, idx) => (
+ {feature}
+ ))}
+
+
+
+ {FEATURES.deployment.map((feature, idx) => (
+ {feature}
+ ))}
+
+
+
+ {/* CTA */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default PricingPage;
diff --git a/src/app/snippets/page.tsx b/src/app/snippets/page.tsx
index 5f237c3..a76a0fb 100644
--- a/src/app/snippets/page.tsx
+++ b/src/app/snippets/page.tsx
@@ -182,7 +182,6 @@ function SnippetsPage() {
- {/* edge case: empty state */}
{filteredSnippets.length === 0 && (
+
+
+ );
+}
+export default LoginButton;
\ No newline at end of file