public: add supporter

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-08 22:45:22 +01:00
parent 49d2c7512a
commit 720b56aba6
11 changed files with 450 additions and 182 deletions

View File

@@ -148,15 +148,6 @@ export default async function Page({
</div>
</div>
</div>
<div className="col">
<Image
src={article?.data.cover}
alt={article?.data.title}
width={323}
height={181}
className="rounded-lg w-full md:w-auto"
/>
</div>
</div>
</div>
<div className="relative">
@@ -166,8 +157,24 @@ export default async function Page({
<Body />
</div>
</div>
<aside className="hidden md:block pl-12 pb-12">
<aside className="pl-12 pb-12 gap-8 col">
<Toc toc={article?.data.toc} />
<section className="overflow-hidden relative bg-foreground dark:bg-background-dark text-background dark:text-foreground rounded-xl py-16">
<SingleSwirl className="pointer-events-none absolute top-0 bottom-0 left-0 size-[300px]" />
<SingleSwirl className="pointer-events-none rotate-180 absolute top-0 bottom-0 -right-0 opacity-50 size-[300px]" />
<div className="container center-center col">
<SectionHeader
className="mb-8"
title="Try it"
description="Give it a spin for free. No credit card required."
/>
<Button size="lg" variant="secondary" asChild>
<Link href="https://dashboard.openpanel.dev/onboarding">
Get started today!
</Link>
</Button>
</div>
</section>
</aside>
</div>
@@ -189,29 +196,6 @@ export default async function Page({
</div>
</div>
)}
<div className="absolute top-0 -right-[300px] w-[300px] pl-12 h-full article:block hidden">
<div className="sticky top-32 col gap-8">
<Toc toc={article?.data.toc} />
<section className="overflow-hidden relative bg-foreground dark:bg-background-dark text-background dark:text-foreground rounded-xl py-16">
<SingleSwirl className="pointer-events-none absolute top-0 bottom-0 left-0 size-[300px]" />
<SingleSwirl className="pointer-events-none rotate-180 absolute top-0 bottom-0 -right-0 opacity-50 size-[300px]" />
<div className="container center-center col">
<SectionHeader
className="mb-8"
title="Try it"
description="Give it a spin for free. No credit card required."
/>
<Button size="lg" variant="secondary" asChild>
<Link href="https://dashboard.openpanel.dev/onboarding">
Get started today!
</Link>
</Button>
</div>
</section>
</div>
</div>
</div>
</article>
</div>

View File

@@ -1,86 +0,0 @@
import { url } from '@/app/layout.config';
import { pageSource } from '@/lib/source';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import Script from 'next/script';
export async function generateMetadata(): Promise<Metadata> {
const page = await pageSource.getPage(['pricing']);
if (!page) {
return {
title: 'Page Not Found',
};
}
return {
title: page.data.title,
description: page.data.description,
alternates: {
canonical: url(page.url),
},
openGraph: {
title: page.data.title,
description: page.data.description,
type: 'website',
url: url(page.url),
},
twitter: {
card: 'summary_large_image',
title: page.data.title,
description: page.data.description,
},
};
}
export default async function Page() {
const page = await pageSource.getPage(['pricing']);
const Body = page?.data.body;
if (!page || !Body) {
return notFound();
}
// Create the JSON-LD data
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: page.data.title,
publisher: {
'@type': 'Organization',
name: 'OpenPanel',
logo: {
'@type': 'ImageObject',
url: url('/logo.png'),
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url(page.url),
},
};
return (
<div>
<Script
id="page-schema"
strategy="beforeInteractive"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article className="container max-w-4xl col">
<div className="pt-16 pb-8 col gap-3">
<h1 className="text-5xl font-bold">{page.data.title}</h1>
{page.data.description && (
<p className="text-muted-foreground text-xl">
{page.data.description}
</p>
)}
</div>
<div className="prose">
<Body />
</div>
</article>
</div>
);
}

View File

@@ -0,0 +1,288 @@
import { url } from '@/app/layout.config';
import { HeroContainer } from '@/components/hero';
import { Section, SectionHeader } from '@/components/section';
import { Faq } from '@/components/sections/faq';
import { SupporterPerks } from '@/components/sections/supporter-perks';
import { Testimonials } from '@/components/sections/testimonials';
import { Tag } from '@/components/tag';
import { Button } from '@/components/ui/button';
import {
ArrowDownIcon,
HeartHandshakeIcon,
SparklesIcon,
ZapIcon,
} from 'lucide-react';
import type { Metadata } from 'next';
import Link from 'next/link';
import Script from 'next/script';
export const metadata: Metadata = {
title: 'Become a Supporter',
description:
'Support OpenPanel and get exclusive perks like latest Docker images, prioritized support, and early access to new features.',
alternates: {
canonical: url('/supporter'),
},
openGraph: {
title: 'Become a Supporter',
description:
'Support OpenPanel and get exclusive perks like latest Docker images, prioritized support, and early access to new features.',
type: 'website',
url: url('/supporter'),
},
twitter: {
card: 'summary_large_image',
title: 'Become a Supporter',
description:
'Support OpenPanel and get exclusive perks like latest Docker images, prioritized support, and early access to new features.',
},
};
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Become a Supporter',
publisher: {
'@type': 'Organization',
name: 'OpenPanel',
logo: {
'@type': 'ImageObject',
url: url('/logo.png'),
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url('/supporter'),
},
};
export default function SupporterPage() {
return (
<div>
<Script
id="supporter-schema"
strategy="beforeInteractive"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<HeroContainer>
<div className="container relative z-10 col sm:py-44 max-sm:pt-32">
<div className="col gap-8 text-center">
<div className="col gap-4">
<Tag className="self-center">
<HeartHandshakeIcon className="size-4 text-rose-600" />
Support Open-Source Analytics
</Tag>
<h1 className="text-4xl md:text-5xl font-extrabold leading-[1.1]">
Help us build the future of{' '}
<span className="text-primary">open analytics</span>
</h1>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
Your support accelerates development, funds infrastructure, and
helps us build features faster. Plus, you get exclusive perks
and early access to everything we ship.
</p>
</div>
<div className="col gap-4 justify-center items-center">
<Button size="lg" asChild>
<Link href="https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV">
Become a Supporter
<SparklesIcon className="size-4" />
</Link>
</Button>
<p className="text-sm text-muted-foreground">
Starting at $20/month Cancel anytime
</p>
</div>
</div>
</div>
</HeroContainer>
<div className="container max-w-7xl">
{/* Main Content with Sidebar */}
<div className="grid lg:grid-cols-[1fr_380px] gap-8 mb-16">
{/* Main Content */}
<div className="col gap-12">
{/* Why Support Section */}
<section className="col gap-6">
<h2 className="text-3xl font-bold">Why your support matters</h2>
<div className="col gap-6 text-muted-foreground">
<p className="text-lg">
We're not a big corporation just a small team passionate
about building something useful for developers. OpenPanel
started because we believed analytics tools shouldn't be
complicated or locked behind expensive enterprise
subscriptions.
</p>
<p>When you become a supporter, you're directly funding:</p>
<ul className="col gap-3 list-none pl-0">
<li className="flex items-start gap-3">
<ZapIcon className="size-5 text-primary mt-0.5 shrink-0" />
<div>
<strong className="text-foreground">
Active Development
</strong>
<p className="text-sm mt-1">
More time fixing bugs, adding features, and improving
documentation
</p>
</div>
</li>
<li className="flex items-start gap-3">
<ZapIcon className="size-5 text-primary mt-0.5 shrink-0" />
<div>
<strong className="text-foreground">
Infrastructure
</strong>
<p className="text-sm mt-1">
Keeping servers running, CI/CD pipelines, and
development tools
</p>
</div>
</li>
<li className="flex items-start gap-3">
<ZapIcon className="size-5 text-primary mt-0.5 shrink-0" />
<div>
<strong className="text-foreground">Independence</strong>
<p className="text-sm mt-1">
Staying focused on what matters: building a tool
developers actually want
</p>
</div>
</li>
</ul>
<p>
No corporate speak, no fancy promises just honest work on
making OpenPanel better for everyone. Every contribution, no
matter the size, helps us stay independent and focused on what
matters.
</p>
</div>
</section>
{/* What You Get Section */}
<section className="col gap-6">
<h2 className="text-3xl font-bold">
What you get as a supporter
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div className="p-6 rounded-lg border bg-card">
<h3 className="font-semibold text-lg mb-2">
🚀 Latest Docker Images
</h3>
<p className="text-sm text-muted-foreground mb-3">
Get bleeding-edge builds on every commit. Access new
features weeks before public release.
</p>
<Link
href="/docs/self-hosting/supporter-access-latest-docker-images"
className="text-sm text-primary hover:underline"
>
Learn more →
</Link>
</div>
<div className="p-6 rounded-lg border bg-card">
<h3 className="font-semibold text-lg mb-2">
💬 Prioritized Support
</h3>
<p className="text-sm text-muted-foreground mb-3">
Get help faster with priority support in our Discord
community. Your questions get answered first.
</p>
</div>
<div className="p-6 rounded-lg border bg-card">
<h3 className="font-semibold text-lg mb-2">
✨ Feature Requests
</h3>
<p className="text-sm text-muted-foreground mb-3">
Your ideas and feature requests get prioritized in our
roadmap. Shape the future of OpenPanel.
</p>
</div>
<div className="p-6 rounded-lg border bg-card">
<h3 className="font-semibold text-lg mb-2">
⭐ Exclusive Discord Role
</h3>
<p className="text-sm text-muted-foreground mb-3">
Special badge and recognition in our community. Show your
support with pride.
</p>
</div>
</div>
</section>
{/* Impact Section */}
<section className="p-8 rounded-xl border bg-gradient-to-br from-primary/5 to-primary/10">
<h2 className="text-2xl font-bold mb-4">Your impact</h2>
<p className="text-muted-foreground mb-6">
Every dollar you contribute goes directly into development,
infrastructure, and making OpenPanel better. Here's what your
support enables:
</p>
<div className="grid md:grid-cols-3 gap-4">
<div className="text-center">
<div className="text-3xl font-bold text-primary mb-2">
100%
</div>
<div className="text-sm text-muted-foreground">
Open Source
</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-primary mb-2">
24/7
</div>
<div className="text-sm text-muted-foreground">
Active Development
</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-primary mb-2"></div>
<div className="text-sm text-muted-foreground">
Self-Hostable
</div>
</div>
</div>
</section>
</div>
{/* Sidebar */}
<aside className="lg:block hidden">
<SupporterPerks />
</aside>
</div>
{/* Mobile Perks */}
<div className="lg:hidden mb-16">
<SupporterPerks />
</div>
{/* CTA Section */}
<Section className="container my-0 py-20">
<SectionHeader
tag={
<Tag>
<ArrowDownIcon className="size-4" strokeWidth={1.5} />
Starting at $20/month
</Tag>
}
title="Ready to support OpenPanel?"
description="Join our community of supporters and help us build the best open-source alternative to Mixpanel. Every contribution helps accelerate development and make OpenPanel better for everyone."
/>
<div className="center-center">
<Button size="lg" asChild>
<Link href="https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV">
Become a Supporter Now
<HeartHandshakeIcon className="size-4" />
</Link>
</Button>
</div>
</Section>
</div>
<div className="lg:-mx-20 xl:-mx-40 not-prose mt-16">
<Testimonials />
<Faq />
</div>
</div>
);
}

View File

@@ -28,6 +28,12 @@ export const baseOptions: BaseLayoutProps = {
url: '/pricing',
active: 'nested-url',
},
{
type: 'main',
text: 'Supporter',
url: '/supporter',
active: 'nested-url',
},
{
type: 'main',
text: 'Documentation',

View File

@@ -25,6 +25,12 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
changeFrequency: 'weekly',
priority: 0.5,
},
{
url: url('/supporter'),
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.7,
},
...articles.map((item) => ({
url: url(item.url),
lastModified: item.data.lastModified,

View File

@@ -12,7 +12,7 @@ function formatStars(stars: number) {
}
export function GithubButton() {
const [stars, setStars] = useState(3_700);
const [stars, setStars] = useState(4_800);
useEffect(() => {
getGithubRepoInfo().then((res) => {
if (res?.stargazers_count) {

View File

@@ -4,7 +4,6 @@ import { cn } from '@/lib/utils';
import { AnimatePresence, motion } from 'framer-motion';
import { MenuIcon } from 'lucide-react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useEffect, useRef, useState } from 'react';
import { GithubButton } from './github-button';
import { Logo } from './logo';

View File

@@ -0,0 +1,130 @@
import { cn } from '@/lib/utils';
import {
CheckIcon,
HeartHandshakeIcon,
MessageSquareIcon,
RocketIcon,
SparklesIcon,
StarIcon,
ZapIcon,
PackageIcon,
} from 'lucide-react';
import Link from 'next/link';
const perks = [
{
icon: PackageIcon,
title: 'Latest Docker Images',
description: 'Access to bleeding-edge builds on every commit',
href: '/docs/self-hosting/supporter-access-latest-docker-images',
highlight: true,
},
{
icon: MessageSquareIcon,
title: 'Prioritized Support',
description: 'Get help faster with priority Discord support',
highlight: true,
},
{
icon: RocketIcon,
title: 'Feature Requests',
description: 'Your ideas get prioritized in our roadmap',
highlight: true,
},
{
icon: StarIcon,
title: 'Exclusive Discord Role',
description: 'Special badge and recognition in our community',
},
{
icon: SparklesIcon,
title: 'Early Access',
description: 'Try new features before public release',
},
{
icon: ZapIcon,
title: 'Direct Impact',
description: 'Your support directly funds development',
},
];
export function SupporterPerks({ className }: { className?: string }) {
return (
<div
className={cn(
'col gap-4 p-6 rounded-xl border bg-card',
'sticky top-24',
className,
)}
>
<div className="col gap-2 mb-2">
<div className="row gap-2 items-center">
<HeartHandshakeIcon className="size-5 text-primary" />
<h3 className="font-semibold text-lg">Supporter Perks</h3>
</div>
<p className="text-sm text-muted-foreground">
Everything you get when you support OpenPanel
</p>
</div>
<div className="col gap-3">
{perks.map((perk, index) => {
const Icon = perk.icon;
return (
<div
key={index}
className={cn(
'col gap-1.5 p-3 rounded-lg border transition-colors',
perk.highlight
? 'bg-primary/5 border-primary/20'
: 'bg-background border-border',
)}
>
<div className="row gap-2 items-start">
<Icon
className={cn(
'size-4 mt-0.5 shrink-0',
perk.highlight ? 'text-primary' : 'text-muted-foreground',
)}
/>
<div className="col gap-0.5 flex-1 min-w-0">
<div className="row gap-2 items-center">
<h4
className={cn(
'font-medium text-sm',
perk.highlight && 'text-primary',
)}
>
{perk.title}
</h4>
{perk.highlight && (
<CheckIcon className="size-3.5 text-primary shrink-0" />
)}
</div>
<p className="text-xs text-muted-foreground">
{perk.description}
</p>
{perk.href && (
<Link
href={perk.href}
className="text-xs text-primary hover:underline mt-1"
>
Learn more
</Link>
)}
</div>
</div>
</div>
);
})}
</div>
<div className="mt-4 pt-4 border-t">
<p className="text-xs text-muted-foreground text-center">
Starting at <strong className="text-foreground">$20/month</strong>
</p>
</div>
</div>
);
}

View File

@@ -1,35 +0,0 @@
import { HeartHandshakeIcon } from 'lucide-react';
import Link from 'next/link';
import { SingleSwirl } from '../Swirls';
import { SectionHeader } from '../section';
import { Tag } from '../tag';
import { Button } from '../ui/button';
export function Supporter() {
return (
<section className="overflow-hidden relative bg-foreground dark:bg-background-dark text-background dark:text-foreground rounded-xl p-0 pb-32 pt-16 mx-auto">
<SingleSwirl className="pointer-events-none absolute top-0 bottom-0 left-0" />
<SingleSwirl className="pointer-events-none rotate-180 absolute top-0 bottom-0 -right-0 opacity-50" />
<div className="container center-center col">
<SectionHeader
tag={
<Tag>
<HeartHandshakeIcon className="size-4" />
Support OpenPanel
</Tag>
}
title="Support the future of open analytics"
description="Join our community of supporters and help us build the best open-source alternative to Mixpanel. Every contribution helps keep OpenPanel free and accessible."
/>
<Button size="lg" variant="secondary" asChild>
<Link href="https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV">
Become a supporter
</Link>
</Button>
<div className="text-xs text-muted-foreground max-w-sm mt-4">
Pay what you want and help us keep the lights on.
</div>
</div>
</section>
);
}

View File

@@ -31,7 +31,7 @@ Our private Docker images are hosted on GitHub's container registry and protecte
### Step 1: Become a supporter
Support starts at just **$20/month** and includes:
[Become a supporter](/supporter) to get access to exclusive Docker images. Support starts at just **$20/month** and includes:
- Access to all private Docker images
- Priority support in our Discord community
- Direct impact on OpenPanel's development
@@ -128,7 +128,7 @@ Every contribution helps us:
- Dedicate more time to development
- Maintain and improve infrastructure
- Provide better documentation and support
- Keep OpenPanel free and open-source for everyone
- Keep OpenPanel open-source and accessible for everyone
Thank you for being an essential part of OpenPanel's journey. We couldn't do this without supporters like you! 💙

View File

@@ -1,24 +0,0 @@
---
title: Supporter
description: As a OpenPanel supporter you support our project and keeping it floating.
---
import { Supporter } from '@/components/sections/supporter'
import Stats from '@/components/sections/stats';
import Testimonials from '@/components/sections/testimonials';
import Faq from '@/components/sections/faq';
TL;DR [**Become a supporter 🫶**](https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV)
First off, we want to say a massive thank you for even considering supporting OpenPanel. As an open-source project, every single supporter means the world to us. We're not a big corporation just a small team passionate about building something useful for the developer community.
OpenPanel started because we believed monitoring and observability tools shouldn't be complicated or locked behind expensive enterprise subscriptions. We wanted to create something that developers could actually use, modify, and run themselves. That's why we made it open-source it's not just about the code being free, it's about building something together with the community.
If you're using OpenPanel self-hosted and decide to become a supporter, you're helping us keep this project alive and kicking. Your support means we can spend more time fixing bugs, adding features, and improving documentation. It's as simple as that. No corporate speak, no fancy promises just honest work on making OpenPanel better for everyone.
Every contribution, no matter the size, helps us stay independent and focused on what matters: building a tool that actually helps developers in their daily work.
<div className="lg:-mx-20 xl:-mx-40 not-prose mt-8">
<Supporter />
<Testimonials />
<Faq />
</div>