feat: Update styles and layout for improved UI/UX; add GEMINI project documentation

This commit is contained in:
2026-02-22 11:44:07 +01:00
parent b2aead1fbe
commit 3d6e0323ef
7 changed files with 387 additions and 180 deletions

View File

@@ -144,8 +144,8 @@
}, },
"CRON.md": { "CRON.md": {
"type": "-", "type": "-",
"size": 4784, "size": 4875,
"lmtime": 1771620216534, "lmtime": 1771669072264,
"modified": false "modified": false
}, },
"DATABASE.md": { "DATABASE.md": {
@@ -169,8 +169,8 @@
}, },
".env.example": { ".env.example": {
"type": "-", "type": "-",
"size": 442, "size": 616,
"lmtime": 1771626490188, "lmtime": 1771669059812,
"modified": false "modified": false
}, },
".htaccess": { ".htaccess": {
@@ -245,6 +245,12 @@
"size": 829, "size": 829,
"lmtime": 1771620035555, "lmtime": 1771620035555,
"modified": false "modified": false
},
"010_dataforseo_indexed_pages.sql": {
"type": "-",
"size": 238,
"lmtime": 1771668925563,
"modified": false
} }
}, },
"src": { "src": {
@@ -299,14 +305,14 @@
}, },
"SettingsController.php": { "SettingsController.php": {
"type": "-", "type": "-",
"size": 1868, "size": 2267,
"lmtime": 1771626466574, "lmtime": 1771668989725,
"modified": false "modified": false
}, },
"SiteController.php": { "SiteController.php": {
"type": "-", "type": "-",
"size": 16907, "size": 17063,
"lmtime": 1771628191087, "lmtime": 1771668985080,
"modified": false "modified": false
}, },
"TopicController.php": { "TopicController.php": {
@@ -395,14 +401,14 @@
}, },
"Site.php": { "Site.php": {
"type": "-", "type": "-",
"size": 1441, "size": 1668,
"lmtime": 1771627194103, "lmtime": 1771669773477,
"modified": false "modified": false
}, },
"SiteSeoMetric.php": { "SiteSeoMetric.php": {
"type": "-", "type": "-",
"size": 4817, "size": 6296,
"lmtime": 1771627676032, "lmtime": 1771669766557,
"modified": false "modified": false
}, },
"Topic.php": { "Topic.php": {
@@ -419,6 +425,12 @@
} }
}, },
"Services": { "Services": {
"DataForSeoService.php": {
"type": "-",
"size": 3670,
"lmtime": 1771668941654,
"modified": false
},
"FtpService.php": { "FtpService.php": {
"type": "-", "type": "-",
"size": 7464, "size": 7464,
@@ -457,8 +469,8 @@
}, },
"SiteSeoSyncService.php": { "SiteSeoSyncService.php": {
"type": "-", "type": "-",
"size": 3146, "size": 6697,
"lmtime": 1771620084332, "lmtime": 1771669807579,
"modified": false "modified": false
}, },
"TopicBalancer.php": { "TopicBalancer.php": {
@@ -561,8 +573,8 @@
}, },
"seo-stats.php": { "seo-stats.php": {
"type": "-", "type": "-",
"size": 7955, "size": 8683,
"lmtime": 1771627707865, "lmtime": 1771669055520,
"modified": false "modified": false
} }
}, },
@@ -605,16 +617,16 @@
"settings": { "settings": {
"index.php": { "index.php": {
"type": "-", "type": "-",
"size": 7022, "size": 9423,
"lmtime": 1771626482753, "lmtime": 1771669002362,
"modified": false "modified": false
} }
}, },
"sites": { "sites": {
"create.php": { "create.php": {
"type": "-", "type": "-",
"size": 5295, "size": 5744,
"lmtime": 1771620136407, "lmtime": 1771669030881,
"modified": false "modified": false
}, },
"dashboard.php": { "dashboard.php": {
@@ -625,8 +637,8 @@
}, },
"edit.php": { "edit.php": {
"type": "-", "type": "-",
"size": 13837, "size": 14374,
"lmtime": 1771628695879, "lmtime": 1771669040355,
"modified": false "modified": false
}, },
"index.php": { "index.php": {
@@ -637,8 +649,8 @@
}, },
"seo.php": { "seo.php": {
"type": "-", "type": "-",
"size": 7545, "size": 8656,
"lmtime": 1771626686595, "lmtime": 1771669504439,
"modified": false "modified": false
} }
}, },
@@ -665,6 +677,12 @@
"lmtime": 1771626947881, "lmtime": 1771626947881,
"modified": false "modified": false
}, },
"tmp_semstorm_test.php": {
"type": "-",
"size": 1027,
"lmtime": 1771627094790,
"modified": false
},
"TODO.md": { "TODO.md": {
"type": "-", "type": "-",
"size": 233, "size": 233,
@@ -792,12 +810,6 @@
"lmtime": 1771150407034, "lmtime": 1771150407034,
"modified": false "modified": false
} }
},
"tmp_semstorm_test.php": {
"type": "-",
"size": 1027,
"lmtime": 1771627094790,
"modified": false
} }
} }
}, },

85
GEMINI.md Normal file
View File

@@ -0,0 +1,85 @@
# GEMINI.md - BackPRO (SEO Management System)
## Project Overview
**BackPRO** is a custom PHP-based SEO management system designed to automate the management and content generation for a network of WordPress satellite sites. It leverages the WordPress REST API, OpenAI for content generation, and various image APIs (like Freepik) to create and publish SEO-optimized articles.
### Core Technologies
- **Language:** PHP 8.3+ (Strict Types enabled)
- **Framework:** Custom MVC architecture (no external framework like Laravel/Symfony)
- **Database:** MySQL/MariaDB (via PDO)
- **Frontend:** Bootstrap 5, Vanilla CSS/JS, PHP Templates
- **Integrations:**
- WordPress REST API (via Application Passwords)
- OpenAI API (GPT models for content)
- Freepik/Unsplash/Pexels APIs (for images)
- Semstorm/DataForSeo (for SEO metrics)
- **Dependencies:** Managed via Composer (`guzzlehttp/guzzle`, `phpdotenv`)
---
## Architecture & Structure
The project follows a standard MVC pattern with a clear separation of concerns:
- **`index.php`**: Front Controller - entry point for all web requests.
- **`src/Core/`**: The "engine" of the application.
- `App.php`: Application bootstrapper.
- `Router.php`: Maps URLs to controllers (defined in `config/routes.php`).
- `Model.php`: Base class for database interactions using PDO.
- `Controller.php`: Base class for all controllers.
- `Auth.php`: Session-based authentication system.
- **`src/Controllers/`**: Contains application logic (Auth, Dashboard, Sites, Topics, Articles, etc.).
- **`src/Models/`**: Database entity representations.
- **`src/Services/`**: External API clients and complex business logic (e.g., `WordPressService`, `OpenAIService`, `PublisherService`).
- **`templates/`**: PHP-based view templates.
- **`cron/`**: Scripts designed for CLI execution via system cron jobs (automated publishing).
- **`migrations/`**: SQL files for database schema evolution.
---
## Development Guidelines
### 1. Coding Standards
- **Naming:** Follow PSR-4 for class autoloading (Namespace `App\` maps to `src/`).
- **Database:** Always use Prepared Statements via `src/Core/Database.php` or `src/Core/Model.php` to prevent SQL Injection.
- **Security:**
- Use `htmlspecialchars()` in templates for XSS protection.
- Passwords must be hashed using `bcrypt` (via `password_hash`).
- API keys and sensitive data belong in the `.env` file.
- **Error Handling:** Use `App\Helpers\Logger` for logging application events and errors. Logs are stored in `storage/logs/`.
### 2. Working with Models
Models extend `App\Core\Model` and should contain methods for specific data retrieval. Use `$this->db` (a PDO instance) for queries.
### 3. Adding Routes
New endpoints must be registered in `config/routes.php`. Format: `$router->METHOD('/path', 'ControllerName', 'methodName')`.
### 4. Integration Logic
Place logic for interacting with external services in the `src/Services/` directory. These should be designed as reusable components.
---
## Deployment & Running
### Environment Setup
1. **PHP:** Ensure PHP 8.1+ is installed.
2. **Composer:** Run `composer install` to install dependencies.
3. **Configuration:** Copy `.env.example` to `.env` and fill in:
- Database credentials (`DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASS`)
- API Keys (`OPENAI_API_KEY`, `FREEPIK_API_KEY`, etc.)
4. **Database:** Execute SQL scripts from the `migrations/` directory in order.
### Commands & Scripts
- **Web Interface:** Accessible via a web server pointing to the root directory (ensure `.htaccess` is respected).
- **Automation (CRON):**
- `php cron/publish.php` - Triggers the automated publishing cycle.
- `php cron/semstorm.php` - Syncs SEO metrics.
---
## Key Features
- **WordPress Site CRUD:** Add and manage WP instances.
- **Topic Balancer:** Ensures content is distributed evenly across assigned topics.
- **AI Content Engine:** Generates unique articles with contextual images.
- **Remote Installer:** Automates the installation of WordPress and themes on remote servers.
- **SEO Analytics:** Tracks visibility and indexing status via third-party services.

View File

@@ -1,111 +1,172 @@
/* BackPRO - Custom Styles */ /* BackPRO - Compact Modern Dashboard Styles */
:root {
--primary-color: #4f46e5;
--primary-hover: #4338ca;
--bg-main: #f8fafc;
--sidebar-bg: #0f172a;
--sidebar-link: #94a3b8;
--sidebar-link-active: #ffffff;
--sidebar-link-hover: #1e293b;
--card-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--border-color: #e2e8f0;
}
body { body {
background-color: #f4f6f9; background-color: var(--bg-main);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-family: 'Inter', sans-serif;
font-size: 0.875rem; /* Zmniejszona baza (14px zamiast 16px) */
color: #1e293b;
-webkit-font-smoothing: antialiased;
} }
/* Global Links */
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: var(--primary-hover);
}
/* Sidebar - Compact */
.sidebar { .sidebar {
position: sticky; background-color: var(--sidebar-bg);
top: 0; border-right: 1px solid rgba(255, 255, 255, 0.05);
height: 100vh; z-index: 1000;
overflow-y: auto;
} }
.content-area { .sidebar-header {
min-width: 0; padding: 1rem 1.25rem; /* Zmniejszony padding */
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
} }
.sidebar .nav-link { .sidebar .nav-link {
padding: 0.6rem 1rem; color: var(--sidebar-link);
padding: 0.5rem 1rem; /* Zmniejszony padding */
margin: 0.15rem 0.75rem; /* Mniejsze marginesy */
border-radius: 0.375rem; border-radius: 0.375rem;
margin: 0.1rem 0; font-weight: 500;
transition: background-color 0.2s; font-size: 0.85rem; /* Mniejsza czcionka */
display: flex;
align-items: center;
transition: all 0.2s ease;
} }
.sidebar .nav-link:hover { .sidebar .nav-link i {
background-color: rgba(255, 255, 255, 0.1); font-size: 1rem;
margin-right: 0.75rem;
opacity: 0.7;
} }
.sidebar .nav-link.active { /* Header - Slim */
background-color: rgba(255, 255, 255, 0.15); header {
background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border-color) !important;
padding: 0.5rem 1.25rem !important; /* Odchudzony header */
} }
header h5 {
font-size: 1rem; /* Mniejszy tytuł */
}
/* Cards - Compact */
.card { .card {
border: none; background-color: #ffffff;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); border: 1px solid var(--border-color);
border-radius: 0.5rem;
box-shadow: var(--card-shadow);
} }
.table th { .card-header {
padding: 0.75rem 1rem; /* Mniejszy padding */
font-size: 0.9rem;
font-weight: 600;
}
.card-body {
padding: 1rem;
}
/* Tables - High Density */
.table-container {
background: #fff;
border-radius: 0.5rem;
}
.table thead th {
background-color: #f8fafc;
color: #64748b;
font-weight: 600; font-weight: 600;
font-size: 0.85rem;
text-transform: uppercase; text-transform: uppercase;
color: #6c757d; font-size: 0.7rem; /* Bardzo mały, czytelny label */
border-bottom-width: 1px; letter-spacing: 0.05em;
padding: 0.6rem 0.85rem; /* Ciasne komórki */
border-bottom: 1px solid var(--border-color);
} }
.article-content h2 { .table tbody td {
font-size: 1.4rem; padding: 0.6rem 0.85rem; /* Ciasne komórki */
margin-top: 1.5rem; font-size: 0.85rem;
} }
.article-content h3 { /* Stats Cards - Compact */
font-size: 1.2rem; .stat-card {
margin-top: 1.2rem; padding: 1rem;
} }
.article-content p { .stat-icon {
line-height: 1.7; width: 36px;
} height: 36px;
border-radius: 8px;
.article-content,
.article-content * {
overflow-wrap: anywhere;
}
.btn-group .btn + form .btn {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.bp-toast-container {
z-index: 2000;
max-width: 420px;
}
.bp-toast {
border-radius: 12px;
}
.bp-toast .toast-body {
font-size: 0.95rem;
}
.bp-toast.text-bg-warning .btn-close {
filter: none;
}
.bp-toast-fallback {
margin-bottom: 0.5rem;
padding: 0.75rem 0.95rem;
border-radius: 10px;
background: #1f2a44;
color: #fff;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.25);
}
.bp-confirm-modal .modal-content {
border: 0;
border-radius: 14px;
box-shadow: 0 1rem 2rem rgba(19, 31, 56, 0.2);
}
.bp-confirm-modal .modal-title {
font-size: 1.1rem; font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 1.25rem; /* Mniejsza wartość */
font-weight: 700; font-weight: 700;
} }
.bp-confirm-modal .modal-body p { .stat-label {
color: #445066; font-size: 0.75rem;
line-height: 1.5; }
/* Badges - Small */
.badge {
padding: 0.25em 0.6em;
font-size: 0.75rem;
font-weight: 600;
}
/* Buttons - Slim */
.btn {
border-radius: 0.375rem;
padding: 0.35rem 0.75rem;
font-size: 0.85rem;
}
.btn-sm {
padding: 0.2rem 0.5rem;
font-size: 0.75rem;
}
/* Main Content Padding */
main.p-4 {
padding: 1.25rem !important; /* Mniejszy margines główny */
}
/* Forms - Compact */
.form-control, .form-select {
padding: 0.4rem 0.6rem;
font-size: 0.875rem;
border-radius: 0.375rem;
}
.article-content {
font-size: 0.95rem; /* Nieco mniejszy tekst artykułu */
line-height: 1.6;
} }

View File

@@ -67,4 +67,13 @@ class Router
http_response_code(404); http_response_code(404);
echo '<h1>404 - Strona nie znaleziona</h1>'; echo '<h1>404 - Strona nie znaleziona</h1>';
} }
public static function isCurrent(string $path): bool
{
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = rtrim($uri, '/') ?: '/';
$path = rtrim($path, '/') ?: '/';
return $uri === $path;
}
} }

View File

@@ -1,14 +1,21 @@
<header class="bg-white border-bottom p-3 d-flex justify-content-between align-items-center"> <header class="bg-white border-bottom p-3 d-flex justify-content-between align-items-center">
<div></div> <div class="d-flex align-items-center">
<div class="d-flex align-items-center gap-3"> <!-- Można tu dodać tytuł strony w zależności od routera -->
<form method="post" action="/publish/run" class="d-inline" data-confirm="Uruchomic publikacje teraz?"> <h5 class="mb-0 fw-semibold text-secondary px-2">Panel Sterowania</h5>
<button type="submit" class="btn btn-sm btn-success"> </div>
<i class="bi bi-play-circle me-1"></i>Publikuj teraz <div class="d-flex align-items-center gap-3 pe-2">
<form method="post" action="/publish/run" class="d-inline" data-confirm="Uruchomić publikację teraz?">
<button type="submit" class="btn btn-sm btn-primary shadow-sm px-3">
<i class="bi bi-play-circle-fill me-1"></i> Uruchom Publikację
</button> </button>
</form> </form>
<a href="/logout" class="btn btn-sm btn-outline-secondary"> <div class="border-start ms-2 ps-3 d-flex align-items-center">
<i class="bi bi-box-arrow-right me-1"></i>Wyloguj <a href="/settings" class="btn btn-sm btn-outline-secondary border-0 px-2" title="Ustawienia">
</a> <i class="bi bi-gear fs-5"></i>
</a>
<a href="/logout" class="btn btn-sm btn-outline-danger border-0 px-2" title="Wyloguj">
<i class="bi bi-power fs-5"></i>
</a>
</div>
</div> </div>
</header> </header>

View File

@@ -4,26 +4,31 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BackPRO - Zarządzanie Zapleczem SEO</title> <title>BackPRO - Zarządzanie Zapleczem SEO</title>
<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@300;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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link href="/assets/css/app.css" rel="stylesheet"> <link href="/assets/css/app.css" rel="stylesheet">
</head> </head>
<body> <body class="bg-light-subtle">
<?php if (\App\Core\Auth::check()): ?> <?php if (\App\Core\Auth::check()): ?>
<div class="d-flex"> <div class="d-flex min-vh-100">
<?php require __DIR__ . '/sidebar.php'; ?> <?php require __DIR__ . '/sidebar.php'; ?>
<div class="flex-grow-1 content-area"> <div class="flex-grow-1 d-flex flex-column content-area">
<?php require __DIR__ . '/header.php'; ?> <?php require __DIR__ . '/header.php'; ?>
<main class="p-4"> <main class="p-4 flex-grow-1">
<?php if (!empty($flashMessages)): ?> <div class="container-fluid p-0">
<?php foreach ($flashMessages as $flash): ?> <?php if (!empty($flashMessages)): ?>
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show"> <?php foreach ($flashMessages as $flash): ?>
<?= htmlspecialchars($flash['message']) ?> <div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show shadow-sm border-0">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <?= htmlspecialchars($flash['message']) ?>
</div> <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<?php endforeach; ?> </div>
<?php endif; ?> <?php endforeach; ?>
<?= $content ?> <?php endif; ?>
<?= $content ?>
</div>
</main> </main>
</div> </div>
</div> </div>

View File

@@ -1,53 +1,81 @@
<nav class="sidebar bg-dark text-white d-flex flex-column flex-shrink-0" style="width: 250px; min-height: 100vh;"> <nav class="sidebar d-flex flex-column flex-shrink-0 min-vh-100" style="width: 260px;">
<div class="p-3 border-bottom border-secondary"> <div class="sidebar-header">
<h5 class="mb-0"><i class="bi bi-globe2"></i> BackPRO</h5> <div class="d-flex align-items-center">
<small class="text-secondary">Zarządzanie Zapleczem SEO</small> <div class="bg-primary rounded-3 p-2 me-3 shadow-sm">
<i class="bi bi-rocket-takeoff-fill text-white fs-4"></i>
</div>
<div>
<h5 class="mb-0 text-white fw-bold tracking-tight">BackPRO</h5>
<p class="text-secondary small mb-0" style="font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;">SEO Engine</p>
</div>
</div>
</div> </div>
<ul class="nav flex-column p-2 flex-grow-1">
<li class="nav-item"> <div class="flex-grow-1 py-4 overflow-y-auto">
<a class="nav-link text-white" href="/"> <div class="px-4 mb-3 text-uppercase text-secondary small fw-bold" style="font-size: 0.65rem; letter-spacing: 0.1em;">Menu główne</div>
<i class="bi bi-speedometer2 me-2"></i>Dashboard <ul class="nav flex-column mb-4">
</a> <li class="nav-item">
</li> <a class="nav-link <?= \App\Core\Router::isCurrent('/') ? 'active' : '' ?>" href="/">
<li class="nav-item"> <i class="bi bi-grid-1x2-fill"></i>Dashboard
<a class="nav-link text-white" href="/sites"> </a>
<i class="bi bi-wordpress me-2"></i>Strony </li>
</a> <li class="nav-item">
</li> <a class="nav-link <?= \App\Core\Router::isCurrent('/sites') ? 'active' : '' ?>" href="/sites">
<li class="nav-item"> <i class="bi bi-browser-safari"></i>Strony WP
<a class="nav-link text-white" href="/seo/stats"> </a>
<i class="bi bi-graph-up-arrow me-2"></i>Statystyki SEO </li>
</a> <li class="nav-item">
</li> <a class="nav-link <?= \App\Core\Router::isCurrent('/seo/stats') ? 'active' : '' ?>" href="/seo/stats">
<li class="nav-item"> <i class="bi bi-bar-chart-line-fill"></i>Statystyki SEO
<a class="nav-link text-white" href="/global-topics"> </a>
<i class="bi bi-bookmarks me-2"></i>Tematy </li>
</a> </ul>
</li>
<li class="nav-item"> <div class="px-4 mb-3 text-uppercase text-secondary small fw-bold" style="font-size: 0.65rem; letter-spacing: 0.1em;">Zasoby i Treści</div>
<a class="nav-link text-white" href="/articles"> <ul class="nav flex-column mb-4">
<i class="bi bi-file-earmark-text me-2"></i>Artykuły <li class="nav-item">
</a> <a class="nav-link <?= \App\Core\Router::isCurrent('/global-topics') ? 'active' : '' ?>" href="/global-topics">
</li> <i class="bi bi-tags-fill"></i>Biblioteka Tematów
<li class="nav-item"> </a>
<a class="nav-link text-white" href="/logs"> </li>
<i class="bi bi-journal-text me-2"></i>Log <li class="nav-item">
</a> <a class="nav-link <?= \App\Core\Router::isCurrent('/articles') ? 'active' : '' ?>" href="/articles">
</li> <i class="bi bi-journal-richtext"></i>Artykuły
<li class="nav-item"> </a>
<a class="nav-link text-white" href="/installer"> </li>
<i class="bi bi-cloud-upload me-2"></i>Instalator WP </ul>
</a>
</li> <div class="px-4 mb-3 text-uppercase text-secondary small fw-bold" style="font-size: 0.65rem; letter-spacing: 0.1em;">Administracja</div>
<li class="nav-item"> <ul class="nav flex-column">
<a class="nav-link text-white" href="/settings"> <li class="nav-item">
<i class="bi bi-gear me-2"></i>Ustawienia <a class="nav-link <?= \App\Core\Router::isCurrent('/installer') ? 'active' : '' ?>" href="/installer">
</a> <i class="bi bi-magic"></i>Instalator WP
</li> </a>
</ul> </li>
<div class="p-3 border-top border-secondary"> <li class="nav-item">
<small class="text-secondary">Zalogowano jako:</small><br> <a class="nav-link <?= \App\Core\Router::isCurrent('/settings') ? 'active' : '' ?>" href="/settings">
<span class="text-white"><?= htmlspecialchars(\App\Core\Auth::user()['username'] ?? '') ?></span> <i class="bi bi-sliders"></i>Ustawienia API
<a href="/change-password" class="d-block text-secondary small mt-1"><i class="bi bi-key me-1"></i>Zmień hasło</a> </a>
</li>
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/logs') ? 'active' : '' ?>" href="/logs">
<i class="bi bi-shield-lock-fill"></i>Dziennik Zdarzeń
</a>
</li>
</ul>
</div>
<div class="p-4 bg-black bg-opacity-10 border-top border-white border-opacity-5">
<div class="d-flex align-items-center gap-3">
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center text-white" style="width: 32px; height: 32px; font-size: 0.8rem; font-weight: 600;">
<?= strtoupper(substr(\App\Core\Auth::user()['username'] ?? 'U', 0, 1)) ?>
</div>
<div class="overflow-hidden">
<div class="text-white small text-truncate fw-medium"><?= htmlspecialchars(\App\Core\Auth::user()['username'] ?? 'Użytkownik') ?></div>
<a href="/logout" class="text-secondary small text-decoration-none hover-white d-flex align-items-center">
<i class="bi bi-box-arrow-right me-1"></i> Wyloguj
</a>
</div>
</div>
</div> </div>
</nav> </nav>