public: update landing page
This commit is contained in:
@@ -5,7 +5,7 @@ export const revalidate = 3600;
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="container mt-[150px]">
|
||||
<div className="container mt-[150px] max-w-2xl">
|
||||
<article className="prose">
|
||||
<Heading1>Privacy Policy</Heading1>
|
||||
<p>Last updated: February 22, 2024</p>
|
||||
|
||||
@@ -5,7 +5,7 @@ export const revalidate = 3600;
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="container mt-[150px]">
|
||||
<div className="container mt-[150px] max-w-2xl">
|
||||
<article className="prose">
|
||||
<Heading1>Terms and Conditions</Heading1>
|
||||
<p>Last updated: February 22, 2024</p>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import type { SVGProps } from 'react';
|
||||
|
||||
export const Blob1 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M162.9 81.7c6.8 18.6-7.8 46.3-29.5 61.3-21.6 15-50.3 17.4-70.1 3.8-19.8-13.5-30.8-42.9-23.2-62.7 7.6-19.7 33.7-29.9 60.9-30.2 27.1-.3 55.2 9.2 61.9 27.8Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob2 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M142.7 86.2c9.4 28.8 11.5 60-2.1 70-13.6 9.9-42.8-1.5-63.9-18.2-21.2-16.7-34.2-38.8-28.9-62C53 52.9 76.5 28.7 96.6 29.8c20.1 1.1 36.8 27.5 46.1 56.4Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob3 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M135.5 88.9c4.3 12.9-2.5 29.9-18.7 44-16.2 14-41.6 25.1-54.7 16.3-13.1-8.8-14-37.6-5.6-56.1C64.9 74.7 82.4 66.4 99 66.8c16.6.3 32.2 9.2 36.5 22.1Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob4 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M161.5 88.5c9.2 20.1 1.7 54.1-18.1 67.6-19.8 13.5-51.9 6.6-76.8-11.2-25-17.9-42.8-46.7-36-63.3 6.7-16.6 38.1-21 66.8-20.2 28.7.9 54.8 7 64.1 27.1Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob5 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M144.6 81.1c3.8 15.9-9.2 32.9-27.5 47.4-18.4 14.6-42.2 26.7-58 17.6-15.7-9-23.5-39.3-15.3-61.3 8.1-21.9 32.2-35.6 54.4-35 22.2.5 42.7 15.4 46.4 31.3Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob6 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M158 83.8c9.5 26.6 4.2 60.6-14.8 74.1-19.1 13.5-52 6.5-68.5-8.9-16.5-15.3-16.6-39-9.7-62 7-23.1 21-45.5 40.1-47.2 19.1-1.7 43.4 17.5 52.9 44Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob7 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M163.8 81.2c6.3 17.6-9.8 44.3-28.5 55.1-18.8 10.7-40.3 5.4-58.3-7.1C58.9 116.6 44.2 96.9 48.6 82c4.4-14.9 27.9-24.9 54-25.7 26.1-.9 54.9 7.4 61.2 24.9Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob8 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M140.2 93.2c8.6 20.4 10.1 49.3-2.1 57.8-12.2 8.6-38.2-3.3-54.2-17.6s-22.1-31-17.8-45.5c4.4-14.5 19.1-26.6 34.4-26.8 15.3-.2 31 11.7 39.7 32.1Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob9 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M150.6 84.9c6.7 19.2-2 44.7-22.1 60.8-20.1 16.2-51.8 23-73.6 8.9C33 140.5 21 105.4 30.1 82.8 39.2 60.2 69.6 50 95.8 51.4c26.2 1.3 48.1 14.3 54.8 33.5Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob10 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M162 75.8c6.7 24.7-7.5 52.1-30.7 69.5s-55.4 24.8-77.8 10.4C31 141.4 18.3 105.4 27.7 77 37 48.6 68.5 27.9 98.1 28.5c29.5.6 57.2 22.6 63.9 47.3Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob11 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M164.3 77.3c7.7 25.5-5.2 55.9-26.2 69.9-21 14.1-50.2 11.8-66.2-1.5-16.1-13.3-19-37.7-12.2-62 6.9-24.3 23.6-48.7 46.1-50.6 22.5-1.9 50.8 18.7 58.5 44.2Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Blob12 = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
|
||||
<path d="M140 81.8c5.8 23.1.4 44.7-14.1 55.7-14.6 11-38.2 11.4-59-1.5C46 123.1 27.8 96.9 33.8 73.6c6-23.4 36.1-43.8 59.7-41.7 23.6 2.1 40.7 26.8 46.5 49.9Z" />
|
||||
</svg>
|
||||
);
|
||||
@@ -1,75 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from '@/components/ui/carousel';
|
||||
import Autoplay from 'embla-carousel-autoplay';
|
||||
import Image from 'next/image';
|
||||
|
||||
const images = [
|
||||
{
|
||||
title: 'Beautiful overview, everything is clickable to get more details',
|
||||
url: '/demo-2/1.png',
|
||||
},
|
||||
{
|
||||
title: 'Histogram, perfect for showing active users',
|
||||
url: '/demo-2/2.png',
|
||||
},
|
||||
{ title: 'Make your overview public', url: '/demo-2/3.png' },
|
||||
{
|
||||
title: 'See real time events from your users',
|
||||
url: '/demo-2/4.png',
|
||||
},
|
||||
{ title: 'The classic line chart', url: '/demo-2/5.png' },
|
||||
{
|
||||
title: 'Bar charts to see your most popular content',
|
||||
url: '/demo-2/6.png',
|
||||
},
|
||||
{ title: 'Get nice metric cards with graphs', url: '/demo-2/7.png' },
|
||||
];
|
||||
|
||||
export function PreviewCarousel() {
|
||||
return (
|
||||
<Carousel
|
||||
className="w-full"
|
||||
opts={{ loop: true }}
|
||||
plugins={[
|
||||
Autoplay({
|
||||
delay: 2000,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<CarouselContent>
|
||||
{images.map((item) => (
|
||||
<CarouselItem
|
||||
key={item.url}
|
||||
className="flex-[0_0_80%] max-w-3xl pl-8"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
aspectRatio: 2982 / 1484,
|
||||
}}
|
||||
>
|
||||
<div className="p-1 rounded-xl overflow-hidden bg-gradient-to-b from-blue-100/50 to-white/50">
|
||||
<Image
|
||||
priority
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
src={item.url}
|
||||
width={2982 * 0.5}
|
||||
height={1484 * 0.5}
|
||||
alt={item.title}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious className="hidden md:visible" />
|
||||
<CarouselNext className="hidden md:visible" />
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export function Heading1({ children, className }: Props) {
|
||||
return (
|
||||
<h1
|
||||
className={cn(
|
||||
'text-4xl md:text-5xl font-bold text-slate-800 !leading-tight',
|
||||
'text-4xl md:text-5xl font-bold text-slate-900 !leading-tight font-serif',
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -39,7 +39,10 @@ export function Heading1({ children, className }: Props) {
|
||||
export function Heading2({ children, className }: Props) {
|
||||
return (
|
||||
<h2
|
||||
className={cn('text-4xl md:text-5xl font-bold text-slate-800', className)}
|
||||
className={cn(
|
||||
'text-4xl md:text-5xl font-bold text-slate-900 font-serif',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
@@ -49,7 +52,23 @@ export function Heading2({ children, className }: Props) {
|
||||
export function Heading3({ children, className }: Props) {
|
||||
return (
|
||||
<h3
|
||||
className={cn('text-2xl md:text-3xl font-bold text-slate-800', className)}
|
||||
className={cn(
|
||||
'text-2xl md:text-3xl font-bold text-slate-900 font-serif',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
export function Heading4({ children, className }: Props) {
|
||||
return (
|
||||
<h3
|
||||
className={cn(
|
||||
'text-xl md:text-2xl font-bold text-slate-900 font-serif',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
|
||||
145
apps/public/src/app/features.tsx
Normal file
145
apps/public/src/app/features.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Heading3 } from './copy';
|
||||
|
||||
interface FeatureItem {
|
||||
title: string;
|
||||
description: string | React.ReactNode;
|
||||
className: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
const features: FeatureItem[] = [
|
||||
{
|
||||
title: 'Visualize Your Data',
|
||||
description: (
|
||||
<p>
|
||||
Gain a deep understanding of your data with our visualization tools.
|
||||
</p>
|
||||
),
|
||||
className: '',
|
||||
image: '/demo-3/img-1.png',
|
||||
},
|
||||
{
|
||||
title: 'Get a good overview',
|
||||
description: (
|
||||
<p>
|
||||
Even though we want to provide advanced charts and graphs, we also want
|
||||
you to understand your data at a glance.
|
||||
</p>
|
||||
),
|
||||
className: 'bg-slate-100',
|
||||
image: '/demo-3/img-2.png',
|
||||
},
|
||||
{
|
||||
title: 'Real-Time Data Access',
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
Access all your events in real-time. No delays or waiting for data to
|
||||
be accessible.
|
||||
</p>
|
||||
<p>
|
||||
Mark events as conversions to highlight and soon notifications with
|
||||
out iOS/Android app.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
className: '',
|
||||
image: '/demo-3/img-3.png',
|
||||
},
|
||||
{
|
||||
title: 'Unlimited dashboards with charts',
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
Create beautiful charts and graphs to visualize your data and share
|
||||
them with your team.
|
||||
</p>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<div className="border border-border px-3 py-1 rounded">
|
||||
✅ Linear
|
||||
</div>
|
||||
<div className="border border-border px-3 py-1 rounded">✅ Area</div>
|
||||
<div className="border border-border px-3 py-1 rounded">✅ Bar</div>
|
||||
<div className="border border-border px-3 py-1 rounded">✅ Map</div>
|
||||
<div className="border border-border px-3 py-1 rounded">✅ Pie</div>
|
||||
<div className="border border-border px-3 py-1 rounded">
|
||||
✅ Funnels
|
||||
</div>
|
||||
<div className="border border-border px-3 py-1 rounded">
|
||||
✅ Histogram
|
||||
</div>
|
||||
<div className="border border-border px-3 py-1 rounded">
|
||||
✅ Metrics
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
className: 'bg-slate-100',
|
||||
image: '/demo-3/img-4.png',
|
||||
},
|
||||
{
|
||||
title: 'Understand your users',
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
Deep dive into your user's behavior and understand how they interact
|
||||
with your app/website.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
className: '',
|
||||
image: '/demo-3/img-5.png',
|
||||
},
|
||||
];
|
||||
|
||||
export function Features() {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{features.map((feature, i) => {
|
||||
return (
|
||||
<Feature key={feature.title} {...feature} even={i % 2 === 0}>
|
||||
{feature.description}
|
||||
</Feature>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Feature({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
image,
|
||||
even,
|
||||
}: FeatureItem & { even: boolean; children: React.ReactNode }) {
|
||||
return (
|
||||
<section className={cn('py-16 group', className)}>
|
||||
<div
|
||||
className={cn(
|
||||
'container flex min-h-[300px] items-center gap-16 justify-between max-md:flex-col-reverse',
|
||||
!even && 'md:flex-row-reverse'
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col w-full">
|
||||
<Heading3 className="mb-2">{title}</Heading3>
|
||||
<div className="prose-xl">{children}</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Image
|
||||
src={image}
|
||||
alt={title}
|
||||
width={600}
|
||||
height={400}
|
||||
className="border-8 border-black/5 rounded-xl w-full max-w-xl group-hover:rotate-1 group-hover:scale-[101%] transition-transform duration-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,56 +1,36 @@
|
||||
import { Tooltip, TooltipContent } from '@/components/ui/tooltip';
|
||||
import { TooltipTrigger } from '@radix-ui/react-tooltip';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { PreviewCarousel } from './carousel';
|
||||
import { Heading1, Lead2 } from './copy';
|
||||
import { JoinWaitlistHero } from './join-waitlist-hero';
|
||||
import { SocialProofServer } from './social-proof';
|
||||
import { SocialProof } from './social-proof/social-proof';
|
||||
|
||||
const avatars = [
|
||||
'https://api.dicebear.com/7.x/adventurer/svg?seed=Chester&backgroundColor=b6e3f4',
|
||||
'https://api.dicebear.com/7.x/adventurer/svg?seed=Casper&backgroundColor=c0aede',
|
||||
'https://api.dicebear.com/7.x/adventurer/svg?seed=Boo&backgroundColor=ffdfbf',
|
||||
];
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<div className="flex py-32 flex-col items-center w-full text-center bg-[#1F54FF] relative overflow-hidden">
|
||||
{/* <div className="inset-0 absolute h-full w-full bg-[radial-gradient(circle,rgba(255,255,255,0.1)_0%,rgba(255,255,255,0)_100%)]"></div> */}
|
||||
<div className="inset-0 absolute h-full w-full flex items-center justify-center">
|
||||
<div className="w-[600px] h-[600px] ring-1 ring-white/05 rounded-full shrink-0"></div>
|
||||
</div>
|
||||
<div className="inset-0 absolute h-full w-full flex items-center justify-center">
|
||||
<div className="w-[900px] h-[900px] ring-1 ring-white/10 rounded-full shrink-0"></div>
|
||||
</div>
|
||||
<div className="inset-0 absolute h-full w-full flex items-center justify-center">
|
||||
<div className="w-[1200px] h-[1200px] ring-1 ring-white/20 rounded-full shrink-0"></div>
|
||||
</div>
|
||||
<div className="relative flex flex-col items-center max-w-3xl">
|
||||
<Image
|
||||
width={64}
|
||||
height={64}
|
||||
src="/logo-white.png"
|
||||
alt="Openpanel Logo"
|
||||
className="w-16 h-16 mb-8"
|
||||
/>
|
||||
<Heading1 className="mb-4 text-white">
|
||||
An open-source
|
||||
<br />
|
||||
alternative to Mixpanel
|
||||
</Heading1>
|
||||
<Lead2 className="text-white/70 font-light">
|
||||
The power of Mixpanel, the ease of Plausible <br />
|
||||
and nothing from Google Analytics 😉
|
||||
</Lead2>
|
||||
<div className="my-12 w-full flex flex-col items-center">
|
||||
<div className="relative overflow-hidden">
|
||||
{/* <div className="bg-blue-50 w-2/5 h-full absolute top-0 right-0"></div> */}
|
||||
<div className="container md:h-screen min-h-[700px] flex flex-col md:flex-row items-center relative max-md:pt-32 gap-4 md:gap-8">
|
||||
<div className="flex-1 lg:min-w-[400px] sm:min-w-[350px] max-md:text-center">
|
||||
<Heading1 className="mb-4 text-slate-950">
|
||||
An open-source
|
||||
<br />
|
||||
alternative to Mixpanel
|
||||
</Heading1>
|
||||
<Lead2 className="mb-12">
|
||||
The power of Mixpanel, the ease of Plausible and nothing from Google
|
||||
Analytics 😉
|
||||
</Lead2>
|
||||
<JoinWaitlistHero />
|
||||
<SocialProofServer className="mt-6" />
|
||||
</div>
|
||||
<div className="mt-16 md:pt-8 w-full">
|
||||
<div className="bg-black/5 md:p-2 rounded-2xl h-[max(90vh,650px)] flex">
|
||||
<iframe
|
||||
src="https://dashboard.openpanel.dev/share/overview/ZQsEhG"
|
||||
className="w-full h-full rounded-xl h-[max(90vh,650px)]"
|
||||
title="Openpanel Dashboard"
|
||||
scrolling="no"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PreviewCarousel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { cn } from '@/utils/cn';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface JoinWaitlistProps {
|
||||
className?: string;
|
||||
@@ -20,51 +19,33 @@ interface JoinWaitlistProps {
|
||||
export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
|
||||
const [value, setValue] = useState('');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// @ts-ignore
|
||||
window.op?.('event', 'waitlist_open');
|
||||
window.op('event', 'waitlist_success');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (success) {
|
||||
// @ts-ignore
|
||||
window.op?.('event', 'waitlist_success', {
|
||||
email: value,
|
||||
});
|
||||
}
|
||||
}, [success]);
|
||||
|
||||
const renderSuccess = () => (
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Thanks so much!</DialogTitle>
|
||||
<DialogDescription>
|
||||
You're now on the waiting list. We'll let you know when we're ready.
|
||||
Should be within a month or two 🚀
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setOpen(false)}>Got it!</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
);
|
||||
|
||||
const renderForm = () => (
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Almost there!</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter your email to join the waiting list. We'll let you know when
|
||||
we're ready.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Thanks so much!</DialogTitle>
|
||||
<DialogDescription>
|
||||
You're now on the waiting list. We'll let you know when we're
|
||||
ready. Should be within a month or two 🚀
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setOpen(false)}>Got it!</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<form
|
||||
className="w-full max-w-md"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
fetch('/api/waitlist', {
|
||||
@@ -75,7 +56,7 @@ export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
|
||||
},
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
setSuccess(true);
|
||||
setOpen(true);
|
||||
}
|
||||
});
|
||||
}}
|
||||
@@ -95,42 +76,6 @@ export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
{success ? renderSuccess() : renderForm()}
|
||||
</Dialog>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
size="lg"
|
||||
className="text-lg h-12"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
Join waitlist now
|
||||
</Button>
|
||||
|
||||
<div className="relative">
|
||||
<Link
|
||||
href="https://dashboard.openpanel.dev/share/overview/ZQsEhG"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
>
|
||||
<Button size="lg" variant="outline" className="text-lg h-12">
|
||||
Demo
|
||||
</Button>
|
||||
</Link>
|
||||
<img
|
||||
src="/clickable-demo.png"
|
||||
className="w-44 shrink-0 absolute left-full -top-8 translate-x-10 max-w-none"
|
||||
alt="Clickable demo button"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { Metadata } from 'next';
|
||||
import { Bricolage_Grotesque } from 'next/font/google';
|
||||
import { Bricolage_Grotesque, Inter } from 'next/font/google';
|
||||
|
||||
import { OpenpanelProvider } from '@openpanel/nextjs';
|
||||
|
||||
@@ -9,6 +9,8 @@ import { defaultMeta } from './meta';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
|
||||
import { Navbar } from './navbar';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
...defaultMeta,
|
||||
alternates: {
|
||||
@@ -16,10 +18,17 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
const font = Bricolage_Grotesque({
|
||||
const head = Bricolage_Grotesque({
|
||||
display: 'swap',
|
||||
subsets: ['latin'],
|
||||
weight: ['400', '700'],
|
||||
variable: '--font-serif',
|
||||
});
|
||||
const body = Inter({
|
||||
display: 'swap',
|
||||
subsets: ['latin'],
|
||||
weight: ['400', '700'],
|
||||
variable: '--font-sans',
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -31,10 +40,12 @@ export default function RootLayout({
|
||||
<html lang="en" className="light">
|
||||
<body
|
||||
className={cn(
|
||||
'min-h-screen antialiased grainy text-slate-600',
|
||||
font.className
|
||||
'min-h-screen antialiased grainy text-slate-900 font-sans',
|
||||
head.variable,
|
||||
body.variable
|
||||
)}
|
||||
>
|
||||
<Navbar darkText />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
|
||||
@@ -9,6 +9,8 @@ export const defaultMeta: Metadata = {
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
url: 'https://openpanel.dev',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://openpanel.dev/ogimage.png',
|
||||
|
||||
@@ -18,7 +18,7 @@ export function Navbar({ darkText = false, className }: Props) {
|
||||
className={cn('absolute top-0 left-0 right-0 z-10', textColor, className)}
|
||||
>
|
||||
<div className="container flex justify-between items-center py-4">
|
||||
<Logo />
|
||||
<Logo className="max-sm:[&_span]:hidden" />
|
||||
<nav className="flex gap-4">
|
||||
{pathname !== '/' && <Link href="/">Home</Link>}
|
||||
<a href="https://docs.openpanel.dev" target="_blank">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { db } from '@openpanel/db';
|
||||
import { ALink } from '@/components/ui/button';
|
||||
import { ExternalLinkIcon } from 'lucide-react';
|
||||
|
||||
import { Heading2, Lead2, Paragraph } from './copy';
|
||||
import { Features } from './features';
|
||||
import { Hero } from './hero';
|
||||
import { Navbar } from './navbar';
|
||||
import { Sections } from './section';
|
||||
import { Pricing } from './pricing';
|
||||
import { PunchLines } from './punch-lines';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const revalidate = 3600;
|
||||
@@ -11,22 +13,36 @@ export const revalidate = 3600;
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Navbar darkText={false} className="[&_img]:hidden" />
|
||||
<Hero />
|
||||
<div className="container">
|
||||
<div className="my-24">
|
||||
<Heading2 className="md:text-5xl mb-2 leading-none">
|
||||
<div className="py-24 bg-gradient-to-bl from-blue-600 to-blue-800">
|
||||
<div className="container">
|
||||
<Heading2 className="md:text-5xl mb-2 leading-none text-white">
|
||||
Analytics should be easy
|
||||
<br />
|
||||
and powerful
|
||||
</Heading2>
|
||||
<Lead2>
|
||||
<Lead2 className="text-white/80">
|
||||
The power of Mixpanel, the ease of Plausible and nothing from Google
|
||||
Analytics 😉
|
||||
Analytics 😉 Curious how it looks?
|
||||
</Lead2>
|
||||
<ALink
|
||||
href="https://dashboard.openpanel.dev/share/overview/ZQsEhG"
|
||||
target="_blank"
|
||||
className="mt-8"
|
||||
variant={'outline'}
|
||||
>
|
||||
Check out the demo
|
||||
<ExternalLinkIcon className="w-4 h-4 ml-2" />
|
||||
</ALink>
|
||||
</div>
|
||||
<Sections />
|
||||
</div>
|
||||
|
||||
<Features />
|
||||
|
||||
<PunchLines />
|
||||
|
||||
<Pricing />
|
||||
|
||||
<div className="container mt-40">
|
||||
<div className="flex flex-col md:flex-row gap-8">
|
||||
<div className="mb-4 md:w-1/2 flex-shrink-0 relative">
|
||||
|
||||
72
apps/public/src/app/pricing.tsx
Normal file
72
apps/public/src/app/pricing.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { CheckIcon } from 'lucide-react';
|
||||
|
||||
import { Heading2, Lead2 } from './copy';
|
||||
|
||||
export function Pricing() {
|
||||
return (
|
||||
<div className="bg-slate-200 py-32" id="#pricing">
|
||||
<div className="container">
|
||||
<section className="container flex flex-col gap-6 md:max-w-[64rem]">
|
||||
<div className="mx-auto flex w-full flex-col gap-4 md:max-w-[58rem]">
|
||||
<Heading2>Simple, transparent pricing</Heading2>
|
||||
<Lead2 className="max-w-[85%] leading-normal text-muted-foreground sm:text-lg sm:leading-7">
|
||||
Everything is included, just decide how many events you want to
|
||||
track each month.
|
||||
</Lead2>
|
||||
</div>
|
||||
<div className="grid w-full items-start gap-10 rounded-lg border md:p-10 md:grid-cols-[1fr_200px]">
|
||||
<div className="grid gap-6">
|
||||
<h3 className="text-xl font-bold sm:text-2xl">
|
||||
What's included for{' '}
|
||||
<span className="bg-slate-300 rounded px-0.5">all plans</span>
|
||||
</h3>
|
||||
<ul className="grid gap-3 text-sm text-muted-foreground sm:grid-cols-2">
|
||||
<li className="flex items-center">
|
||||
<CheckIcon className="mr-2 h-4 w-4" /> Unlimited websites/apps
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<CheckIcon className="mr-2 h-4 w-4" /> Unlimited Users
|
||||
</li>
|
||||
|
||||
<li className="flex items-center">
|
||||
<CheckIcon className="mr-2 h-4 w-4" /> Unlimted dashboards
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<CheckIcon className="mr-2 h-4 w-4" /> Unlimted charts
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<CheckIcon className="mr-2 h-4 w-4" /> Unlimted tracked
|
||||
profiles
|
||||
</li>
|
||||
<li className="flex items-center font-bold text-slate-900">
|
||||
<CheckIcon className="mr-2 h-4 w-4" /> Yes, its that simple
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-left md:text-right">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
From
|
||||
</p>
|
||||
<h4 className="text-7xl font-bold">$10</h4>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
billed monthly
|
||||
</p>
|
||||
</div>
|
||||
{/* <Link href="/login" className={cn(buttonVariants({ size: "lg" }))}>
|
||||
Get Started
|
||||
</Link> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto flex w-full max-w-[58rem] flex-col gap-4">
|
||||
<p className="max-w-[85%] leading-normal text-muted-foreground sm:leading-7">
|
||||
Exact pricing will come soon, but we asure you, it will be cheaper
|
||||
than the competition.
|
||||
</p>
|
||||
<p className="font-bold">During beta everything is free!</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
141
apps/public/src/app/punch-lines.tsx
Normal file
141
apps/public/src/app/punch-lines.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { LucideIcon, LucideProps } from 'lucide-react';
|
||||
import {
|
||||
ClockIcon,
|
||||
CloudIcon,
|
||||
CookieIcon,
|
||||
DollarSignIcon,
|
||||
HandshakeIcon,
|
||||
KeyIcon,
|
||||
} from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Heading2, Heading3, Heading4 } from './copy';
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: 'Own Your Own Data',
|
||||
description: (
|
||||
<p>
|
||||
All our serveres are hosted in EU (Stockholm) and we are fully GDPR
|
||||
compliant.
|
||||
</p>
|
||||
),
|
||||
icon: KeyIcon,
|
||||
color: '#2563EB',
|
||||
className: 'bg-blue-light',
|
||||
},
|
||||
{
|
||||
title: 'Cloud or Self-Hosting',
|
||||
description: (
|
||||
<p>
|
||||
Choose between the flexibility of cloud-based hosting or the autonomy of
|
||||
self-hosting to tailor your analytics infrastructure to your needs.
|
||||
</p>
|
||||
),
|
||||
icon: CloudIcon,
|
||||
color: '#ff7557',
|
||||
className: '', // 'bg-[#ff7557]',
|
||||
},
|
||||
{
|
||||
title: 'Real-Time Events',
|
||||
description: (
|
||||
<p>
|
||||
Stay up-to-date with real-time event tracking, enabling instant insights
|
||||
into user actions as they happen.
|
||||
</p>
|
||||
),
|
||||
icon: ClockIcon,
|
||||
color: '#7fe1d8',
|
||||
className: '', // bg-[#7fe1d8]
|
||||
},
|
||||
{
|
||||
title: 'No cookies!',
|
||||
description: (
|
||||
<p>
|
||||
Our trackers are cookie-free, skip that annyoing cookie consent banner!
|
||||
</p>
|
||||
),
|
||||
icon: CookieIcon,
|
||||
color: '#f8bc3c',
|
||||
className: 'bg-blue-dark', //'bg-[#f8bc3c]',
|
||||
},
|
||||
{
|
||||
title: 'Cost-Effective',
|
||||
description: (
|
||||
<p>
|
||||
We have combined the best from Mixpanel and Plausible. Cut the costs and
|
||||
keep the features.
|
||||
</p>
|
||||
),
|
||||
icon: DollarSignIcon,
|
||||
color: '#0f7ea0',
|
||||
className: 'bg-[#3ba974]',
|
||||
},
|
||||
{
|
||||
title: 'Predictable pricing',
|
||||
description: (
|
||||
<p>You only pay for events, everything else is included. No surprises.</p>
|
||||
),
|
||||
icon: HandshakeIcon,
|
||||
color: '#0f7ea0',
|
||||
className: 'bg-[#3ba974]',
|
||||
},
|
||||
{
|
||||
title: 'First Class React Native Support',
|
||||
description: (
|
||||
<p>
|
||||
Our SDK is built with React Native in mind, making it easy to integrate
|
||||
with your mobile apps.
|
||||
</p>
|
||||
),
|
||||
icon: (({ className }: LucideProps) => {
|
||||
return (
|
||||
<Image
|
||||
src="/react-native.svg"
|
||||
alt="React Native"
|
||||
className={cn(className, 'p-3')}
|
||||
width={50}
|
||||
height={50}
|
||||
/>
|
||||
);
|
||||
}) as unknown as LucideIcon,
|
||||
color: '#3ba974',
|
||||
className: 'bg-[#e19900]',
|
||||
},
|
||||
];
|
||||
|
||||
export function PunchLines() {
|
||||
return (
|
||||
<div className="bg-slate-700 py-32">
|
||||
<Heading2 className="text-white text-center mb-16">
|
||||
Not convinced?
|
||||
</Heading2>
|
||||
<div className="container">
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{items.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<div
|
||||
className="border border-border p-6 rounded-xl bg-white"
|
||||
key={item.title}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'h-14 w-14 rounded-full flex items-center justify-center mb-4',
|
||||
item.color
|
||||
)}
|
||||
style={{ background: item.color }}
|
||||
>
|
||||
<Icon color="#fff" />
|
||||
</div>
|
||||
<Heading4>{item.title}</Heading4>
|
||||
<div className="prose">{item.description}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { LucideIcon, LucideProps } from 'lucide-react';
|
||||
import {
|
||||
ArrowUpFromDotIcon,
|
||||
BarChart2Icon,
|
||||
BellIcon,
|
||||
BookmarkIcon,
|
||||
CheckCircle,
|
||||
ClockIcon,
|
||||
CloudIcon,
|
||||
CloudLightningIcon,
|
||||
CompassIcon,
|
||||
ConeIcon,
|
||||
DatabaseIcon,
|
||||
DollarSignIcon,
|
||||
DownloadIcon,
|
||||
FilterIcon,
|
||||
FolderIcon,
|
||||
HandCoinsIcon,
|
||||
HandshakeIcon,
|
||||
KeyIcon,
|
||||
PieChartIcon,
|
||||
RouteIcon,
|
||||
ServerIcon,
|
||||
ShieldPlusIcon,
|
||||
ShoppingCartIcon,
|
||||
StarIcon,
|
||||
ThumbsUpIcon,
|
||||
TrendingUpIcon,
|
||||
UserRoundSearchIcon,
|
||||
UsersIcon,
|
||||
WebhookIcon,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { Widget } from './widget';
|
||||
|
||||
interface SectionItem {
|
||||
title: string;
|
||||
description: string | React.ReactNode;
|
||||
icon: LucideIcon;
|
||||
color: string;
|
||||
soon?: string;
|
||||
icons: LucideIcon[];
|
||||
className: string;
|
||||
}
|
||||
|
||||
const sections: SectionItem[] = [
|
||||
{
|
||||
title: 'Own Your Own Data',
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
Take control of your data privacy and ownership with our platform,
|
||||
ensuring full transparency and security.
|
||||
</p>
|
||||
<p>
|
||||
All our serveres are hosted in EU (Stockholm) and we are fully GDPR
|
||||
compliant.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
icon: KeyIcon,
|
||||
color: '#2563EB',
|
||||
icons: [FolderIcon, DatabaseIcon, ShieldPlusIcon, KeyIcon],
|
||||
className: 'bg-blue-light',
|
||||
},
|
||||
{
|
||||
title: 'Cloud or Self-Hosting',
|
||||
description: (
|
||||
<p>
|
||||
Choose between the flexibility of cloud-based hosting or the autonomy of
|
||||
self-hosting to tailor your analytics infrastructure to your needs.
|
||||
</p>
|
||||
),
|
||||
icon: CloudIcon,
|
||||
color: '#ff7557',
|
||||
icons: [CloudIcon, CheckCircle, ServerIcon, DownloadIcon],
|
||||
className: '', // 'bg-[#ff7557]',
|
||||
},
|
||||
{
|
||||
title: 'Real-Time Events',
|
||||
description: (
|
||||
<p>
|
||||
Stay up-to-date with real-time event tracking, enabling instant insights
|
||||
into user actions as they happen.
|
||||
</p>
|
||||
),
|
||||
icon: ClockIcon,
|
||||
color: '#7fe1d8',
|
||||
icons: [CloudLightningIcon, ShoppingCartIcon, ArrowUpFromDotIcon],
|
||||
className: '', // bg-[#7fe1d8]
|
||||
},
|
||||
{
|
||||
title: 'Deep Dive into User Behaviors',
|
||||
description: (
|
||||
<p>
|
||||
Gain profound insights into user behavior with comprehensive analytics
|
||||
tools, allowing you to understand your audience's actions and
|
||||
preferences.
|
||||
</p>
|
||||
),
|
||||
icon: UserRoundSearchIcon,
|
||||
color: '#f8bc3c',
|
||||
icons: [UsersIcon, RouteIcon, BookmarkIcon],
|
||||
className: 'bg-blue-dark', //'bg-[#f8bc3c]',
|
||||
},
|
||||
{
|
||||
title: 'Powerful Report Explorer',
|
||||
description: (
|
||||
<p>
|
||||
Explore and analyze your data effortlessly with our powerful report
|
||||
explorer, simplifying the process of deriving meaningful insights.
|
||||
</p>
|
||||
),
|
||||
icon: CompassIcon,
|
||||
color: '#b3596e',
|
||||
icons: [ThumbsUpIcon, TrendingUpIcon, PieChartIcon, BarChart2Icon],
|
||||
className: 'bg-[#ff7557]',
|
||||
},
|
||||
{
|
||||
soon: 'Coming soon',
|
||||
title: 'Funnels',
|
||||
description: (
|
||||
<p>
|
||||
Track user conversion funnels seamlessly, providing valuable insights
|
||||
into user journey optimization.
|
||||
</p>
|
||||
),
|
||||
icon: ConeIcon,
|
||||
color: '#72bef4',
|
||||
icons: [ConeIcon, FilterIcon],
|
||||
className: '', //'bg-[#72bef4]',
|
||||
},
|
||||
{
|
||||
soon: 'Coming with our native app',
|
||||
title: 'Push Notifications',
|
||||
description: (
|
||||
<p>
|
||||
Stay informed about conversions, events, and peaks with our upcoming
|
||||
push notification tool, empowering you to monitor and respond to
|
||||
critical activities in real-time.
|
||||
</p>
|
||||
),
|
||||
icon: BellIcon,
|
||||
color: '#ffb27a',
|
||||
icons: [WebhookIcon, BellIcon],
|
||||
className: '', //'bg-[#ffb27a]',
|
||||
},
|
||||
{
|
||||
title: 'Cost-Effective Alternative to Mixpanel',
|
||||
description: (
|
||||
<p>
|
||||
Enjoy the same powerful analytics capabilities as Mixpanel at a fraction
|
||||
of the cost, ensuring affordability without compromising on quality.
|
||||
</p>
|
||||
),
|
||||
icon: DollarSignIcon,
|
||||
color: '#0f7ea0',
|
||||
icons: [DollarSignIcon, HandCoinsIcon, HandshakeIcon, StarIcon],
|
||||
className: 'bg-[#3ba974]',
|
||||
},
|
||||
{
|
||||
soon: 'Something Plausible lacks',
|
||||
title: 'Great Support for React Native',
|
||||
description: (
|
||||
<p>
|
||||
Benefit from robust support for React Native, ensuring seamless
|
||||
integration and compatibility for your projects, a feature notably
|
||||
lacking in other platforms like Plausible.
|
||||
</p>
|
||||
),
|
||||
icon: (({ className }: LucideProps) => {
|
||||
return (
|
||||
<img src="/react-native.svg" alt="React Native" className={className} />
|
||||
);
|
||||
}) as unknown as LucideIcon,
|
||||
color: '#3ba974',
|
||||
icons: [FolderIcon, DatabaseIcon, ShieldPlusIcon, KeyIcon],
|
||||
className: 'bg-[#e19900]',
|
||||
},
|
||||
];
|
||||
|
||||
// To lazy to think now...
|
||||
function checkIndex(index: number) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 4:
|
||||
case 7:
|
||||
case 8:
|
||||
case 10:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function Sections() {
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 gap-y-16">
|
||||
{sections.map((section, i) => {
|
||||
const even = checkIndex(i);
|
||||
|
||||
const offsets = even
|
||||
? [
|
||||
'-top-10 -left-10 rotate-12',
|
||||
'top-10 -rotate-12',
|
||||
'-right-5',
|
||||
'-right-10 -top-20',
|
||||
]
|
||||
: ['-top-10 -left-20 rotate-12', 'top-10 -rotate-12', '-right-5'];
|
||||
|
||||
const className = even
|
||||
? cn('[&_*]:text-white/90 col-span-2', section.className)
|
||||
: cn('border border-border', section.className);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
key={section.title}
|
||||
title={section.title}
|
||||
className={className}
|
||||
icons={section.icons}
|
||||
offsets={offsets}
|
||||
>
|
||||
{section.description}
|
||||
</Widget>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export async function SocialProofServer(props: Props) {
|
||||
const waitlistCount = await db.waitlist.count();
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<SocialProof count={waitlistCount} {...props} />;
|
||||
<SocialProof count={waitlistCount} {...props} />
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { cn } from '@/utils/cn';
|
||||
// import { StarIcon } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface JoinWaitlistProps {
|
||||
@@ -25,15 +16,15 @@ interface JoinWaitlistProps {
|
||||
|
||||
export function SocialProof({ className, count }: JoinWaitlistProps) {
|
||||
return (
|
||||
<div className={cn('flex gap-2 justify-center items-center', className)}>
|
||||
<div className={cn('flex items-center gap-2', className)}>
|
||||
<div className="flex">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src="/clickhouse.png"
|
||||
width={24}
|
||||
height={24}
|
||||
width={40}
|
||||
height={40}
|
||||
alt="Clickhouse"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
@@ -44,8 +35,8 @@ export function SocialProof({ className, count }: JoinWaitlistProps) {
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src="/getdreams.png"
|
||||
width={24}
|
||||
height={24}
|
||||
width={40}
|
||||
height={40}
|
||||
alt="GetDreams"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
@@ -56,17 +47,27 @@ export function SocialProof({ className, count }: JoinWaitlistProps) {
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src="/kiddokitchen.png"
|
||||
width={24}
|
||||
height={24}
|
||||
width={40}
|
||||
height={40}
|
||||
alt="KiddoKitchen"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>KiddoKitchen is here</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<p className="text-white">
|
||||
{count} early birds have already signed up! 🚀
|
||||
</p>
|
||||
<div>
|
||||
<p className="text-left">{count} early birds have signed up! 🚀</p>
|
||||
{/* <div className="flex gap-0.5">
|
||||
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
|
||||
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
|
||||
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
|
||||
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
|
||||
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// <div class="flex flex-col gap-y-2 mt-5 lg:mt-3"><p class="text-gray-700 dark:text-gray-100 text-xs w-24 text-start font-semibold whitespace-nowrap">What users think</p><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1744063824431370240/BbVtyCiy_normal.png" alt="feedback_0"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Been a long time Mixpanel user and without a doubt there's a bunch of room to innovate. I'm confident Openpanel is on the right path! ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flame w-5 h-5 text-red-600 fill-orange-400 animate-pulse"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path></svg></div><img class="w-5 h-5 rounded-full border border-2 border-red-500" src="https://pbs.twimg.com/profile_images/1751607056316944384/8E4F88FL_normal.jpg" alt="feedback_1"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ I have used Openpanel for the last 6 months (since I’m the creator) for 3 different sites/apps. It’s a great analytics product that has everything you need. Still lacking a native app but will work hard to make that a reality! ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1701887174042324992/g2GBIQay_normal.jpg" alt="feedback_2"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ would be cool if it was easier to edit text after image is generated ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flame w-5 h-5 text-red-600 fill-orange-400 animate-pulse"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path></svg></div><img class="w-5 h-5 rounded-full border border-2 border-red-500" src="https://pbs.twimg.com/profile_images/1194368464946974728/1D2biimN_normal.jpg" alt="feedback_3"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Awesome product, very easy to use and understand. I miss a native app and the documentation could be improved. Otherwise I love it. ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://lh3.googleusercontent.com/a/ACg8ocIWiGTd3nWE5etp-CFhxrTKFvSLSJJd7pPmiM9SNJ9sAg=s96-c" alt="feedback_4"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ I have used Open panel since the private beta and i'm super impressed by the product already, the speed after you give feedback to actually get the features is truly amazing! Can't wait to see where Openpanel are in 6 months! ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://lh3.googleusercontent.com/a/ACg8ocKymAw_YoIrfoGp-bWMlDsXgM6St0dzaVJ7m_lGNXDtrA=s96-c" alt="feedback_5"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Impressively fast UI and easy to integrate! Added it alongside my current analytics tool for my native app in less than an hour. ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1735771119980879872/Mx5MlB9e_normal.jpg" alt="feedback_6"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Im using plausible and find it pleasing but limited.
|
||||
// Im looking forward to trying out Openpanel, the demo pictures and the page look professional. The listed features seem to be broader then plausible. :) ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1767459527006334976/unbMENPG_normal.jpg" alt="feedback_7"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Incredibly easy to implement and a joy to use. 5/5 would recommend. ”</span></div></div>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
|
||||
import { Heading3 } from './copy';
|
||||
|
||||
interface WidgetProps {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
icons: LucideIcon[];
|
||||
offsets: string[];
|
||||
}
|
||||
|
||||
export function Widget({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
icons,
|
||||
offsets,
|
||||
}: WidgetProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'p-10 rounded-xl relative overflow-hidden flex flex-col hover:scale-[101%] transition-all duration-300 ease-in-out bg-white hover:shadow min-h-[300px] max-md:col-span-3',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Heading3 className="mb-2">{title}</Heading3>
|
||||
<div className="prose-xl">{children}</div>
|
||||
<div className="flex justify-between mt-auto">
|
||||
{icons.map((Icon, i) => (
|
||||
<Icon
|
||||
key={i}
|
||||
size={120}
|
||||
className={cn('flex-shrink-0 opacity-5 relative', offsets?.[i])}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user