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 "$ProjectName-web"
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: number" } 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 ────────────────────────────────
$firstEntity = ($Entities | Where-Object { $_ -ne "User" } | Select-Object -First 1).ToLower() + "s"

$privateRouteType = if ($isTS) { "({ children }: { children: 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>
  );
}
"@


# ─── LOGIN PAGE ───────────────────────────────────────────
Write-File "pages\LoginPage.$ext" @"
import { LoginForm } from '../components/auth/LoginForm';
 
export function LoginPage() {
  return (
    <div style={{
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      height: '100vh',
      backgroundColor: '#0f172a'
    }}>
      <div style={{
        width: '100%',
        maxWidth: '400px',
        padding: '2rem',
        borderRadius: '0.5rem',
        backgroundColor: '#1e293b',
        border: '1px solid #334155',
        boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)'
      }}>
        <h2 style={{
          textAlign: 'center',
          marginBottom: '1.5rem',
          color: '#f8fafc',
          fontWeight: 600
        }}>$($L.uiLogin)</h2>
        <LoginForm />
      </div>
    </div>
  );
}
"@



# Not: main.$ext burada yazilmiyor — Write-Frontend-EntityPages.ps1, React Query
# QueryClientProvider'i iceren nihai main.$ext dosyasini dispatcher sirasinda
# (Config -> Components -> Pages -> EntityPages) her zaman ustune yazar.

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>
"@


# ─── GLOBAL CSS ───────────────────────────────────────────
Write-File "global.css" @'
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #0f172a;
  color: #f8fafc;
}
'@


# ─── TSCONFIG MODERNIZE ───────────────────────────────────
if ($isTS) {
    Write-File "tsconfig.json" @'
{
  "compilerOptions": {
    "target": "ES2022",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ES2022"],
    "module": "ESNext",
    "skipLibCheck": true,
 
    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
 
    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src", "main.tsx", "vite-env.d.ts", "types/**/*.ts", "services/**/*.ts", "hooks/**/*.ts", "context/**/*.ts"]
}
'@


    Write-File "tsconfig.node.json" @'
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}
'@

}