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,
"ignore": [
".next/**",
".content-collections/**",
"node_modules/**",
"dist/**",
"build/**",
@ -45,6 +46,7 @@
},
"ignore": [
".next/**",
".content-collections/**",
"node_modules/**",
"dist/**",
"build/**",
@ -55,7 +57,7 @@
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"quoteStyle": "single",
"trailingCommas": "es5",
"semicolons": "always"
}

View File

@ -1,4 +1,4 @@
import { AppSidebar } from "@/components/app-sidebar";
import { AppSidebar } from '@/components/app-sidebar';
import {
Breadcrumb,
BreadcrumbItem,
@ -6,20 +6,20 @@ import {
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Separator } from "@/components/ui/separator";
} from '@/components/ui/breadcrumb';
import { Separator } from '@/components/ui/separator';
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
} from '@/components/ui/sidebar';
export default function Page() {
return (
<SidebarProvider>
<AppSidebar />
<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">
<SidebarTrigger className="-ml-1" />
<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 Content2 from "@/components/blocks/content/content-2";
import FAQs from "@/components/blocks/faq/faqs";
import FeaturesSection from "@/components/blocks/features/features-8";
import HeroSection from "@/components/blocks/hero/hero-section-4";
import LogoCloud from "@/components/blocks/logo-cloud/logo-cloud";
import Pricing from "@/components/blocks/pricing/pricing";
import StatsSection from "@/components/blocks/stats/stats";
import CallToAction3 from '@/components/blocks/call-to-action/call-to-action-3';
import Content2 from '@/components/blocks/content/content-2';
import FAQs from '@/components/blocks/faq/faqs';
import FeaturesSection from '@/components/blocks/features/features-8';
import HeroSection from '@/components/blocks/hero/hero-section-4';
import LogoCloud from '@/components/blocks/logo-cloud/logo-cloud';
import Pricing from '@/components/blocks/pricing/pricing';
import StatsSection from '@/components/blocks/stats/stats';
import { getTranslations } from 'next-intl/server';
interface HomePageProps {
params: Promise<{ locale: string }>;
};
}
export default async function HomePage(props: HomePageProps) {
const params = await props.params;

View File

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

View File

@ -25,8 +25,8 @@ export async function generateMetadata(
title: 'Changelog',
description: 'Track all updates and improvements to our platform',
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 { Card } from '@/components/ui/card';
@ -35,23 +35,17 @@ export default function ContactPage() {
<form action="" className="mt-8 space-y-4">
<div className="space-y-2">
<Label htmlFor="name">
{t('name')}
</Label>
<Label htmlFor="name">{t('name')}</Label>
<Input type="text" id="name" required />
</div>
<div className="space-y-2">
<Label htmlFor="email">
{t('email')}
</Label>
<Label htmlFor="email">{t('email')}</Label>
<Input type="email" id="email" required />
</div>
<div className="space-y-2">
<Label htmlFor="msg">
{t('message')}
</Label>
<Label htmlFor="msg">{t('message')}</Label>
<Textarea id="msg" rows={3} />
</div>
@ -61,5 +55,5 @@ export default function ContactPage() {
</form>
</Card>
</div>
)
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import Content from "@/components/blocks/content/content";
import Content2 from "@/components/blocks/content/content-2";
import Content3 from "@/components/blocks/content/content-3";
import Content4 from "@/components/blocks/content/content-4";
import Content5 from "@/components/blocks/content/content-5";
import Content6 from "@/components/blocks/content/content-6";
import Content from '@/components/blocks/content/content';
import Content2 from '@/components/blocks/content/content-2';
import Content3 from '@/components/blocks/content/content-3';
import Content4 from '@/components/blocks/content/content-4';
import Content5 from '@/components/blocks/content/content-5';
import Content6 from '@/components/blocks/content/content-6';
interface ContentPageProps {
params: Promise<{ locale: string }>;
};
}
export default async function ContentPage(props: ContentPageProps) {
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 {
params: Promise<{ locale: string }>;
};
}
/**
* https://nsui.irung.me/faqs

View File

@ -1,15 +1,15 @@
import Features from "@/components/blocks/features/features";
import Features2 from "@/components/blocks/features/features-2";
import Features4 from "@/components/blocks/features/features-4";
import Features5 from "@/components/blocks/features/features-5";
import Features6 from "@/components/blocks/features/features-6";
import Features7 from "@/components/blocks/features/features-7";
import Features8 from "@/components/blocks/features/features-8";
import Features9 from "@/components/blocks/features/features-9";
import Features from '@/components/blocks/features/features';
import Features2 from '@/components/blocks/features/features-2';
import Features4 from '@/components/blocks/features/features-4';
import Features5 from '@/components/blocks/features/features-5';
import Features6 from '@/components/blocks/features/features-6';
import Features7 from '@/components/blocks/features/features-7';
import Features8 from '@/components/blocks/features/features-8';
import Features9 from '@/components/blocks/features/features-9';
interface FeaturesPageProps {
params: Promise<{ locale: string }>;
};
}
export default async function FeaturesPage(props: FeaturesPageProps) {
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">
<Features />
<Features2 />
<Features4 />
<Features5 />
<Features6 />
<Features7 />
<Features8 />
<Features9 />
</div>
</>

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import Stats from "@/components/blocks/stats/stats";
import Stats2 from "@/components/blocks/stats/stats-2";
import Stats3 from "@/components/blocks/stats/stats-3";
import Stats4 from "@/components/blocks/stats/stats-4";
import Stats from '@/components/blocks/stats/stats';
import Stats2 from '@/components/blocks/stats/stats-2';
import Stats3 from '@/components/blocks/stats/stats-3';
import Stats4 from '@/components/blocks/stats/stats-4';
interface StatsPageProps {
params: Promise<{ locale: string }>;
};
}
/**
* 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">
<Stats />
<Stats2 />
<Stats3 />
<Stats4 />
</div>
</>

View File

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

View File

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

View File

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

View File

@ -15,12 +15,12 @@ export async function generateMetadata(): Promise<Metadata> {
export default async function BlogPage({
params,
searchParams
searchParams,
}: NextPageProps) {
const resolvedParams = await params;
const { locale } = resolvedParams;
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 startIndex = (currentPage - 1) * 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
const filteredPosts = localePosts.length > 0
? localePosts
: allPosts.filter((post) => post.published);
const filteredPosts =
localePosts.length > 0
? localePosts
: allPosts.filter((post) => post.published);
// Sort posts by date (newest first)
const sortedPosts = [...filteredPosts].sort(
@ -67,4 +68,4 @@ export default async function BlogPage({
)}
</div>
);
}
}

View File

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

View File

@ -19,13 +19,13 @@ import '@/styles/mdx.css';
* Gets the blog post from the params
* @param props - The props of the page
* @returns The blog post
*
*
* How it works:
* 1. /[locale]/blog/first-post:
* params.slug = ["first-post"]
* slug becomes "first-post" after join('/')
* Matches post where slugAsParams === "first-post" AND locale === params.locale
*
*
* 2. /[locale]/blog/2023/year-review:
* params.slug = ["2023", "year-review"]
* slug becomes "2023/year-review" after join('/')
@ -44,7 +44,8 @@ async function getBlogPostFromParams(props: NextPageProps) {
// Find post with matching slug and locale
const post = allPosts.find(
(post) =>
(post.slugAsParams === slug || (!slug && post.slugAsParams === 'index')) &&
(post.slugAsParams === slug ||
(!slug && post.slugAsParams === 'index')) &&
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
const defaultPost = allPosts.find(
(post) =>
(post.slugAsParams === slug || (!slug && post.slugAsParams === 'index'))
post.slugAsParams === slug || (!slug && post.slugAsParams === 'index')
);
return defaultPost;
@ -76,8 +77,8 @@ export async function generateMetadata(
title: post.title,
description: post.description,
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 date = getLocaleDate(publishDate);
const toc = await getTableOfContents(post.content);
const t = await getTranslations("BlogPage");
const t = await getTranslations('BlogPage');
return (
<div className="flex flex-col gap-8">
@ -105,8 +106,8 @@ export default async function BlogPostPage(props: NextPageProps) {
{post.image && (
<Image
src={post.image}
alt={post.title || "image for blog post"}
title={post.title || "image for blog post"}
alt={post.title || 'image for blog post'}
title={post.title || 'image for blog post'}
loading="eager"
fill
className="object-cover"
@ -150,7 +151,7 @@ export default async function BlogPostPage(props: NextPageProps) {
<div className="space-y-4 lg:sticky lg:top-24">
{/* author info */}
<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="relative h-8 w-8 flex-shrink-0">
{post.author?.avatar && (
@ -168,26 +169,29 @@ export default async function BlogPostPage(props: NextPageProps) {
{/* categories */}
<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">
{post.categories?.filter(Boolean).map((category) => (
category && (
<li key={category.slug}>
<LocaleLink
href={`/blog/category/${category.slug}`}
className="text-sm font-medium hover:text-primary"
>
{category.name}
</LocaleLink>
</li>
)
))}
{post.categories?.filter(Boolean).map(
(category) =>
category && (
<li key={category.slug}>
<LocaleLink
href={`/blog/category/${category.slug}`}
className="text-sm font-medium hover:text-primary"
>
{category.name}
</LocaleLink>
</li>
)
)}
</ul>
</div>
{/* table of contents */}
<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">
<BlogToc toc={toc} />
</div>
@ -200,4 +204,4 @@ export default async function BlogPostPage(props: NextPageProps) {
{/* TODO: add newsletter */}
</div>
);
}
}

View File

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

View File

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

View File

@ -2,12 +2,12 @@ import { notFound } from 'next/navigation';
/**
* 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).
*
*
* https://next-intl.dev/docs/environments/error-files#catching-unknown-routes
*/
export default function CatchAllPage() {
notFound();
}
}

View File

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

View File

@ -1,11 +1,11 @@
import { ForgotPasswordForm } from "@/components/auth/forgot-password-form";
import { siteConfig } from "@/config/site";
import { constructMetadata } from "@/lib/metadata";
import { Routes } from "@/routes";
import { ForgotPasswordForm } from '@/components/auth/forgot-password-form';
import { siteConfig } from '@/config/site';
import { constructMetadata } from '@/lib/metadata';
import { Routes } from '@/routes';
export const metadata = constructMetadata({
title: "Forgot Password",
description: "Forgot your password? Reset it.",
title: 'Forgot Password',
description: 'Forgot your password? Reset it.',
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,
@ -12,9 +12,7 @@ export default function AuthLayout({
return (
<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" />
<div className="flex w-full max-w-sm flex-col gap-6">
{children}
</div>
<div className="flex w-full max-w-sm flex-col gap-6">{children}</div>
</div>
);
}

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import { ResetPasswordForm } from "@/components/auth/reset-password-form";
import { siteConfig } from "@/config/site";
import { constructMetadata } from "@/lib/metadata";
import { ResetPasswordForm } from '@/components/auth/reset-password-form';
import { siteConfig } from '@/config/site';
import { constructMetadata } from '@/lib/metadata';
export const metadata = constructMetadata({
title: "Reset Password",
description: "Set a new password",
title: 'Reset Password',
description: 'Set a new 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
*
* 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
*
* 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
* 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
*/

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

View File

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

View File

@ -1,12 +1,12 @@
import { Logo } from "@/components/logo";
import { Button } from "@/components/ui/button";
import { LocaleLink } from "@/i18n/navigation";
import { useTranslations } from "next-intl";
import { Logo } from '@/components/logo';
import { Button } from '@/components/ui/button';
import { LocaleLink } from '@/i18n/navigation';
import { useTranslations } from 'next-intl';
/**
* Note that `app/[locale]/[...rest]/page.tsx`
* 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#catching-non-localized-requests
*/

View File

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

View File

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

View File

@ -1,15 +1,15 @@
import {ReactNode} from 'react';
import { ReactNode } from 'react';
interface Props {
children: ReactNode;
};
}
/**
* Since we have a `not-found.tsx` page on the root, a layout file
* is required, even if it's just passing children through.
*
*
* 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;
}

View File

@ -3,12 +3,12 @@ import { siteConfig } from '@/config/site';
/**
* 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
*
*
* 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/
*
*
* @returns {MetadataRoute.Manifest} The manifest configuration object
*/
export default function manifest(): MetadataRoute.Manifest {
@ -25,14 +25,14 @@ export default function manifest(): MetadataRoute.Manifest {
src: '/android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'maskable'
purpose: 'maskable',
},
{
src: '/android-chrome-512x512.png',
sizes: '512x512',
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
*
*
* This page renders when a route like `/unknown.txt` is requested.
* In this case, the layout at `app/[locale]/layout.tsx` receives
* an invalid value as the `[locale]` param and calls `notFound()`.
*
*
* https://next-intl.dev/docs/environments/error-files#catching-non-localized-requests
*/
export default function GlobalNotFound() {

View File

@ -1,5 +1,5 @@
import { siteConfig } from "@/config/site";
import type { MetadataRoute } from "next";
import { siteConfig } from '@/config/site';
import type { MetadataRoute } from 'next';
/**
* 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 {
return {
rules: {
userAgent: "*",
allow: "/",
userAgent: '*',
allow: '/',
},
sitemap: `${siteConfig.url}/sitemap.xml`,
};