+
-
+
{name}
@@ -234,42 +233,66 @@ function PremiumCard({
- {features.map((feature, i) => (
-
- {isFeatureMore(feature) ? (
-
- ) : (
-
- )}
- {getFeatureLabel(feature)}
- {isFeatureSoon(feature) && (
- Soon
- )}
-
- ))}
+ {features.map((feature, i) => {
+ const colors = [
+ "text-amber-500",
+ "text-emerald-500",
+ "text-sky-500",
+ "text-violet-500",
+ "text-pink-500",
+ "text-cyan-500",
+ "text-rose-500",
+ "text-indigo-500",
+ ];
+ const iconColor = isFeatureMore(feature) ? "text-amber-500" : (colors[i % colors.length]);
+ return (
+
+ {isFeatureMore(feature) ? (
+
+ ) : (
+
+ )}
+ {getFeatureLabel(feature)}
+ {isFeatureSoon(feature) && (
+ Soon
+ )}
+
+ );
+ })}
-
- {comingSoon ? "Coming Soon" : cta}
- {!comingSoon &&
}
-
+ {checkout && !comingSoon ? (
+
+ ) : (
+
+ {comingSoon ? "Coming Soon" : cta}
+ {!comingSoon &&
}
+
+ )}
diff --git a/components/bilalUi/pro/index.ts b/components/bilalUi/pro/index.ts
index 1fd4028..0225402 100644
--- a/components/bilalUi/pro/index.ts
+++ b/components/bilalUi/pro/index.ts
@@ -3,3 +3,4 @@ export { ProBadge } from "./pro-badge";
export { ProCard } from "./pro-card";
export { ProFeatureCard } from "./pro-feature-card";
export { ProComingSoon } from "./pro-coming-soon";
+export { ProLock } from "./pro-lock";
diff --git a/components/bilalUi/pro/license-dialog.tsx b/components/bilalUi/pro/license-dialog.tsx
new file mode 100644
index 0000000..cffb710
--- /dev/null
+++ b/components/bilalUi/pro/license-dialog.tsx
@@ -0,0 +1,143 @@
+"use client";
+
+import { useState } from "react";
+import { Crown, Check, X, Loader2, KeyRound } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { Badge } from "@/components/ui/badge";
+
+interface LicenseDialogProps {
+ open: boolean;
+ onClose: () => void;
+ onVerify: (key: string) => Promise<{ success: boolean; error?: string }>;
+}
+
+export function LicenseDialog({
+ open,
+ onClose,
+ onVerify,
+}: LicenseDialogProps) {
+ const [key, setKey] = useState("");
+ const [status, setStatus] = useState<
+ "idle" | "loading" | "success" | "error"
+ >("idle");
+ const [error, setError] = useState("");
+
+ if (!open) return null;
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ if (!key.trim()) return;
+
+ setStatus("loading");
+ setError("");
+
+ const result = await onVerify(key.trim());
+
+ if (result.success) {
+ setStatus("success");
+ setTimeout(() => {
+ onClose();
+ setKey("");
+ setStatus("idle");
+ }, 1200);
+ } else {
+ setStatus("error");
+ setError(result.error ?? "Invalid license key");
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ Pro License
+
+
+
+
+
+ Enter your license key
+
+
+ Paste the license key you received after purchase to unlock all
+ Pro components.
+
+
+
+
+
+ Didn't get a key? Check your email receipt or{" "}
+
+ visit LemonSqueezy orders
+
+
+
+
+
+ );
+}
diff --git a/components/bilalUi/pro/pro-lock.tsx b/components/bilalUi/pro/pro-lock.tsx
new file mode 100644
index 0000000..46b9d01
--- /dev/null
+++ b/components/bilalUi/pro/pro-lock.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import { useState } from "react";
+import { Crown, LockKeyhole, Sparkles, KeyRound } from "lucide-react";
+import Link from "next/link";
+import { cn } from "@/lib/utils";
+import { Badge } from "@/components/ui/badge";
+import { useProAccess } from "@/hooks/use-pro-access";
+import { LicenseDialog } from "./license-dialog";
+
+interface ProLockProps {
+ label?: string;
+ className?: string;
+}
+
+export function ProLock({
+ label = "This is a Pro component",
+ className,
+}: ProLockProps) {
+ const { hasAccess, verifyAndStore } = useProAccess();
+ const [showDialog, setShowDialog] = useState(false);
+
+ if (hasAccess) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ Pro
+
+
+
+
{label}
+
+ Unlock all premium component variants, page blocks, and full
+ templates with a single $15 payment.
+
+
+
+
+
+
+ Unlock with Pro
+
+
+
+
+
+
+
setShowDialog(false)}
+ onVerify={verifyAndStore}
+ />
+ >
+ );
+}
diff --git a/config/lemon.ts b/config/lemon.ts
new file mode 100644
index 0000000..5bc8007
--- /dev/null
+++ b/config/lemon.ts
@@ -0,0 +1,18 @@
+export function getLemonConfig() {
+ return {
+ apiKey: process.env.LEMONSQUEEZY_API_KEY!,
+ storeId: Number(process.env.LEMONSQUEEZY_STORE_ID!),
+ productId: Number(process.env.LEMONSQUEEZY_PRODUCT_ID!),
+ variantId: Number(process.env.LEMONSQUEEZY_VARIANT_ID!),
+ webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET!,
+ };
+}
+
+export function isLemonConfigured() {
+ return (
+ !!process.env.LEMONSQUEEZY_API_KEY &&
+ !!process.env.LEMONSQUEEZY_STORE_ID &&
+ !!process.env.LEMONSQUEEZY_PRODUCT_ID &&
+ !!process.env.LEMONSQUEEZY_VARIANT_ID
+ );
+}
diff --git a/config/navigation.ts b/config/navigation.ts
index 72732b6..c33f9da 100644
--- a/config/navigation.ts
+++ b/config/navigation.ts
@@ -337,34 +337,52 @@ export const navigationSections: NavSection[] = [
items: [
{
id: "blocks-introduction",
- title: "Introduction",
+ title: "Overview",
href: "/docs/blocks/introduction",
- description: "Overview of reusable multi-component blocks",
+ description: "Browse all reusable multi-component blocks",
icon: "Map",
},
{
- id: "blocks-hero-sections",
- title: "Hero Sections",
- href: "/docs/blocks/hero-sections",
- description: "Ready-to-use hero block patterns",
+ id: "blocks-hero",
+ title: "Hero",
+ href: "/docs/blocks/hero",
+ description: "Hero section blocks for landing pages",
icon: "PanelTop",
- isComingSoon: true,
},
{
- id: "blocks-pricing-sections",
- title: "Pricing Sections",
- href: "/docs/blocks/pricing-sections",
- description: "Composable pricing table and plan blocks",
- icon: "ListFilter",
- isComingSoon: true,
+ id: "blocks-features",
+ title: "Features",
+ href: "/docs/blocks/features",
+ description: "Feature grid and showcase blocks",
+ icon: "LayoutGrid",
},
{
- id: "blocks-dashboard-shells",
- title: "Dashboard Shells",
- href: "/docs/blocks/dashboard-shells",
- description: "Starter dashboard structures and app shells",
- icon: "RectangleEllipsis",
- isComingSoon: true,
+ id: "blocks-cta",
+ title: "CTA",
+ href: "/docs/blocks/cta",
+ description: "Call-to-action blocks",
+ icon: "MousePointerClick",
+ },
+ {
+ id: "blocks-dashboard",
+ title: "Dashboard",
+ href: "/docs/blocks/dashboard",
+ description: "Dashboard layout blocks",
+ icon: "LayoutDashboard",
+ },
+ {
+ id: "blocks-pricing",
+ title: "Pricing",
+ href: "/docs/blocks/pricing",
+ description: "Pricing table blocks",
+ icon: "CreditCard",
+ },
+ {
+ id: "blocks-testimonials",
+ title: "Testimonials",
+ href: "/docs/blocks/testimonials",
+ description: "Testimonial and social proof blocks",
+ icon: "MessageSquareQuote",
},
],
},
diff --git a/config/pro.ts b/config/pro.ts
new file mode 100644
index 0000000..c549a21
--- /dev/null
+++ b/config/pro.ts
@@ -0,0 +1,56 @@
+export const PRO_FREE_MODE = true;
+
+export const PRO_COMPONENTS: Record = {
+ button: { freeCount: 5 },
+ badge: { freeCount: 5 },
+ accordion: { freeCount: 5 },
+ avatar: { freeCount: 5 },
+ breadcrumb: { freeCount: 5 },
+ checkbox: { freeCount: 5 },
+ dialog: { freeCount: 5 },
+ drawer: { freeCount: 5 },
+ "dropdown-menu": { freeCount: 5 },
+ input: { freeCount: 5 },
+ "input-otp": { freeCount: 5 },
+ popover: { freeCount: 5 },
+ progress: { freeCount: 5 },
+ "radio-group": { freeCount: 5 },
+ select: { freeCount: 5 },
+ separator: { freeCount: 5 },
+ sheet: { freeCount: 5 },
+ skeleton: { freeCount: 5 },
+ slider: { freeCount: 5 },
+ switch: { freeCount: 5 },
+ tabs: { freeCount: 5 },
+ textarea: { freeCount: 5 },
+ toast: { freeCount: 5 },
+ "toggle-group": { freeCount: 5 },
+ tooltip: { freeCount: 5 },
+ card: { freeCount: 3 },
+ "date-picker": { freeCount: 2 },
+ alert: { freeCount: 4 },
+};
+
+export function isProComponent(
+ componentName: string,
+ variantIndex?: number,
+): boolean {
+ if (PRO_FREE_MODE) return false;
+
+ if (!variantIndex !== undefined && variantIndex === undefined) {
+ return false;
+ }
+
+ const config = PRO_COMPONENTS[componentName];
+ if (!config) return false;
+
+ if (variantIndex !== undefined) {
+ return variantIndex >= config.freeCount;
+ }
+
+ return true;
+}
+
+export function getFreeCount(componentName: string): number {
+ return PRO_COMPONENTS[componentName]?.freeCount ?? 5;
+}
diff --git a/content/docs/blocks/cta.mdx b/content/docs/blocks/cta.mdx
new file mode 100644
index 0000000..26ba9aa
--- /dev/null
+++ b/content/docs/blocks/cta.mdx
@@ -0,0 +1,6 @@
+---
+title: CTA
+description: Call-to-action blocks for conversions and user engagement.
+---
+
+CTA blocks are coming soon. These will include centered CTAs, banner CTAs, and multi-action layouts.
diff --git a/content/docs/blocks/dashboard.mdx b/content/docs/blocks/dashboard.mdx
new file mode 100644
index 0000000..16a28bb
--- /dev/null
+++ b/content/docs/blocks/dashboard.mdx
@@ -0,0 +1,6 @@
+---
+title: Dashboard
+description: Dashboard shell and layout blocks for admin panels and apps.
+---
+
+Dashboard blocks are coming soon. These will include sidebar layouts, analytics panels, and data tables.
diff --git a/content/docs/blocks/features.mdx b/content/docs/blocks/features.mdx
new file mode 100644
index 0000000..b2b095f
--- /dev/null
+++ b/content/docs/blocks/features.mdx
@@ -0,0 +1,6 @@
+---
+title: Features
+description: Feature grid and showcase blocks for highlighting product capabilities.
+---
+
+Features blocks are coming soon. These will include icon grids, numbered lists, and feature comparison layouts.
diff --git a/content/docs/blocks/hero.mdx b/content/docs/blocks/hero.mdx
new file mode 100644
index 0000000..24abadc
--- /dev/null
+++ b/content/docs/blocks/hero.mdx
@@ -0,0 +1,6 @@
+---
+title: Hero
+description: Hero section blocks for landing pages and marketing sites.
+---
+
+Hero section blocks are coming soon. These will include centered heroes, SaaS heroes, and split layout options.
diff --git a/content/docs/blocks/introduction.mdx b/content/docs/blocks/introduction.mdx
index 5f41bfb..1bf7cb7 100644
--- a/content/docs/blocks/introduction.mdx
+++ b/content/docs/blocks/introduction.mdx
@@ -3,11 +3,7 @@ title: Blocks
description: Multi-component sections you can copy and ship as complete layouts.
---
-import { Callout } from "@/components/ui/callout";
-
Blocks are reusable section-level UI patterns made from multiple components.
They are designed to help you ship complete pages faster.
-
- Block templates are being prepared and will roll out soon.
-
+Choose a category from the sidebar to browse available blocks.
diff --git a/content/docs/blocks/meta.json b/content/docs/blocks/meta.json
index 5102013..57a4aa1 100644
--- a/content/docs/blocks/meta.json
+++ b/content/docs/blocks/meta.json
@@ -1,5 +1,11 @@
{
"pages": [
- "introduction"
+ "introduction",
+ "hero",
+ "features",
+ "cta",
+ "dashboard",
+ "pricing",
+ "testimonials"
]
}
diff --git a/content/docs/blocks/pricing.mdx b/content/docs/blocks/pricing.mdx
new file mode 100644
index 0000000..0ce502e
--- /dev/null
+++ b/content/docs/blocks/pricing.mdx
@@ -0,0 +1,6 @@
+---
+title: Pricing
+description: Pricing table and plan comparison blocks.
+---
+
+Pricing blocks are coming soon. These will include tiered pricing, comparison tables, and feature breakdowns.
diff --git a/content/docs/blocks/testimonials.mdx b/content/docs/blocks/testimonials.mdx
new file mode 100644
index 0000000..24b4ada
--- /dev/null
+++ b/content/docs/blocks/testimonials.mdx
@@ -0,0 +1,6 @@
+---
+title: Testimonials
+description: Testimonial and social proof blocks for building trust.
+---
+
+Testimonials blocks are coming soon. These will include card grids, carousels, and featured quote layouts.
diff --git a/content/docs/components/badge.mdx b/content/docs/components/badge.mdx
index 24cea72..42a8a88 100644
--- a/content/docs/components/badge.mdx
+++ b/content/docs/components/badge.mdx
@@ -70,21 +70,21 @@ Token-style badges with removable actions.
### Badge With Icon
Leading icon treatment for plan and state labels.
-
+
### Shortcut Badge
Badges paired with keyboard shortcuts for quick actions.
-
+
### Badge Status Row
One state shown in all four appearances.
-
+
@@ -94,6 +94,7 @@ Alpha through deprecated release lifecycle examples.
@@ -101,7 +102,7 @@ Alpha through deprecated release lifecycle examples.
### Changelog Badge
Release note labels for updates, fixes, docs, and perf changes.
-
+
@@ -111,6 +112,7 @@ Flags for runtime, lab, legacy, and headless availability.
@@ -121,6 +123,7 @@ Circular count badges for inbox, alerts, and review totals.
@@ -128,62 +131,62 @@ Circular count badges for inbox, alerts, and review totals.
### Plan Tiers Badge
Pricing and plan emphasis with larger badge sizing.
-
+
### Tag Cloud Badge
Mixed-size tags with removable actions.
-
+
### Status Pills Badge
Rounded status pills including a live animated dot state.
-
+
### Special Types Badge
WIP, lab, request, and featured metadata styles.
-
+
### Outline Row Badge
Outline treatment across many supported badge variants.
-
+
### Ghost Row Badge
Ghost appearance examples for lightweight metadata.
-
+
### Inline Text Badge
Inline badge usage inside prose and UI copy.
-
+
### Disabled Badge
Disabled states across multiple variants and appearances.
-
+
### Version Table Badge
Structured package table with version and lifecycle badges.
-
+
diff --git a/content/docs/components/button.mdx b/content/docs/components/button.mdx
index 87aaed7..b3a307b 100644
--- a/content/docs/components/button.mdx
+++ b/content/docs/components/button.mdx
@@ -74,91 +74,91 @@ A vibrant gradient border button with multiple color schemes and animation effec
### Pulse Button
Pulse ring emphasis for a primary action.
-
+
Pulse
### Gradient Shift Button
Animated gradient surface with hover emphasis.
-
+
Gradient
### Slide Reveal Button
Reveal effect on hover for text or icon swaps.
-
+
Slide Reveal
### Bounce Button
Subtle bounce animation for repeated calls to action.
-
+
Bounce
### Shimmer Button
Animated shimmer for loading or featured actions.
-
+
Shimmer
### Rotate Button
Micro-rotation feedback on hover.
-
+
Rotate
### Glow Button
Glowing outline on hover to draw attention.
-
+
Glow
### Flip Button
Perspective tilt for playful depth.
-
+
Flip
### Fill Button
Border-to-fill transition for secondary CTAs.
-
+
Fill
### Float Button
Slow floating motion for ambient emphasis.
-
+
Float
### Neon Button
Neon-like accent for dark surfaces.
-
+
Neon
### Ink Spread Button
Ink-like hover spread using pseudo-elements.
-
+
Ink Spread
### Spring Button
Springy scale response for tactile feedback.
-
+
Spring
@@ -168,6 +168,7 @@ Underline reveal for minimal text actions.
Underline
@@ -175,28 +176,28 @@ Underline reveal for minimal text actions.
### Ripple Button
Click ripple feedback with motion.
-
+
Ripple
### Expand Button
Larger scale expansion on hover.
-
+
Expand
### Glass Button
Glassmorphism styling for translucent surfaces.
-
+
Glass
### Slide Up Button
Subtle upward motion on hover.
-
+
Slide Up
@@ -206,6 +207,7 @@ Disabled skeleton-style loading state.
Loading
@@ -216,6 +218,7 @@ Rotating gradient border treatment.
Rotate Border
diff --git a/hooks/use-pro-access.ts b/hooks/use-pro-access.ts
new file mode 100644
index 0000000..3c67b73
--- /dev/null
+++ b/hooks/use-pro-access.ts
@@ -0,0 +1,49 @@
+"use client";
+
+import { useState, useEffect, useCallback } from "react";
+
+const LICENSE_KEY = "bilal-ui-license-key";
+const LICENSE_STATUS = "bilal-ui-license-status";
+
+export function useProAccess() {
+ const [hasAccess, setHasAccess] = useState(false);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const stored = localStorage.getItem(LICENSE_STATUS);
+ setHasAccess(stored === "verified");
+ setLoading(false);
+ }, []);
+
+ const verifyAndStore = useCallback(async (key: string) => {
+ setLoading(true);
+ try {
+ const res = await fetch("/api/lemon/verify", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ licenseKey: key }),
+ });
+ const data = await res.json();
+
+ if (data.valid) {
+ localStorage.setItem(LICENSE_KEY, key);
+ localStorage.setItem(LICENSE_STATUS, "verified");
+ setHasAccess(true);
+ return { success: true };
+ }
+ return { success: false, error: data.error ?? "Invalid license key" };
+ } catch {
+ return { success: false, error: "Failed to verify license key" };
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ const clearAccess = useCallback(() => {
+ localStorage.removeItem(LICENSE_KEY);
+ localStorage.removeItem(LICENSE_STATUS);
+ setHasAccess(false);
+ }, []);
+
+ return { hasAccess, loading, verifyAndStore, clearAccess };
+}
diff --git a/lib/lemon.ts b/lib/lemon.ts
new file mode 100644
index 0000000..49545ce
--- /dev/null
+++ b/lib/lemon.ts
@@ -0,0 +1,61 @@
+import { lemonSqueezySetup, createCheckout } from "@lemonsqueezy/lemonsqueezy.js";
+import { getLemonConfig } from "@/config/lemon";
+
+export function setupLemon() {
+ const { apiKey } = getLemonConfig();
+ lemonSqueezySetup({ apiKey });
+}
+
+export async function createCheckoutUrl(opts?: {
+ redirectUrl?: string;
+ email?: string;
+ name?: string;
+}): Promise {
+ setupLemon();
+ const { storeId, variantId } = getLemonConfig();
+
+ const response = await createCheckout(storeId, variantId, {
+ productOptions: opts?.redirectUrl
+ ? {
+ redirectUrl: opts.redirectUrl,
+ enabledVariants: [variantId],
+ }
+ : { enabledVariants: [variantId] },
+ checkoutData: {
+ email: opts?.email,
+ name: opts?.name,
+ },
+ });
+
+ if (response.error) {
+ console.error("LemonSqueezy checkout error:", response.error);
+ return null;
+ }
+
+ return response.data.data.attributes.url;
+}
+
+export async function verifyLicenseKey(
+ licenseKey: string,
+): Promise<{ valid: boolean; error?: string }> {
+ try {
+ const res = await fetch(
+ "https://api.lemonsqueezy.com/v1/licenses/validate",
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ license_key: licenseKey }),
+ },
+ );
+
+ const data = await res.json();
+
+ if (data.valid && data.license_key?.status === "active") {
+ return { valid: true };
+ }
+
+ return { valid: false, error: data.error ?? "License key is not valid" };
+ } catch (err) {
+ return { valid: false, error: "Failed to verify license key" };
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 88e6efd..af76e9d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@base-ui/react": "^1.2.0",
"@hookform/resolvers": "^5.2.2",
+ "@lemonsqueezy/lemonsqueezy.js": "^4.0.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.8",
@@ -1664,6 +1665,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@lemonsqueezy/lemonsqueezy.js": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@lemonsqueezy/lemonsqueezy.js/-/lemonsqueezy.js-4.0.0.tgz",
+ "integrity": "sha512-xcY1/lDrY7CpIF98WKiL1ElsfoVhddP7FT0fw7ssOzrFqQsr44HgolKrQZxd9SywsCPn12OTOUieqDIokI3mFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@mdx-js/mdx": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz",
diff --git a/package.json b/package.json
index c1ae297..734a18e 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@base-ui/react": "^1.2.0",
"@hookform/resolvers": "^5.2.2",
+ "@lemonsqueezy/lemonsqueezy.js": "^4.0.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.8",