Ashraf Al-Kassem Claude Opus 4.6 commited on
Commit
778d373
Β·
1 Parent(s): 6acfd72

fix: add missing postcss config, redesign templates UI, link auth to product app

Browse files

- Add postcss.config.mjs (was missing, caused all Tailwind CSS to not render)
- Redesign templates page: icon containers, stats strip, featured spotlight
with gradient-border, "How it works" light section, cockpit-grid browse grid
- Redesign TemplateCard: category-colored icons, hover arrows, dividers
- Redesign CategoryFilter: count badges on tabs, better spacing
- Link website to product app: Header login/signup β†’ APP_URL/login|signup,
CTASection defaults to APP_URL/signup, PlanCard CTA β†’ APP_URL/signup,
Footer adds Log in + Sign Up links, all pages updated
- Add NEXT_PUBLIC_APP_URL env var (defaults to localhost:3000)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Website/postcss.config.mjs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
Website/src/app/about/page.tsx CHANGED
@@ -267,8 +267,7 @@ export default function AboutPage() {
267
  subheadline="Start with the Free plan or book a demo to see LeadPilot in action."
268
  primaryLabel="Book a Demo"
269
  primaryHref="/contact"
270
- secondaryLabel="Get Started Free"
271
- secondaryHref="/plans"
272
  />
273
  </>
274
  );
 
267
  subheadline="Start with the Free plan or book a demo to see LeadPilot in action."
268
  primaryLabel="Book a Demo"
269
  primaryHref="/contact"
270
+ secondaryLabel="Sign Up Free"
 
271
  />
272
  </>
273
  );
Website/src/app/features/page.tsx CHANGED
@@ -182,8 +182,7 @@ export default async function FeaturesPage() {
182
  subheadline="Start with the Free plan today. No credit card. No setup fees."
183
  primaryLabel="Book a Demo"
184
  primaryHref="/contact"
185
- secondaryLabel="See Plans"
186
- secondaryHref="/plans"
187
  />
188
  </>
189
  );
 
182
  subheadline="Start with the Free plan today. No credit card. No setup fees."
183
  primaryLabel="Book a Demo"
184
  primaryHref="/contact"
185
+ secondaryLabel="Sign Up Free"
 
186
  />
187
  </>
188
  );
Website/src/app/page.tsx CHANGED
@@ -10,7 +10,7 @@ import {
10
  import CTASection from "@/components/CTASection";
11
  import FeatureCard from "@/components/FeatureCard";
12
  import PlanCard from "@/components/PlanCard";
13
- import { getPlans, getModules, getIntegrationProviders, getPublicTemplates } from "@/lib/api";
14
  import { MODULE_MARKETING_MAP } from "@/lib/moduleMap";
15
 
16
  export const metadata: Metadata = {
@@ -463,7 +463,7 @@ export default async function HomePage() {
463
  priceNote={pricing.priceNote}
464
  features={featureLines}
465
  ctaLabel={pricing.available ? "Get Started Free" : "Coming Soon"}
466
- ctaHref="/plans"
467
  available={pricing.available}
468
  highlighted={plan.name === "free"}
469
  />
@@ -488,8 +488,8 @@ export default async function HomePage() {
488
  subheadline="LeadPilot automates lead capture, qualification, and routing so your team can focus on closing."
489
  primaryLabel="Book a Demo"
490
  primaryHref="/contact"
491
- secondaryLabel="Start Free"
492
- secondaryHref="/plans"
493
  />
494
  </>
495
  );
 
10
  import CTASection from "@/components/CTASection";
11
  import FeatureCard from "@/components/FeatureCard";
12
  import PlanCard from "@/components/PlanCard";
13
+ import { getPlans, getModules, getIntegrationProviders, getPublicTemplates, APP_URL } from "@/lib/api";
14
  import { MODULE_MARKETING_MAP } from "@/lib/moduleMap";
15
 
16
  export const metadata: Metadata = {
 
463
  priceNote={pricing.priceNote}
464
  features={featureLines}
465
  ctaLabel={pricing.available ? "Get Started Free" : "Coming Soon"}
466
+ ctaHref={`${APP_URL}/signup`}
467
  available={pricing.available}
468
  highlighted={plan.name === "free"}
469
  />
 
488
  subheadline="LeadPilot automates lead capture, qualification, and routing so your team can focus on closing."
489
  primaryLabel="Book a Demo"
490
  primaryHref="/contact"
491
+ secondaryLabel="Sign Up Free"
492
+ secondaryHref={`${APP_URL}/signup`}
493
  />
494
  </>
495
  );
Website/src/app/plans/PlansClientContent.tsx CHANGED
@@ -12,6 +12,7 @@ import {
12
  } from "lucide-react";
13
  import Link from "next/link";
14
  import type { CatalogPlan } from "@/lib/api";
 
15
  import { MODULE_MARKETING_MAP } from "@/lib/moduleMap";
16
 
17
  // ── Pricing config (not in DB β€” marketing copy) ─────────────────────
@@ -370,15 +371,15 @@ export default function PlansClientContent({ plans }: { plans: CatalogPlan[] })
370
 
371
  {/* CTA */}
372
  {pricing.available ? (
373
- <Link
374
- href="/contact"
375
  className="block text-center px-6 py-3 rounded-xl font-semibold text-white text-sm transition-all duration-200"
376
  style={{ background: "linear-gradient(135deg, #0F766E, #14B8A6)" }}
377
  onMouseEnter={(e) => { e.currentTarget.style.boxShadow = "0 0 25px rgba(15,118,110,0.45)"; }}
378
  onMouseLeave={(e) => { e.currentTarget.style.boxShadow = "none"; }}
379
  >
380
  Get Started Free
381
- </Link>
382
  ) : (
383
  <a
384
  href="#waitlist"
 
12
  } from "lucide-react";
13
  import Link from "next/link";
14
  import type { CatalogPlan } from "@/lib/api";
15
+ import { APP_URL } from "@/lib/api";
16
  import { MODULE_MARKETING_MAP } from "@/lib/moduleMap";
17
 
18
  // ── Pricing config (not in DB β€” marketing copy) ─────────────────────
 
371
 
372
  {/* CTA */}
373
  {pricing.available ? (
374
+ <a
375
+ href={`${APP_URL}/signup`}
376
  className="block text-center px-6 py-3 rounded-xl font-semibold text-white text-sm transition-all duration-200"
377
  style={{ background: "linear-gradient(135deg, #0F766E, #14B8A6)" }}
378
  onMouseEnter={(e) => { e.currentTarget.style.boxShadow = "0 0 25px rgba(15,118,110,0.45)"; }}
379
  onMouseLeave={(e) => { e.currentTarget.style.boxShadow = "none"; }}
380
  >
381
  Get Started Free
382
+ </a>
383
  ) : (
384
  <a
385
  href="#waitlist"
Website/src/app/templates/CategoryFilter.tsx CHANGED
@@ -16,40 +16,64 @@ export default function CategoryFilter({ templates, categories }: Props) {
16
  ? templates.filter((t) => t.category === active)
17
  : templates;
18
 
 
 
 
 
 
 
19
  return (
20
  <>
21
  {/* Filter tabs */}
22
- <div className="flex flex-wrap items-center gap-2 justify-center mb-12">
23
  <button
24
  onClick={() => setActive(null)}
25
- className={`px-3 py-1.5 rounded-lg text-xs font-medium ${
26
  active === null ? "category-link-active" : "category-link"
27
  }`}
28
  >
29
  All
 
 
 
 
 
 
 
 
 
30
  </button>
31
  {categories.map((cat) => (
32
  <button
33
  key={cat.key}
34
  onClick={() => setActive(cat.key)}
35
- className={`px-3 py-1.5 rounded-lg text-xs font-medium ${
36
  active === cat.key ? "category-link-active" : "category-link"
37
  }`}
38
  >
39
  {cat.label}
 
 
 
 
 
 
 
 
 
40
  </button>
41
  ))}
42
  </div>
43
 
44
  {/* Template grid */}
45
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
46
  {filtered.map((t) => (
47
  <TemplateCard key={t.id} template={t} />
48
  ))}
49
  </div>
50
 
51
  {filtered.length === 0 && (
52
- <p className="text-center text-sm py-12" style={{ color: "#64748B" }}>
53
  No templates in this category yet. Check back soon.
54
  </p>
55
  )}
 
16
  ? templates.filter((t) => t.category === active)
17
  : templates;
18
 
19
+ // Count per category
20
+ const countMap: Record<string, number> = {};
21
+ for (const t of templates) {
22
+ countMap[t.category] = (countMap[t.category] ?? 0) + 1;
23
+ }
24
+
25
  return (
26
  <>
27
  {/* Filter tabs */}
28
+ <div className="flex flex-wrap items-center gap-2 justify-center mb-14">
29
  <button
30
  onClick={() => setActive(null)}
31
+ className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-semibold transition-all duration-200 ${
32
  active === null ? "category-link-active" : "category-link"
33
  }`}
34
  >
35
  All
36
+ <span
37
+ className="text-[10px] font-bold px-1.5 py-0.5 rounded-md"
38
+ style={{
39
+ background: active === null ? "rgba(20,184,166,0.2)" : "rgba(255,255,255,0.06)",
40
+ color: active === null ? "#14B8A6" : "#475569",
41
+ }}
42
+ >
43
+ {templates.length}
44
+ </span>
45
  </button>
46
  {categories.map((cat) => (
47
  <button
48
  key={cat.key}
49
  onClick={() => setActive(cat.key)}
50
+ className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-semibold transition-all duration-200 ${
51
  active === cat.key ? "category-link-active" : "category-link"
52
  }`}
53
  >
54
  {cat.label}
55
+ <span
56
+ className="text-[10px] font-bold px-1.5 py-0.5 rounded-md"
57
+ style={{
58
+ background: active === cat.key ? "rgba(20,184,166,0.2)" : "rgba(255,255,255,0.06)",
59
+ color: active === cat.key ? "#14B8A6" : "#475569",
60
+ }}
61
+ >
62
+ {countMap[cat.key] ?? 0}
63
+ </span>
64
  </button>
65
  ))}
66
  </div>
67
 
68
  {/* Template grid */}
69
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
70
  {filtered.map((t) => (
71
  <TemplateCard key={t.id} template={t} />
72
  ))}
73
  </div>
74
 
75
  {filtered.length === 0 && (
76
+ <p className="text-center text-sm py-16" style={{ color: "#64748B" }}>
77
  No templates in this category yet. Check back soon.
78
  </p>
79
  )}
Website/src/app/templates/TemplateCard.tsx CHANGED
@@ -1,55 +1,105 @@
1
  import type { CatalogTemplate } from "@/lib/api";
2
- import { Star } from "lucide-react";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  interface Props {
5
  template: CatalogTemplate;
 
6
  }
7
 
8
- export default function TemplateCard({ template: t }: Props) {
 
 
 
9
  return (
10
- <div className="catalog-card p-6 flex flex-col">
11
- {/* Top row: category + featured */}
12
- <div className="flex items-center gap-2 mb-3">
13
- <span className="badge-category" data-category={t.category}>
14
- {t.category.replace(/_/g, " ")}
15
- </span>
16
- {t.is_featured && (
17
- <span className="inline-flex items-center gap-1 text-[10px] font-semibold" style={{ color: "#FBBF24" }}>
18
- <Star className="w-3 h-3 fill-current" aria-hidden="true" />
19
- Featured
20
- </span>
21
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </div>
23
 
24
- {/* Name + description */}
25
- <h3 className="text-white font-semibold text-base mb-1">{t.name}</h3>
 
 
 
 
 
 
 
26
  <p
27
- className="text-sm leading-relaxed line-clamp-2 flex-1 mb-4"
28
  style={{ color: "rgba(148,163,184,0.85)" }}
29
  >
30
  {t.description}
31
  </p>
32
 
33
- {/* Platform + integration badges */}
34
- <div className="flex flex-wrap gap-1.5 mb-3">
35
- {t.platforms.map((p) => (
36
- <span key={p} className="badge-platform">{p}</span>
37
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  </div>
39
 
40
  {/* Required integrations */}
41
  {t.required_integrations.length > 0 && (
42
- <p className="text-[10px] mb-2" style={{ color: "#475569" }}>
43
  Requires: {t.required_integrations.join(", ")}
44
  </p>
45
  )}
46
-
47
- {/* Clone count */}
48
- {t.clone_count > 0 && (
49
- <p className="text-[10px]" style={{ color: "#475569" }}>
50
- {t.clone_count.toLocaleString()} clones
51
- </p>
52
- )}
53
  </div>
54
  );
55
  }
 
1
  import type { CatalogTemplate } from "@/lib/api";
2
+ import type { LucideIcon } from "lucide-react";
3
+ import {
4
+ Star,
5
+ Copy,
6
+ ArrowUpRight,
7
+ Target,
8
+ TrendingUp,
9
+ Headphones,
10
+ UserPlus,
11
+ } from "lucide-react";
12
+
13
+ const CATEGORY_META: Record<string, { color: string; Icon: LucideIcon }> = {
14
+ lead_generation: { color: "#14B8A6", Icon: Target },
15
+ sales: { color: "#60A5FA", Icon: TrendingUp },
16
+ customer_support: { color: "#FBBF24", Icon: Headphones },
17
+ onboarding: { color: "#C084FC", Icon: UserPlus },
18
+ };
19
 
20
  interface Props {
21
  template: CatalogTemplate;
22
+ featured?: boolean;
23
  }
24
 
25
+ export default function TemplateCard({ template: t, featured = false }: Props) {
26
+ const meta = CATEGORY_META[t.category] ?? { color: "#14B8A6", Icon: Target };
27
+ const { color, Icon } = meta;
28
+
29
  return (
30
+ <div
31
+ className={`group relative flex flex-col ${featured ? "gradient-border p-7" : "catalog-card p-6"}`}
32
+ >
33
+ {/* Header: icon + hover arrow */}
34
+ <div className="flex items-start justify-between mb-4">
35
+ <div
36
+ className="w-11 h-11 rounded-xl flex items-center justify-center shrink-0"
37
+ style={{
38
+ background: `${color}15`,
39
+ border: `1px solid ${color}30`,
40
+ }}
41
+ >
42
+ <Icon className="w-5 h-5" style={{ color }} aria-hidden="true" />
43
+ </div>
44
+ <div className="flex items-center gap-2">
45
+ {t.is_featured && (
46
+ <Star
47
+ className="w-3.5 h-3.5 fill-current"
48
+ style={{ color: "#FBBF24" }}
49
+ aria-hidden="true"
50
+ />
51
+ )}
52
+ <ArrowUpRight
53
+ className="w-4 h-4 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
54
+ style={{ color }}
55
+ aria-hidden="true"
56
+ />
57
+ </div>
58
  </div>
59
 
60
+ {/* Category badge */}
61
+ <span className="badge-category self-start mb-3" data-category={t.category}>
62
+ {t.category.replace(/_/g, " ")}
63
+ </span>
64
+
65
+ {/* Name */}
66
+ <h3 className="text-white font-bold text-base mb-1.5">{t.name}</h3>
67
+
68
+ {/* Description */}
69
  <p
70
+ className={`text-sm leading-relaxed ${featured ? "line-clamp-3" : "line-clamp-2"} flex-1 mb-4`}
71
  style={{ color: "rgba(148,163,184,0.85)" }}
72
  >
73
  {t.description}
74
  </p>
75
 
76
+ {/* Divider */}
77
+ <div className="mb-3" style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }} />
78
+
79
+ {/* Footer: platforms + clone count */}
80
+ <div className="flex items-center justify-between">
81
+ <div className="flex flex-wrap gap-1.5">
82
+ {t.platforms.map((p) => (
83
+ <span key={p} className="badge-platform">{p}</span>
84
+ ))}
85
+ </div>
86
+ {t.clone_count > 0 && (
87
+ <span
88
+ className="inline-flex items-center gap-1 text-[10px]"
89
+ style={{ color: "#475569" }}
90
+ >
91
+ <Copy className="w-3 h-3" aria-hidden="true" />
92
+ {t.clone_count.toLocaleString()}
93
+ </span>
94
+ )}
95
  </div>
96
 
97
  {/* Required integrations */}
98
  {t.required_integrations.length > 0 && (
99
+ <p className="text-[10px] mt-2" style={{ color: "#475569" }}>
100
  Requires: {t.required_integrations.join(", ")}
101
  </p>
102
  )}
 
 
 
 
 
 
 
103
  </div>
104
  );
105
  }
Website/src/app/templates/page.tsx CHANGED
@@ -1,4 +1,6 @@
1
  import type { Metadata } from "next";
 
 
2
  import { getPublicTemplates, getTemplateCategories } from "@/lib/api";
3
  import CTASection from "@/components/CTASection";
4
  import CategoryFilter from "./CategoryFilter";
@@ -10,13 +12,38 @@ export const metadata: Metadata = {
10
  "Browse LeadPilot's library of pre-built automation templates for WhatsApp, Instagram, Messenger, and more. Clone and customise in minutes.",
11
  };
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  export default async function TemplatesPage() {
14
  const [templates, categories] = await Promise.all([
15
  getPublicTemplates(),
16
  getTemplateCategories(),
17
  ]);
18
 
19
- const featuredTemplates = templates.filter((t) => t.is_featured);
 
20
 
21
  return (
22
  <>
@@ -36,12 +63,25 @@ export default async function TemplatesPage() {
36
  }}
37
  />
38
  <div className="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
39
- <p
40
- className="text-xs font-bold uppercase tracking-widest mb-4"
41
- style={{ color: "#14B8A6" }}
 
 
 
42
  >
43
- Template Catalog
44
- </p>
 
 
 
 
 
 
 
 
 
 
45
  <h1
46
  id="templates-hero-heading"
47
  className="text-4xl sm:text-5xl md:text-6xl font-bold text-white mb-6 tracking-tight"
@@ -60,43 +100,197 @@ export default async function TemplatesPage() {
60
  </div>
61
  </section>
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  {/* ─── FEATURED TEMPLATES ───────────────────────────────────── */}
64
  {featuredTemplates.length > 0 && (
65
  <section
66
- className="py-16 border-b"
67
- style={{ background: "#0E1826", borderColor: "rgba(255,255,255,0.06)" }}
68
  aria-labelledby="featured-heading"
69
  >
70
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
71
- <h2
72
- id="featured-heading"
73
- className="text-xs font-bold uppercase tracking-widest mb-8 text-center"
74
- style={{ color: "#14B8A6" }}
75
- >
76
- Featured Templates
77
- </h2>
78
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  {featuredTemplates.map((t) => (
80
- <TemplateCard key={t.id} template={t} />
81
  ))}
82
  </div>
83
  </div>
84
  </section>
85
  )}
86
 
87
- {/* ─── ALL TEMPLATES (filterable) ───────────────────────────── */}
88
  <section
89
  className="py-24"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  style={{ background: "#0B1320" }}
91
  aria-labelledby="all-templates-heading"
92
  >
93
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
 
 
 
 
94
  <div className="text-center mb-10">
 
 
 
 
 
 
95
  <h2
96
  id="all-templates-heading"
97
- className="text-2xl font-bold text-white tracking-tight mb-2"
98
  >
99
- All Templates
100
  </h2>
101
  <p className="text-sm" style={{ color: "#64748B" }}>
102
  {templates.length} templates across {categories.length} categories
@@ -113,8 +307,7 @@ export default async function TemplatesPage() {
113
  subheadline="Clone a template, customise it for your business, and go live in minutes."
114
  primaryLabel="Book a Demo"
115
  primaryHref="/contact"
116
- secondaryLabel="Start Free"
117
- secondaryHref="/plans"
118
  />
119
  </>
120
  );
 
1
  import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+ import { ArrowRight, Copy, Sliders, Rocket } from "lucide-react";
4
  import { getPublicTemplates, getTemplateCategories } from "@/lib/api";
5
  import CTASection from "@/components/CTASection";
6
  import CategoryFilter from "./CategoryFilter";
 
12
  "Browse LeadPilot's library of pre-built automation templates for WhatsApp, Instagram, Messenger, and more. Clone and customise in minutes.",
13
  };
14
 
15
+ const howItWorks = [
16
+ {
17
+ Icon: Copy,
18
+ step: "01",
19
+ title: "Clone",
20
+ description:
21
+ "Pick a template that matches your use case. One click copies the entire flow β€” nodes, logic, and AI prompts β€” into your workspace.",
22
+ },
23
+ {
24
+ Icon: Sliders,
25
+ step: "02",
26
+ title: "Customise",
27
+ description:
28
+ "Swap in your brand voice, tweak qualification questions, and wire up your integrations. The visual builder makes it drag-and-drop simple.",
29
+ },
30
+ {
31
+ Icon: Rocket,
32
+ step: "03",
33
+ title: "Launch",
34
+ description:
35
+ "Publish your flow and start capturing leads immediately. Monitor performance in real time and iterate whenever you need.",
36
+ },
37
+ ];
38
+
39
  export default async function TemplatesPage() {
40
  const [templates, categories] = await Promise.all([
41
  getPublicTemplates(),
42
  getTemplateCategories(),
43
  ]);
44
 
45
+ const featuredTemplates = templates.filter((t) => t.is_featured).slice(0, 3);
46
+ const totalClones = templates.reduce((sum, t) => sum + t.clone_count, 0);
47
 
48
  return (
49
  <>
 
63
  }}
64
  />
65
  <div className="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
66
+ <div
67
+ className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full mb-8"
68
+ style={{
69
+ background: "rgba(15,118,110,0.15)",
70
+ border: "1px solid rgba(20,184,166,0.3)",
71
+ }}
72
  >
73
+ <span
74
+ className="w-2 h-2 rounded-full animate-pulse-glow"
75
+ style={{ background: "#14B8A6" }}
76
+ aria-hidden="true"
77
+ />
78
+ <span
79
+ className="text-xs font-semibold tracking-wider"
80
+ style={{ color: "#14B8A6" }}
81
+ >
82
+ TEMPLATE CATALOG
83
+ </span>
84
+ </div>
85
  <h1
86
  id="templates-hero-heading"
87
  className="text-4xl sm:text-5xl md:text-6xl font-bold text-white mb-6 tracking-tight"
 
100
  </div>
101
  </section>
102
 
103
+ {/* ─── STATS STRIP ─────────────────────────────────────────── */}
104
+ <section
105
+ className="py-10 border-y"
106
+ style={{ background: "#0E1826", borderColor: "rgba(255,255,255,0.06)" }}
107
+ aria-label="Template catalog stats"
108
+ >
109
+ <div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
110
+ <div className="grid grid-cols-3 gap-8 text-center">
111
+ <div>
112
+ <p className="text-2xl sm:text-3xl font-bold text-white">
113
+ {templates.length}
114
+ </p>
115
+ <p
116
+ className="text-xs font-medium uppercase tracking-wider mt-1"
117
+ style={{ color: "#475569" }}
118
+ >
119
+ Templates
120
+ </p>
121
+ </div>
122
+ <div>
123
+ <p className="text-2xl sm:text-3xl font-bold text-white">
124
+ {categories.length}
125
+ </p>
126
+ <p
127
+ className="text-xs font-medium uppercase tracking-wider mt-1"
128
+ style={{ color: "#475569" }}
129
+ >
130
+ Categories
131
+ </p>
132
+ </div>
133
+ <div>
134
+ <p className="text-2xl sm:text-3xl font-bold text-white">
135
+ {totalClones > 0 ? totalClones.toLocaleString() : "β€”"}
136
+ </p>
137
+ <p
138
+ className="text-xs font-medium uppercase tracking-wider mt-1"
139
+ style={{ color: "#475569" }}
140
+ >
141
+ Total Clones
142
+ </p>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </section>
147
+
148
  {/* ─── FEATURED TEMPLATES ───────────────────────────────────── */}
149
  {featuredTemplates.length > 0 && (
150
  <section
151
+ className="py-24 relative overflow-hidden"
152
+ style={{ background: "#0B1320" }}
153
  aria-labelledby="featured-heading"
154
  >
155
+ <div
156
+ className="absolute inset-0 pointer-events-none"
157
+ aria-hidden="true"
158
+ style={{
159
+ background:
160
+ "radial-gradient(ellipse 50% 60% at 50% 100%, rgba(15,118,110,0.15) 0%, transparent 70%)",
161
+ }}
162
+ />
163
+ <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
164
+ <div className="text-center mb-14">
165
+ <p
166
+ className="text-xs font-bold uppercase tracking-widest mb-3"
167
+ style={{ color: "#14B8A6" }}
168
+ >
169
+ Staff Picks
170
+ </p>
171
+ <h2
172
+ id="featured-heading"
173
+ className="text-3xl sm:text-4xl font-bold text-white tracking-tight"
174
+ >
175
+ Featured templates
176
+ </h2>
177
+ <p
178
+ className="mt-4 text-base max-w-xl mx-auto"
179
+ style={{ color: "#64748B" }}
180
+ >
181
+ Our most popular and battle-tested automation flows, ready to deploy.
182
+ </p>
183
+ </div>
184
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
185
  {featuredTemplates.map((t) => (
186
+ <TemplateCard key={t.id} template={t} featured />
187
  ))}
188
  </div>
189
  </div>
190
  </section>
191
  )}
192
 
193
+ {/* ─── HOW TEMPLATES WORK ──────────────────────────────────── */}
194
  <section
195
  className="py-24"
196
+ style={{ background: "#F8FAFC" }}
197
+ aria-labelledby="how-templates-work"
198
+ >
199
+ <div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
200
+ <div className="text-center mb-16">
201
+ <p
202
+ className="text-xs font-bold uppercase tracking-widest mb-3"
203
+ style={{ color: "#0F766E" }}
204
+ >
205
+ How It Works
206
+ </p>
207
+ <h2
208
+ id="how-templates-work"
209
+ className="text-3xl sm:text-4xl font-bold tracking-tight"
210
+ style={{ color: "#0F172A" }}
211
+ >
212
+ From template to live flow in three steps
213
+ </h2>
214
+ </div>
215
+
216
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
217
+ {howItWorks.map(({ Icon, step, title, description }) => (
218
+ <div key={step} className="text-center">
219
+ <div
220
+ className="w-12 h-12 rounded-xl flex items-center justify-center mx-auto mb-5"
221
+ style={{
222
+ background: "rgba(15,118,110,0.08)",
223
+ border: "1px solid rgba(15,118,110,0.15)",
224
+ }}
225
+ >
226
+ <Icon
227
+ className="w-5 h-5"
228
+ style={{ color: "#0F766E" }}
229
+ aria-hidden="true"
230
+ />
231
+ </div>
232
+ <span
233
+ className="text-xl font-bold block mb-2"
234
+ style={{
235
+ background: "linear-gradient(135deg, #0F766E, #14B8A6)",
236
+ WebkitBackgroundClip: "text",
237
+ WebkitTextFillColor: "transparent",
238
+ backgroundClip: "text",
239
+ }}
240
+ >
241
+ {step}
242
+ </span>
243
+ <h3
244
+ className="font-semibold text-base mb-2"
245
+ style={{ color: "#1E293B" }}
246
+ >
247
+ {title}
248
+ </h3>
249
+ <p
250
+ className="text-sm leading-relaxed"
251
+ style={{ color: "#64748B" }}
252
+ >
253
+ {description}
254
+ </p>
255
+ </div>
256
+ ))}
257
+ </div>
258
+
259
+ <div className="text-center mt-12">
260
+ <Link
261
+ href="/contact"
262
+ className="inline-flex items-center gap-2 text-sm font-semibold text-link-primary"
263
+ >
264
+ Need help getting started? Contact us
265
+ <ArrowRight className="w-4 h-4" aria-hidden="true" />
266
+ </Link>
267
+ </div>
268
+ </div>
269
+ </section>
270
+
271
+ {/* ─── ALL TEMPLATES (filterable) ───────────────────────────── */}
272
+ <section
273
+ className="py-24 relative overflow-hidden"
274
  style={{ background: "#0B1320" }}
275
  aria-labelledby="all-templates-heading"
276
  >
277
+ <div
278
+ className="absolute inset-0 cockpit-grid opacity-20"
279
+ aria-hidden="true"
280
+ />
281
+ <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
282
  <div className="text-center mb-10">
283
+ <p
284
+ className="text-xs font-bold uppercase tracking-widest mb-3"
285
+ style={{ color: "#14B8A6" }}
286
+ >
287
+ Browse All
288
+ </p>
289
  <h2
290
  id="all-templates-heading"
291
+ className="text-3xl sm:text-4xl font-bold text-white tracking-tight mb-2"
292
  >
293
+ Explore the full catalog
294
  </h2>
295
  <p className="text-sm" style={{ color: "#64748B" }}>
296
  {templates.length} templates across {categories.length} categories
 
307
  subheadline="Clone a template, customise it for your business, and go live in minutes."
308
  primaryLabel="Book a Demo"
309
  primaryHref="/contact"
310
+ secondaryLabel="Sign Up Free"
 
311
  />
312
  </>
313
  );
Website/src/app/use-cases/page.tsx CHANGED
@@ -414,8 +414,7 @@ export default function UseCasesPage() {
414
  subheadline="Start with the Free plan β€” no credit card, no commitment."
415
  primaryLabel="Book a Demo"
416
  primaryHref="/contact"
417
- secondaryLabel="Get Started Free"
418
- secondaryHref="/plans"
419
  />
420
  </>
421
  );
 
414
  subheadline="Start with the Free plan β€” no credit card, no commitment."
415
  primaryLabel="Book a Demo"
416
  primaryHref="/contact"
417
+ secondaryLabel="Sign Up Free"
 
418
  />
419
  </>
420
  );
Website/src/components/CTASection.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import Link from "next/link";
 
2
 
3
  interface CTASectionProps {
4
  headline?: string;
@@ -9,13 +10,36 @@ interface CTASectionProps {
9
  secondaryHref?: string;
10
  }
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  export default function CTASection({
13
  headline = "Your leads aren't waiting. Neither should you.",
14
  subheadline = "Start capturing and qualifying leads today β€” no credit card required.",
15
  primaryLabel = "Book a Demo",
16
  primaryHref = "/contact",
17
- secondaryLabel = "Start Free",
18
- secondaryHref = "/plans",
19
  }: CTASectionProps) {
20
  return (
21
  <section
@@ -48,18 +72,18 @@ export default function CTASection({
48
  </p>
49
 
50
  <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
51
- <Link
52
  href={primaryHref}
53
  className="px-8 py-3.5 rounded-xl font-semibold text-sm btn-primary"
54
  >
55
  {primaryLabel}
56
- </Link>
57
- <Link
58
  href={secondaryHref}
59
  className="px-8 py-3.5 rounded-xl font-semibold text-sm btn-ghost-dark"
60
  >
61
  {secondaryLabel}
62
- </Link>
63
  </div>
64
  </div>
65
  </section>
 
1
  import Link from "next/link";
2
+ import { APP_URL } from "@/lib/api";
3
 
4
  interface CTASectionProps {
5
  headline?: string;
 
10
  secondaryHref?: string;
11
  }
12
 
13
+ function SmartLink({
14
+ href,
15
+ className,
16
+ children,
17
+ }: {
18
+ href: string;
19
+ className: string;
20
+ children: React.ReactNode;
21
+ }) {
22
+ if (href.startsWith("http")) {
23
+ return (
24
+ <a href={href} className={className}>
25
+ {children}
26
+ </a>
27
+ );
28
+ }
29
+ return (
30
+ <Link href={href} className={className}>
31
+ {children}
32
+ </Link>
33
+ );
34
+ }
35
+
36
  export default function CTASection({
37
  headline = "Your leads aren't waiting. Neither should you.",
38
  subheadline = "Start capturing and qualifying leads today β€” no credit card required.",
39
  primaryLabel = "Book a Demo",
40
  primaryHref = "/contact",
41
+ secondaryLabel = "Sign Up Free",
42
+ secondaryHref = `${APP_URL}/signup`,
43
  }: CTASectionProps) {
44
  return (
45
  <section
 
72
  </p>
73
 
74
  <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
75
+ <SmartLink
76
  href={primaryHref}
77
  className="px-8 py-3.5 rounded-xl font-semibold text-sm btn-primary"
78
  >
79
  {primaryLabel}
80
+ </SmartLink>
81
+ <SmartLink
82
  href={secondaryHref}
83
  className="px-8 py-3.5 rounded-xl font-semibold text-sm btn-ghost-dark"
84
  >
85
  {secondaryLabel}
86
+ </SmartLink>
87
  </div>
88
  </div>
89
  </section>
Website/src/components/Footer.tsx CHANGED
@@ -1,6 +1,8 @@
1
  import Link from "next/link";
2
  import { Zap, Twitter, Linkedin, Github } from "lucide-react";
3
 
 
 
4
  const footerNav = {
5
  Product: [
6
  { label: "How it Works", href: "/product" },
@@ -14,6 +16,8 @@ const footerNav = {
14
  { label: "About", href: "/about" },
15
  { label: "Contact", href: "/contact" },
16
  { label: "Book a Demo", href: "/contact" },
 
 
17
  ],
18
  Legal: [
19
  { label: "Privacy Policy", href: "#" },
@@ -77,12 +81,15 @@ export default function Footer() {
77
  <ul className="space-y-3" role="list">
78
  {links.map((link) => (
79
  <li key={link.label}>
80
- <Link
81
- href={link.href}
82
- className="text-sm footer-link"
83
- >
84
- {link.label}
85
- </Link>
 
 
 
86
  </li>
87
  ))}
88
  </ul>
 
1
  import Link from "next/link";
2
  import { Zap, Twitter, Linkedin, Github } from "lucide-react";
3
 
4
+ const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
5
+
6
  const footerNav = {
7
  Product: [
8
  { label: "How it Works", href: "/product" },
 
16
  { label: "About", href: "/about" },
17
  { label: "Contact", href: "/contact" },
18
  { label: "Book a Demo", href: "/contact" },
19
+ { label: "Log in", href: `${APP_URL}/login` },
20
+ { label: "Sign Up", href: `${APP_URL}/signup` },
21
  ],
22
  Legal: [
23
  { label: "Privacy Policy", href: "#" },
 
81
  <ul className="space-y-3" role="list">
82
  {links.map((link) => (
83
  <li key={link.label}>
84
+ {link.href.startsWith("http") ? (
85
+ <a href={link.href} className="text-sm footer-link">
86
+ {link.label}
87
+ </a>
88
+ ) : (
89
+ <Link href={link.href} className="text-sm footer-link">
90
+ {link.label}
91
+ </Link>
92
+ )}
93
  </li>
94
  ))}
95
  </ul>
Website/src/components/Header.tsx CHANGED
@@ -5,6 +5,8 @@ import { usePathname } from "next/navigation";
5
  import { useState } from "react";
6
  import { Menu, X, Zap } from "lucide-react";
7
 
 
 
8
  const navLinks = [
9
  { label: "Product", href: "/product" },
10
  { label: "Features", href: "/features" },
@@ -76,17 +78,17 @@ export default function Header() {
76
 
77
  {/* Desktop CTAs */}
78
  <div className="hidden md:flex items-center gap-3">
79
- <Link
80
- href="/login"
81
  className="text-sm font-medium transition-colors duration-200"
82
  style={{ color: "rgba(241,245,249,0.6)" }}
83
  onMouseEnter={(e) => { e.currentTarget.style.color = "#F1F5F9"; }}
84
  onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(241,245,249,0.6)"; }}
85
  >
86
  Log in
87
- </Link>
88
- <Link
89
- href="/contact"
90
  className="px-4 py-2 rounded-lg text-sm font-semibold text-white transition-all duration-200"
91
  style={{ background: "linear-gradient(135deg, #0F766E, #14B8A6)" }}
92
  onMouseEnter={(e) => {
@@ -96,8 +98,8 @@ export default function Header() {
96
  e.currentTarget.style.boxShadow = "none";
97
  }}
98
  >
99
- Book a Demo
100
- </Link>
101
  </div>
102
 
103
  {/* Mobile menu button */}
@@ -138,22 +140,22 @@ export default function Header() {
138
  })}
139
  </ul>
140
  <div className="flex flex-col gap-2 mt-4 pt-4" style={{ borderTop: "1px solid rgba(255,255,255,0.07)" }}>
141
- <Link
142
- href="/login"
143
  className="block px-3 py-2 rounded-md text-sm font-medium text-center"
144
  style={{ color: "rgba(241,245,249,0.7)" }}
145
  onClick={() => setMobileOpen(false)}
146
  >
147
  Log in
148
- </Link>
149
- <Link
150
- href="/contact"
151
  className="block px-4 py-2 rounded-lg text-sm font-semibold text-white text-center"
152
  style={{ background: "linear-gradient(135deg, #0F766E, #14B8A6)" }}
153
  onClick={() => setMobileOpen(false)}
154
  >
155
- Book a Demo
156
- </Link>
157
  </div>
158
  </div>
159
  )}
 
5
  import { useState } from "react";
6
  import { Menu, X, Zap } from "lucide-react";
7
 
8
+ const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
9
+
10
  const navLinks = [
11
  { label: "Product", href: "/product" },
12
  { label: "Features", href: "/features" },
 
78
 
79
  {/* Desktop CTAs */}
80
  <div className="hidden md:flex items-center gap-3">
81
+ <a
82
+ href={`${APP_URL}/login`}
83
  className="text-sm font-medium transition-colors duration-200"
84
  style={{ color: "rgba(241,245,249,0.6)" }}
85
  onMouseEnter={(e) => { e.currentTarget.style.color = "#F1F5F9"; }}
86
  onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(241,245,249,0.6)"; }}
87
  >
88
  Log in
89
+ </a>
90
+ <a
91
+ href={`${APP_URL}/signup`}
92
  className="px-4 py-2 rounded-lg text-sm font-semibold text-white transition-all duration-200"
93
  style={{ background: "linear-gradient(135deg, #0F766E, #14B8A6)" }}
94
  onMouseEnter={(e) => {
 
98
  e.currentTarget.style.boxShadow = "none";
99
  }}
100
  >
101
+ Sign Up Free
102
+ </a>
103
  </div>
104
 
105
  {/* Mobile menu button */}
 
140
  })}
141
  </ul>
142
  <div className="flex flex-col gap-2 mt-4 pt-4" style={{ borderTop: "1px solid rgba(255,255,255,0.07)" }}>
143
+ <a
144
+ href={`${APP_URL}/login`}
145
  className="block px-3 py-2 rounded-md text-sm font-medium text-center"
146
  style={{ color: "rgba(241,245,249,0.7)" }}
147
  onClick={() => setMobileOpen(false)}
148
  >
149
  Log in
150
+ </a>
151
+ <a
152
+ href={`${APP_URL}/signup`}
153
  className="block px-4 py-2 rounded-lg text-sm font-semibold text-white text-center"
154
  style={{ background: "linear-gradient(135deg, #0F766E, #14B8A6)" }}
155
  onClick={() => setMobileOpen(false)}
156
  >
157
+ Sign Up Free
158
+ </a>
159
  </div>
160
  </div>
161
  )}
Website/src/components/PlanCard.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import Link from "next/link";
2
  import { Check, Clock } from "lucide-react";
 
3
 
4
  interface PlanCardProps {
5
  name: string;
@@ -20,7 +21,7 @@ export default function PlanCard({
20
  priceNote,
21
  features,
22
  ctaLabel,
23
- ctaHref = "/plans",
24
  available,
25
  highlighted = false,
26
  }: PlanCardProps) {
@@ -114,12 +115,12 @@ export default function PlanCard({
114
  </ul>
115
 
116
  {available ? (
117
- <Link
118
  href={ctaHref}
119
  className="block text-center px-6 py-3 rounded-xl font-semibold text-sm btn-primary"
120
  >
121
  {ctaLabel}
122
- </Link>
123
  ) : (
124
  <button
125
  disabled
 
1
  import Link from "next/link";
2
  import { Check, Clock } from "lucide-react";
3
+ import { APP_URL } from "@/lib/api";
4
 
5
  interface PlanCardProps {
6
  name: string;
 
21
  priceNote,
22
  features,
23
  ctaLabel,
24
+ ctaHref = `${APP_URL}/signup`,
25
  available,
26
  highlighted = false,
27
  }: PlanCardProps) {
 
115
  </ul>
116
 
117
  {available ? (
118
+ <a
119
  href={ctaHref}
120
  className="block text-center px-6 py-3 rounded-xl font-semibold text-sm btn-primary"
121
  >
122
  {ctaLabel}
123
+ </a>
124
  ) : (
125
  <button
126
  disabled
Website/src/lib/api.ts CHANGED
@@ -5,6 +5,9 @@
5
 
6
  const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
7
 
 
 
 
8
  // ── Types ────────────────────────────────────────────────────────────
9
 
10
  export interface CatalogPlan {
 
5
 
6
  const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
7
 
8
+ /** Base URL for the product app (login, signup, dashboard). */
9
+ export const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
10
+
11
  // ── Types ────────────────────────────────────────────────────────────
12
 
13
  export interface CatalogPlan {