feat(web): scaffold Next.js 16 app with Tailwind 4 + Geist
This commit is contained in:
parent
21e8e5b582
commit
161ffec84c
12
apps/web/next.config.ts
Normal file
12
apps/web/next.config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
output: "standalone",
|
||||||
|
transpilePackages: ["@cmbot/db", "@cmbot/shared"],
|
||||||
|
experimental: {
|
||||||
|
typedRoutes: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
36
apps/web/package.json
Normal file
36
apps/web/package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "@cmbot/web",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --hostname 0.0.0.0",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start --hostname 0.0.0.0",
|
||||||
|
"lint": "next lint",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@cmbot/db": "workspace:*",
|
||||||
|
"@cmbot/shared": "workspace:*",
|
||||||
|
"geist": "^1.7.0",
|
||||||
|
"next": "^16.0.0",
|
||||||
|
"pg": "^8.13.0",
|
||||||
|
"pino": "^9.5.0",
|
||||||
|
"pino-pretty": "^11.3.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.0.0",
|
||||||
|
"@types/node": "^22.7.0",
|
||||||
|
"@types/pg": "^8.11.10",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"tailwindcss": "^4.0.0",
|
||||||
|
"typescript": "^5.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
apps/web/postcss.config.mjs
Normal file
5
apps/web/postcss.config.mjs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
13
apps/web/src/app/globals.css
Normal file
13
apps/web/src/app/globals.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-sans: "Geist", system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
29
apps/web/src/app/layout.tsx
Normal file
29
apps/web/src/app/layout.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { Metadata, Viewport } from "next";
|
||||||
|
import { GeistSans } from "geist/font/sans";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "cm WhatsApp Bot",
|
||||||
|
description: "Self-hosted WhatsApp reminder bot",
|
||||||
|
applicationName: "cm WhatsApp Bot",
|
||||||
|
appleWebApp: {
|
||||||
|
capable: true,
|
||||||
|
title: "cm WA Bot",
|
||||||
|
statusBarStyle: "default",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: [
|
||||||
|
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
|
||||||
|
{ media: "(prefers-color-scheme: dark)", color: "#0a0a0a" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className={GeistSans.className}>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
apps/web/src/app/page.tsx
Normal file
10
apps/web/src/app/page.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<main className="p-8">
|
||||||
|
<h1 className="text-2xl font-semibold">cm WhatsApp Bot</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Web app skeleton — wired up. Real dashboard arrives in Task 13.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
apps/web/src/env.ts
Normal file
11
apps/web/src/env.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const envSchema = z.object({
|
||||||
|
DATABASE_URL: z.string().url(),
|
||||||
|
DATA_DIR: z.string().min(1).default("/data"),
|
||||||
|
MEDIA_DIR: z.string().min(1).default("/data/media"),
|
||||||
|
WEB_PORT: z.string().regex(/^\d+$/).transform((s) => Number(s)).default("3000"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Env = z.infer<typeof envSchema>;
|
||||||
|
export const env = envSchema.parse(process.env);
|
||||||
20
apps/web/tsconfig.json
Normal file
20
apps/web/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
|
"plugins": [{ "name": "next" }],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", ".next/types/**/*.ts", "src/**/*.ts", "src/**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
679
pnpm-lock.yaml
generated
679
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user