diff options
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/landing/package.json | 1 | ||||
| -rw-r--r-- | apps/landing/src/App.tsx | 17 | ||||
| -rw-r--r-- | apps/landing/src/Homepage.tsx | 44 | ||||
| -rw-r--r-- | apps/landing/src/Navbar.tsx | 59 | ||||
| -rw-r--r-- | apps/landing/src/Pricing.tsx | 224 | ||||
| -rw-r--r-- | apps/landing/src/constants.ts | 4 |
6 files changed, 301 insertions, 48 deletions
diff --git a/apps/landing/package.json b/apps/landing/package.json index 3343a542..e6db4d83 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -22,6 +22,7 @@ "lucide-react": "^0.501.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router": "^7.7.1", "react-select": "^5.8.0", "sharp": "^0.33.3", "tailwind-merge": "^2.2.1", 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"; |
