fix(dashboard): pagination and login

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-03-01 13:33:55 +01:00
parent b801d6a8ef
commit 6251d143d1
11 changed files with 218 additions and 221 deletions

View File

@@ -1,119 +1,102 @@
import { LogoSquare } from '@/components/logo';
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
import { Link } from '@tanstack/react-router';
import { CodeIcon, CreditCardIcon, DollarSignIcon } from 'lucide-react';
import { SellingPoint } from './selling-points';
import Autoplay from 'embla-carousel-autoplay';
import { QuoteIcon } from 'lucide-react';
const onboardingSellingPoints = [
const testimonials = [
{
key: 'get-started',
render: () => (
<SellingPoint
bgImage="/img-6.webp"
title="Get started in minutes"
description={
<>
<p>
<DollarSignIcon className="size-4 inline-block mr-1 relative -top-0.5" />
Free trial
</p>
<p>
<CreditCardIcon className="size-4 inline-block mr-1 relative -top-0.5" />
No credit card required
</p>
<p>
<CodeIcon className="size-4 inline-block mr-1 relative -top-0.5" />
Add our tracking code and get insights in real-time.
</p>
</>
}
/>
),
key: 'thomas',
bgImage: '/img-1.webp',
quote:
"OpenPanel is BY FAR the best open-source analytics I've ever seen. Better UX/UI, many more features, and incredible support from the founder.",
author: 'Thomas Sanlis',
site: 'uneed.best',
},
{
key: 'welcome',
render: () => (
<SellingPoint
bgImage="/img-1.webp"
title="Best open-source alternative"
description="Mixpanel too expensive, Google Analytics has no privacy, Amplitude old and boring"
/>
),
key: 'julien',
bgImage: '/img-2.webp',
quote:
'After testing several product analytics tools, we chose OpenPanel and we are very satisfied. Profiles and Conversion Events are our favorite features.',
author: 'Julien Hany',
site: 'strackr.com',
},
{
key: 'selling-point-2',
render: () => (
<SellingPoint
bgImage="/img-2.webp"
title="Fast and reliable"
description="Never miss a beat with our real-time analytics"
/>
),
key: 'piotr',
bgImage: '/img-3.webp',
quote:
'The Overview tab is great — it has everything I need. The UI is beautiful, clean, modern, very pleasing to the eye.',
author: 'Piotr Kulpinski',
site: 'producthunt.com',
},
{
key: 'selling-point-3',
render: () => (
<SellingPoint
bgImage="/img-3.webp"
title="Easy to use"
description="Compared to other tools we have kept it simple"
/>
),
},
{
key: 'selling-point-4',
render: () => (
<SellingPoint
bgImage="/img-4.webp"
title="Privacy by default"
description="We have built our platform with privacy at its heart"
/>
),
},
{
key: 'selling-point-5',
render: () => (
<SellingPoint
bgImage="/img-5.webp"
title="Open source"
description="You can inspect the code and self-host if you choose"
/>
),
key: 'selfhost',
bgImage: '/img-4.webp',
quote:
"After paying a lot to PostHog for years, OpenPanel gives us the same — in many ways better — analytics while keeping full ownership of our data. We don't want to run any business without OpenPanel anymore.",
author: 'Self-hosting user',
site: undefined,
},
];
function TestimonialSlide({
bgImage,
quote,
author,
site,
}: {
bgImage: string;
quote: string;
author: string;
site?: string;
}) {
return (
<div className="relative flex flex-col justify-end h-full p-10 select-none">
<img
src={bgImage}
className="absolute inset-0 w-full h-full object-cover"
alt=""
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-black/10" />
<div className="relative z-10 flex flex-col gap-4">
<QuoteIcon className="size-10 text-white/40 stroke-1" />
<blockquote className="text-3xl font-medium text-white leading-relaxed">
{quote}
</blockquote>
<figcaption className="text-white/60 text-sm">
{author}
{site && <span className="ml-1 text-white/40">· {site}</span>}
</figcaption>
</div>
</div>
);
}
export function OnboardingLeftPanel() {
return (
<div className="sticky top-0 h-screen overflow-hidden">
{/* Carousel */}
<div className="flex items-center justify-center h-full mt-24">
<Carousel
className="w-full h-full [&>div]:h-full [&>div]:min-h-full"
opts={{
loop: true,
align: 'center',
}}
opts={{ loop: true, align: 'center' }}
plugins={[Autoplay({ delay: 6000, stopOnInteraction: false })]}
>
<CarouselContent className="h-full">
{onboardingSellingPoints.map((point, index) => (
<CarouselItem
key={`onboarding-point-${point.key}`}
className="p-8 pb-32 pt-0"
>
{testimonials.map((t) => (
<CarouselItem key={t.key} className="p-8 pb-32 pt-0">
<div className="rounded-xl min-h-full h-full overflow-hidden bg-card border border-border shadow-lg">
{point.render()}
<TestimonialSlide
bgImage={t.bgImage}
quote={t.quote}
author={t.author}
site={t.site}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="left-12 bottom-30 top-auto" />
<CarouselNext className="right-12 bottom-30 top-auto" />
</Carousel>
</div>
</div>