Update UI design across multiple pages and components

Several changes have been made to improve the user interface and enhance the user experience. This includes redesigning Auth buttons, modifying website layouts and routing, tweaking heading and text sizes for clarity, and revamping the marketing, documentation, and pricing pages. These changes collectively contribute to a cleaner, more concise and navigable interface.
This commit is contained in:
giancarlo
2024-04-09 13:35:12 +08:00
parent 80952dc445
commit e7f2660032
21 changed files with 179 additions and 166 deletions

View File

@@ -3,7 +3,7 @@ import { withI18n } from '~/lib/i18n/with-i18n';
function CookiePolicyPage() {
return (
<div className={'mt-8'}>
<div>
<div className={'container mx-auto'}>
<SitePageHeader
title={`Cookie Policy`}

View File

@@ -9,7 +9,7 @@ const YEAR = new Date().getFullYear();
export function SiteFooter() {
return (
<footer className={'py-8 lg:py-24'}>
<footer className={'border-t py-8 xl:py-12 2xl:py-14'}>
<div className={'container mx-auto'}>
<div className={'flex flex-col space-y-8 lg:flex-row lg:space-y-0'}>
<div

View File

@@ -10,6 +10,7 @@ import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import { useUser } from '@kit/supabase/hooks/use-user';
import { Button } from '@kit/ui/button';
import { ModeToggle } from '@kit/ui/mode-toggle';
import { Trans } from '@kit/ui/trans';
import featuresFlagConfig from '~/config/feature-flags.config';
@@ -55,10 +56,18 @@ function SuspendedPersonalAccountDropdown(props: { user: User | null }) {
function AuthButtons() {
return (
<div className={'hidden space-x-2 lg:flex'}>
<div className={'hidden space-x-0.5 lg:flex'}>
<ModeToggle />
<Link href={pathsConfig.auth.signIn}>
<Button variant={'link'}>
<Trans i18nKey={'auth:signIn'} />
</Button>
</Link>
<Link href={pathsConfig.auth.signUp}>
<Button variant={'outline'}>
<Trans i18nKey={'auth:signUp'} />
<Button className={'rounded-full'}>
<Trans i18nKey={'auth:getStarted'} />
<ChevronRight className={'h-4'} />
</Button>
</Link>

View File

@@ -1,27 +1,23 @@
import type { User } from '@supabase/supabase-js';
import { ModeToggle } from '@kit/ui/mode-toggle';
import { SiteHeaderAccountSection } from '~/(marketing)/_components/site-header-account-section';
import { SiteNavigation } from '~/(marketing)/_components/site-navigation';
import { AppLogo } from '~/components/app-logo';
export function SiteHeader(props: { user?: User | null }) {
return (
<div className={'container mx-auto'}>
<div className="flex h-16 items-center justify-between">
<div className={'w-4/12'}>
<AppLogo />
</div>
<div className={'border-b'}>
<div className={'container mx-auto'}>
<div className="flex h-16 items-center justify-between">
<div className={'flex w-6/12 items-center space-x-8'}>
<AppLogo />
<div className={'hidden w-4/12 justify-center lg:flex'}>
<SiteNavigation />
</div>
<SiteNavigation />
</div>
<div className={'flex flex-1 items-center justify-end space-x-4'}>
<ModeToggle />
<SiteHeaderAccountSection user={props.user ?? null} />
<div className={'flex flex-1 items-center justify-end space-x-1'}>
<SiteHeaderAccountSection user={props.user ?? null} />
</div>
</div>
</div>
</div>

View File

@@ -9,9 +9,8 @@ import { cn, isRouteActive } from '@kit/ui/utils';
const getClassName = (path: string, currentPathName: string) => {
const isActive = isRouteActive(path, currentPathName);
return cn(`text-sm transition-all px-4 py-2 rounded-full font-medium`, {
'bg-muted': isActive,
'hover:bg-muted active:bg-muted/50': !isActive,
return cn(`text-sm font-medium text-primary`, {
'hover:underline': !isActive,
});
};

View File

@@ -45,11 +45,7 @@ export function SiteNavigation() {
<>
<div className={'hidden items-center lg:flex'}>
<NavigationMenu>
<NavigationMenuList
className={
'space-x-1.5 rounded-full px-1 py-2 shadow-sm dark:shadow-primary/10'
}
>
<NavigationMenuList className={'space-x-6'}>
{NavItems}
</NavigationMenuList>
</NavigationMenu>

View File

@@ -6,16 +6,18 @@ export function SitePageHeader(props: {
className?: string;
}) {
return (
<div
className={cn('flex flex-col items-center space-y-4', props.className)}
>
<h1 className={'text-center text-3xl font-semibold xl:text-4xl'}>
{props.title}
</h1>
<div className={cn('border-b py-8 xl:py-12 2xl:py-14', props.className)}>
<div className={'container flex flex-col space-y-4'}>
<h1 className={'text-3xl font-semibold xl:text-5xl'}>{props.title}</h1>
<h2 className={'text-center text-xl text-muted-foreground xl:text-2xl'}>
<span className={'font-normal'}>{props.subtitle}</span>
</h2>
<h2
className={
'text-base text-secondary-foreground xl:text-lg 2xl:text-xl'
}
>
<span className={'font-normal'}>{props.subtitle}</span>
</h2>
</div>
</div>
);
}

View File

@@ -4,7 +4,7 @@ import { notFound } from 'next/navigation';
import { createCmsClient } from '@kit/cms';
import Post from '~/(marketing)/blog/_components/post';
import { Post } from '~/(marketing)/blog/_components/post';
import { withI18n } from '~/lib/i18n/with-i18n';
export async function generateMetadata({
@@ -55,11 +55,7 @@ async function BlogPost({ params }: { params: { slug: string } }) {
notFound();
}
return (
<div className={'container mx-auto'}>
<Post post={post} content={post.content} />
</div>
);
return <Post post={post} content={post.content} />;
}
export default withI18n(BlogPost);

View File

@@ -1,6 +1,12 @@
import Link from 'next/link';
import { ArrowLeft } from 'lucide-react';
import { Cms } from '@kit/cms';
import { Heading } from '@kit/ui/heading';
import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils';
import { CoverImage } from '~/(marketing)/blog/_components/cover-image';
import { DateFormatter } from '~/(marketing)/blog/_components/date-formatter';
@@ -11,27 +17,37 @@ export const PostHeader: React.FC<{
const { title, publishedAt, description, image } = post;
return (
<div className={'flex flex-col space-y-8'}>
<div className={'flex flex-col space-y-2'}>
<Heading level={1}>{title}</Heading>
<div className={'flex flex-col'}>
<div className={'container'}>
<Link href={'/blog'}>
<Button variant={'link'}>
<ArrowLeft className={'h-4'} />
<Heading level={3}>
<p
className={'font-normal text-muted-foreground'}
dangerouslySetInnerHTML={{ __html: description ?? '' }}
/>
</Heading>
<span>
<Trans i18nKey={'marketing:backToBlog'} />
</span>
</Button>
</Link>
</div>
<div className="flex flex-row items-center space-x-2 text-muted-foreground">
<div className={'text-sm'}>
<DateFormatter dateString={publishedAt.toISOString()} />
</div>
<div className={cn('border-b py-8')}>
<div className={'container flex flex-col space-y-4'}>
<h1 className={'text-3xl font-semibold xl:text-5xl'}>{title}</h1>
<h2 className={'text-base text-secondary-foreground xl:text-lg'}>
<span
className={'font-normal'}
dangerouslySetInnerHTML={{ __html: description ?? '' }}
/>
</h2>
<DateFormatter dateString={publishedAt.toISOString()} />
</div>
</div>
<If condition={image}>
{(imageUrl) => (
<div className="relative mx-auto h-[378px] w-full justify-center">
<div className="relative mx-auto mt-8 flex h-[378px] w-full max-w-2xl justify-center">
<CoverImage
preloadImage
className="rounded-md"

View File

@@ -24,7 +24,7 @@ export function PostPreview({
const slug = `/blog/${post.slug}`;
return (
<div className="rounded-xl transition-shadow duration-500">
<div className="flex flex-col space-y-4 rounded-lg transition-shadow duration-500">
<If condition={image}>
{(imageUrl) => (
<div className="relative mb-2 w-full" style={{ height }}>
@@ -39,23 +39,23 @@ export function PostPreview({
)}
</If>
<div className={'px-1'}>
<div className="flex flex-col space-y-1 py-2">
<h3 className="text-2xl font-bold leading-snug dark:text-white">
<div className={'flex flex-col space-y-2 px-1'}>
<div className={'flex flex-col space-y-1'}>
<h3 className="text-2xl font-semibold leading-snug">
<Link href={slug} className="hover:underline">
{title}
</Link>
</h3>
</div>
<div className="mb-2 flex flex-row items-center space-x-2 text-sm">
<div className="text-muted-foreground">
<DateFormatter dateString={publishedAt.toISOString()} />
<div className="flex flex-row items-center space-x-2 text-sm">
<div className="text-muted-foreground">
<DateFormatter dateString={publishedAt.toISOString()} />
</div>
</div>
</div>
<p
className="mb-4 text-sm leading-relaxed text-muted-foreground"
className="mb-4 text-base leading-relaxed text-secondary-foreground"
dangerouslySetInnerHTML={{ __html: description ?? '' }}
/>
</div>

View File

@@ -9,14 +9,14 @@ export const Post: React.FC<{
content: string;
}> = ({ post, content }) => {
return (
<div className={'mx-auto flex max-w-2xl flex-col space-y-6'}>
<div>
<PostHeader post={post} />
<article className={styles.HTML}>
<ContentRenderer content={content} />
</article>
<div className={'mx-auto flex max-w-2xl flex-col space-y-6'}>
<article className={styles.HTML}>
<ContentRenderer content={content} />
</article>
</div>
</div>
);
};
export default Post;

View File

@@ -24,13 +24,13 @@ async function BlogPage() {
});
return (
<div className={'container mx-auto'}>
<div className={'flex flex-col space-y-12 xl:space-y-24'}>
<SitePageHeader
title={t('marketing:blog')}
subtitle={t('marketing:blogSubtitle')}
/>
<div className={'flex flex-col space-y-12'}>
<SitePageHeader
title={t('marketing:blog')}
subtitle={t('marketing:blogSubtitle')}
/>
<div className={'container mx-auto'}>
<GridList>
{posts.map((post, idx) => {
return <PostPreview key={idx} post={post} />;

View File

@@ -48,18 +48,18 @@ async function DocumentationPage({ params }: PageParams) {
const description = page?.description ?? '';
return (
<div className={'container mx-auto'}>
<div>
<SitePageHeader
title={page.title}
subtitle={description}
className={'items-start'}
/>
<div
className={
'relative mx-auto flex max-w-4xl grow flex-col space-y-4 px-8'
'container relative mx-auto flex max-w-4xl grow flex-col space-y-4 py-6'
}
>
<SitePageHeader
title={page.title}
subtitle={description}
className={'items-start'}
/>
<article className={styles.HTML}>
<ContentRenderer content={page.content} />
</article>

View File

@@ -27,13 +27,15 @@ async function DocsPage() {
return (
<PageBody>
<div className={'flex flex-col items-center space-y-12 xl:space-y-24'}>
<div className={'flex flex-col space-y-12 xl:space-y-24'}>
<SitePageHeader
title={t('marketing:documentation')}
subtitle={t('marketing:documentationSubtitle')}
/>
<DocsCards cards={cards} />
<div className={'flex flex-col items-center'}>
<DocsCards cards={cards} />
</div>
</div>
</PageBody>
);

View File

@@ -1,4 +1,9 @@
import { ChevronDown } from 'lucide-react';
import Link from 'next/link';
import { ArrowRight, ChevronDown } from 'lucide-react';
import { Button } from '@kit/ui/button';
import { Trans } from '@kit/ui/trans';
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
@@ -60,34 +65,40 @@ async function FAQPage() {
};
return (
<div>
<>
<script
key={'ld:json'}
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
<div className={'container mx-auto'}>
<div className={'flex flex-col space-y-12 xl:space-y-24'}>
<SitePageHeader
title={t('marketing:faq')}
subtitle={t('marketing:faqSubtitle')}
/>
<div className={'flex flex-col space-y-4 xl:space-y-8'}>
<SitePageHeader
title={t('marketing:faq')}
subtitle={t('marketing:faqSubtitle')}
/>
<div
className={
'm-auto flex w-full max-w-xl items-center justify-center'
}
>
<div className="flex w-full flex-col">
{faqItems.map((item, index) => {
return <FaqItem key={index} item={item} />;
})}
</div>
<div className={'container flex flex-col space-y-8 pb-16'}>
<div className="flex w-full max-w-xl flex-col">
{faqItems.map((item, index) => {
return <FaqItem key={index} item={item} />;
})}
</div>
<div>
<Link href={'/contact'}>
<Button variant={'outline'}>
<span>
<Trans i18nKey={'marketing:contactFaq'} />
</span>
<ArrowRight className={'ml-2 w-4'} />
</Button>
</Link>
</div>
</div>
</div>
</div>
</>
);
}
@@ -102,7 +113,7 @@ function FaqItem({
};
}>) {
return (
<details className={'group border-b px-2 py-4'}>
<details className={'group border-b px-2 py-4 last:border-b-transparent'}>
<summary
className={
'flex items-center justify-between hover:cursor-pointer hover:underline'

View File

@@ -1,5 +1,4 @@
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { Separator } from '@kit/ui/separator';
import { SiteFooter } from '~/(marketing)/_components/site-footer';
import { SiteHeader } from '~/(marketing)/_components/site-header';
@@ -9,13 +8,11 @@ async function SiteLayout(props: React.PropsWithChildren) {
const user = await getUser();
return (
<div className={'flex flex-col space-y-6 xl:space-y-10 2xl:space-y-12'}>
<div className={'flex flex-col'}>
<SiteHeader user={user} />
{props.children}
<Separator />
<SiteFooter />
</div>
);

View File

@@ -13,7 +13,7 @@ import { withI18n } from '~/lib/i18n/with-i18n';
function Home() {
return (
<div className={'flex flex-col space-y-24'}>
<div className={'flex flex-col space-y-24 py-16'}>
<div className={'container mx-auto flex flex-col space-y-24'}>
<div
className={
@@ -22,7 +22,11 @@ function Home() {
' duration-1000 slide-in-from-top-12'
}
>
<div className={'flex w-full flex-1 flex-col items-center space-y-8'}>
<div
className={
'flex w-full flex-1 flex-col items-center space-y-8 xl:space-y-12 2xl:space-y-14'
}
>
<Pill>
<span>The leading SaaS Starter Kit for ambitious developers</span>
</Pill>
@@ -53,9 +57,7 @@ function Home() {
</span>
</Heading>
</div>
</div>
<div>
<MainCallToActionButton />
</div>
</div>
@@ -70,9 +72,7 @@ function Home() {
<Image
priority
className={
'rounded-2xl' +
' border animate-in fade-in' +
' delay-300 duration-1000 ease-out zoom-in-50 fill-mode-both'
'rounded-lg border delay-500 duration-1000 ease-out animate-in fade-in zoom-in-50 fill-mode-both'
}
width={3069}
height={1916}
@@ -115,20 +115,20 @@ function Home() {
<div className={'flex flex-col space-y-4'}>
<FeatureShowcaseContainer>
<LeftFeatureContainer>
<div className={'flex flex-col space-y-2'}>
<div className={'flex flex-col space-y-1'}>
<Heading level={2}>Authentication</Heading>
<Heading level={3} className={'text-muted-foreground'}>
Secure and Easy-to-Use Authentication for Your SaaS Website
and API
</Heading>
</div>
<div>
Our authentication system is built on top of the
industry-leading PaaS such as Supabase and Firebase. It is
secure, easy-to-use, and fully customizable. It supports
email/password, social logins, and more.
</div>
<div>
Our authentication system is built on top of the
industry-leading PaaS such as Supabase and Firebase. It is
secure, easy-to-use, and fully customizable. It supports
email/password, social logins, and more.
</div>
</LeftFeatureContainer>
@@ -155,18 +155,18 @@ function Home() {
</LeftFeatureContainer>
<RightFeatureContainer>
<div className={'flex flex-col space-y-2'}>
<div className={'flex flex-col space-y-1'}>
<Heading level={2}>Dashboard</Heading>
<Heading level={3} className={'text-muted-foreground'}>
A fantastic dashboard to manage your SaaS business
</Heading>
</div>
<div>
Our dashboard offers an overview of your SaaS business. It
shows at a glance all you need to know about your business. It
is fully customizable and extendable.
</div>
<div>
Our dashboard offers an overview of your SaaS business. It shows
at a glance all you need to know about your business. It is
fully customizable and extendable.
</div>
</RightFeatureContainer>
</FeatureShowcaseContainer>
@@ -247,14 +247,18 @@ function FeatureShowcaseContainer(props: React.PropsWithChildren) {
function LeftFeatureContainer(props: React.PropsWithChildren) {
return (
<div className={'flex w-full flex-col space-y-8 lg:w-6/12'}>
<div className={'flex w-full flex-col space-y-6 lg:w-6/12'}>
{props.children}
</div>
);
}
function RightFeatureContainer(props: React.PropsWithChildren) {
return <div className={'flex w-full lg:w-6/12'}>{props.children}</div>;
return (
<div className={'flex w-full flex-col space-y-6 lg:w-6/12'}>
{props.children}
</div>
);
}
function MainCallToActionButton() {

View File

@@ -18,13 +18,15 @@ async function PricingPage() {
const { t } = await createI18nServerInstance();
return (
<div className={'container mx-auto mt-8 flex flex-col space-y-24'}>
<div className={'flex flex-col space-y-12'}>
<SitePageHeader
title={t('marketing:pricing')}
subtitle={t('marketing:pricingSubtitle')}
/>
<PricingTable paths={pathsConfig.auth} config={billingConfig} />
<div className={'container mx-auto pb-8 xl:pb-16'}>
<PricingTable paths={pathsConfig.auth} config={billingConfig} />
</div>
</div>
);
}

View File

@@ -9,7 +9,7 @@ const LogoImage: React.FC<{
return (
<svg
width={width}
className={cn(`w-[95px] sm:w-[105px]`, className)}
className={cn(`w-[95px]`, className)}
viewBox="0 0 733 140"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -7,6 +7,8 @@
"faqSubtitle": "Frequently asked questions about the platform",
"pricing": "Pricing",
"pricingSubtitle": "Pricing plans and payment options",
"backToBlog": "Back to blog",
"contactFaq": "If you have any questions, please contact us",
"contact": "Contact",
"about": "About",
"product": "Product",