feat(web): scaffold Next.js 14 app with Tailwind and next-intl
- App Router under src/app/[locale] with locale 'it' as default - next-intl middleware and getRequestConfig with setRequestLocale - Tailwind CSS with shared font variable and content from packages/ui - ESLint via @ketopath/eslint-config/nextjs, tsconfig via @ketopath/tsconfig/nextjs - Inter font, base layout, home page with translated copy and disclaimer Tweaks to shared eslint-config: - Disable consistent-type-imports for .cjs files (next parser is not @typescript-eslint) - Configure import resolver to discover tsconfig in apps/* and packages/* - Drop *.config.* ignore patterns (overrides handle them with env.node) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
87ca5af765
commit
94eeb1d817
18 files changed
+249
-11
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
# Next.js
|
||||||
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Clerk (verrà configurato in uno step dedicato)
|
||||||
|
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
|
||||||
|
# CLERK_SECRET_KEY=
|
||||||
|
|
||||||
|
# Sentry (verrà configurato in uno step dedicato)
|
||||||
|
# NEXT_PUBLIC_SENTRY_DSN=
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/** @type {import('eslint').Linter.Config} */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['@ketopath/eslint-config/nextjs.cjs'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.next/
|
||||||
|
out/
|
||||||
|
next-env.d.ts.bak
|
||||||
|
.vercel/
|
||||||
|
*.tsbuildinfo
|
||||||
Whitespace-only changes.
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Home": {
|
||||||
|
"title": "KetoPath",
|
||||||
|
"tagline": "Il tuo percorso personalizzato verso uno stile di vita chetogenico sostenibile.",
|
||||||
|
"ctaPrimary": "Inizia gratis",
|
||||||
|
"ctaSecondary": "Scopri come funziona",
|
||||||
|
"disclaimer": "Le informazioni fornite da questa app non sostituiscono il parere di un medico."
|
||||||
|
},
|
||||||
|
"Metadata": {
|
||||||
|
"title": "KetoPath — Chetogenica e digiuno intermittente personalizzati",
|
||||||
|
"description": "Web app italiana per la gestione strutturata di chetogenica e digiuno intermittente, con piani adattivi, tracking e lista della spesa automatica."
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
|
||||||
|
const withNextIntl = createNextIntlPlugin('./src/i18n.ts');
|
||||||
|
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
transpilePackages: ['@ketopath/shared', '@ketopath/ui'],
|
||||||
|
experimental: {
|
||||||
|
typedRoutes: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withNextIntl(nextConfig);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@ketopath/web",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --port 3000",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start --port 3000",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "next lint --max-warnings=0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ketopath/shared": "workspace:*",
|
||||||
|
"@ketopath/ui": "workspace:*",
|
||||||
|
"next": "^14.2.5",
|
||||||
|
"next-intl": "^3.17.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ketopath/eslint-config": "workspace:*",
|
||||||
|
"@ketopath/tsconfig": "workspace:*",
|
||||||
|
"@types/node": "^20.12.7",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"eslint-config-next": "^14.2.5",
|
||||||
|
"postcss": "^8.4.39",
|
||||||
|
"tailwindcss": "^3.4.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { Inter } from 'next/font/google';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import { getMessages, getTranslations, setRequestLocale } from 'next-intl/server';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { locales, type Locale } from '@/i18n';
|
||||||
|
|
||||||
|
import '@/styles/globals.css';
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-sans',
|
||||||
|
display: 'swap',
|
||||||
|
});
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return locales.map((locale) => ({ locale }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params: { locale },
|
||||||
|
}: {
|
||||||
|
params: { locale: string };
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const t = await getTranslations({ locale, namespace: 'Metadata' });
|
||||||
|
return {
|
||||||
|
title: t('title'),
|
||||||
|
description: t('description'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function LocaleLayout({
|
||||||
|
children,
|
||||||
|
params: { locale },
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
params: { locale: string };
|
||||||
|
}) {
|
||||||
|
if (!locales.includes(locale as Locale)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequestLocale(locale);
|
||||||
|
|
||||||
|
const messages = await getMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale} className={inter.variable}>
|
||||||
|
<body className="min-h-screen bg-white font-sans text-slate-900 antialiased">
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
{children}
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { setRequestLocale } from 'next-intl/server';
|
||||||
|
|
||||||
|
export default function HomePage({ params: { locale } }: { params: { locale: string } }) {
|
||||||
|
setRequestLocale(locale);
|
||||||
|
const t = useTranslations('Home');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="mx-auto flex min-h-screen max-w-3xl flex-col items-start justify-center gap-6 px-6 py-16">
|
||||||
|
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{t('title')}</h1>
|
||||||
|
<p className="text-lg text-slate-600">{t('tagline')}</p>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-md bg-emerald-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-emerald-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
|
||||||
|
>
|
||||||
|
{t('ctaPrimary')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-400"
|
||||||
|
>
|
||||||
|
{t('ctaSecondary')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="mt-8 text-xs text-slate-500">{t('disclaimer')}</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { getRequestConfig } from 'next-intl/server';
|
||||||
|
|
||||||
|
export const locales = ['it'] as const;
|
||||||
|
export const defaultLocale = 'it' as const;
|
||||||
|
|
||||||
|
export type Locale = (typeof locales)[number];
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ locale }) => {
|
||||||
|
if (!locales.includes(locale as Locale)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale: locale as Locale,
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import createMiddleware from 'next-intl/middleware';
|
||||||
|
|
||||||
|
import { defaultLocale, locales } from './i18n';
|
||||||
|
|
||||||
|
export default createMiddleware({
|
||||||
|
locales,
|
||||||
|
defaultLocale,
|
||||||
|
localePrefix: 'as-needed',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
html {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Config } from 'tailwindcss';
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: ['./src/**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['var(--font-sans)', 'system-ui', 'sans-serif'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "@ketopath/tsconfig/nextjs.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [{ "name": "next" }]
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules", ".next"]
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ module.exports = {
|
|||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
typescript: {
|
typescript: {
|
||||||
alwaysTryTypes: true,
|
alwaysTryTypes: true,
|
||||||
|
project: ['tsconfig.json', 'apps/*/tsconfig.json', 'packages/*/tsconfig.json'],
|
||||||
},
|
},
|
||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
@@ -48,18 +49,9 @@ module.exports = {
|
|||||||
env: { node: true },
|
env: { node: true },
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ignorePatterns: [
|
ignorePatterns: ['node_modules/', 'dist/', 'build/', '.next/', 'out/', 'coverage/'],
|
||||||
'node_modules/',
|
|
||||||
'dist/',
|
|
||||||
'build/',
|
|
||||||
'.next/',
|
|
||||||
'out/',
|
|
||||||
'coverage/',
|
|
||||||
'*.config.js',
|
|
||||||
'*.config.cjs',
|
|
||||||
'*.config.mjs',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
Generated
BIN
Binary file not shown.
Reference in new issue
Block a user