modules/frontend/Write-Frontend-React-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 $isTS = ($FrontendMode -eq "ReactTS") $extTs = if ($isTS) { "ts" } else { "js" } $ext = if ($isTS) { "tsx" } else { "jsx" } $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 } # Vite default dosyalarini temizle @("App.css","App.tsx","App.jsx","App.ts","App.js","index.css", "assets\react.svg","main.tsx","main.jsx") | ForEach-Object { Remove-Item (Join-Path $base $_) -ErrorAction SilentlyContinue } Remove-Item (Join-Path $base "public") -Recurse -ErrorAction SilentlyContinue # ─── TYPES ──────────────────────────────────────────────── $typeInterfaces = "" if ($isTS) { foreach ($e in $Entities) { if ($e -eq "User") { continue } $extraField = if ($e -eq "Product") { " price: number;" } else { "" } $typeInterfaces += "export interface $e { id: number; name: string;$extraField createdAt: string; }`n" } Write-File "types\index.$extTs" (Proj @" export interface LoginRequest { username: string; password: string; } export interface LoginResponse { token: string; username: string; } $typeInterfaces export interface ApiError { message?: string; errors?: string[]; } "@) } else { Write-File "types\index.$extTs" @" // Type definitions (JSDoc used in individual files) "@ } # ─── SERVICES ───────────────────────────────────────────── if ($isTS) { Write-File "services\authService.$extTs" (Proj @" import type { LoginRequest, LoginResponse } from '../types'; const API_BASE = import.meta.env.VITE_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; }, logout: () => { localStorage.removeItem('token'); localStorage.removeItem('username'); }, getToken: () => localStorage.getItem('token'), getUsername: () => localStorage.getItem('username'), saveSession: (d: LoginResponse) => { localStorage.setItem('token', d.token); localStorage.setItem('username', d.username); }, }; "@) } else { Write-File "services\authService.$extTs" (Proj @" const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:8080/api'; export const authService = { login: async (data) => { 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; }, logout: () => { localStorage.removeItem('token'); localStorage.removeItem('username'); }, getToken: () => localStorage.getItem('token'), getUsername: () => localStorage.getItem('username'), saveSession: (d) => { 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() $addBody = if ($e -eq "Product") { "{ name, price }" } else { "{ name }" } if ($isTS) { $addParams = if ($e -eq "Product") { "name: string, price: string" } else { "name: string" } Write-File "services\$lowE`Service.$extTs" (Proj @" import type { $e } from '../types'; const API_BASE = import.meta.env.VITE_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.'); const json = await res.json(); return json.data ?? json; }, getPaged: async (page = 1, size = 20) => { const res = await fetch(BTICK`${API_BASE}/$pE/paged?page=`${page}&size=`${size}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.data ?? json; }, remove: async (id: number): Promise<void> => { const res = await fetch(BTICK`${API_BASE}/$pE/`${id}BTICK, { method: 'DELETE', headers: authHeaders(), }); if (res.status === 401) throw new Error('UNAUTHORIZED'); if (!res.ok) throw new Error('$e silinemedi.'); }, }; "@) } else { $addParams = if ($e -eq "Product") { "name, price" } else { "name" } Write-File "services\$lowE`Service.$extTs" (Proj @" const API_BASE = import.meta.env.VITE_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 () => { 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.'); const json = await res.json(); return json.data ?? json; }, getPaged: async (page = 1, size = 20) => { const res = await fetch(BTICK`${API_BASE}/$pE/paged?page=`${page}&size=`${size}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) => { 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.data ?? json; }, remove: async (id) => { const res = await fetch(BTICK`${API_BASE}/$pE/`${id}BTICK, { method: 'DELETE', headers: authHeaders(), }); if (res.status === 401) throw new Error('UNAUTHORIZED'); if (!res.ok) throw new Error('$e silinemedi.'); }, }; "@) } } # ─── CONTEXT ────────────────────────────────────────────── if ($isTS) { Write-File "context\AuthContext.$ext" (Proj @" import { createContext, useContext, useState, type ReactNode } from 'react'; import { authService } from '../services/authService'; interface AuthContextType { username: string | null; isAuthenticated: boolean; login: (token: string, uname: string) => void; logout: () => void; } const AuthContext = createContext<AuthContextType | null>(null); export function AuthProvider({ children }: { children: ReactNode }) { const [username, setUsername] = useState<string | null>(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; } "@) } else { Write-File "context\AuthContext.$ext" (Proj @" import { createContext, useContext, useState } from 'react'; import { authService } from '../services/authService'; const AuthContext = createContext(null); export function AuthProvider({ children }) { const [username, setUsername] = useState(authService.getUsername()); const login = (token, uname) => { 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; } "@) } # ─── ENTRY POINTS & DEPLOY ──────────────────────────────── $routes = "" foreach ($e in $Entities) { if ($e -eq "User") { continue } $lowE = $e.ToLower() $routes += "<Route path='/$lowE`s' element={<PrivateRoute><$e`Page /></PrivateRoute>} />`n" } $firstEntity = ($Entities | Where-Object { $_ -ne "User" } | Select-Object -First 1).ToLower() + "s" $privateRouteType = if ($isTS) { "({ children }: { children: React.ReactNode })" } else { "({ children })" } $reactImport = if ($isTS) { "`nimport type { ReactNode } from 'react';" } else { "" } Write-File "App.$ext" @" import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';$reactImport import { AuthProvider, useAuth } from './context/AuthContext'; import { LoginPage } from './pages/LoginPage'; $( ($Entities | Where-Object { $_ -ne "User" } | ForEach-Object { "import { $_`PageV2 } from './pages/$_`PageV2';" }) -join "`n" ) function PrivateRoute$privateRouteType { const { isAuthenticated } = useAuth(); return isAuthenticated ? <>{children}</> : <Navigate to="/login" replace />; } export default function App() { return ( <AuthProvider> <BrowserRouter> <Routes> <Route path="/login" element={<LoginPage />} /> <Route path="/" element={<PrivateRoute><Navigate to="/$firstEntity" replace /></PrivateRoute>} /> $( ($Entities | Where-Object { $_ -ne "User" } | ForEach-Object { $lowE = $_.ToLower(); "<Route path='/$($lowE)s' element={<PrivateRoute><$_`PageV2 /></PrivateRoute>} />" }) -join "`n " ) </Routes> </BrowserRouter> </AuthProvider> ); } "@ $rootCall = if ($isTS) { "createRoot(document.getElementById('root')!).render(<StrictMode><App /></StrictMode>);" } else { "createRoot(document.getElementById('root')).render(<StrictMode><App /></StrictMode>);" } Write-File "main.$ext" @" import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './global.css'; import App from './App'; $rootCall "@ if ($isTS) { Write-File "vite-env.d.ts" @' /// <reference types="vite/client" /> declare module '*.module.css' { const classes: Record<string, string>; export default classes; } '@ } Write-File ".env" "VITE_API_URL=http://localhost:8080/api`n" Write-File ".env.docker" "VITE_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 nginx:alpine AS runner COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/dist /usr/share/nginx/html EXPOSE 80 '@ Write-File ".dockerignore" @' node_modules dist .git .env *.md '@ Write-File "nginx.conf" @' server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } gzip on; gzip_types text/css application/javascript application/json; } '@ Write-File "index.html" @" <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>$ProjectName</title> </head> <body> <div id="root"></div> <script type="module" src="/main.$ext"></script> </body> </html> "@ |