feat: add basic webui
This commit is contained in:
53
apps/web/app/(home)/components/cases.tsx
Normal file
53
apps/web/app/(home)/components/cases.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
35
apps/web/app/(home)/components/cta.tsx
Normal file
35
apps/web/app/(home)/components/cta.tsx
Normal 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>
|
||||
);
|
||||
55
apps/web/app/(home)/components/faq.tsx
Normal file
55
apps/web/app/(home)/components/faq.tsx
Normal 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>
|
||||
);
|
||||
63
apps/web/app/(home)/components/features.tsx
Normal file
63
apps/web/app/(home)/components/features.tsx
Normal 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>
|
||||
);
|
||||
64
apps/web/app/(home)/components/hero.tsx
Normal file
64
apps/web/app/(home)/components/hero.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
75
apps/web/app/(home)/components/stats.tsx
Normal file
75
apps/web/app/(home)/components/stats.tsx
Normal 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>
|
||||
);
|
||||
78
apps/web/app/(home)/components/testimonials.tsx
Normal file
78
apps/web/app/(home)/components/testimonials.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
41
apps/web/app/(home)/page.tsx
Normal file
41
apps/web/app/(home)/page.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user