aboutsummaryrefslogtreecommitdiffstats
path: root/apps/landing/src
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-07-19 17:49:48 +0000
committerMohamed Bassem <me@mbassem.com>2025-08-04 02:51:16 +0000
commit141f411b714b88516071d7dba73cec54f32d3a23 (patch)
treeb449e8e51abaa8045bda67bdf3521c4320aed0cd /apps/landing/src
parenteb720c486fefebc679883c54219fa55063b1dcc6 (diff)
downloadkarakeep-141f411b714b88516071d7dba73cec54f32d3a23.tar.zst
feat(landing): The pricing page
Diffstat (limited to 'apps/landing/src')
-rw-r--r--apps/landing/src/App.tsx17
-rw-r--r--apps/landing/src/Homepage.tsx44
-rw-r--r--apps/landing/src/Navbar.tsx59
-rw-r--r--apps/landing/src/Pricing.tsx224
-rw-r--r--apps/landing/src/constants.ts4
5 files changed, 300 insertions, 48 deletions
diff --git a/apps/landing/src/App.tsx b/apps/landing/src/App.tsx
index ddf92e32..45a019c5 100644
--- a/apps/landing/src/App.tsx
+++ b/apps/landing/src/App.tsx
@@ -1,13 +1,18 @@
import Homepage from "@/src/Homepage";
+import Pricing from "@/src/Pricing";
import Privacy from "@/src/Privacy";
+import { BrowserRouter, Route, Routes } from "react-router";
import "@karakeep/tailwind-config/globals.css";
export default function App() {
- // Poor man router
- if (window.location.pathname === "/privacy") {
- return <Privacy />;
- }
-
- return <Homepage />;
+ return (
+ <BrowserRouter>
+ <Routes>
+ <Route path="/" element={<Homepage />} />
+ <Route path="/pricing" element={<Pricing />} />
+ <Route path="/privacy" element={<Privacy />} />
+ </Routes>
+ </BrowserRouter>
+ );
}
diff --git a/apps/landing/src/Homepage.tsx b/apps/landing/src/Homepage.tsx
index 7ab7fdfd..f88f35af 100644
--- a/apps/landing/src/Homepage.tsx
+++ b/apps/landing/src/Homepage.tsx
@@ -12,16 +12,13 @@ import {
WalletCards,
} from "lucide-react";
+import { DEMO_LINK, DOCS_LINK, GITHUB_LINK } from "./constants";
+import NavBar from "./Navbar";
import appStoreBadge from "/app-store-badge.png?url";
import chromeExtensionBadge from "/chrome-extension-badge.png?url";
import firefoxAddonBadge from "/firefox-addon.png?url";
import playStoreBadge from "/google-play-badge.webp?url";
import screenshot from "/hero.webp?url";
-import Logo from "/icons/karakeep-full.svg?url";
-
-const GITHUB_LINK = "https://github.com/karakeep-app/karakeep";
-const DOCS_LINK = "https://docs.karakeep.app";
-const DEMO_LINK = "https://try.karakeep.app";
const platforms = [
{
@@ -93,43 +90,6 @@ const featuresList = [
const currentYear = new Date().getFullYear();
-function NavBar() {
- return (
- <div className="flex justify-between px-3 py-4">
- <img src={Logo} alt="logo" className="w-36" />
- <div className="hidden items-center gap-6 sm:flex">
- <a
- href={DOCS_LINK}
- target="_blank"
- className="flex justify-center gap-2 text-center"
- rel="noreferrer"
- >
- Docs
- </a>
- <a
- href={GITHUB_LINK}
- target="_blank"
- className="flex justify-center gap-2 text-center"
- rel="noreferrer"
- >
- GitHub
- </a>
- <a
- href={DEMO_LINK}
- target="_blank"
- className={cn(
- "text flex h-full w-28 gap-2",
- buttonVariants({ variant: "default" }),
- )}
- rel="noreferrer"
- >
- Try Demo
- </a>
- </div>
- </div>
- );
-}
-
function Hero() {
return (
<div className="mt-10 flex flex-grow flex-col items-center justify-center gap-6 sm:mt-20">
diff --git a/apps/landing/src/Navbar.tsx b/apps/landing/src/Navbar.tsx
new file mode 100644
index 00000000..6d252db4
--- /dev/null
+++ b/apps/landing/src/Navbar.tsx
@@ -0,0 +1,59 @@
+import { buttonVariants } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+import { Link } from "react-router";
+
+import { DEMO_LINK, DOCS_LINK, GITHUB_LINK } from "./constants";
+import Logo from "/icons/karakeep-full.svg?url";
+
+export default function NavBar() {
+ return (
+ <div className="flex justify-between px-3 py-4">
+ <Link to="/">
+ <img src={Logo} alt="logo" className="w-36" />
+ </Link>
+ <div className="hidden items-center gap-6 sm:flex">
+ <Link to="/pricing" className="flex justify-center gap-2 text-center">
+ Pricing
+ </Link>
+ <a
+ href={DOCS_LINK}
+ target="_blank"
+ className="flex justify-center gap-2 text-center"
+ rel="noreferrer"
+ >
+ Docs
+ </a>
+ <a
+ href={GITHUB_LINK}
+ target="_blank"
+ className="flex justify-center gap-2 text-center"
+ rel="noreferrer"
+ >
+ GitHub
+ </a>
+ <a
+ href="https://cloud.karakeep.app"
+ target="_blank"
+ className={cn(
+ "text flex h-full w-20 gap-2",
+ buttonVariants({ variant: "outline" }),
+ )}
+ rel="noreferrer"
+ >
+ Login
+ </a>
+ <a
+ href={DEMO_LINK}
+ target="_blank"
+ className={cn(
+ "text flex h-full w-28 gap-2",
+ buttonVariants({ variant: "default" }),
+ )}
+ rel="noreferrer"
+ >
+ Try Demo
+ </a>
+ </div>
+ </div>
+ );
+}
diff --git a/apps/landing/src/Pricing.tsx b/apps/landing/src/Pricing.tsx
new file mode 100644
index 00000000..9a1c06f0
--- /dev/null
+++ b/apps/landing/src/Pricing.tsx
@@ -0,0 +1,224 @@
+import { buttonVariants } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+import { Check, ExternalLink } from "lucide-react";
+
+import { DOCS_LINK, GITHUB_LINK, WAITLIST_LINK } from "./constants";
+import NavBar from "./Navbar";
+
+const pricingTiers = [
+ {
+ name: "Free",
+ price: "$0",
+ period: "",
+ description: "Trying Karakeep out",
+ features: [
+ "10 bookmarks",
+ "20MB storage",
+ "Mobile & web apps",
+ "Browser extensions",
+ ],
+ buttonText: "Join Waitlist",
+ buttonVariant: "outline" as const,
+ popular: false,
+ },
+ {
+ name: "Pro",
+ price: "$4",
+ period: "per month",
+ description: "For serious bookmark collectors",
+ features: [
+ "50,000 bookmarks",
+ "50GB storage",
+ "AI-powered tagging",
+ "Full-text search",
+ "Mobile & web apps",
+ "Browser extensions",
+ ],
+ buttonText: "Join Waitlist",
+ buttonVariant: "default" as const,
+ popular: true,
+ },
+ {
+ name: "Self-Hosted",
+ price: "Free",
+ period: "forever",
+ description: "Complete control and privacy",
+ features: [
+ "Unlimited bookmarks",
+ "Unlimited storage",
+ "Complete data control",
+ "Mobile & web apps",
+ "Browser extensions",
+ "Community support",
+ ],
+ buttonText: "View on GitHub",
+ buttonVariant: "outline" as const,
+ popular: false,
+ isGitHub: true,
+ },
+];
+
+function PricingHeader() {
+ return (
+ <div className="text-center">
+ <h1 className="text-4xl font-bold sm:text-6xl">
+ Simple{" "}
+ <span className="bg-gradient-to-r from-purple-600 to-red-600 bg-clip-text text-transparent">
+ Pricing
+ </span>
+ </h1>
+ <p className="mt-4 text-lg text-gray-600">
+ Choose the plan that works best for you
+ </p>
+ </div>
+ );
+}
+
+function PricingCards() {
+ return (
+ <div className="mx-auto mt-16 grid max-w-6xl grid-cols-1 gap-8 px-6 md:grid-cols-3">
+ {pricingTiers.map((tier) => (
+ <div
+ key={tier.name}
+ className={cn(
+ "relative rounded-2xl border bg-white p-8 shadow-sm",
+ tier.popular && "border-purple-500 shadow-lg",
+ )}
+ >
+ <div className="text-center">
+ <h3 className="text-xl font-semibold">{tier.name}</h3>
+ <div className="mt-4 flex items-baseline justify-center">
+ <span className="text-4xl font-bold">{tier.price}</span>
+ {tier.period && (
+ <span className="ml-1 text-gray-500">/{tier.period}</span>
+ )}
+ </div>
+ <p className="mt-2 text-gray-600">{tier.description}</p>
+ </div>
+
+ <ul className="mt-8 space-y-3">
+ {tier.features.map((feature) => (
+ <li key={feature} className="flex items-center">
+ <Check className="h-5 w-5 text-green-500" />
+ <span className="ml-3 text-gray-700">{feature}</span>
+ </li>
+ ))}
+ </ul>
+
+ <div className="mt-8">
+ {tier.isGitHub ? (
+ <a
+ href={GITHUB_LINK}
+ target="_blank"
+ className={cn(
+ "flex w-full items-center justify-center gap-2",
+ buttonVariants({ variant: tier.buttonVariant, size: "lg" }),
+ )}
+ rel="noreferrer"
+ >
+ <ExternalLink className="h-4 w-4" />
+ {tier.buttonText}
+ </a>
+ ) : (
+ <a
+ href={WAITLIST_LINK}
+ target="_blank"
+ className={cn(
+ "flex w-full items-center justify-center",
+ buttonVariants({ variant: tier.buttonVariant, size: "lg" }),
+ )}
+ rel="noreferrer"
+ >
+ {tier.buttonText}
+ </a>
+ )}
+ </div>
+ </div>
+ ))}
+ </div>
+ );
+}
+
+function FAQ() {
+ const faqs = [
+ {
+ question: "What happens to my data if I cancel?",
+ answer:
+ "Your data will be available for 30 days after cancellation. You can export your bookmarks at any time.",
+ },
+ {
+ question: "Are there any restrictions in the self-hosted version?",
+ answer:
+ "No. The selhosted version is completely free, fully-featured, and open source. You just need to provide your own hosting infrastructure.",
+ },
+ {
+ question: "Do you offer refunds?",
+ answer: "Yes, we offer a 7-day money-back guarantee for all paid plans.",
+ },
+ ];
+
+ return (
+ <div className="mx-auto mt-24 max-w-4xl px-6">
+ <h2 className="text-center text-3xl font-bold">
+ Frequently Asked Questions
+ </h2>
+ <div className="mt-12 grid gap-8 md:grid-cols-2">
+ {faqs.map((faq) => (
+ <div key={faq.question}>
+ <h3 className="text-lg font-semibold">{faq.question}</h3>
+ <p className="mt-2 text-gray-600">{faq.answer}</p>
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+}
+
+function Footer() {
+ const currentYear = new Date().getFullYear();
+
+ return (
+ <div className="mt-24 flex items-center justify-between bg-gray-100 px-10 py-6 text-sm">
+ <div>
+ © 2024-{currentYear}{" "}
+ <a href="https://localhostlabs.co.uk" target="_blank" rel="noreferrer">
+ Localhost Labs Ltd
+ </a>
+ </div>
+ <div className="flex items-center gap-6">
+ <a
+ href={DOCS_LINK}
+ target="_blank"
+ className="flex justify-center gap-2 text-center"
+ rel="noreferrer"
+ >
+ Docs
+ </a>
+ <a
+ href={GITHUB_LINK}
+ target="_blank"
+ className="flex justify-center gap-2 text-center"
+ rel="noreferrer"
+ >
+ GitHub
+ </a>
+ </div>
+ </div>
+ );
+}
+
+export default function Pricing() {
+ return (
+ <div className="min-h-screen bg-gray-50">
+ <div className="container mx-auto">
+ <NavBar />
+ <div className="py-16">
+ <PricingHeader />
+ <PricingCards />
+ <FAQ />
+ </div>
+ </div>
+ <Footer />
+ </div>
+ );
+}
diff --git a/apps/landing/src/constants.ts b/apps/landing/src/constants.ts
new file mode 100644
index 00000000..b75cecae
--- /dev/null
+++ b/apps/landing/src/constants.ts
@@ -0,0 +1,4 @@
+export const GITHUB_LINK = "https://github.com/karakeep-app/karakeep";
+export const DOCS_LINK = "https://docs.karakeep.app";
+export const DEMO_LINK = "https://try.karakeep.app";
+export const WAITLIST_LINK = "https://tally.so/r/wo8zzx";