public: hero improvements
This commit is contained in:
20
apps/public/src/app/(static)/layout.tsx
Normal file
20
apps/public/src/app/(static)/layout.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Navbar } from '../navbar';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Layout({ children }: Props) {
|
||||
return (
|
||||
<>
|
||||
<Navbar darkText />
|
||||
<div
|
||||
className="opacity-50 w-full h-screen text-blue-950 bg-[radial-gradient(circle_at_2px_2px,#D9DEF6_2px,transparent_0)] absolute top-0 left-0 right-0 z-0"
|
||||
style={{
|
||||
backgroundSize: '70px 70px',
|
||||
}}
|
||||
/>
|
||||
<div className="relative">{children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Heading1 } from '../copy';
|
||||
import { Heading1 } from '../../copy';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const revalidate = 3600;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Heading1 } from '../copy';
|
||||
import { Heading1 } from '../../copy';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const revalidate = 3600;
|
||||
@@ -1,8 +1,12 @@
|
||||
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',
|
||||
@@ -10,7 +14,7 @@ const avatars = [
|
||||
'https://api.dicebear.com/7.x/adventurer/svg?seed=Boo&backgroundColor=ffdfbf',
|
||||
];
|
||||
|
||||
export function Hero({ waitlistCount }: { waitlistCount: number }) {
|
||||
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> */}
|
||||
@@ -37,16 +41,12 @@ export function Hero({ waitlistCount }: { waitlistCount: number }) {
|
||||
alternative to Mixpanel
|
||||
</Heading1>
|
||||
<Lead2 className="text-white/70 font-light">
|
||||
Mixpanel + Plausible = <span className="text-white">Openpanel!</span>{' '}
|
||||
<br />A simple analytics tool that your wallet can afford.
|
||||
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">
|
||||
<JoinWaitlistHero />
|
||||
<div className="mt-6 flex justify-center items-center">
|
||||
<p className="text-white">
|
||||
{waitlistCount} people have already signed up! 🚀
|
||||
</p>
|
||||
</div>
|
||||
<SocialProofServer className="mt-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@ export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// @ts-ignore
|
||||
window.op('event', 'waitlist_open');
|
||||
window.op?.('event', 'waitlist_open');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (success) {
|
||||
// @ts-ignore
|
||||
window.op('event', 'waitlist_success', {
|
||||
window.op?.('event', 'waitlist_success', {
|
||||
email: value,
|
||||
});
|
||||
}
|
||||
@@ -113,15 +113,23 @@ export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
|
||||
>
|
||||
Join waitlist now
|
||||
</Button>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -35,39 +35,10 @@ export default function RootLayout({
|
||||
font.className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="w-full h-screen text-blue-950 bg-[radial-gradient(circle_at_2px_2px,#D9DEF6_2px,transparent_0)] absolute top-0 left-0 right-0 z-0"
|
||||
style={{
|
||||
backgroundSize: '70px 70px',
|
||||
}}
|
||||
/>
|
||||
<div className="relative">{children}</div>
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
<Script
|
||||
src="https://openpanel.dev/op.js"
|
||||
async
|
||||
defer
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script
|
||||
id="openpanel"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.op =
|
||||
window.op ||
|
||||
function (...args) {
|
||||
(window.op.q = window.op.q || []).push(args);
|
||||
};
|
||||
window.op('ctor', {
|
||||
clientId: '301c6dc1-424c-4bc3-9886-a8beab09b615',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
{/* 301c6dc1-424c-4bc3-9886-a8beab09b615 */}
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
34
apps/public/src/app/navbar.tsx
Normal file
34
apps/public/src/app/navbar.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { Logo } from '@/components/Logo';
|
||||
import { cn } from '@/utils/cn';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
interface Props {
|
||||
darkText?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Navbar({ darkText = false, className }: Props) {
|
||||
const pathname = usePathname();
|
||||
const textColor = darkText ? 'text-blue-dark' : 'text-white';
|
||||
return (
|
||||
<div
|
||||
className={cn('absolute top-0 left-0 right-0 z-10', textColor, className)}
|
||||
>
|
||||
<div className="container flex justify-between items-center py-4">
|
||||
<Logo />
|
||||
<nav className="flex gap-4">
|
||||
{pathname !== '/' && <Link href="/">Home</Link>}
|
||||
<a href="https://docs.openpanel.dev" target="_blank">
|
||||
Docs
|
||||
</a>
|
||||
<a href="https://dashboard.openpanel.dev" target="_blank">
|
||||
Sign in
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import { db } from '@openpanel/db';
|
||||
|
||||
import { PreviewCarousel } from './carousel';
|
||||
import { Heading2, Lead2, Paragraph } from './copy';
|
||||
import { Hero } from './hero';
|
||||
import { Navbar } from './navbar';
|
||||
import { Sections } from './section';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const revalidate = 3600;
|
||||
|
||||
export default async function Page() {
|
||||
const waitlistCount = await db.waitlist.count();
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Hero waitlistCount={waitlistCount} />
|
||||
<Navbar darkText={false} className="[&_img]:hidden" />
|
||||
<Hero />
|
||||
<div className="container">
|
||||
<div className="my-24">
|
||||
<Heading2 className="md:text-5xl mb-2 leading-none">
|
||||
|
||||
17
apps/public/src/app/social-proof/index.tsx
Normal file
17
apps/public/src/app/social-proof/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
|
||||
import { db } from '@openpanel/db';
|
||||
|
||||
import { SocialProof } from './social-proof';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
export async function SocialProofServer(props: Props) {
|
||||
const waitlistCount = await db.waitlist.count();
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<SocialProof count={waitlistCount} {...props} />;
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
72
apps/public/src/app/social-proof/social-proof.tsx
Normal file
72
apps/public/src/app/social-proof/social-proof.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'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 Image from 'next/image';
|
||||
|
||||
interface JoinWaitlistProps {
|
||||
className?: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export function SocialProof({ className, count }: JoinWaitlistProps) {
|
||||
return (
|
||||
<div className={cn('flex gap-2 justify-center items-center', className)}>
|
||||
<div className="flex">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src="/clickhouse.png"
|
||||
width={24}
|
||||
height={24}
|
||||
alt="Clickhouse"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Clickhouse is here</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="-mx-3">
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src="/getdreams.png"
|
||||
width={24}
|
||||
height={24}
|
||||
alt="GetDreams"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>GetDreams is here</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src="/kiddokitchen.png"
|
||||
width={24}
|
||||
height={24}
|
||||
alt="KiddoKitchen"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>KiddoKitchen is here</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<p className="text-white">
|
||||
{count} early birds have already signed up! 🚀
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-100%] md:translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user