40061-vm/index.php
Flatlogic Bot f9536ee19b 01
2026-05-24 07:14:49 +00:00

334 lines
20 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/app.php';
app_boot();
$defaults = default_mail_account_input();
$formData = $defaults;
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_account') {
[$formData, $errors] = validate_mail_account_input($_POST);
if (empty($errors)) {
try {
$accountId = save_mail_account($formData);
flash('success', 'Mailbox je spremljen. Otvaram live inbox pregled.');
header('Location: mailbox.php?id=' . $accountId);
exit;
} catch (Throwable $exception) {
$errors['form'] = 'Spremanje nije uspjelo. Provjeri bazu i pokušaj ponovno.';
}
}
}
$accounts = get_mail_accounts();
$flash = pull_flash();
$dbReady = db_ready();
$dbError = app_db_error();
$latestSync = 'Not yet';
$syncedAccounts = 0;
foreach ($accounts as $account) {
if (!empty($account['last_sync_at'])) {
$syncedAccounts++;
if ($latestSync === 'Not yet' || strtotime((string) $account['last_sync_at']) > strtotime((string) $latestSync)) {
$latestSync = (string) $account['last_sync_at'];
}
}
}
$pageTitle = project_name() . ' — POP3 mailbox dashboard';
$projectBaseDescription = trim((string) ($_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: ''));
$pageDescription = $projectBaseDescription !== ''
? $projectBaseDescription . ' — Dashboard for POP3 mailbox setup, MySQL account storage, and live inbox access.'
: 'Configure local POP3 mailboxes, store connection settings in MySQL, and open a clean inbox view from one lightweight PHP interface.';
$projectDescription = $projectBaseDescription !== '' ? $projectBaseDescription : $pageDescription;
$projectImageUrl = project_image_url();
?>
<!doctype html>
<html lang="hr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= h($pageTitle) ?></title>
<meta name="description" content="<?= h($pageDescription) ?>">
<?php if ($projectDescription): ?>
<meta property="og:description" content="<?= h($projectDescription) ?>">
<meta property="twitter:description" content="<?= h($projectDescription) ?>">
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= h($projectImageUrl) ?>">
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>">
<?php endif; ?>
<meta property="og:title" content="<?= h($pageTitle) ?>">
<meta property="twitter:title" content="<?= h($pageTitle) ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h(asset_version('assets/css/custom.css')) ?>">
</head>
<body>
<?php if ($flash): ?>
<div class="toast-shell">
<div class="toast align-items-center border-0" role="status" aria-live="polite" aria-atomic="true">
<div class="d-flex">
<div class="toast-body"><?= h($flash['message']) ?></div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
<?php endif; ?>
<div class="container app-shell py-4 py-lg-5">
<nav class="navbar app-nav navbar-expand-lg">
<div class="container-fluid px-0">
<a class="navbar-brand d-flex align-items-center gap-3 m-0" href="index.php">
<span class="brand-mark">WM</span>
<span class="brand-copy">
<strong><?= h(project_name()) ?></strong>
<small>Local POP3 workspace</small>
</span>
</a>
<div class="d-flex flex-wrap align-items-center gap-2 ms-auto">
<a class="btn btn-sm btn-outline-secondary" href="#setup">Dodaj račun</a>
<a class="btn btn-sm btn-outline-secondary" href="#accounts">Računi</a>
<a class="btn btn-sm btn-outline-secondary" href="xampp-setup.php">XAMPP setup</a>
<a class="btn btn-sm btn-outline-secondary" href="healthz.php" target="_blank" rel="noopener">Healthz</a>
</div>
</div>
</nav>
<header class="hero-grid">
<section class="hero-card">
<div class="page-eyebrow mb-2">Initial MVP slice</div>
<h1 class="hero-title">Dodaj POP3 mailbox i odmah otvori inbox iz preglednika.</h1>
<p class="section-subtitle">Ova prva verzija pokriva najbitniji tok: spremi POP3 postavke u MySQL, otvori live pregled inboxa i pročitaj poruku bez izlaska iz aplikacije. Dizajn je namjerno čist i lagan za lokalni XAMPP setup.</p>
<div class="hero-actions">
<a class="btn btn-primary" href="#setup">Konfiguriraj mailbox</a>
<a class="btn btn-outline-secondary" href="#accounts">Pogledaj spremljene račune</a>
</div>
<div class="d-flex flex-wrap gap-2 mt-3">
<span class="inline-note">POP3 read workflow</span>
<span class="inline-note">MySQL account storage</span>
<span class="inline-note">Inbox detail view</span>
</div>
</section>
<aside class="metrics-grid">
<div class="metric-card">
<div class="metric-label">Configured accounts</div>
<div class="metric-value"><?= h((string) count($accounts)) ?></div>
<div class="metric-hint">Broj mailboxa spremljenih u lokalnoj bazi.</div>
</div>
<div class="metric-card">
<div class="metric-label">Successful syncs</div>
<div class="metric-value"><?= h((string) $syncedAccounts) ?></div>
<div class="metric-hint">Računi koji su već otvoreni kroz inbox ekran.</div>
</div>
<div class="metric-card">
<div class="metric-label">Environment</div>
<div class="metric-value" style="font-size:1.1rem;">PHP <?= h(PHP_VERSION) ?></div>
<div class="metric-hint">Vrijeme: <?= h(gmdate('Y-m-d H:i')) ?> UTC · <a class="text-decoration-underline" href="xampp-setup.php">xampp-setup.php</a> · <a class="text-decoration-underline" href="healthz.php" target="_blank" rel="noopener">/healthz</a></div>
</div>
</aside>
</header>
<?php if (!$dbReady): ?>
<div class="alert alert-danger border-0 mb-4" role="alert">
<strong>Baza nije dostupna.</strong> Forma i lista su prikazane, ali spremanje neće raditi dok se MySQL veza ne podigne.
<div class="small mt-2">Za lokalni XAMPP pokreni Apache + MySQL, zatim otvori <a class="text-decoration-underline" href="xampp-setup.php">xampp-setup.php</a> da automatski kreiraš bazu <strong><?= h(DB_NAME) ?></strong>.</div>
<div class="small mt-2">Trenutna konfiguracija: <?= h(DB_USER) ?>@<?= h(DB_HOST) ?>:<?= h((string) DB_PORT) ?> / <?= h(DB_NAME) ?><?= DB_PASS === '' ? ' · bez lozinke' : ' · lozinka postavljena' ?></div>
<?php if ($dbError): ?>
<div class="small mt-2"><?= h($dbError) ?></div>
<?php endif; ?>
</div>
<?php endif; ?>
<main class="row g-4">
<section id="setup" class="col-12 col-xl-7">
<div class="section-card">
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
<div>
<div class="overline mb-1">Create / input</div>
<h2 class="section-title">Dodaj POP3 račun</h2>
</div>
<span class="soft-badge">Server-side validation · encrypted password at rest</span>
</div>
<?php if (!empty($errors['form'])): ?>
<div class="alert alert-danger border-0 mb-4" role="alert"><?= h($errors['form']) ?></div>
<?php endif; ?>
<form method="post" class="row g-3" novalidate>
<input type="hidden" name="action" value="save_account">
<div class="col-md-6">
<label for="label" class="form-label">Naziv mailboxa</label>
<input type="text" class="form-control<?= isset($errors['label']) ? ' is-invalid' : '' ?>" id="label" name="label" value="<?= h((string) $formData['label']) ?>" placeholder="npr. Lokalni support" <?= $dbReady ? '' : 'disabled' ?>>
<?php if (isset($errors['label'])): ?><div class="validation-note"><?= h($errors['label']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="email_address" class="form-label">Email adresa <span class="helper-text">(opcionalno)</span></label>
<input type="email" class="form-control<?= isset($errors['email_address']) ? ' is-invalid' : '' ?>" id="email_address" name="email_address" value="<?= h((string) $formData['email_address']) ?>" placeholder="mailbox@example.local" <?= $dbReady ? '' : 'disabled' ?>>
<?php if (isset($errors['email_address'])): ?><div class="validation-note"><?= h($errors['email_address']) ?></div><?php endif; ?>
</div>
<div class="col-md-8">
<label for="pop3_host" class="form-label">POP3 host</label>
<input type="text" class="form-control<?= isset($errors['pop3_host']) ? ' is-invalid' : '' ?>" id="pop3_host" name="pop3_host" value="<?= h((string) $formData['pop3_host']) ?>" placeholder="127.0.0.1" <?= $dbReady ? '' : 'disabled' ?>>
<?php if (isset($errors['pop3_host'])): ?><div class="validation-note"><?= h($errors['pop3_host']) ?></div><?php endif; ?>
</div>
<div class="col-md-4">
<label for="pop3_port" class="form-label">Port</label>
<input type="number" class="form-control<?= isset($errors['pop3_port']) ? ' is-invalid' : '' ?>" id="pop3_port" name="pop3_port" value="<?= h((string) $formData['pop3_port']) ?>" min="1" max="65535" <?= $dbReady ? '' : 'disabled' ?>>
<?php if (isset($errors['pop3_port'])): ?><div class="validation-note"><?= h($errors['pop3_port']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="security_mode" class="form-label">Sigurnost veze</label>
<select class="form-select" id="security_mode" name="security_mode" data-security-select data-port-target="#pop3_port" <?= $dbReady ? '' : 'disabled' ?>>
<option value="plain" <?= ($formData['security_mode'] ?? '') === 'plain' ? 'selected' : '' ?>>Plain (110)</option>
<option value="ssl" <?= ($formData['security_mode'] ?? '') === 'ssl' ? 'selected' : '' ?>>SSL/TLS (995)</option>
</select>
</div>
<div class="col-md-6">
<label for="sync_limit" class="form-label">Poruka za dohvat</label>
<input type="number" class="form-control<?= isset($errors['sync_limit']) ? ' is-invalid' : '' ?>" id="sync_limit" name="sync_limit" value="<?= h((string) $formData['sync_limit']) ?>" min="5" max="50" <?= $dbReady ? '' : 'disabled' ?>>
<?php if (isset($errors['sync_limit'])): ?><div class="validation-note"><?= h($errors['sync_limit']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="username" class="form-label">Korisničko ime</label>
<input type="text" class="form-control<?= isset($errors['username']) ? ' is-invalid' : '' ?>" id="username" name="username" value="<?= h((string) $formData['username']) ?>" placeholder="korisnik ili email" <?= $dbReady ? '' : 'disabled' ?>>
<?php if (isset($errors['username'])): ?><div class="validation-note"><?= h($errors['username']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="password" class="form-label">Lozinka</label>
<input type="password" class="form-control<?= isset($errors['password']) ? ' is-invalid' : '' ?>" id="password" name="password" value="<?= h((string) $formData['password']) ?>" placeholder="••••••••" <?= $dbReady ? '' : 'disabled' ?>>
<?php if (isset($errors['password'])): ?><div class="validation-note"><?= h($errors['password']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" id="leave_on_server" name="leave_on_server" <?= !empty($formData['leave_on_server']) ? 'checked' : '' ?> <?= $dbReady ? '' : 'disabled' ?>>
<label class="form-check-label" for="leave_on_server">Ostavi poruke na serveru (read-only POP3 slice)</label>
</div>
</div>
<div class="col-12 d-flex flex-wrap align-items-center gap-3 pt-2">
<button type="submit" class="btn btn-primary" <?= $dbReady ? '' : 'disabled' ?>>Spremi mailbox</button>
<span class="helper-text">Savjet za lokalni test: host <strong>127.0.0.1</strong>, port <strong>110</strong>, bez enkripcije.</span>
</div>
</form>
</div>
</section>
<aside class="col-12 col-xl-5">
<div class="section-card stack-md h-100">
<div>
<div class="overline mb-1">Confirmation / guide</div>
<h2 class="section-title">Što dobivaš u ovoj isporuci</h2>
</div>
<div class="quick-steps">
<article class="quick-step">
<h3>1. Spremanje računa</h3>
<p>POP3 postavke se validiraju na serveru i spremaju u MySQL, a lozinka se šifrira prije upisa u bazu.</p>
</article>
<article class="quick-step">
<h3>2. Live inbox ekran</h3>
<p>Nakon spremanja otvara se mailbox detalj koji čita najnovije poruke direktno preko POP3 veze.</p>
</article>
<article class="quick-step">
<h3>3. Pregled statusa</h3>
<p>Na dashboardu ostaju vidljivi status zadnje sinkronizacije, broj poruka i brzi linkovi prema svakom inboxu.</p>
</article>
</div>
<div class="surface-muted p-3 rounded-4 border">
<div class="overline mb-2">Next after MVP</div>
<ul class="help-list">
<li>SMTP compose + Sent folder tok.</li>
<li>Lokalni cache headera/poruka za bržu pretragu.</li>
<li>Uređivanje i deaktivacija mailbox računa.</li>
</ul>
</div>
</div>
</aside>
<section id="accounts" class="col-12">
<div class="section-card">
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
<div>
<div class="overline mb-1">List</div>
<h2 class="section-title">Spremljeni mailbox računi</h2>
</div>
<div class="d-flex flex-wrap gap-2">
<span class="soft-badge">Last sync: <?= h($latestSync === 'Not yet' ? $latestSync : format_datetime($latestSync)) ?></span>
<span class="soft-badge">Accounts: <?= h((string) count($accounts)) ?></span>
</div>
</div>
<?php if ($accounts): ?>
<div class="table-shell">
<table class="table">
<thead>
<tr>
<th>Mailbox</th>
<th>POP3</th>
<th>Status</th>
<th>Zadnja sinkronizacija</th>
<th class="text-end">Akcija</th>
</tr>
</thead>
<tbody>
<?php foreach ($accounts as $account): ?>
<tr>
<td>
<div class="fw-semibold"><?= h($account['label']) ?></div>
<div class="small text-secondary"><?= h((string) ($account['email_address'] ?: $account['username'])) ?></div>
</td>
<td>
<div class="fw-semibold"><?= h($account['pop3_host']) ?>:<?= h((string) $account['pop3_port']) ?></div>
<div class="small text-secondary"><?= h(security_label((string) $account['security_mode'])) ?> · limit <?= h((string) $account['sync_limit']) ?></div>
</td>
<td>
<span class="status-badge <?= h(status_tone((string) $account['last_status'])) ?>"><?= h((string) ($account['last_status'] ?: 'Ready')) ?></span>
<div class="small text-secondary mt-1"><?= h((string) $account['last_message_count']) ?> poruka</div>
</td>
<td>
<div class="fw-semibold"><?= h(format_datetime($account['last_sync_at'])) ?></div>
<div class="small text-secondary">Dodano <?= h(format_datetime($account['created_at'])) ?></div>
</td>
<td class="text-end">
<a class="btn btn-sm btn-primary" href="mailbox.php?id=<?= h((string) $account['id']) ?>">Otvori inbox</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="empty-panel">
<h3 class="h5 mb-2">Još nema spremljenih mailbox računa.</h3>
<p class="empty-copy mb-3">Dodaj prvi POP3 račun kako bi dashboard dobio live inbox i detail prikaz poruka.</p>
<div class="d-flex justify-content-center flex-wrap gap-2">
<span class="soft-badge">Primjer hosta: 127.0.0.1</span>
<span class="soft-badge">Primjer porta: 110</span>
<span class="soft-badge">Sigurnost: plain</span>
</div>
</div>
<?php endif; ?>
</div>
</section>
</main>
<footer class="d-flex flex-column flex-lg-row justify-content-between gap-3 pt-4 footer-copy">
<div>Thin slice is ready: create account → confirmation redirect → list → inbox detail.</div>
<div class="d-flex gap-3 flex-wrap">
<a class="text-decoration-underline" href="healthz.php" target="_blank" rel="noopener">Open /healthz</a>
<?php if ($accounts): ?>
<a class="text-decoration-underline" href="mailbox.php?id=<?= h((string) $accounts[0]['id']) ?>">Open latest inbox</a>
<?php endif; ?>
</div>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
<script src="assets/js/main.js?v=<?= h(asset_version('assets/js/main.js')) ?>" defer></script>
</body>
</html>