refactor: format src/app folder

This commit is contained in:
javayhu 2025-03-09 20:01:07 +08:00
parent 02bdb93bbd
commit 92ec1b14c5
45 changed files with 352 additions and 350 deletions

View File

@ -9,6 +9,7 @@
"ignoreUnknown": true, "ignoreUnknown": true,
"ignore": [ "ignore": [
".next/**", ".next/**",
".content-collections/**",
"node_modules/**", "node_modules/**",
"dist/**", "dist/**",
"build/**", "build/**",
@ -45,6 +46,7 @@
}, },
"ignore": [ "ignore": [
".next/**", ".next/**",
".content-collections/**",
"node_modules/**", "node_modules/**",
"dist/**", "dist/**",
"build/**", "build/**",
@ -55,7 +57,7 @@
}, },
"javascript": { "javascript": {
"formatter": { "formatter": {
"quoteStyle": "double", "quoteStyle": "single",
"trailingCommas": "es5", "trailingCommas": "es5",
"semicolons": "always" "semicolons": "always"
} }

View File

@ -1,4 +1,4 @@
import { AppSidebar } from "@/components/app-sidebar"; import { AppSidebar } from '@/components/app-sidebar';
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,
@ -6,20 +6,20 @@ import {
BreadcrumbList, BreadcrumbList,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"; } from '@/components/ui/breadcrumb';
import { Separator } from "@/components/ui/separator"; import { Separator } from '@/components/ui/separator';
import { import {
SidebarInset, SidebarInset,
SidebarProvider, SidebarProvider,
SidebarTrigger, SidebarTrigger,
} from "@/components/ui/sidebar"; } from '@/components/ui/sidebar';
export default function Page() { export default function Page() {
return ( return (
<SidebarProvider> <SidebarProvider>
<AppSidebar /> <AppSidebar />
<SidebarInset> <SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"> <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4"> <div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" /> <SidebarTrigger className="-ml-1" />
<Separator orientation="vertical" className="mr-2 h-4" /> <Separator orientation="vertical" className="mr-2 h-4" />

View File

@ -1,16 +1,16 @@
import CallToAction3 from "@/components/blocks/call-to-action/call-to-action-3"; import CallToAction3 from '@/components/blocks/call-to-action/call-to-action-3';
import Content2 from "@/components/blocks/content/content-2"; import Content2 from '@/components/blocks/content/content-2';
import FAQs from "@/components/blocks/faq/faqs"; import FAQs from '@/components/blocks/faq/faqs';
import FeaturesSection from "@/components/blocks/features/features-8"; import FeaturesSection from '@/components/blocks/features/features-8';
import HeroSection from "@/components/blocks/hero/hero-section-4"; import HeroSection from '@/components/blocks/hero/hero-section-4';
import LogoCloud from "@/components/blocks/logo-cloud/logo-cloud"; import LogoCloud from '@/components/blocks/logo-cloud/logo-cloud';
import Pricing from "@/components/blocks/pricing/pricing"; import Pricing from '@/components/blocks/pricing/pricing';
import StatsSection from "@/components/blocks/stats/stats"; import StatsSection from '@/components/blocks/stats/stats';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
interface HomePageProps { interface HomePageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
export default async function HomePage(props: HomePageProps) { export default async function HomePage(props: HomePageProps) {
const params = await props.params; const params = await props.params;

View File

@ -1,15 +1,15 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from "@/components/ui/button"; import { Button } from '@/components/ui/button';
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import { MailIcon, UserCircleIcon } from "lucide-react"; import { MailIcon, UserCircleIcon } from 'lucide-react';
import { getTranslations } from "next-intl/server"; import { getTranslations } from 'next-intl/server';
import Image from "next/image"; import Image from 'next/image';
/** /**
* inspired by https://astro-nomy.vercel.app/about * inspired by https://astro-nomy.vercel.app/about
*/ */
export default async function AboutPage() { export default async function AboutPage() {
const t = await getTranslations("AboutPage"); const t = await getTranslations('AboutPage');
return ( return (
<section className="space-y-8 pb-16"> <section className="space-y-8 pb-16">
@ -31,10 +31,10 @@ export default async function AboutPage() {
</Avatar> </Avatar>
<div> <div>
<h1 className="text-4xl font-heading text-foreground"> <h1 className="text-4xl font-heading text-foreground">
{t("authorName")} {t('authorName')}
</h1> </h1>
<p className="text-base text-muted-foreground mt-2"> <p className="text-base text-muted-foreground mt-2">
{t("authorBio")} {t('authorBio')}
</p> </p>
</div> </div>
</div> </div>
@ -42,15 +42,13 @@ export default async function AboutPage() {
{/* introduction */} {/* introduction */}
<div> <div>
<p className="mb-8 text-base text-muted-foreground"> <p className="mb-8 text-base text-muted-foreground">
{t("authorIntroduction")} {t('authorIntroduction')}
</p> </p>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button className="rounded-lg"> <Button className="rounded-lg">
<MailIcon className="mr-1 size-4" /> <MailIcon className="mr-1 size-4" />
<a href={`mailto:${siteConfig.mail}`}> <a href={`mailto:${siteConfig.mail}`}>{t('talkWithMe')}</a>
{t("talkWithMe")}
</a>
</Button> </Button>
</div> </div>
</div> </div>
@ -65,14 +63,17 @@ export default async function AboutPage() {
{/* Mobile view (1 column) */} {/* Mobile view (1 column) */}
<div className="grid grid-cols-1 gap-4 sm:hidden"> <div className="grid grid-cols-1 gap-4 sm:hidden">
{images.map((image, index) => ( {images.map((image, index) => (
<div key={index} className="overflow-hidden rounded-xl aspect-[4/3]"> <div
key={index}
className="overflow-hidden rounded-xl aspect-[4/3]"
>
<Image <Image
className="w-full h-full object-cover" className="w-full h-full object-cover"
src={image.image} src={image.image}
alt={image.alt} alt={image.alt}
width={800} width={800}
height={900} height={900}
loading={index < 2 ? "eager" : "lazy"} loading={index < 2 ? 'eager' : 'lazy'}
priority={index < 2} priority={index < 2}
/> />
</div> </div>
@ -83,14 +84,17 @@ export default async function AboutPage() {
<div className="hidden sm:grid sm:grid-cols-2 md:hidden gap-4"> <div className="hidden sm:grid sm:grid-cols-2 md:hidden gap-4">
<div className="space-y-4"> <div className="space-y-4">
{images.slice(0, 4).map((image, index) => ( {images.slice(0, 4).map((image, index) => (
<div key={index} className="overflow-hidden rounded-xl aspect-[4/3]"> <div
key={index}
className="overflow-hidden rounded-xl aspect-[4/3]"
>
<Image <Image
className="w-full h-full object-cover" className="w-full h-full object-cover"
src={image.image} src={image.image}
alt={image.alt} alt={image.alt}
width={800} width={800}
height={900} height={900}
loading={index < 2 ? "eager" : "lazy"} loading={index < 2 ? 'eager' : 'lazy'}
priority={index < 2} priority={index < 2}
/> />
</div> </div>
@ -98,14 +102,17 @@ export default async function AboutPage() {
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
{images.slice(4, 8).map((image, index) => ( {images.slice(4, 8).map((image, index) => (
<div key={index} className="overflow-hidden rounded-xl aspect-[4/3]"> <div
key={index}
className="overflow-hidden rounded-xl aspect-[4/3]"
>
<Image <Image
className="w-full h-full object-cover" className="w-full h-full object-cover"
src={image.image} src={image.image}
alt={image.alt} alt={image.alt}
width={800} width={800}
height={900} height={900}
loading={index < 2 ? "eager" : "lazy"} loading={index < 2 ? 'eager' : 'lazy'}
priority={index < 1} priority={index < 1}
/> />
</div> </div>
@ -237,50 +244,50 @@ const images: ImagesProps[] = [
// first column // first column
{ {
image: image:
"https://images.pexels.com/photos/15372903/pexels-photo-15372903/free-photo-of-computer-setup-with-big-monitor-screen.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/15372903/pexels-photo-15372903/free-photo-of-computer-setup-with-big-monitor-screen.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "setup desktop", alt: 'setup desktop',
}, },
{ {
image: image:
"https://images.pexels.com/photos/1049317/pexels-photo-1049317.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/1049317/pexels-photo-1049317.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "friends smiles", alt: 'friends smiles',
}, },
// second column // second column
{ {
image: image:
"https://images.pexels.com/photos/3712095/pexels-photo-3712095.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/3712095/pexels-photo-3712095.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "grey cat", alt: 'grey cat',
}, },
{ {
image: image:
"https://images.pexels.com/photos/9293249/pexels-photo-9293249.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/9293249/pexels-photo-9293249.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "home building", alt: 'home building',
}, },
{ {
image: image:
"https://images.pexels.com/photos/375467/pexels-photo-375467.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/375467/pexels-photo-375467.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "pizza laptop", alt: 'pizza laptop',
}, },
// third column // third column
{ {
image: image:
"https://images.pexels.com/photos/1230302/pexels-photo-1230302.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/1230302/pexels-photo-1230302.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "hike and sunset", alt: 'hike and sunset',
}, },
{ {
image: image:
"https://images.pexels.com/photos/5500779/pexels-photo-5500779.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/5500779/pexels-photo-5500779.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "chinese lantern", alt: 'chinese lantern',
}, },
// fourth column // fourth column
{ {
image: image:
"https://images.pexels.com/photos/2090644/pexels-photo-2090644.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/2090644/pexels-photo-2090644.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "the great wheel", alt: 'the great wheel',
}, },
{ {
image: image:
"https://images.pexels.com/photos/7418632/pexels-photo-7418632.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 'https://images.pexels.com/photos/7418632/pexels-photo-7418632.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
alt: "dalmatian", alt: 'dalmatian',
}, },
]; ];

View File

@ -25,8 +25,8 @@ export async function generateMetadata(
title: 'Changelog', title: 'Changelog',
description: 'Track all updates and improvements to our platform', description: 'Track all updates and improvements to our platform',
type: 'article', type: 'article',
url: `${getBaseUrl()}/changelog` url: `${getBaseUrl()}/changelog`,
} },
}; };
} }

View File

@ -1,4 +1,4 @@
"use client"; 'use client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
@ -35,23 +35,17 @@ export default function ContactPage() {
<form action="" className="mt-8 space-y-4"> <form action="" className="mt-8 space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="name"> <Label htmlFor="name">{t('name')}</Label>
{t('name')}
</Label>
<Input type="text" id="name" required /> <Input type="text" id="name" required />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email"> <Label htmlFor="email">{t('email')}</Label>
{t('email')}
</Label>
<Input type="email" id="email" required /> <Input type="email" id="email" required />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="msg"> <Label htmlFor="msg">{t('message')}</Label>
{t('message')}
</Label>
<Textarea id="msg" rows={3} /> <Textarea id="msg" rows={3} />
</div> </div>
@ -61,5 +55,5 @@ export default function ContactPage() {
</form> </form>
</Card> </Card>
</div> </div>
) );
} }

View File

@ -17,7 +17,7 @@ export async function generateMetadata(
const locale = params.locale as string; const locale = params.locale as string;
const page = await getCustomPage('cookie-policy', locale); const page = await getCustomPage('cookie-policy', locale);
if (!page) { if (!page) {
return {}; return {};
} }
@ -29,8 +29,8 @@ export async function generateMetadata(
title: page.title, title: page.title,
description: page.description, description: page.description,
type: 'article', type: 'article',
url: `${getBaseUrl()}/cookie-policy` url: `${getBaseUrl()}/cookie-policy`,
} },
}; };
} }
@ -42,7 +42,7 @@ export default async function CookiePolicyPage(props: NextPageProps) {
const locale = params.locale as string; const locale = params.locale as string;
const page = await getCustomPage('cookie-policy', locale); const page = await getCustomPage('cookie-policy', locale);
if (!page) { if (!page) {
notFound(); notFound();
} }
@ -55,4 +55,4 @@ export default async function CookiePolicyPage(props: NextPageProps) {
content={page.body.code} content={page.body.code}
/> />
); );
} }

View File

@ -4,9 +4,7 @@ import { PropsWithChildren } from 'react';
export default function LegalLayout({ children }: PropsWithChildren) { export default function LegalLayout({ children }: PropsWithChildren) {
return ( return (
<Container className="py-16 px-4"> <Container className="py-16 px-4">
<div className="mx-auto"> <div className="mx-auto">{children}</div>
{children}
</div>
</Container> </Container>
); );
} }

View File

@ -17,7 +17,7 @@ export async function generateMetadata(
const locale = params.locale as string; const locale = params.locale as string;
const page = await getCustomPage('privacy-policy', locale); const page = await getCustomPage('privacy-policy', locale);
if (!page) { if (!page) {
return {}; return {};
} }
@ -29,8 +29,8 @@ export async function generateMetadata(
title: page.title, title: page.title,
description: page.description, description: page.description,
type: 'article', type: 'article',
url: `${getBaseUrl()}/privacy-policy` url: `${getBaseUrl()}/privacy-policy`,
} },
}; };
} }
@ -42,7 +42,7 @@ export default async function PrivacyPolicyPage(props: NextPageProps) {
const locale = params.locale as string; const locale = params.locale as string;
const page = await getCustomPage('privacy-policy', locale); const page = await getCustomPage('privacy-policy', locale);
if (!page) { if (!page) {
notFound(); notFound();
} }
@ -55,4 +55,4 @@ export default async function PrivacyPolicyPage(props: NextPageProps) {
content={page.body.code} content={page.body.code}
/> />
); );
} }

View File

@ -17,7 +17,7 @@ export async function generateMetadata(
const locale = params.locale as string; const locale = params.locale as string;
const page = await getCustomPage('terms-of-service', locale); const page = await getCustomPage('terms-of-service', locale);
if (!page) { if (!page) {
return {}; return {};
} }
@ -29,8 +29,8 @@ export async function generateMetadata(
title: page.title, title: page.title,
description: page.description, description: page.description,
type: 'article', type: 'article',
url: `${getBaseUrl()}/terms-of-service` url: `${getBaseUrl()}/terms-of-service`,
} },
}; };
} }
@ -42,7 +42,7 @@ export default async function TermsOfServicePage(props: NextPageProps) {
const locale = params.locale as string; const locale = params.locale as string;
const page = await getCustomPage('terms-of-service', locale); const page = await getCustomPage('terms-of-service', locale);
if (!page) { if (!page) {
notFound(); notFound();
} }
@ -55,4 +55,4 @@ export default async function TermsOfServicePage(props: NextPageProps) {
content={page.body.code} content={page.body.code}
/> />
); );
} }

View File

@ -1,4 +1,4 @@
"use client"; 'use client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
@ -7,7 +7,7 @@ import { Label } from '@/components/ui/label';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
/** /**
* *
*/ */
export default function WaitlistPage() { export default function WaitlistPage() {
const t = useTranslations('WaitlistPage'); const t = useTranslations('WaitlistPage');
@ -34,9 +34,7 @@ export default function WaitlistPage() {
<form action="" className="mt-8 space-y-4"> <form action="" className="mt-8 space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email"> <Label htmlFor="email">{t('email')}</Label>
{t('email')}
</Label>
<Input type="email" id="email" required /> <Input type="email" id="email" required />
</div> </div>
@ -46,5 +44,5 @@ export default function WaitlistPage() {
</form> </form>
</Card> </Card>
</div> </div>
) );
} }

View File

@ -1,10 +1,10 @@
import CallToAction from "@/components/blocks/call-to-action/call-to-action"; import CallToAction from '@/components/blocks/call-to-action/call-to-action';
import CallToAction2 from "@/components/blocks/call-to-action/call-to-action-2"; import CallToAction2 from '@/components/blocks/call-to-action/call-to-action-2';
import CallToAction3 from "@/components/blocks/call-to-action/call-to-action-3"; import CallToAction3 from '@/components/blocks/call-to-action/call-to-action-3';
interface CallToActionPageProps { interface CallToActionPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
/** /**
* https://nsui.irung.me/call-to-action * https://nsui.irung.me/call-to-action

View File

@ -1,13 +1,13 @@
import Content from "@/components/blocks/content/content"; import Content from '@/components/blocks/content/content';
import Content2 from "@/components/blocks/content/content-2"; import Content2 from '@/components/blocks/content/content-2';
import Content3 from "@/components/blocks/content/content-3"; import Content3 from '@/components/blocks/content/content-3';
import Content4 from "@/components/blocks/content/content-4"; import Content4 from '@/components/blocks/content/content-4';
import Content5 from "@/components/blocks/content/content-5"; import Content5 from '@/components/blocks/content/content-5';
import Content6 from "@/components/blocks/content/content-6"; import Content6 from '@/components/blocks/content/content-6';
interface ContentPageProps { interface ContentPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
export default async function ContentPage(props: ContentPageProps) { export default async function ContentPage(props: ContentPageProps) {
const params = await props.params; const params = await props.params;

View File

@ -1,8 +1,8 @@
import FAQs from "@/components/blocks/faq/faqs"; import FAQs from '@/components/blocks/faq/faqs';
interface FAQPageProps { interface FAQPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
/** /**
* https://nsui.irung.me/faqs * https://nsui.irung.me/faqs

View File

@ -1,15 +1,15 @@
import Features from "@/components/blocks/features/features"; import Features from '@/components/blocks/features/features';
import Features2 from "@/components/blocks/features/features-2"; import Features2 from '@/components/blocks/features/features-2';
import Features4 from "@/components/blocks/features/features-4"; import Features4 from '@/components/blocks/features/features-4';
import Features5 from "@/components/blocks/features/features-5"; import Features5 from '@/components/blocks/features/features-5';
import Features6 from "@/components/blocks/features/features-6"; import Features6 from '@/components/blocks/features/features-6';
import Features7 from "@/components/blocks/features/features-7"; import Features7 from '@/components/blocks/features/features-7';
import Features8 from "@/components/blocks/features/features-8"; import Features8 from '@/components/blocks/features/features-8';
import Features9 from "@/components/blocks/features/features-9"; import Features9 from '@/components/blocks/features/features-9';
interface FeaturesPageProps { interface FeaturesPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
export default async function FeaturesPage(props: FeaturesPageProps) { export default async function FeaturesPage(props: FeaturesPageProps) {
const params = await props.params; const params = await props.params;
@ -18,19 +18,19 @@ export default async function FeaturesPage(props: FeaturesPageProps) {
<> <>
<div className="mt-8 flex flex-col gap-16 pb-16"> <div className="mt-8 flex flex-col gap-16 pb-16">
<Features /> <Features />
<Features2 /> <Features2 />
<Features4 /> <Features4 />
<Features5 /> <Features5 />
<Features6 /> <Features6 />
<Features7 /> <Features7 />
<Features8 /> <Features8 />
<Features9 /> <Features9 />
</div> </div>
</> </>

View File

@ -1,11 +1,11 @@
import HeroSection from "@/components/blocks/hero/hero-section"; import HeroSection from '@/components/blocks/hero/hero-section';
import HeroSection2 from "@/components/blocks/hero/hero-section-2"; import HeroSection2 from '@/components/blocks/hero/hero-section-2';
import HeroSection3 from "@/components/blocks/hero/hero-section-3"; import HeroSection3 from '@/components/blocks/hero/hero-section-3';
import HeroSection4 from "@/components/blocks/hero/hero-section-4"; import HeroSection4 from '@/components/blocks/hero/hero-section-4';
interface HeroPageProps { interface HeroPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
/** /**
* https://nsui.irung.me/hero-section * https://nsui.irung.me/hero-section
@ -19,7 +19,7 @@ export default async function HeroPage(props: HeroPageProps) {
<HeroSection /> <HeroSection />
<HeroSection2 /> <HeroSection2 />
<HeroSection3 /> <HeroSection3 />
<HeroSection4 /> <HeroSection4 />

View File

@ -1,12 +1,12 @@
import Pricing3 from "@/components/blocks/pricing/pricing-3"; import Pricing3 from '@/components/blocks/pricing/pricing-3';
import Pricing4 from "@/components/blocks/pricing/pricing-4"; import Pricing4 from '@/components/blocks/pricing/pricing-4';
import Pricing5 from "@/components/blocks/pricing/pricing-5"; import Pricing5 from '@/components/blocks/pricing/pricing-5';
import PricingComparator from "@/components/pricing-comparator"; import PricingComparator from '@/components/pricing-comparator';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
interface PricingPageProps { interface PricingPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
/** /**
* https://nsui.irung.me/pricing * https://nsui.irung.me/pricing

View File

@ -1,11 +1,11 @@
import Stats from "@/components/blocks/stats/stats"; import Stats from '@/components/blocks/stats/stats';
import Stats2 from "@/components/blocks/stats/stats-2"; import Stats2 from '@/components/blocks/stats/stats-2';
import Stats3 from "@/components/blocks/stats/stats-3"; import Stats3 from '@/components/blocks/stats/stats-3';
import Stats4 from "@/components/blocks/stats/stats-4"; import Stats4 from '@/components/blocks/stats/stats-4';
interface StatsPageProps { interface StatsPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
/** /**
* https://nsui.irung.me/stats * https://nsui.irung.me/stats
@ -17,11 +17,11 @@ export default async function StatsPage(props: StatsPageProps) {
<> <>
<div className="mt-8 flex flex-col gap-16 pb-16"> <div className="mt-8 flex flex-col gap-16 pb-16">
<Stats /> <Stats />
<Stats2 /> <Stats2 />
<Stats3 /> <Stats3 />
<Stats4 /> <Stats4 />
</div> </div>
</> </>

View File

@ -1,12 +1,12 @@
import Testimonials from "@/components/blocks/testimonials/testimonials"; import Testimonials from '@/components/blocks/testimonials/testimonials';
import Testimonials2 from "@/components/blocks/testimonials/testimonials-2"; import Testimonials2 from '@/components/blocks/testimonials/testimonials-2';
import Testimonials4 from "@/components/blocks/testimonials/testimonials-4"; import Testimonials4 from '@/components/blocks/testimonials/testimonials-4';
import Testimonials5 from "@/components/blocks/testimonials/testimonials-5"; import Testimonials5 from '@/components/blocks/testimonials/testimonials-5';
import Testimonials6 from "@/components/blocks/testimonials/testimonials-6"; import Testimonials6 from '@/components/blocks/testimonials/testimonials-6';
interface TestimonialsPageProps { interface TestimonialsPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
/** /**
* https://nsui.irung.me/testimonials * https://nsui.irung.me/testimonials
@ -20,11 +20,11 @@ export default async function TestimonialsPage(props: TestimonialsPageProps) {
<Testimonials /> <Testimonials />
<Testimonials2 /> <Testimonials2 />
<Testimonials4 /> <Testimonials4 />
<Testimonials5 /> <Testimonials5 />
<Testimonials6 /> <Testimonials6 />
</div> </div>
</> </>

View File

@ -1,12 +1,12 @@
import BlogGrid from "@/components/blog/blog-grid"; import BlogGrid from '@/components/blog/blog-grid';
import EmptyGrid from "@/components/shared/empty-grid"; import EmptyGrid from '@/components/shared/empty-grid';
import CustomPagination from "@/components/shared/pagination"; import CustomPagination from '@/components/shared/pagination';
import { POSTS_PER_PAGE } from "@/lib/constants"; import { POSTS_PER_PAGE } from '@/lib/constants';
import { allPosts, allCategories } from "content-collections"; import { allPosts, allCategories } from 'content-collections';
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import { constructMetadata } from "@/lib/metadata"; import { constructMetadata } from '@/lib/metadata';
import type { Metadata } from "next"; import type { Metadata } from 'next';
import { NextPageProps } from "@/types/next-page-props"; import { NextPageProps } from '@/types/next-page-props';
export async function generateMetadata({ export async function generateMetadata({
params, params,
@ -23,15 +23,15 @@ export async function generateMetadata({
if (!category) { if (!category) {
console.warn( console.warn(
`generateMetadata, category not found for slug: ${slug}, locale: ${locale}`, `generateMetadata, category not found for slug: ${slug}, locale: ${locale}`
); );
return; return;
} }
const ogImageUrl = new URL(`${siteConfig.url}/api/og`); const ogImageUrl = new URL(`${siteConfig.url}/api/og`);
ogImageUrl.searchParams.append("title", category.name); ogImageUrl.searchParams.append('title', category.name);
ogImageUrl.searchParams.append("description", category.description || ""); ogImageUrl.searchParams.append('description', category.description || '');
ogImageUrl.searchParams.append("type", "Blog Category"); ogImageUrl.searchParams.append('type', 'Blog Category');
return constructMetadata({ return constructMetadata({
title: `${category.name}`, title: `${category.name}`,
@ -48,7 +48,7 @@ export default async function BlogCategoryPage({
const resolvedParams = await params; const resolvedParams = await params;
const { slug, locale } = resolvedParams; const { slug, locale } = resolvedParams;
const resolvedSearchParams = await searchParams; const resolvedSearchParams = await searchParams;
const { page } = resolvedSearchParams as { [key: string]: string } || {}; const { page } = (resolvedSearchParams as { [key: string]: string }) || {};
const currentPage = page ? Number(page) : 1; const currentPage = page ? Number(page) : 1;
const startIndex = (currentPage - 1) * POSTS_PER_PAGE; const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
const endIndex = startIndex + POSTS_PER_PAGE; const endIndex = startIndex + POSTS_PER_PAGE;
@ -59,16 +59,16 @@ export default async function BlogCategoryPage({
); );
// Filter posts by category and locale // Filter posts by category and locale
const filteredPosts = allPosts.filter( const filteredPosts = allPosts.filter((post) => {
(post) => { if (!post.published || post.locale !== locale) {
if (!post.published || post.locale !== locale) { return false;
return false;
}
// Check if any of the post's categories match the current category slug
return post.categories.some(category => category && category.slug === slug);
} }
);
// Check if any of the post's categories match the current category slug
return post.categories.some(
(category) => category && category.slug === slug
);
});
// Sort posts by date (newest first) // Sort posts by date (newest first)
const sortedPosts = [...filteredPosts].sort( const sortedPosts = [...filteredPosts].sort(

View File

@ -8,35 +8,33 @@ import { NextPageProps } from '@/types/next-page-props';
interface BlogListLayoutProps extends PropsWithChildren, NextPageProps {} interface BlogListLayoutProps extends PropsWithChildren, NextPageProps {}
export default async function BlogListLayout({ export default async function BlogListLayout({
children, children,
params params,
}: BlogListLayoutProps) { }: BlogListLayoutProps) {
const resolvedParams = await params; const resolvedParams = await params;
const { locale } = resolvedParams; const { locale } = resolvedParams;
const t = await getTranslations("BlogPage"); const t = await getTranslations('BlogPage');
// Filter categories by locale // Filter categories by locale
// console.log("allCategories", allCategories); // console.log("allCategories", allCategories);
const categoryList = allCategories.filter( const categoryList = allCategories.filter(
category => category.locale === locale (category) => category.locale === locale
); );
return ( return (
<div className="mb-16"> <div className="mb-16">
<div className="mt-8 w-full flex flex-col items-center justify-center gap-8"> <div className="mt-8 w-full flex flex-col items-center justify-center gap-8">
<HeaderSection <HeaderSection
titleAs="h2" titleAs="h2"
title={t("title")} title={t('title')}
subtitle={t("subtitle")} subtitle={t('subtitle')}
/> />
<BlogCategoryFilter categoryList={categoryList} /> <BlogCategoryFilter categoryList={categoryList} />
</div> </div>
<Container className="mt-8 px-4"> <Container className="mt-8 px-4">{children}</Container>
{children}
</Container>
</div> </div>
); );
} }

View File

@ -1,4 +1,4 @@
import { BlogGridSkeleton } from "@/components/blog/blog-grid"; import { BlogGridSkeleton } from '@/components/blog/blog-grid';
export default function Loading() { export default function Loading() {
return <BlogGridSkeleton />; return <BlogGridSkeleton />;

View File

@ -15,12 +15,12 @@ export async function generateMetadata(): Promise<Metadata> {
export default async function BlogPage({ export default async function BlogPage({
params, params,
searchParams searchParams,
}: NextPageProps) { }: NextPageProps) {
const resolvedParams = await params; const resolvedParams = await params;
const { locale } = resolvedParams; const { locale } = resolvedParams;
const resolvedSearchParams = await searchParams; const resolvedSearchParams = await searchParams;
const { page } = resolvedSearchParams as { [key: string]: string } || {}; const { page } = (resolvedSearchParams as { [key: string]: string }) || {};
const currentPage = page ? Number(page) : 1; const currentPage = page ? Number(page) : 1;
const startIndex = (currentPage - 1) * POSTS_PER_PAGE; const startIndex = (currentPage - 1) * POSTS_PER_PAGE;
const endIndex = startIndex + POSTS_PER_PAGE; const endIndex = startIndex + POSTS_PER_PAGE;
@ -31,9 +31,10 @@ export default async function BlogPage({
); );
// If no posts found for the current locale, show all published posts // If no posts found for the current locale, show all published posts
const filteredPosts = localePosts.length > 0 const filteredPosts =
? localePosts localePosts.length > 0
: allPosts.filter((post) => post.published); ? localePosts
: allPosts.filter((post) => post.published);
// Sort posts by date (newest first) // Sort posts by date (newest first)
const sortedPosts = [...filteredPosts].sort( const sortedPosts = [...filteredPosts].sort(
@ -67,4 +68,4 @@ export default async function BlogPage({
)} )}
</div> </div>
); );
} }

View File

@ -4,9 +4,7 @@ import { PropsWithChildren } from 'react';
export default function BlogPostLayout({ children }: PropsWithChildren) { export default function BlogPostLayout({ children }: PropsWithChildren) {
return ( return (
<Container className="py-8 px-4"> <Container className="py-8 px-4">
<div className="mx-auto"> <div className="mx-auto">{children}</div>
{children}
</div>
</Container> </Container>
); );
} }

View File

@ -19,13 +19,13 @@ import '@/styles/mdx.css';
* Gets the blog post from the params * Gets the blog post from the params
* @param props - The props of the page * @param props - The props of the page
* @returns The blog post * @returns The blog post
* *
* How it works: * How it works:
* 1. /[locale]/blog/first-post: * 1. /[locale]/blog/first-post:
* params.slug = ["first-post"] * params.slug = ["first-post"]
* slug becomes "first-post" after join('/') * slug becomes "first-post" after join('/')
* Matches post where slugAsParams === "first-post" AND locale === params.locale * Matches post where slugAsParams === "first-post" AND locale === params.locale
* *
* 2. /[locale]/blog/2023/year-review: * 2. /[locale]/blog/2023/year-review:
* params.slug = ["2023", "year-review"] * params.slug = ["2023", "year-review"]
* slug becomes "2023/year-review" after join('/') * slug becomes "2023/year-review" after join('/')
@ -44,7 +44,8 @@ async function getBlogPostFromParams(props: NextPageProps) {
// Find post with matching slug and locale // Find post with matching slug and locale
const post = allPosts.find( const post = allPosts.find(
(post) => (post) =>
(post.slugAsParams === slug || (!slug && post.slugAsParams === 'index')) && (post.slugAsParams === slug ||
(!slug && post.slugAsParams === 'index')) &&
post.locale === locale post.locale === locale
); );
@ -52,7 +53,7 @@ async function getBlogPostFromParams(props: NextPageProps) {
// If no post found with the current locale, try to find one with the default locale // If no post found with the current locale, try to find one with the default locale
const defaultPost = allPosts.find( const defaultPost = allPosts.find(
(post) => (post) =>
(post.slugAsParams === slug || (!slug && post.slugAsParams === 'index')) post.slugAsParams === slug || (!slug && post.slugAsParams === 'index')
); );
return defaultPost; return defaultPost;
@ -76,8 +77,8 @@ export async function generateMetadata(
title: post.title, title: post.title,
description: post.description, description: post.description,
type: 'article', type: 'article',
url: `${getBaseUrl()}${post.slug}` url: `${getBaseUrl()}${post.slug}`,
} },
}; };
} }
@ -90,7 +91,7 @@ export default async function BlogPostPage(props: NextPageProps) {
const publishDate = post.date; const publishDate = post.date;
const date = getLocaleDate(publishDate); const date = getLocaleDate(publishDate);
const toc = await getTableOfContents(post.content); const toc = await getTableOfContents(post.content);
const t = await getTranslations("BlogPage"); const t = await getTranslations('BlogPage');
return ( return (
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
@ -105,8 +106,8 @@ export default async function BlogPostPage(props: NextPageProps) {
{post.image && ( {post.image && (
<Image <Image
src={post.image} src={post.image}
alt={post.title || "image for blog post"} alt={post.title || 'image for blog post'}
title={post.title || "image for blog post"} title={post.title || 'image for blog post'}
loading="eager" loading="eager"
fill fill
className="object-cover" className="object-cover"
@ -150,7 +151,7 @@ export default async function BlogPostPage(props: NextPageProps) {
<div className="space-y-4 lg:sticky lg:top-24"> <div className="space-y-4 lg:sticky lg:top-24">
{/* author info */} {/* author info */}
<div className="bg-muted/50 rounded-lg p-6"> <div className="bg-muted/50 rounded-lg p-6">
<h2 className="text-lg font-semibold mb-4">{t("author")}</h2> <h2 className="text-lg font-semibold mb-4">{t('author')}</h2>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="relative h-8 w-8 flex-shrink-0"> <div className="relative h-8 w-8 flex-shrink-0">
{post.author?.avatar && ( {post.author?.avatar && (
@ -168,26 +169,29 @@ export default async function BlogPostPage(props: NextPageProps) {
{/* categories */} {/* categories */}
<div className="bg-muted/50 rounded-lg p-6"> <div className="bg-muted/50 rounded-lg p-6">
<h2 className="text-lg font-semibold mb-4">{t("categories")}</h2> <h2 className="text-lg font-semibold mb-4">{t('categories')}</h2>
<ul className="flex flex-wrap gap-4"> <ul className="flex flex-wrap gap-4">
{post.categories?.filter(Boolean).map((category) => ( {post.categories?.filter(Boolean).map(
category && ( (category) =>
<li key={category.slug}> category && (
<LocaleLink <li key={category.slug}>
href={`/blog/category/${category.slug}`} <LocaleLink
className="text-sm font-medium hover:text-primary" href={`/blog/category/${category.slug}`}
> className="text-sm font-medium hover:text-primary"
{category.name} >
</LocaleLink> {category.name}
</li> </LocaleLink>
) </li>
))} )
)}
</ul> </ul>
</div> </div>
{/* table of contents */} {/* table of contents */}
<div className="bg-muted/50 rounded-lg p-6 hidden lg:block"> <div className="bg-muted/50 rounded-lg p-6 hidden lg:block">
<h2 className="text-lg font-semibold mb-4">{t("tableOfContents")}</h2> <h2 className="text-lg font-semibold mb-4">
{t('tableOfContents')}
</h2>
<div className="max-h-[calc(100vh-18rem)] overflow-y-auto"> <div className="max-h-[calc(100vh-18rem)] overflow-y-auto">
<BlogToc toc={toc} /> <BlogToc toc={toc} />
</div> </div>
@ -200,4 +204,4 @@ export default async function BlogPostPage(props: NextPageProps) {
{/* TODO: add newsletter */} {/* TODO: add newsletter */}
</div> </div>
); );
} }

View File

@ -1,7 +1,8 @@
import { Footer } from "@/components/layout/footer"; import { Footer } from '@/components/layout/footer';
import { Navbar } from "@/components/layout/navbar"; import { Navbar } from '@/components/layout/navbar';
import { ReactNode } from 'react';
export default function MarketingLayout({ children }: { children: React.ReactNode }) { export default function MarketingLayout({ children }: { children: ReactNode }) {
return ( return (
<div className="flex flex-col min-h-screen"> <div className="flex flex-col min-h-screen">
<Navbar scroll={true} /> <Navbar scroll={true} />
@ -9,4 +10,4 @@ export default function MarketingLayout({ children }: { children: React.ReactNod
<Footer /> <Footer />
</div> </div>
); );
} }

View File

@ -1,12 +1,12 @@
import Pricing3 from "@/components/blocks/pricing/pricing-3"; import Pricing3 from '@/components/blocks/pricing/pricing-3';
import Pricing4 from "@/components/blocks/pricing/pricing-4"; import Pricing4 from '@/components/blocks/pricing/pricing-4';
import Pricing5 from "@/components/blocks/pricing/pricing-5"; import Pricing5 from '@/components/blocks/pricing/pricing-5';
import PricingComparator from "@/components/pricing-comparator"; import PricingComparator from '@/components/pricing-comparator';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
interface PricingPageProps { interface PricingPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
export default async function PricingPage(props: PricingPageProps) { export default async function PricingPage(props: PricingPageProps) {
const params = await props.params; const params = await props.params;

View File

@ -2,12 +2,12 @@ import { notFound } from 'next/navigation';
/** /**
* Catching unknown routes * Catching unknown routes
* *
* all requests that are matched within the [locale] segment will render * all requests that are matched within the [locale] segment will render
* the not-found page when an unknown route is encountered (e.g. /en/unknown). * the not-found page when an unknown route is encountered (e.g. /en/unknown).
* *
* https://next-intl.dev/docs/environments/error-files#catching-unknown-routes * https://next-intl.dev/docs/environments/error-files#catching-unknown-routes
*/ */
export default function CatchAllPage() { export default function CatchAllPage() {
notFound(); notFound();
} }

View File

@ -1,11 +1,11 @@
import { ErrorCard } from "@/components/auth/error-card"; import { ErrorCard } from '@/components/auth/error-card';
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import { constructMetadata } from "@/lib/metadata"; import { constructMetadata } from '@/lib/metadata';
import { Routes } from "@/routes"; import { Routes } from '@/routes';
export const metadata = constructMetadata({ export const metadata = constructMetadata({
title: "Auth Error", title: 'Auth Error',
description: "Auth Error", description: 'Auth Error',
canonicalUrl: `${siteConfig.url}${Routes.AuthError}`, canonicalUrl: `${siteConfig.url}${Routes.AuthError}`,
}); });

View File

@ -1,11 +1,11 @@
import { ForgotPasswordForm } from "@/components/auth/forgot-password-form"; import { ForgotPasswordForm } from '@/components/auth/forgot-password-form';
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import { constructMetadata } from "@/lib/metadata"; import { constructMetadata } from '@/lib/metadata';
import { Routes } from "@/routes"; import { Routes } from '@/routes';
export const metadata = constructMetadata({ export const metadata = constructMetadata({
title: "Forgot Password", title: 'Forgot Password',
description: "Forgot your password? Reset it.", description: 'Forgot your password? Reset it.',
canonicalUrl: `${siteConfig.url}${Routes.ForgotPassword}`, canonicalUrl: `${siteConfig.url}${Routes.ForgotPassword}`,
}); });

View File

@ -1,4 +1,4 @@
import BackButtonSmall from "@/components/shared/back-button-small"; import BackButtonSmall from '@/components/shared/back-button-small';
/** /**
* auth layout is different from other public layouts, * auth layout is different from other public layouts,
@ -12,9 +12,7 @@ export default function AuthLayout({
return ( return (
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10"> <div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
<BackButtonSmall className="absolute top-6 left-6" /> <BackButtonSmall className="absolute top-6 left-6" />
<div className="flex w-full max-w-sm flex-col gap-6"> <div className="flex w-full max-w-sm flex-col gap-6">{children}</div>
{children}
</div>
</div> </div>
); );
} }

View File

@ -1,4 +1,4 @@
import { Loader2Icon } from "lucide-react"; import { Loader2Icon } from 'lucide-react';
export default function Loading() { export default function Loading() {
return <Loader2Icon className="my-32 mx-auto size-6 animate-spin" />; return <Loader2Icon className="my-32 mx-auto size-6 animate-spin" />;

View File

@ -1,36 +1,36 @@
import { LoginForm } from "@/components/auth/login-form"; import { LoginForm } from '@/components/auth/login-form';
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import { LocaleLink } from "@/i18n/navigation"; import { LocaleLink } from '@/i18n/navigation';
import { constructMetadata } from "@/lib/metadata"; import { constructMetadata } from '@/lib/metadata';
import { Routes } from "@/routes"; import { Routes } from '@/routes';
import { useTranslations } from "next-intl"; import { useTranslations } from 'next-intl';
export const metadata = constructMetadata({ export const metadata = constructMetadata({
title: "Login", title: 'Login',
description: "Login to your account", description: 'Login to your account',
canonicalUrl: `${siteConfig.url}${Routes.Login}`, canonicalUrl: `${siteConfig.url}${Routes.Login}`,
}); });
const LoginPage = () => { const LoginPage = () => {
const t = useTranslations("AuthPage.login"); const t = useTranslations('AuthPage.login');
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<LoginForm /> <LoginForm />
<div className="text-balance text-center text-xs text-muted-foreground"> <div className="text-balance text-center text-xs text-muted-foreground">
{t("byClickingContinue")} {t('byClickingContinue')}
<LocaleLink <LocaleLink
href={Routes.TermsOfService} href={Routes.TermsOfService}
className="underline underline-offset-4 hover:text-primary" className="underline underline-offset-4 hover:text-primary"
> >
{t("termsOfService")} {t('termsOfService')}
</LocaleLink>{" "} </LocaleLink>{' '}
{t("and")}{" "} {t('and')}{' '}
<LocaleLink <LocaleLink
href={Routes.PrivacyPolicy} href={Routes.PrivacyPolicy}
className="underline underline-offset-4 hover:text-primary" className="underline underline-offset-4 hover:text-primary"
> >
{t("privacyPolicy")} {t('privacyPolicy')}
</LocaleLink> </LocaleLink>
</div> </div>
</div> </div>

View File

@ -1,11 +1,11 @@
import { RegisterForm } from "@/components/auth/register-form"; import { RegisterForm } from '@/components/auth/register-form';
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import { constructMetadata } from "@/lib/metadata"; import { constructMetadata } from '@/lib/metadata';
import { Routes } from "@/routes"; import { Routes } from '@/routes';
export const metadata = constructMetadata({ export const metadata = constructMetadata({
title: "Register", title: 'Register',
description: "Create an account to get started", description: 'Create an account to get started',
canonicalUrl: `${siteConfig.url}${Routes.Register}`, canonicalUrl: `${siteConfig.url}${Routes.Register}`,
}); });

View File

@ -1,10 +1,10 @@
import { ResetPasswordForm } from "@/components/auth/reset-password-form"; import { ResetPasswordForm } from '@/components/auth/reset-password-form';
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import { constructMetadata } from "@/lib/metadata"; import { constructMetadata } from '@/lib/metadata';
export const metadata = constructMetadata({ export const metadata = constructMetadata({
title: "Reset Password", title: 'Reset Password',
description: "Set a new password", description: 'Set a new password',
canonicalUrl: `${siteConfig.url}/auth/reset-password`, canonicalUrl: `${siteConfig.url}/auth/reset-password`,
}); });

View File

@ -1,12 +1,12 @@
"use client"; 'use client';
import { lazy } from "react"; import { lazy } from 'react';
/** /**
* Move error content to a separate chunk and load it only when needed * Move error content to a separate chunk and load it only when needed
* *
* Note that error.tsx is loaded right after your app has initialized. * Note that error.tsx is loaded right after your app has initialized.
* If your app is performance-sensitive and you want to avoid loading translation functionality * If your app is performance-sensitive and you want to avoid loading translation functionality
* from next-intl as part of this bundle, you can export a lazy reference from your error file. * from next-intl as part of this bundle, you can export a lazy reference from your error file.
* https://next-intl.dev/docs/environments/error-files#errorjs * https://next-intl.dev/docs/environments/error-files#errorjs
*/ */

View File

@ -1,9 +1,9 @@
import { fontSourceSans, fontSourceSerif4 } from "@/assets/fonts"; import { fontSourceSans, fontSourceSerif4 } from '@/assets/fonts';
import { TailwindIndicator } from '@/components/tailwind-indicator'; import { TailwindIndicator } from '@/components/tailwind-indicator';
import { routing } from '@/i18n/routing'; import { routing } from '@/i18n/routing';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
import { GeistMono } from "geist/font/mono"; import { GeistMono } from 'geist/font/mono';
import { GeistSans } from "geist/font/sans"; import { GeistSans } from 'geist/font/sans';
import { NextIntlClientProvider } from 'next-intl'; import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server'; import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
@ -14,48 +14,54 @@ import { Providers } from './providers';
import '@/styles/globals.css'; import '@/styles/globals.css';
interface LocaleLayoutProps { interface LocaleLayoutProps {
children: ReactNode; children: ReactNode;
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}; }
/** /**
* 1. Locale Layout * 1. Locale Layout
* https://next-intl.dev/docs/getting-started/app-router/with-i18n-routing#layout * https://next-intl.dev/docs/getting-started/app-router/with-i18n-routing#layout
* *
* 2. NextIntlClientProvider * 2. NextIntlClientProvider
* https://next-intl.dev/docs/usage/configuration#nextintlclientprovider * https://next-intl.dev/docs/usage/configuration#nextintlclientprovider
*/ */
export default async function LocaleLayout({ children, params }: LocaleLayoutProps) { export default async function LocaleLayout({
const { locale } = await params; children,
params,
}: LocaleLayoutProps) {
const { locale } = await params;
// Ensure that the incoming `locale` is valid // Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) { if (!routing.locales.includes(locale as any)) {
notFound(); notFound();
} }
// Providing all messages to the client side // Providing all messages to the client side
const messages = await getMessages(); const messages = await getMessages();
return ( return (
<html lang={locale} suppressHydrationWarning> <html lang={locale} suppressHydrationWarning>
<body suppressHydrationWarning className={cn( <body
"size-full antialiased", suppressHydrationWarning
GeistSans.className, className={cn(
fontSourceSerif4.variable, 'size-full antialiased',
fontSourceSans.variable, GeistSans.className,
GeistSans.variable, fontSourceSerif4.variable,
GeistMono.variable, fontSourceSans.variable,
)}> GeistSans.variable,
<NextIntlClientProvider messages={messages}> GeistMono.variable
<Providers> )}
{children} >
<NextIntlClientProvider messages={messages}>
<Providers>
{children}
<Toaster richColors position="top-right" offset={64} /> <Toaster richColors position="top-right" offset={64} />
<TailwindIndicator /> <TailwindIndicator />
</Providers> </Providers>
</NextIntlClientProvider> </NextIntlClientProvider>
</body> </body>
</html> </html>
); );
} }

View File

@ -1,4 +1,4 @@
import { Loader2Icon } from "lucide-react"; import { Loader2Icon } from 'lucide-react';
export default function Loading() { export default function Loading() {
return <Loader2Icon className="my-32 mx-auto size-6 animate-spin" />; return <Loader2Icon className="my-32 mx-auto size-6 animate-spin" />;

View File

@ -1,12 +1,12 @@
import { Logo } from "@/components/logo"; import { Logo } from '@/components/logo';
import { Button } from "@/components/ui/button"; import { Button } from '@/components/ui/button';
import { LocaleLink } from "@/i18n/navigation"; import { LocaleLink } from '@/i18n/navigation';
import { useTranslations } from "next-intl"; import { useTranslations } from 'next-intl';
/** /**
* Note that `app/[locale]/[...rest]/page.tsx` * Note that `app/[locale]/[...rest]/page.tsx`
* is necessary for this page to render. * is necessary for this page to render.
* *
* https://next-intl.dev/docs/environments/error-files#not-foundjs * https://next-intl.dev/docs/environments/error-files#not-foundjs
* https://next-intl.dev/docs/environments/error-files#catching-non-localized-requests * https://next-intl.dev/docs/environments/error-files#catching-non-localized-requests
*/ */

View File

@ -1,12 +1,11 @@
"use client"; 'use client';
import * as React from "react"; import * as React from 'react';
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from 'next-themes';
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from '@/components/ui/tooltip';
import { PropsWithChildren } from 'react';
export function Providers({ export function Providers({ children }: PropsWithChildren) {
children
}: React.PropsWithChildren) {
return ( return (
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
@ -14,9 +13,7 @@ export function Providers({
enableSystem enableSystem
disableTransitionOnChange disableTransitionOnChange
> >
<TooltipProvider> <TooltipProvider>{children}</TooltipProvider>
{children}
</TooltipProvider>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@ -1,4 +1,4 @@
import { auth } from "@/lib/auth"; // path to your auth file import { auth } from '@/lib/auth'; // path to your auth file
import { toNextJsHandler } from "better-auth/next-js"; import { toNextJsHandler } from 'better-auth/next-js';
export const { POST, GET } = toNextJsHandler(auth); export const { POST, GET } = toNextJsHandler(auth);

View File

@ -1,15 +1,15 @@
import {ReactNode} from 'react'; import { ReactNode } from 'react';
interface Props { interface Props {
children: ReactNode; children: ReactNode;
}; }
/** /**
* Since we have a `not-found.tsx` page on the root, a layout file * Since we have a `not-found.tsx` page on the root, a layout file
* is required, even if it's just passing children through. * is required, even if it's just passing children through.
* *
* https://next-intl.dev/docs/environments/error-files#catching-non-localized-requests * https://next-intl.dev/docs/environments/error-files#catching-non-localized-requests
*/ */
export default function RootLayout({children}: Props) { export default function RootLayout({ children }: Props) {
return children; return children;
} }

View File

@ -3,12 +3,12 @@ import { siteConfig } from '@/config/site';
/** /**
* Generates the Web App Manifest for the application * Generates the Web App Manifest for the application
* *
* TODO: https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/app/manifest.ts * TODO: https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/app/manifest.ts
* *
* The manifest.json provides metadata used when the web app is installed on a * The manifest.json provides metadata used when the web app is installed on a
* user's mobile device or desktop. See https://web.dev/add-manifest/ * user's mobile device or desktop. See https://web.dev/add-manifest/
* *
* @returns {MetadataRoute.Manifest} The manifest configuration object * @returns {MetadataRoute.Manifest} The manifest configuration object
*/ */
export default function manifest(): MetadataRoute.Manifest { export default function manifest(): MetadataRoute.Manifest {
@ -25,14 +25,14 @@ export default function manifest(): MetadataRoute.Manifest {
src: '/android-chrome-192x192.png', src: '/android-chrome-192x192.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png', type: 'image/png',
purpose: 'maskable' purpose: 'maskable',
}, },
{ {
src: '/android-chrome-512x512.png', src: '/android-chrome-512x512.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png', type: 'image/png',
purpose: 'maskable' purpose: 'maskable',
} },
] ],
}; };
} }

View File

@ -1,14 +1,14 @@
"use client"; 'use client';
import Error from "next/error"; import Error from 'next/error';
/** /**
* Catching non-localized requests * Catching non-localized requests
* *
* This page renders when a route like `/unknown.txt` is requested. * This page renders when a route like `/unknown.txt` is requested.
* In this case, the layout at `app/[locale]/layout.tsx` receives * In this case, the layout at `app/[locale]/layout.tsx` receives
* an invalid value as the `[locale]` param and calls `notFound()`. * an invalid value as the `[locale]` param and calls `notFound()`.
* *
* https://next-intl.dev/docs/environments/error-files#catching-non-localized-requests * https://next-intl.dev/docs/environments/error-files#catching-non-localized-requests
*/ */
export default function GlobalNotFound() { export default function GlobalNotFound() {

View File

@ -1,5 +1,5 @@
import { siteConfig } from "@/config/site"; import { siteConfig } from '@/config/site';
import type { MetadataRoute } from "next"; import type { MetadataRoute } from 'next';
/** /**
* https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots * https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots
@ -7,8 +7,8 @@ import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots { export default function robots(): MetadataRoute.Robots {
return { return {
rules: { rules: {
userAgent: "*", userAgent: '*',
allow: "/", allow: '/',
}, },
sitemap: `${siteConfig.url}/sitemap.xml`, sitemap: `${siteConfig.url}/sitemap.xml`,
}; };