mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-05-03 10:14:54 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
|
||||
export default async function CenteredLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { SignIn } from '@clerk/nextjs';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { getI18nPath } from '@/utils/Helpers';
|
||||
|
||||
type ISignInPageProps = {
|
||||
params: Promise<{ locale: string }>;
|
||||
};
|
||||
|
||||
export async function generateMetadata(props: ISignInPageProps): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'SignIn',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
description: t('meta_description'),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function SignInPage(props: ISignInPageProps) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<SignIn path={getI18nPath('/sign-in', locale)} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { SignUp } from '@clerk/nextjs';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { getI18nPath } from '@/utils/Helpers';
|
||||
|
||||
type ISignUpPageProps = {
|
||||
params: Promise<{ locale: string }>;
|
||||
};
|
||||
|
||||
export async function generateMetadata(props: ISignUpPageProps): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'SignUp',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
description: t('meta_description'),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function SignUpPage(props: ISignUpPageProps) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<SignUp path={getI18nPath('/sign-up', locale)} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { SignOutButton } from '@clerk/nextjs';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import Link from 'next/link';
|
||||
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
|
||||
import { BaseTemplate } from '@/templates/BaseTemplate';
|
||||
|
||||
export default async function DashboardLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'DashboardLayout',
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseTemplate
|
||||
leftNav={(
|
||||
<>
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('dashboard_link')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/user-profile/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('user_profile_link')}
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
rightNav={(
|
||||
<>
|
||||
<li>
|
||||
<SignOutButton>
|
||||
<button className="border-none text-gray-700 hover:text-gray-900" type="button">
|
||||
{t('sign_out')}
|
||||
</button>
|
||||
</SignOutButton>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<LocaleSwitcher />
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</BaseTemplate>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { Hello } from '@/components/Hello';
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'Dashboard',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
};
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="py-5 [&_p]:my-6">
|
||||
<Hello />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { UserProfile } from '@clerk/nextjs';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { getI18nPath } from '@/utils/Helpers';
|
||||
|
||||
type IUserProfilePageProps = {
|
||||
params: Promise<{ locale: string }>;
|
||||
};
|
||||
|
||||
export async function generateMetadata(props: IUserProfilePageProps): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'UserProfile',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function UserProfilePage(props: IUserProfilePageProps) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<div className="my-6 -ml-16">
|
||||
<UserProfile
|
||||
path={getI18nPath('/dashboard/user-profile', locale)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ClerkProvider } from '@clerk/nextjs';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import { routing } from '@/libs/I18nRouting';
|
||||
import { ClerkLocalizations } from '@/utils/AppConfig';
|
||||
|
||||
export default async function AuthLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
const clerkLocale = ClerkLocalizations.supportedLocales[locale] ?? ClerkLocalizations.defaultLocale;
|
||||
let signInUrl = '/sign-in';
|
||||
let signUpUrl = '/sign-up';
|
||||
let dashboardUrl = '/dashboard';
|
||||
let afterSignOutUrl = '/';
|
||||
|
||||
if (locale !== routing.defaultLocale) {
|
||||
signInUrl = `/${locale}${signInUrl}`;
|
||||
signUpUrl = `/${locale}${signUpUrl}`;
|
||||
dashboardUrl = `/${locale}${dashboardUrl}`;
|
||||
afterSignOutUrl = `/${locale}${afterSignOutUrl}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
appearance={{
|
||||
cssLayerName: 'clerk', // Ensure Clerk is compatible with Tailwind CSS v4
|
||||
}}
|
||||
localization={clerkLocale}
|
||||
signInUrl={signInUrl}
|
||||
signUpUrl={signUpUrl}
|
||||
signInFallbackRedirectUrl={dashboardUrl}
|
||||
signUpFallbackRedirectUrl={dashboardUrl}
|
||||
afterSignOutUrl={afterSignOutUrl}
|
||||
>
|
||||
{props.children}
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
|
||||
type IAboutProps = {
|
||||
params: Promise<{ locale: string }>;
|
||||
};
|
||||
|
||||
export async function generateMetadata(props: IAboutProps): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'About',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
description: t('meta_description'),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function About(props: IAboutProps) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'About',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{t('about_paragraph')}</p>
|
||||
|
||||
<div className="mt-2 text-center text-sm">
|
||||
{`${t('translation_powered_by')} `}
|
||||
<a
|
||||
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://l.crowdin.com/next-js"
|
||||
>
|
||||
Crowdin
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href="https://l.crowdin.com/next-js">
|
||||
<Image
|
||||
className="mx-auto mt-2"
|
||||
src="/assets/images/crowdin-dark.png"
|
||||
alt="Crowdin Translation Management System"
|
||||
width={128}
|
||||
height={26}
|
||||
/>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
import { CounterForm } from '@/components/CounterForm';
|
||||
import { CurrentCount } from '@/components/CurrentCount';
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'Counter',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
description: t('meta_description'),
|
||||
};
|
||||
}
|
||||
|
||||
export default function Counter() {
|
||||
const t = useTranslations('Counter');
|
||||
|
||||
return (
|
||||
<>
|
||||
<CounterForm />
|
||||
|
||||
<div className="mt-3">
|
||||
<CurrentCount />
|
||||
</div>
|
||||
|
||||
<div className="mt-5 text-center text-sm">
|
||||
{`${t('security_powered_by')} `}
|
||||
<a
|
||||
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://launch.arcjet.com/Q6eLbRE"
|
||||
>
|
||||
Arcjet
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://launch.arcjet.com/Q6eLbRE"
|
||||
>
|
||||
<Image
|
||||
className="mx-auto mt-2"
|
||||
src="/assets/images/arcjet-light.svg"
|
||||
alt="Arcjet"
|
||||
width={128}
|
||||
height={38}
|
||||
/>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import Link from 'next/link';
|
||||
import { DemoBanner } from '@/components/DemoBanner';
|
||||
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
|
||||
import { BaseTemplate } from '@/templates/BaseTemplate';
|
||||
|
||||
export default async function Layout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'RootLayout',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DemoBanner />
|
||||
<BaseTemplate
|
||||
leftNav={(
|
||||
<>
|
||||
<li>
|
||||
<Link
|
||||
href="/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('home_link')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/about/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('about_link')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/counter/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('counter_link')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/portfolio/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('portfolio_link')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
href="https://github.com/ixartz/Next-js-Boilerplate"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
rightNav={(
|
||||
<>
|
||||
<li>
|
||||
<Link
|
||||
href="/sign-in/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('sign_in_link')}
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/sign-up/"
|
||||
className="border-none text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
{t('sign_up_link')}
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<LocaleSwitcher />
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<div className="py-5 text-xl [&_p]:my-6">{props.children}</div>
|
||||
</BaseTemplate>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { Sponsors } from '@/components/Sponsors';
|
||||
|
||||
type IIndexProps = {
|
||||
params: Promise<{ locale: string }>;
|
||||
};
|
||||
|
||||
export async function generateMetadata(props: IIndexProps): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'Index',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
description: t('meta_description'),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Index(props: IIndexProps) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'Index',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
{`Follow `}
|
||||
<a
|
||||
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://twitter.com/ixartz"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
@Ixartz on Twitter
|
||||
</a>
|
||||
{` for updates and more information about the boilerplate.`}
|
||||
</p>
|
||||
<h2 className="mt-5 text-2xl font-bold">
|
||||
Boilerplate Code for Your Next.js Project with Tailwind CSS
|
||||
</h2>
|
||||
<p className="text-base">
|
||||
Next.js Boilerplate is a developer-friendly starter code for Next.js projects, built with Tailwind CSS and TypeScript.
|
||||
{' '}
|
||||
<span role="img" aria-label="zap">
|
||||
⚡️
|
||||
</span>
|
||||
{' '}
|
||||
Designed with developer experience in mind, it includes:
|
||||
</p>
|
||||
<ul className="mt-3 text-base">
|
||||
<li>🚀 Next.js with App Router support</li>
|
||||
<li>🔥 TypeScript for type checking</li>
|
||||
<li>💎 Tailwind CSS integration</li>
|
||||
<li>
|
||||
🔒 Authentication with
|
||||
{' '}
|
||||
<a
|
||||
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://clerk.com?utm_source=github&utm_medium=sponsorship&utm_campaign=nextjs-boilerplate"
|
||||
>
|
||||
Clerk
|
||||
</a>
|
||||
{' '}
|
||||
(includes passwordless, social, and multi-factor auth)
|
||||
</li>
|
||||
<li>📦 ORM with DrizzleORM (PostgreSQL, SQLite, MySQL support)</li>
|
||||
<li>
|
||||
💽 Dev database with PGlite and production with Neon (PostgreSQL)
|
||||
</li>
|
||||
<li>
|
||||
🌐 Multi-language support (i18n) with next-intl and
|
||||
{' '}
|
||||
<a
|
||||
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://l.crowdin.com/next-js"
|
||||
>
|
||||
Crowdin
|
||||
</a>
|
||||
</li>
|
||||
<li>🔴 Form handling (React Hook Form) and validation (Zod)</li>
|
||||
<li>📏 Linting and formatting (ESLint, Prettier)</li>
|
||||
<li>🦊 Git hooks and commit linting (Husky, Commitlint)</li>
|
||||
<li>🦺 Testing suite (Vitest, React Testing Library, Playwright)</li>
|
||||
<li>🎉 Storybook for UI development</li>
|
||||
<li>
|
||||
🐰 AI-powered code reviews with
|
||||
{' '}
|
||||
<a
|
||||
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://www.coderabbit.ai?utm_source=next_js_starter&utm_medium=github&utm_campaign=next_js_starter_oss_2025"
|
||||
>
|
||||
CodeRabbit
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
🚨 Error monitoring (
|
||||
<a
|
||||
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://sentry.io/for/nextjs/?utm_source=github&utm_medium=paid-community&utm_campaign=general-fy25q1-nextjs&utm_content=github-banner-nextjsboilerplate-logo"
|
||||
>
|
||||
Sentry
|
||||
</a>
|
||||
) and logging (LogTape, an alternative to Pino.js)
|
||||
</li>
|
||||
<li>🖥️ Monitoring as Code (Checkly)</li>
|
||||
<li>
|
||||
🔐 Security and bot protection (
|
||||
<a
|
||||
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://launch.arcjet.com/Q6eLbRE"
|
||||
>
|
||||
Arcjet
|
||||
</a>
|
||||
)
|
||||
</li>
|
||||
<li>🤖 SEO optimization (metadata, JSON-LD, Open Graph tags)</li>
|
||||
<li>⚙️ Development tools (VSCode config, bundler analyzer, changelog generation)</li>
|
||||
</ul>
|
||||
<p className="text-base">
|
||||
Our sponsors' exceptional support has made this project possible.
|
||||
Their services integrate seamlessly with the boilerplate, and we
|
||||
recommend trying them out.
|
||||
</p>
|
||||
<h2 className="mt-5 text-2xl font-bold">{t('sponsors_title')}</h2>
|
||||
<Sponsors />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
import { routing } from '@/libs/I18nRouting';
|
||||
|
||||
type IPortfolioDetailProps = {
|
||||
params: Promise<{ slug: string; locale: string }>;
|
||||
};
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales
|
||||
.map(locale =>
|
||||
Array.from(Array.from({ length: 6 }).keys()).map(elt => ({
|
||||
slug: `${elt}`,
|
||||
locale,
|
||||
})),
|
||||
)
|
||||
.flat(1);
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: IPortfolioDetailProps): Promise<Metadata> {
|
||||
const { locale, slug } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'PortfolioSlug',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title', { slug }),
|
||||
description: t('meta_description', { slug }),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function PortfolioDetail(props: IPortfolioDetailProps) {
|
||||
const { locale, slug } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'PortfolioSlug',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="capitalize">{t('header', { slug })}</h1>
|
||||
<p>{t('content')}</p>
|
||||
|
||||
<div className="mt-5 text-center text-sm">
|
||||
{`${t('code_review_powered_by')} `}
|
||||
<a
|
||||
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://www.coderabbit.ai?utm_source=next_js_starter&utm_medium=github&utm_campaign=next_js_starter_oss_2025"
|
||||
>
|
||||
CodeRabbit
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://www.coderabbit.ai?utm_source=next_js_starter&utm_medium=github&utm_campaign=next_js_starter_oss_2025"
|
||||
>
|
||||
<Image
|
||||
className="mx-auto mt-2"
|
||||
src="/assets/images/coderabbit-logo-light.svg"
|
||||
alt="CodeRabbit"
|
||||
width={128}
|
||||
height={22}
|
||||
/>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const dynamicParams = false;
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
type IPortfolioProps = {
|
||||
params: Promise<{ locale: string }>;
|
||||
};
|
||||
|
||||
export async function generateMetadata(props: IPortfolioProps): Promise<Metadata> {
|
||||
const { locale } = await props.params;
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'Portfolio',
|
||||
});
|
||||
|
||||
return {
|
||||
title: t('meta_title'),
|
||||
description: t('meta_description'),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Portfolio(props: IPortfolioProps) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations({
|
||||
locale,
|
||||
namespace: 'Portfolio',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{t('presentation')}</p>
|
||||
|
||||
<div className="grid grid-cols-1 justify-items-start gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{Array.from(Array.from({ length: 6 }).keys()).map(elt => (
|
||||
<Link
|
||||
className="hover:text-blue-700"
|
||||
key={elt}
|
||||
href={`/portfolio/${elt}`}
|
||||
>
|
||||
{t('portfolio_name', { name: elt })}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 text-center text-sm">
|
||||
{`${t('error_reporting_powered_by')} `}
|
||||
<a
|
||||
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://sentry.io/for/nextjs/?utm_source=github&utm_medium=paid-community&utm_campaign=general-fy25q1-nextjs&utm_content=github-banner-nextjsboilerplate-logo"
|
||||
>
|
||||
Sentry
|
||||
</a>
|
||||
{` - ${t('coverage_powered_by')} `}
|
||||
<a
|
||||
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
|
||||
href="https://about.codecov.io/codecov-free-trial/?utm_source=github&utm_medium=paid-community&utm_campaign=general-fy25q1-nextjs&utm_content=github-banner-nextjsboilerplate-logo"
|
||||
>
|
||||
Codecov
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://sentry.io/for/nextjs/?utm_source=github&utm_medium=paid-community&utm_campaign=general-fy25q1-nextjs&utm_content=github-banner-nextjsboilerplate-logo"
|
||||
>
|
||||
<Image
|
||||
className="mx-auto mt-2"
|
||||
src="/assets/images/sentry-dark.png"
|
||||
alt="Sentry"
|
||||
width={128}
|
||||
height={38}
|
||||
/>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { headers } from 'next/headers';
|
||||
import { NextResponse } from 'next/server';
|
||||
import * as z from 'zod';
|
||||
import { db } from '@/libs/DB';
|
||||
import { logger } from '@/libs/Logger';
|
||||
import { counterSchema } from '@/models/Schema';
|
||||
import { CounterValidation } from '@/validations/CounterValidation';
|
||||
|
||||
export const PUT = async (request: Request) => {
|
||||
const json = await request.json();
|
||||
const parse = CounterValidation.safeParse(json);
|
||||
|
||||
if (!parse.success) {
|
||||
return NextResponse.json(z.treeifyError(parse.error), { status: 422 });
|
||||
}
|
||||
|
||||
// `x-e2e-random-id` is used for end-to-end testing to make isolated requests
|
||||
// The default value is 0 when there is no `x-e2e-random-id` header
|
||||
const id = Number((await headers()).get('x-e2e-random-id')) || 0;
|
||||
|
||||
const count = await db
|
||||
.insert(counterSchema)
|
||||
.values({ id, count: parse.data.increment })
|
||||
.onConflictDoUpdate({
|
||||
target: counterSchema.id,
|
||||
set: { count: sql`${counterSchema.count} + ${parse.data.increment}` },
|
||||
})
|
||||
.returning();
|
||||
|
||||
logger.info('Counter has been incremented');
|
||||
|
||||
return NextResponse.json({
|
||||
count: count[0]?.count,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { hasLocale, NextIntlClientProvider } from 'next-intl';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { PostHogProvider } from '@/components/analytics/PostHogProvider';
|
||||
import { DemoBadge } from '@/components/DemoBadge';
|
||||
import { routing } from '@/libs/I18nRouting';
|
||||
import '@/styles/global.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
icons: [
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
url: '/apple-touch-icon.png',
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
sizes: '32x32',
|
||||
url: '/favicon-32x32.png',
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
sizes: '16x16',
|
||||
url: '/favicon-16x16.png',
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
url: '/favicon.ico',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map(locale => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function RootLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await props.params;
|
||||
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
setRequestLocale(locale);
|
||||
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<NextIntlClientProvider>
|
||||
<PostHogProvider>
|
||||
{props.children}
|
||||
</PostHogProvider>
|
||||
|
||||
<DemoBadge />
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import NextError from 'next/error';
|
||||
import { useEffect } from 'react';
|
||||
import { routing } from '@/libs/I18nRouting';
|
||||
|
||||
export default function GlobalError(props: {
|
||||
error: Error & { digest?: string };
|
||||
}) {
|
||||
useEffect(() => {
|
||||
Sentry.captureException(props.error);
|
||||
}, [props.error]);
|
||||
|
||||
return (
|
||||
<html lang={routing.defaultLocale}>
|
||||
<body>
|
||||
{/* `NextError` is the default Next.js error page component. Its type
|
||||
definition requires a `statusCode` prop. However, since the App Router
|
||||
does not expose status codes for errors, we simply pass 0 to render a
|
||||
generic error message. */}
|
||||
<NextError statusCode={0} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { getBaseUrl } from '@/utils/Helpers';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: '/dashboard',
|
||||
},
|
||||
sitemap: `${getBaseUrl()}/sitemap.xml`,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { getBaseUrl } from '@/utils/Helpers';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: `${getBaseUrl()}/`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'daily',
|
||||
priority: 0.7,
|
||||
},
|
||||
// Add more URLs here
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user