modules/frontend/Write-Frontend-React-Components.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")
$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
}

# ─── COMMON COMPONENTS ────────────────────────────────────
if ($isTS) {
    Write-File "components\common\Button.$ext" @"
import { type ButtonHTMLAttributes } from 'react';
import styles from './Button.module.css';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: string;
  loading?: boolean;
}
export function Button({ children, variant = 'primary', loading, disabled, className = '', ...props }: ButtonProps) {
  return (
    <button
      className={BTICK`${styles.btn} `${styles[variant ?? 'primary']} `${className}BTICK}
      disabled={disabled || loading}
      {...props}
    >
      {loading && <span className={styles.spinner} />}
      {children}
    </button>
  );
}
"@

} else {
    Write-File "components\common\Button.$ext" @"
import styles from './Button.module.css';
export function Button({ children, variant = 'primary', loading, disabled, className = '', ...props }) {
  return (
    <button
      className={BTICK`${styles.btn} `${styles[variant]} `${className}BTICK}
      disabled={disabled || loading}
      {...props}
    >
      {loading && <span className={styles.spinner} />}
      {children}
    </button>
  );
}
"@

}

Write-File "components\common\Button.module.css" @'
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 11px 24px; border: none; border-radius: 8px; font-size: 0.92rem; font-weight: 600; cursor: pointer; transition: background 0.18s, transform 0.1s, opacity 0.18s; white-space: nowrap; }
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
.primary { background: #0f3460; color: #fff; }
.primary:hover:not(:disabled) { background: #16213e; }
.danger { background: #ef4444; color: #fff; }
.danger:hover:not(:disabled) { background: #dc2626; }
.ghost { background: transparent; color: #6b7280; border: 1.5px solid #d1d5db; }
.ghost:hover:not(:disabled) { background: #f9fafb; }
.spinner { width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.35); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
'@


if ($isTS) {
    Write-File "components\common\Input.$ext" @"
import { type InputHTMLAttributes } from 'react';
import styles from './Input.module.css';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  label: string;
  error?: string;
  id: string;
}
export function Input({ label, error, id, ...props }: InputProps) {
  return (
    <div className={styles.group}>
      <label htmlFor={id} className={styles.label}>{label}</label>
      <input id={id} className={BTICK`${styles.input} `${error ? styles.inputError : ''}BTICK} {...props} />
      {error && <span className={styles.error}>{error}</span>}
    </div>
  );
}
"@

} else {
    Write-File "components\common\Input.$ext" @"
import styles from './Input.module.css';
export function Input({ label, error, id, ...props }) {
  return (
    <div className={styles.group}>
      <label htmlFor={id} className={styles.label}>{label}</label>
      <input id={id} className={BTICK`${styles.input} `${error ? styles.inputError : ''}BTICK} {...props} />
      {error && <span className={styles.error}>{error}</span>}
    </div>
  );
}
"@

}

Write-File "components\common\Input.module.css" @'
.group { display: flex; flex-direction: column; gap: 5px; }
.label { font-size: 0.83rem; font-weight: 600; color: #374151; }
.input { padding: 11px 14px; border: 1.5px solid #d1d5db; border-radius: 8px; font-size: 0.93rem; outline: none; background: #fff; transition: border-color 0.18s, box-shadow 0.18s; }
.input:focus { border-color: #0f3460; box-shadow: 0 0 0 3px rgba(15,52,96,0.08); }
.inputError { border-color: #ef4444; }
.error { font-size: 0.78rem; color: #ef4444; }
'@


if ($isTS) {
    Write-File "components\common\Alert.$ext" @"
import styles from './Alert.module.css';
interface AlertProps { message: string | null; type?: 'error' | 'success' | 'info'; }
export function Alert({ message, type = 'error' }: AlertProps) {
  if (!message) return null;
  return <div className={BTICK`${styles.alert} `${styles[type]}BTICK}>{message}</div>;
}
"@

} else {
    Write-File "components\common\Alert.$ext" @"
import styles from './Alert.module.css';
export function Alert({ message, type = 'error' }) {
  if (!message) return null;
  return <div className={BTICK`${styles.alert} `${styles[type]}BTICK}>{message}</div>;
}
"@

}

Write-File "components\common\Alert.module.css" @'
.alert { padding: 11px 16px; border-radius: 8px; font-size: 0.88rem; font-weight: 500; animation: fadeIn 0.2s ease; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; } }
.error { background: #fef2f2; color: #dc2626; border: 1px solid #fecaca; }
.success { background: #f0fdf4; color: #16a34a; border: 1px solid #bbf7d0; }
.info { background: #eff6ff; color: #2563eb; border: 1px solid #bfdbfe; }
'@


# ─── AUTH COMPONENTS ──────────────────────────────────────
if ($isTS) {
    Write-File "components\auth\LoginForm.$ext" @"
import { useState, type FormEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { Input } from '../common/Input';
import { Button } from '../common/Button';
import { Alert } from '../common/Alert';
import { authService } from '../../services/authService';
import { useAuth } from '../../context/AuthContext';
import styles from './LoginForm.module.css';
 
export function LoginForm() {
  const navigate = useNavigate();
  const { login } = useAuth();
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
 
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setError(null); setLoading(true);
    try {
      const data = await authService.login({ username, password });
      login(data.token, data.username);
      navigate('/');
    } catch (err) {
      setError(err instanceof Error ? err.message : '$($L.uiLoginFailed)');
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <form className={styles.form} onSubmit={handleSubmit}>
      <Alert message={error} type="error" />
      <Input id="username" label="$($L.uiUsername)" value={username} onChange={e => setUsername(e.target.value)} placeholder="admin" required />
      <Input id="password" label="$($L.uiPassword)" type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="********" required />
      <Button type="submit" loading={loading} style={{ width: '100%', marginTop: 4 }}>$($L.uiLogin)</Button>
    </form>
  );
}
"@

} else {
    Write-File "components\auth\LoginForm.$ext" @"
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Input } from '../common/Input';
import { Button } from '../common/Button';
import { Alert } from '../common/Alert';
import { authService } from '../../services/authService';
import { useAuth } from '../../context/AuthContext';
import styles from './LoginForm.module.css';
 
export function LoginForm() {
  const navigate = useNavigate();
  const { login } = useAuth();
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
 
  const handleSubmit = async (e) => {
    e.preventDefault();
    setError(null); setLoading(true);
    try {
      const data = await authService.login({ username, password });
      login(data.token, data.username);
      navigate('/');
    } catch (err) {
      setError(err instanceof Error ? err.message : '$($L.uiLoginFailed)');
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <form className={styles.form} onSubmit={handleSubmit}>
      <Alert message={error} type="error" />
      <Input id="username" label="$($L.uiUsername)" value={username} onChange={e => setUsername(e.target.value)} placeholder="admin" required />
      <Input id="password" label="$($L.uiPassword)" type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="********" required />
      <Button type="submit" loading={loading} style={{ width: '100%', marginTop: 4 }}>$($L.uiLogin)</Button>
    </form>
  );
}
"@

}

Write-File "components\auth\LoginForm.module.css" @'
.form { display: flex; flex-direction: column; gap: 18px; }
'@


# ─── ENTITY COMPONENTS ────────────────────────────────────
foreach ($e in $Entities) {
    if ($e -eq "User") { continue }
    $lowE = $e.ToLower()
    $dirE = $lowE + "s"

    $formState   = if ($e -eq "Product") { " const [price, setPrice] = useState('');" } else { "" }
    $priceReset  = if ($e -eq "Product") { "`n setPrice('');" } else { "" }
    $formField   = if ($e -eq "Product") { "<Input id=`"eprice`" label=`"$($L.uiPrice)`" type=`"number`" value={price} onChange={e => setPrice(e.target.value)} placeholder=`"0.00`" required />" } else { "" }
    $tableHead   = if ($e -eq "Product") { "<th>$($L.uiPrice)</th>" } else { "" }
    $tableCell   = if ($e -eq "Product") { "<td>{item.price} TL</td>" } else { "" }

    if ($isTS) {
        $formCallTS  = if ($e -eq "Product") { "await ${lowE}Service.add(name, parseFloat(price));" } else { "await ${lowE}Service.add(name);" }
        $typeImport  = "import type { $e } from '../../types';"

        Write-File "components\$dirE\$e`Form.$ext" @"
import { useState, type FormEvent } from 'react';
import { Input } from '../common/Input';
import { Button } from '../common/Button';
import { Alert } from '../common/Alert';
import { ${lowE}Service } from '../../services/${lowE}Service';
import styles from './$e`Form.module.css';
 
export function ${e}Form({ onAdded = () => {} }: { onAdded?: () => void }) {
  const [name, setName] = useState('');
$formState
  const [error, setError] = useState<string | null>(null);
  const [success, setSuccess] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
 
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setError(null); setSuccess(null); setLoading(true);
    try {
      $formCallTS
      setSuccess('$($L.uiEntityAdded -f $e)');
      setName('');$priceReset
      onAdded();
    } catch (err) {
      setError(err instanceof Error ? err.message : '$($L.uiAddFailed)');
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <form className={styles.form} onSubmit={handleSubmit}>
      <Alert message={error} type="error" />
      <Alert message={success} type="success" />
      <div className={styles.row}>
        <Input id="ename" label="$($L.uiEntityName -f $e)" value={name} onChange={e => setName(e.target.value)} placeholder="$e" required />
        $formField
        <div className={styles.btnWrap}>
          <Button type="submit" loading={loading}>$($L.uiAdd)</Button>
        </div>
      </div>
    </form>
  );
}
"@


        Write-File "components\$dirE\$e`Table.$ext" @"
$typeImport
import styles from './${e}Table.module.css';
 
export function ${e}Table({ items, loading }: { items: $e[]; loading: boolean }) {
  if (loading) return <div className={styles.state}>$($L.uiLoading)</div>;
  if (!items.length) return <div className={styles.state}>$($L.uiNoData)</div>;
 
  return (
    <div className={styles.wrapper}>
      <table className={styles.table}>
        <thead><tr><th>#</th><th>$($L.uiEntityName -f $e)</th>$tableHead<th>$($L.uiCreatedAt)</th></tr></thead>
        <tbody>
          {items.map((item, i) => (
            <tr key={item.id}>
              <td>{i + 1}</td>
              <td>{item.name}</td>
              $tableCell
              <td>{new Date(item.createdAt).toLocaleDateString('$($L.uiDateLocale)')}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
"@

    } else {
        $formCallJS  = if ($e -eq "Product") { "await ${lowE}Service.add(name, parseFloat(price));" } else { "await ${lowE}Service.add(name);" }

        Write-File "components\$dirE\$e`Form.$ext" @"
import { useState } from 'react';
import { Input } from '../common/Input';
import { Button } from '../common/Button';
import { Alert } from '../common/Alert';
import { ${lowE}Service } from '../../services/${lowE}Service';
import styles from './$e`Form.module.css';
 
export function ${e}Form({ onAdded }) {
  const [name, setName] = useState('');
$formState
  const [error, setError] = useState(null);
  const [success, setSuccess] = useState(null);
  const [loading, setLoading] = useState(false);
 
  const handleSubmit = async (e) => {
    e.preventDefault();
    setError(null); setSuccess(null); setLoading(true);
    try {
      $formCallJS
      setSuccess('$($L.uiEntityAdded -f $e)');
      setName('');$priceReset
      onAdded();
    } catch (err) {
      setError(err instanceof Error ? err.message : '$($L.uiAddFailed)');
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <form className={styles.form} onSubmit={handleSubmit}>
      <Alert message={error} type="error" />
      <Alert message={success} type="success" />
      <div className={styles.row}>
        <Input id="ename" label="$($L.uiEntityName -f $e)" value={name} onChange={e => setName(e.target.value)} placeholder="$e" required />
        $formField
        <div className={styles.btnWrap}>
          <Button type="submit" loading={loading}>$($L.uiAdd)</Button>
        </div>
      </div>
    </form>
  );
}
"@


        Write-File "components\$dirE\$e`Table.$ext" @"
import styles from './${e}Table.module.css';
 
export function ${e}Table({ items, loading }) {
  if (loading) return <div className={styles.state}>$($L.uiLoading)</div>;
  if (!items.length) return <div className={styles.state}>$($L.uiNoData)</div>;
 
  return (
    <div className={styles.wrapper}>
      <table className={styles.table}>
        <thead><tr><th>#</th><th>$($L.uiEntityName -f $e)</th>$tableHead<th>$($L.uiCreatedAt)</th></tr></thead>
        <tbody>
          {items.map((item, i) => (
            <tr key={item.id}>
              <td>{i + 1}</td>
              <td>{item.name}</td>
              $tableCell
              <td>{new Date(item.createdAt).toLocaleDateString('$($L.uiDateLocale)')}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
"@

    }

    Write-File "components\$dirE\$e`Form.module.css" @'
.form { display: flex; flex-direction: column; gap: 14px; }
.row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.row > div:first-child { flex: 2; min-width: 160px; }
.btnWrap { padding-bottom: 1px; }
'@

    Write-File "components\$dirE\$e`Table.module.css" @'
.wrapper { overflow-x: auto; }
.state { text-align: center; color: #9ca3af; padding: 32px 0; font-size: 0.9rem; }
.table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.table thead tr { background: #f8fafc; }
.table th { padding: 11px 16px; text-align: left; font-weight: 600; color: #6b7280; border-bottom: 2px solid #e5e7eb; white-space: nowrap; }
.table td { padding: 11px 16px; border-bottom: 1px solid #f3f4f6; }
.table tbody tr:hover { background: #f9fafb; }
'@

}

# ─── BARREL INDEX EXPORTS per entity folder ───────────────
$extTs = if ($isTS) { "ts" } else { "js" }
foreach ($e in $Entities) {
    if ($e -eq "User") { continue }
    $dirE = $e.ToLower() + "s"

    Write-File "components\$dirE\index.$extTs" @"
export { ${e}Form } from './${e}Form';
export { ${e}Table } from './${e}Table';
"@

}