feat(frontend): scaffold Next.js app tooling and static assets
Set up the App Router toolchain, Docker build, Apple emoji vendor script, and public branding assets.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
.git
|
||||
.next
|
||||
node_modules
|
||||
*.log
|
||||
@@ -0,0 +1,3 @@
|
||||
# Local `npm run dev` (API on :8080 without Traefik). Leave empty when behind reverse proxy.
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8080
|
||||
NEXT_PUBLIC_WS_URL=ws://localhost:8080/api/ws
|
||||
@@ -0,0 +1,17 @@
|
||||
# deps
|
||||
node_modules/
|
||||
|
||||
# next
|
||||
.next/
|
||||
out/
|
||||
|
||||
# env
|
||||
.env*.local
|
||||
|
||||
# misc
|
||||
*.log
|
||||
.DS_Store
|
||||
npm-debug.log*
|
||||
|
||||
# vendored at install/build from emoji-datasource-apple (~27MB)
|
||||
/public/emoji/
|
||||
@@ -0,0 +1,45 @@
|
||||
FROM node:22-alpine AS deps
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
# Ignore lifecycle scripts here — vendor script is only present after full COPY.
|
||||
RUN npm ci --ignore-scripts
|
||||
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Empty = same-origin (Traefik reverse proxy). For direct API access set build args.
|
||||
ARG NEXT_PUBLIC_API_URL=
|
||||
ARG NEXT_PUBLIC_WS_URL=
|
||||
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
# Vendor Apple emoji PNGs into public/ (external CDNs are often blocked).
|
||||
RUN node scripts/vendor-apple-emoji.mjs && npm run build
|
||||
|
||||
FROM node:22-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN addgroup -S app && adduser -S app -G app
|
||||
|
||||
COPY --from=builder --chown=app:app /app/public ./public
|
||||
COPY --from=builder --chown=app:app /app/.next ./.next
|
||||
COPY --from=builder --chown=app:app /app/package.json ./package.json
|
||||
COPY --from=builder --chown=app:app /app/next.config.mjs ./next.config.mjs
|
||||
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
|
||||
|
||||
# Public assets are required at runtime for /logo.png and for the
|
||||
# /_next/image optimizer's internal fetch of local paths.
|
||||
# next.config.mjs is required at runtime so images.contentDispositionType
|
||||
# and other config overrides are applied (not only baked into manifests).
|
||||
USER app
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,25 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
images: {
|
||||
// Default "attachment" can prevent some clients from rendering /_next/image in <img>.
|
||||
contentDispositionType: "inline",
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
// Student self-service password recovery is disabled.
|
||||
{ source: "/recovery", destination: "/login", permanent: false },
|
||||
];
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/c/:path*",
|
||||
headers: [{ key: "X-Robots-Tag", value: "noindex, nofollow, noarchive" }],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
Generated
+2789
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "pargar-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3000",
|
||||
"prebuild": "node scripts/vendor-apple-emoji.mjs",
|
||||
"vendor:emoji": "node scripts/vendor-apple-emoji.mjs",
|
||||
"build": "next build",
|
||||
"start": "next start -p 3000",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-avatar": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||
"@radix-ui/react-label": "^2.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.2",
|
||||
"@tanstack/react-query": "^5.62.0",
|
||||
"boring-avatars": "^2.0.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"emoji-datasource-apple": "^15.1.2",
|
||||
"framer-motion": "^12.0.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "^15.1.0",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"sonner": "^1.7.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"@types/node": "^22.10.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copy Apple Color Emoji 64px PNGs into public/ so chat never depends on
|
||||
* external CDNs (often blocked). Source: emoji-datasource-apple.
|
||||
*/
|
||||
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const src = join(root, "node_modules/emoji-datasource-apple/img/apple/64");
|
||||
const dest = join(root, "public/emoji/apple/64");
|
||||
|
||||
if (!existsSync(src)) {
|
||||
console.warn("[vendor-apple-emoji] skip: emoji-datasource-apple not installed");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
mkdirSync(dirname(dest), { recursive: true });
|
||||
rmSync(dest, { recursive: true, force: true });
|
||||
cpSync(src, dest, { recursive: true });
|
||||
console.log("[vendor-apple-emoji] copied → public/emoji/apple/64");
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [{ "name": "next" }],
|
||||
"paths": { "@/*": ["./src/*"] }
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user