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>
);
};

View File

@@ -0,0 +1,41 @@
import { showBetaFeature } from '@konobangu/feature-flags';
import { createMetadata } from '@konobangu/seo/metadata';
import type { Metadata } from 'next';
import { Cases } from './components/cases';
import { CTA } from './components/cta';
import { FAQ } from './components/faq';
import { Features } from './components/features';
import { Hero } from './components/hero';
import { Stats } from './components/stats';
import { Testimonials } from './components/testimonials';
const meta = {
title: 'From zero to production in minutes.',
description:
"next-forge is a production-grade boilerplate for modern Next.js apps. It's designed to have everything you need to build your new SaaS app as quick as possible. Authentication, billing, analytics, SEO, and more. It's all here.",
};
export const metadata: Metadata = createMetadata(meta);
const Home = async () => {
const betaFeature = await showBetaFeature();
return (
<>
{betaFeature && (
<div className="w-full bg-black py-2 text-center text-white">
Beta feature now available
</div>
)}
<Hero />
<Cases />
<Features />
<Stats />
<Testimonials />
<FAQ />
<CTA />
</>
);
};
export default Home;