Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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;
}>;
Expand Down
47 changes: 45 additions & 2 deletions convex/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -59,9 +104,7 @@ http.route({
}
}
return new Response("Webhook processed successfully", { status: 200 });

}),
});


export default http;
28 changes: 28 additions & 0 deletions convex/lemonSqueezy.ts
Original file line number Diff line number Diff line change
@@ -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);
},
});
29 changes: 28 additions & 1 deletion convex/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,31 @@ export const getUser = query({
if (!user)return null;
return user;
}
})
})


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 };
},
});
5 changes: 3 additions & 2 deletions src/app/(home)/_components/HeaderProfileBtn.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -16,7 +17,7 @@ function HeaderProfileBtn() {
</UserButton>

<SignedOut>
<SignInButton />
<LoginButton />
</SignedOut>
</>
);
Expand Down
45 changes: 45 additions & 0 deletions src/app/pricing/_components/BeingProPlan.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className=" bg-[#0a0a0f]">
<NavigationHeader />
<div className="relative px-4 h-[80vh] flex items-center justify-center">
<div className="relative max-w-xl mx-auto text-center">
<div className="absolute inset-x-0 -top-px h-px bg-gradient-to-r from-transparent via-blue-500/50 to-transparent" />
<div className="absolute inset-x-0 -bottom-px h-px bg-gradient-to-r from-transparent via-purple-500/50 to-transparent" />
<div className="absolute -inset-0.5 bg-gradient-to-r from-blue-500/30 to-purple-500/30 blur-2xl opacity-10" />

<div className="relative bg-[#12121a]/90 border border-gray-800/50 backdrop-blur-2xl rounded-2xl p-12">
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/[0.05] to-purple-500/[0.05] rounded-2xl" />

<div className="relative">
<div className="inline-flex p-4 rounded-2xl bg-gradient-to-br from-purple-500/10 to-blue-500/10 mb-6 ring-1 ring-gray-800/60">
<Star className="w-8 h-8 text-purple-400" />
</div>

<h1 className="text-3xl font-semibold text-white mb-3">
Pro Plan Active
</h1>
<p className="text-gray-400 mb-8 text-lg">
Experience the full power of professional development
</p>

<Link
href="/"
className="inline-flex items-center justify-center gap-2 w-full px-8 py-4 bg-gradient-to-r from-blue-500/10 to-purple-500/10 hover:from-blue-500/20 hover:to-purple-500/20 text-white rounded-xl transition-all duration-200 border border-gray-800 hover:border-blue-500/50 group"
>
<Command className="w-5 h-5 text-blue-400" />
<span>Open Editor</span>
<ArrowRight className="w-5 h-5 text-purple-400 group-hover:translate-x-0.5 transition-transform" />
</Link>
</div>
</div>
</div>
</div>
</div>
);
}
export default BeingProPlan;
8 changes: 8 additions & 0 deletions src/app/pricing/_components/Category.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const FeatureCategory = ({ children, label }: { children: React.ReactNode; label: string }) => (
<div className="space-y-4">
<h3 className="text-sm font-medium text-gray-400 uppercase tracking-wider">{label}</h3>
<div className="space-y-3">{children}</div>
</div>
);

export default FeatureCategory;
12 changes: 12 additions & 0 deletions src/app/pricing/_components/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Check } from "lucide-react";

const FeatureItem = ({ children }: { children: React.ReactNode }) => (
<div className="flex items-start gap-3 group">
<div className="mt-1 flex-shrink-0 w-5 h-5 rounded-full bg-blue-500/10 flex items-center justify-center border border-blue-500/20 group-hover:border-blue-500/40 group-hover:bg-blue-500/20 transition-colors">
<Check className="w-3 h-3 text-blue-400" />
</div>
<span className="text-gray-400 group-hover:text-gray-300 transition-colors">{children}</span>
</div>
);

export default FeatureItem;
19 changes: 19 additions & 0 deletions src/app/pricing/_components/Upgrade.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Link
href={CHEKOUT_URL}
className="inline-flex items-center justify-center gap-2 px-8 py-4 text-white
bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg
hover:from-blue-600 hover:to-blue-700 transition-all"
>
<Zap className="w-5 h-5" />
Upgrade to Pro
</Link>
);
}
45 changes: 45 additions & 0 deletions src/app/pricing/_constants/index.ts
Original file line number Diff line number Diff line change
@@ -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",
],
};
Loading
Loading