modules/frontend/Write-Frontend-NextJs-Config.ps1
|
param( [string]$ProjectName, [string]$OrmMode, [string]$OutputPath, [string]$FrontendMode, [string[]]$Entities, [string]$Language = 'tr' ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Get-Locale.ps1") $L = Get-Locale -Language $Language $BT = "``" $base = Join-Path $OutputPath "frontend" function Write-File($rel, $rawContent) { $content = $rawContent -replace 'BTICK', $BT Write-CoreFile (Join-Path $base $rel) $content } function Proj($t) { $t -replace '__PROJ__', $ProjectName } # create-next-app demo dosyalarini temizle @("app\page.tsx","app\layout.tsx","app\globals.css") | ForEach-Object { Remove-Item (Join-Path $base $_) -ErrorAction SilentlyContinue } Remove-Item (Join-Path $base "app\fonts") -Recurse -ErrorAction SilentlyContinue Remove-Item (Join-Path $base "public") -Recurse -ErrorAction SilentlyContinue if (-not $Global:DryRun) { New-Item -ItemType Directory -Path (Join-Path $base "public") -Force | Out-Null # Docker build icin public dizini var olmali; Next.js standalone output bunu gerektirir [System.IO.File]::WriteAllText((Join-Path $base "public\.gitkeep"), "") } # ─── TYPES ──────────────────────────────────────────────── $typeInterfaces = "" foreach ($e in $Entities) { if ($e -eq "User") { continue } $extraType = if ($e -eq "Product") { " price: number;" } else { "" } $typeInterfaces += "export interface $e { id: number; name: string;$extraType createdAt: string; }`n" } Write-File "types\index.ts" (Proj @" export interface LoginRequest { username: string; password: string; } export interface LoginResponse { token: string; username: string; } $typeInterfaces export interface ApiError { message?: string; errors?: string[]; } "@) # ─── SERVICES ───────────────────────────────────────────── Write-File "services\authService.ts" (Proj @" import type { LoginRequest, LoginResponse } from '@/types'; const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8080/api'; export const authService = { login: async (data: LoginRequest): Promise<LoginResponse> => { const res = await fetch(BTICK`${API_BASE}/auth/loginBTICK, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); const json = await res.json(); if (!res.ok) throw new Error(json.errors?.[0] ?? json.message ?? '$($L.uiLoginFailed)'); return json as LoginResponse; }, logout: () => { if (typeof window === 'undefined') return; localStorage.removeItem('token'); localStorage.removeItem('username'); }, getToken: () => typeof window !== 'undefined' ? localStorage.getItem('token') : null, getUsername: () => typeof window !== 'undefined' ? localStorage.getItem('username') : null, saveSession: (d: LoginResponse) => { localStorage.setItem('token', d.token); localStorage.setItem('username', d.username); }, }; "@) foreach ($e in $Entities) { if ($e -eq "User") { continue } $pE = Get-Plural $e $lowE = $e.ToLower() $addParams = if ($e -eq "Product") { "name: string, price: number" } else { "name: string" } $addBody = if ($e -eq "Product") { "{ name, price }" } else { "{ name }" } Write-File "services\$lowE`Service.ts" (Proj @" import type { $e } from '@/types'; const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8080/api'; const authHeaders = () => ({ 'Content-Type': 'application/json', Authorization: BTICK Bearer `${localStorage.getItem('token')}BTICK, }); export const $lowE`Service = { getAll: async (): Promise<$e`[]> => { const res = await fetch(BTICK`${API_BASE}/$pE`BTICK, { headers: authHeaders() }); if (res.status === 401) throw new Error('UNAUTHORIZED'); if (!res.ok) throw new Error('$pE yuklenemedi.'); return res.json(); }, add: async ($addParams): Promise<{ id: number }> => { const res = await fetch(BTICK`${API_BASE}/$pE`BTICK, { method: 'POST', headers: authHeaders(), body: JSON.stringify($addBody), }); if (res.status === 401) throw new Error('UNAUTHORIZED'); const json = await res.json(); if (!res.ok) throw new Error(json.errors?.[0] ?? json.message ?? '$e eklenemedi.'); return json; }, }; "@) } # ─── CONTEXT ────────────────────────────────────────────── Write-File "context\AuthContext.tsx" @' 'use client'; import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'; import { authService } from '@/services/authService'; interface AuthContextType { username: string | null; isAuthenticated: boolean; login: (token: string, username: string) => void; logout: () => void; } const AuthContext = createContext<AuthContextType | null>(null); export function AuthProvider({ children }: { children: ReactNode }) { const [username, setUsername] = useState<string | null>(null); useEffect(() => { setUsername(authService.getUsername()); }, []); const login = (token: string, uname: string) => { authService.saveSession({ token, username: uname }); setUsername(uname); }; const logout = () => { authService.logout(); setUsername(null); }; return ( <AuthContext.Provider value={{ username, isAuthenticated: !!username, login, logout }}> {children} </AuthContext.Provider> ); } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used within AuthProvider'); return ctx; } '@ # ─── LAYOUT & GLOBALS ───────────────────────────────────── Write-File "app\layout.tsx" (Proj @" import { AuthProvider } from '@/context/AuthContext'; import './globals.css'; export const metadata = { title: '__PROJ__', description: 'Clean Architecture Generator Output' }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang='tr'> <body> <AuthProvider>{children}</AuthProvider> </body> </html> ); } "@) Write-File "app\globals.css" @' *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; color: #1a1a2e; -webkit-font-smoothing: antialiased; } a { color: inherit; text-decoration: none; } button, input { font-family: inherit; } '@ Write-File ".env.local" "NEXT_PUBLIC_API_URL=http://localhost:8080/api`n" Write-File ".env.docker" "NEXT_PUBLIC_API_URL=http://api:8080/api`n" Write-File "Dockerfile" @' FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"] '@ Write-File ".dockerignore" @' node_modules .next .git .env.local *.md '@ Write-File "next.config.mjs" @' /** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', }; export default nextConfig; '@ Write-File "tsconfig.json" @' { "compilerOptions": { "target": "ES2017", "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": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } '@ |