feat: add basic webui

This commit is contained in:
2024-12-30 06:39:09 +08:00
parent 608a7fb9c6
commit a4c549e7c3
462 changed files with 35900 additions and 2491 deletions

View File

@@ -0,0 +1,53 @@
'use client';
import {
Carousel,
type CarouselApi,
CarouselContent,
CarouselItem,
} from '@konobangu/design-system/components/ui/carousel';
import { useEffect, useState } from 'react';
export const Cases = () => {
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);
useEffect(() => {
if (!api) {
return;
}
setTimeout(() => {
if (api.selectedScrollSnap() + 1 === api.scrollSnapList().length) {
setCurrent(0);
api.scrollTo(0);
} else {
api.scrollNext();
setCurrent(current + 1);
}
}, 1000);
}, [api, current]);
return (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
<div className="flex flex-col gap-10">
<h2 className="text-left font-regular text-xl tracking-tighter md:text-5xl lg:max-w-xl">
Trusted by thousands of businesses worldwide
</h2>
<Carousel setApi={setApi} className="w-full">
<CarouselContent>
{Array.from({ length: 15 }).map((_, index) => (
<CarouselItem className="basis-1/4 lg:basis-1/6" key={index}>
<div className="flex aspect-square items-center justify-center rounded-md bg-muted p-6">
<span className="text-sm">Logo {index + 1}</span>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,35 @@
import { Button } from '@konobangu/design-system/components/ui/button';
import { env } from '@konobangu/env';
import { MoveRight, PhoneCall } from 'lucide-react';
import Link from 'next/link';
export const CTA = () => (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
<div className="flex flex-col items-center gap-8 rounded-md bg-muted p-4 text-center lg:p-14">
<div className="flex flex-col gap-2">
<h3 className="max-w-xl font-regular text-3xl tracking-tighter md:text-5xl">
Try our platform today!
</h3>
<p className="max-w-xl text-lg text-muted-foreground leading-relaxed tracking-tight">
Managing a small business today is already tough. Avoid further
complications by ditching outdated, tedious trade methods. Our goal
is to streamline SMB trade, making it easier and faster than ever.
</p>
</div>
<div className="flex flex-row gap-4">
<Button className="gap-4" variant="outline" asChild>
<Link href="/contact">
Jump on a call <PhoneCall className="h-4 w-4" />
</Link>
</Button>
<Button className="gap-4" asChild>
<Link href={env.NEXT_PUBLIC_APP_URL}>
Sign up here <MoveRight className="h-4 w-4" />
</Link>
</Button>
</div>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,55 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@konobangu/design-system/components/ui/accordion';
import { Button } from '@konobangu/design-system/components/ui/button';
import { PhoneCall } from 'lucide-react';
import Link from 'next/link';
export const FAQ = () => (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
<div className="grid gap-10 lg:grid-cols-2">
<div className="flex flex-col gap-10">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<h4 className="max-w-xl text-left font-regular text-3xl tracking-tighter md:text-5xl">
This is the start of something new
</h4>
<p className="max-w-xl text-left text-lg text-muted-foreground leading-relaxed tracking-tight lg:max-w-lg">
Managing a small business today is already tough. Avoid further
complications by ditching outdated, tedious trade methods. Our
goal is to streamline SMB trade, making it easier and faster
than ever.
</p>
</div>
<div className="">
<Button className="gap-4" variant="outline" asChild>
<Link href="/contact">
Any questions? Reach out <PhoneCall className="h-4 w-4" />
</Link>
</Button>
</div>
</div>
</div>
<Accordion type="single" collapsible className="w-full">
{Array.from({ length: 8 }).map((_, index) => (
<AccordionItem key={index} value={`index-${index}`}>
<AccordionTrigger>
This is the start of something new
</AccordionTrigger>
<AccordionContent>
Managing a small business today is already tough. Avoid further
complications by ditching outdated, tedious trade methods. Our
goal is to streamline SMB trade, making it easier and faster
than ever.
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,63 @@
import { User } from 'lucide-react';
export const Features = () => (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
<div className="flex flex-col gap-10">
<div className="flex flex-col items-start gap-4">
<div className="flex flex-col gap-2">
<h2 className="max-w-xl text-left font-regular text-3xl tracking-tighter md:text-5xl">
Something new!
</h2>
<p className="max-w-xl text-left text-lg text-muted-foreground leading-relaxed tracking-tight lg:max-w-lg">
Managing a small business today is already tough.
</p>
</div>
</div>
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
<div className="flex aspect-square h-full flex-col justify-between rounded-md bg-muted p-6 lg:col-span-2 lg:aspect-auto">
<User className="h-8 w-8 stroke-1" />
<div className="flex flex-col">
<h3 className="text-xl tracking-tight">Pay supplier invoices</h3>
<p className="max-w-xs text-base text-muted-foreground">
Our goal is to streamline SMB trade, making it easier and faster
than ever.
</p>
</div>
</div>
<div className="flex aspect-square flex-col justify-between rounded-md bg-muted p-6">
<User className="h-8 w-8 stroke-1" />
<div className="flex flex-col">
<h3 className="text-xl tracking-tight">Pay supplier invoices</h3>
<p className="max-w-xs text-base text-muted-foreground">
Our goal is to streamline SMB trade, making it easier and faster
than ever.
</p>
</div>
</div>
<div className="flex aspect-square flex-col justify-between rounded-md bg-muted p-6">
<User className="h-8 w-8 stroke-1" />
<div className="flex flex-col">
<h3 className="text-xl tracking-tight">Pay supplier invoices</h3>
<p className="max-w-xs text-base text-muted-foreground">
Our goal is to streamline SMB trade, making it easier and faster
than ever.
</p>
</div>
</div>
<div className="flex aspect-square h-full flex-col justify-between rounded-md bg-muted p-6 lg:col-span-2 lg:aspect-auto">
<User className="h-8 w-8 stroke-1" />
<div className="flex flex-col">
<h3 className="text-xl tracking-tight">Pay supplier invoices</h3>
<p className="max-w-xs text-base text-muted-foreground">
Our goal is to streamline SMB trade, making it easier and faster
than ever.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,64 @@
import { blog } from '@konobangu/cms';
import { Feed } from '@konobangu/cms/components/feed';
import { Button } from '@konobangu/design-system/components/ui/button';
import { env } from '@konobangu/env';
import { MoveRight, PhoneCall } from 'lucide-react';
import { draftMode } from 'next/headers';
import Link from 'next/link';
export const Hero = async () => {
const draft = await draftMode();
return (
<div className="w-full">
<div className="container mx-auto">
<div className="flex flex-col items-center justify-center gap-8 py-20 lg:py-40">
<div>
<Feed queries={[blog.latestPostQuery]} draft={draft.isEnabled}>
{/* biome-ignore lint/suspicious/useAwait: "Server Actions must be async" */}
{async ([data]) => {
'use server';
return (
<Button
variant="secondary"
size="sm"
className="gap-4"
asChild
>
<Link href={`/blog/${data.blog.posts.items.at(0)?._slug}`}>
Read our latest article <MoveRight className="h-4 w-4" />
</Link>
</Button>
);
}}
</Feed>
</div>
<div className="flex flex-col gap-4">
<h1 className="max-w-2xl text-center font-regular text-5xl tracking-tighter md:text-7xl">
This is the start of something new
</h1>
<p className="max-w-2xl text-center text-lg text-muted-foreground leading-relaxed tracking-tight md:text-xl">
Managing a small business today is already tough. Avoid further
complications by ditching outdated, tedious trade methods. Our
goal is to streamline SMB trade, making it easier and faster than
ever.
</p>
</div>
<div className="flex flex-row gap-3">
<Button size="lg" className="gap-4" variant="outline" asChild>
<Link href="/contact">
Get in touch <PhoneCall className="h-4 w-4" />
</Link>
</Button>
<Button size="lg" className="gap-4" asChild>
<Link href={env.NEXT_PUBLIC_APP_URL}>
Sign up <MoveRight className="h-4 w-4" />
</Link>
</Button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,75 @@
import { MoveDownLeft, MoveUpRight } from 'lucide-react';
export const Stats = () => (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
<div className="grid grid-cols-1 gap-10 lg:grid-cols-2">
<div className="flex flex-col items-start gap-4">
<div className="flex flex-col gap-2">
<h2 className="text-left font-regular text-xl tracking-tighter md:text-5xl lg:max-w-xl">
This is the start of something new
</h2>
<p className="text-left text-lg text-muted-foreground leading-relaxed tracking-tight lg:max-w-sm">
Managing a small business today is already tough. Avoid further
complications by ditching outdated, tedious trade methods. Our
goal is to streamline SMB trade, making it easier and faster than
ever.
</p>
</div>
</div>
<div className="flex items-center justify-center">
<div className="grid w-full grid-cols-1 gap-2 text-left sm:grid-cols-2 lg:grid-cols-2">
<div className="flex flex-col justify-between gap-0 rounded-md border p-6">
<MoveUpRight className="mb-10 h-4 w-4 text-primary" />
<h2 className="flex max-w-xl flex-row items-end gap-4 text-left font-regular text-4xl tracking-tighter">
500.000
<span className="text-muted-foreground text-sm tracking-normal">
+20.1%
</span>
</h2>
<p className="max-w-xl text-left text-base text-muted-foreground leading-relaxed tracking-tight">
Monthly active users
</p>
</div>
<div className="flex flex-col justify-between gap-0 rounded-md border p-6">
<MoveDownLeft className="mb-10 h-4 w-4 text-destructive" />
<h2 className="flex max-w-xl flex-row items-end gap-4 text-left font-regular text-4xl tracking-tighter">
20.105
<span className="text-muted-foreground text-sm tracking-normal">
-2%
</span>
</h2>
<p className="max-w-xl text-left text-base text-muted-foreground leading-relaxed tracking-tight">
Daily active users
</p>
</div>
<div className="flex flex-col justify-between gap-0 rounded-md border p-6">
<MoveUpRight className="mb-10 h-4 w-4 text-primary" />
<h2 className="flex max-w-xl flex-row items-end gap-4 text-left font-regular text-4xl tracking-tighter">
$523.520
<span className="text-muted-foreground text-sm tracking-normal">
+8%
</span>
</h2>
<p className="max-w-xl text-left text-base text-muted-foreground leading-relaxed tracking-tight">
Monthly recurring revenue
</p>
</div>
<div className="flex flex-col justify-between gap-0 rounded-md border p-6">
<MoveUpRight className="mb-10 h-4 w-4 text-primary" />
<h2 className="flex max-w-xl flex-row items-end gap-4 text-left font-regular text-4xl tracking-tighter">
$1052
<span className="text-muted-foreground text-sm tracking-normal">
+2%
</span>
</h2>
<p className="max-w-xl text-left text-base text-muted-foreground leading-relaxed tracking-tight">
Cost per acquisition
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,78 @@
'use client';
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@konobangu/design-system/components/ui/avatar';
import {
Carousel,
type CarouselApi,
CarouselContent,
CarouselItem,
} from '@konobangu/design-system/components/ui/carousel';
import { User } from 'lucide-react';
import { useEffect, useState } from 'react';
export const Testimonials = () => {
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);
useEffect(() => {
if (!api) {
return;
}
setTimeout(() => {
if (api.selectedScrollSnap() + 1 === api.scrollSnapList().length) {
setCurrent(0);
api.scrollTo(0);
} else {
api.scrollNext();
setCurrent(current + 1);
}
}, 4000);
}, [api, current]);
return (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
<div className="flex flex-col gap-10">
<h2 className="text-left font-regular text-3xl tracking-tighter md:text-5xl lg:max-w-xl">
Trusted by thousands of businesses worldwide
</h2>
<Carousel setApi={setApi} className="w-full">
<CarouselContent>
{Array.from({ length: 15 }).map((_, index) => (
<CarouselItem className="lg:basis-1/2" key={index}>
<div className="flex aspect-video h-full flex-col justify-between rounded-md bg-muted p-6 lg:col-span-2">
<User className="h-8 w-8 stroke-1" />
<div className="flex flex-col gap-4">
<div className="flex flex-col">
<h3 className="text-xl tracking-tight">
Best decision
</h3>
<p className="max-w-xs text-base text-muted-foreground">
Our goal was to streamline SMB trade, making it easier
and faster than ever and we did it together.
</p>
</div>
<p className="flex flex-row items-center gap-2 text-sm">
<span className="text-muted-foreground">By</span>
<Avatar className="h-6 w-6">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<span>John Johnsen</span>
</p>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</div>
</div>
</div>
);
};