refactor: format src/app folder
This commit is contained in:
parent
02bdb93bbd
commit
92ec1b14c5
@ -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"
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
},
|
||||
];
|
||||
];
|
||||
|
@ -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`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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 />
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BlogGridSkeleton } from "@/components/blog/blog-grid";
|
||||
import { BlogGridSkeleton } from '@/components/blog/blog-grid';
|
||||
|
||||
export default function Loading() {
|
||||
return <BlogGridSkeleton />;
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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}`,
|
||||
});
|
||||
|
||||
|
@ -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}`,
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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" />;
|
||||
|
@ -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>
|
||||
|
@ -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}`,
|
||||
});
|
||||
|
||||
|
@ -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`,
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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" />;
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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`,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user