diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..c26be16 --- /dev/null +++ b/apps/web/.env.example @@ -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= diff --git a/apps/web/.eslintrc.cjs b/apps/web/.eslintrc.cjs new file mode 100644 index 0000000..959b74c --- /dev/null +++ b/apps/web/.eslintrc.cjs @@ -0,0 +1,5 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + extends: ['@ketopath/eslint-config/nextjs.cjs'], +}; diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..2472ac6 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,5 @@ +.next/ +out/ +next-env.d.ts.bak +.vercel/ +*.tsbuildinfo diff --git a/apps/web/.gitkeep b/apps/web/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/apps/web/messages/it.json b/apps/web/messages/it.json new file mode 100644 index 0000000..7705cb3 --- /dev/null +++ b/apps/web/messages/it.json @@ -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." + } +} diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 0000000..40c3d68 --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs new file mode 100644 index 0000000..779ade9 --- /dev/null +++ b/apps/web/next.config.mjs @@ -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); diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..d336931 --- /dev/null +++ b/apps/web/package.json @@ -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" + } +} diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs new file mode 100644 index 0000000..2ef30fc --- /dev/null +++ b/apps/web/postcss.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; + +export default config; diff --git a/apps/web/src/app/[locale]/layout.tsx b/apps/web/src/app/[locale]/layout.tsx new file mode 100644 index 0000000..b053649 --- /dev/null +++ b/apps/web/src/app/[locale]/layout.tsx @@ -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 { + 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 ( + + + + {children} + + + + ); +} diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx new file mode 100644 index 0000000..969901d --- /dev/null +++ b/apps/web/src/app/[locale]/page.tsx @@ -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 ( +
+

{t('title')}

+

{t('tagline')}

+
+ + +
+

{t('disclaimer')}

+
+ ); +} diff --git a/apps/web/src/i18n.ts b/apps/web/src/i18n.ts new file mode 100644 index 0000000..ca7d5f9 --- /dev/null +++ b/apps/web/src/i18n.ts @@ -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, + }; +}); diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts new file mode 100644 index 0000000..5d51b4c --- /dev/null +++ b/apps/web/src/middleware.ts @@ -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|.*\\..*).*)'], +}; diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css new file mode 100644 index 0000000..2857b71 --- /dev/null +++ b/apps/web/src/styles/globals.css @@ -0,0 +1,9 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html { + -webkit-text-size-adjust: 100%; + } +} diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts new file mode 100644 index 0000000..68d0c5a --- /dev/null +++ b/apps/web/tailwind.config.ts @@ -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; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..7bef14d --- /dev/null +++ b/apps/web/tsconfig.json @@ -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"] +} diff --git a/packages/eslint-config/index.cjs b/packages/eslint-config/index.cjs index e6c0688..1c85694 100644 --- a/packages/eslint-config/index.cjs +++ b/packages/eslint-config/index.cjs @@ -18,6 +18,7 @@ module.exports = { 'import/resolver': { typescript: { alwaysTryTypes: true, + project: ['tsconfig.json', 'apps/*/tsconfig.json', 'packages/*/tsconfig.json'], }, node: true, }, @@ -48,18 +49,9 @@ module.exports = { env: { node: true }, rules: { '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/consistent-type-imports': 'off', }, }, ], - ignorePatterns: [ - 'node_modules/', - 'dist/', - 'build/', - '.next/', - 'out/', - 'coverage/', - '*.config.js', - '*.config.cjs', - '*.config.mjs', - ], + ignorePatterns: ['node_modules/', 'dist/', 'build/', '.next/', 'out/', 'coverage/'], }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 885d597..3c085e3 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ