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": {
"type": "-",
"size": 4784,
"lmtime": 1771620216534,
"size": 4875,
"lmtime": 1771669072264,
"modified": false
},
"DATABASE.md": {
@@ -169,8 +169,8 @@
},
".env.example": {
"type": "-",
"size": 442,
"lmtime": 1771626490188,
"size": 616,
"lmtime": 1771669059812,
"modified": false
},
".htaccess": {
@@ -245,6 +245,12 @@
"size": 829,
"lmtime": 1771620035555,
"modified": false
},
"010_dataforseo_indexed_pages.sql": {
"type": "-",
"size": 238,
"lmtime": 1771668925563,
"modified": false
}
},
"src": {
@@ -299,14 +305,14 @@
},
"SettingsController.php": {
"type": "-",
"size": 1868,
"lmtime": 1771626466574,
"size": 2267,
"lmtime": 1771668989725,
"modified": false
},
"SiteController.php": {
"type": "-",
"size": 16907,
"lmtime": 1771628191087,
"size": 17063,
"lmtime": 1771668985080,
"modified": false
},
"TopicController.php": {
@@ -395,14 +401,14 @@
},
"Site.php": {
"type": "-",
"size": 1441,
"lmtime": 1771627194103,
"size": 1668,
"lmtime": 1771669773477,
"modified": false
},
"SiteSeoMetric.php": {
"type": "-",
"size": 4817,
"lmtime": 1771627676032,
"size": 6296,
"lmtime": 1771669766557,
"modified": false
},
"Topic.php": {
@@ -419,6 +425,12 @@
}
},
"Services": {
"DataForSeoService.php": {
"type": "-",
"size": 3670,
"lmtime": 1771668941654,
"modified": false
},
"FtpService.php": {
"type": "-",
"size": 7464,
@@ -457,8 +469,8 @@
},
"SiteSeoSyncService.php": {
"type": "-",
"size": 3146,
"lmtime": 1771620084332,
"size": 6697,
"lmtime": 1771669807579,
"modified": false
},
"TopicBalancer.php": {
@@ -561,8 +573,8 @@
},
"seo-stats.php": {
"type": "-",
"size": 7955,
"lmtime": 1771627707865,
"size": 8683,
"lmtime": 1771669055520,
"modified": false
}
},
@@ -605,16 +617,16 @@
"settings": {
"index.php": {
"type": "-",
"size": 7022,
"lmtime": 1771626482753,
"size": 9423,
"lmtime": 1771669002362,
"modified": false
}
},
"sites": {
"create.php": {
"type": "-",
"size": 5295,
"lmtime": 1771620136407,
"size": 5744,
"lmtime": 1771669030881,
"modified": false
},
"dashboard.php": {
@@ -625,8 +637,8 @@
},
"edit.php": {
"type": "-",
"size": 13837,
"lmtime": 1771628695879,
"size": 14374,
"lmtime": 1771669040355,
"modified": false
},
"index.php": {
@@ -637,8 +649,8 @@
},
"seo.php": {
"type": "-",
"size": 7545,
"lmtime": 1771626686595,
"size": 8656,
"lmtime": 1771669504439,
"modified": false
}
},
@@ -665,6 +677,12 @@
"lmtime": 1771626947881,
"modified": false
},
"tmp_semstorm_test.php": {
"type": "-",
"size": 1027,
"lmtime": 1771627094790,
"modified": false
},
"TODO.md": {
"type": "-",
"size": 233,
@@ -792,12 +810,6 @@
"lmtime": 1771150407034,
"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 {
background-color: #f4f6f9;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: var(--bg-main);
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 {
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
background-color: var(--sidebar-bg);
border-right: 1px solid rgba(255, 255, 255, 0.05);
z-index: 1000;
}
.content-area {
min-width: 0;
.sidebar-header {
padding: 1rem 1.25rem; /* Zmniejszony padding */
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.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;
margin: 0.1rem 0;
transition: background-color 0.2s;
font-weight: 500;
font-size: 0.85rem; /* Mniejsza czcionka */
display: flex;
align-items: center;
transition: all 0.2s ease;
}
.sidebar .nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
.sidebar .nav-link i {
font-size: 1rem;
margin-right: 0.75rem;
opacity: 0.7;
}
.sidebar .nav-link.active {
background-color: rgba(255, 255, 255, 0.15);
/* Header - Slim */
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 {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
background-color: #ffffff;
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-size: 0.85rem;
text-transform: uppercase;
color: #6c757d;
border-bottom-width: 1px;
font-size: 0.7rem; /* Bardzo mały, czytelny label */
letter-spacing: 0.05em;
padding: 0.6rem 0.85rem; /* Ciasne komórki */
border-bottom: 1px solid var(--border-color);
}
.article-content h2 {
font-size: 1.4rem;
margin-top: 1.5rem;
.table tbody td {
padding: 0.6rem 0.85rem; /* Ciasne komórki */
font-size: 0.85rem;
}
.article-content h3 {
font-size: 1.2rem;
margin-top: 1.2rem;
/* Stats Cards - Compact */
.stat-card {
padding: 1rem;
}
.article-content p {
line-height: 1.7;
}
.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 {
.stat-icon {
width: 36px;
height: 36px;
border-radius: 8px;
font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 1.25rem; /* Mniejsza wartość */
font-weight: 700;
}
.bp-confirm-modal .modal-body p {
color: #445066;
line-height: 1.5;
.stat-label {
font-size: 0.75rem;
}
/* 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);
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">
<div></div>
<div class="d-flex align-items-center gap-3">
<form method="post" action="/publish/run" class="d-inline" data-confirm="Uruchomic publikacje teraz?">
<button type="submit" class="btn btn-sm btn-success">
<i class="bi bi-play-circle me-1"></i>Publikuj teraz
<div class="d-flex align-items-center">
<!-- Można tu dodać tytuł strony w zależności od routera -->
<h5 class="mb-0 fw-semibold text-secondary px-2">Panel Sterowania</h5>
</div>
<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>
</form>
<a href="/logout" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-box-arrow-right me-1"></i>Wyloguj
</a>
<div class="border-start ms-2 ps-3 d-flex align-items-center">
<a href="/settings" class="btn btn-sm btn-outline-secondary border-0 px-2" title="Ustawienia">
<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>
</header>

View File

@@ -4,26 +4,31 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link href="/assets/css/app.css" rel="stylesheet">
</head>
<body>
<body class="bg-light-subtle">
<?php if (\App\Core\Auth::check()): ?>
<div class="d-flex">
<div class="d-flex min-vh-100">
<?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'; ?>
<main class="p-4">
<?php if (!empty($flashMessages)): ?>
<?php foreach ($flashMessages as $flash): ?>
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show">
<?= htmlspecialchars($flash['message']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?= $content ?>
<main class="p-4 flex-grow-1">
<div class="container-fluid p-0">
<?php if (!empty($flashMessages)): ?>
<?php foreach ($flashMessages as $flash): ?>
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show shadow-sm border-0">
<?= htmlspecialchars($flash['message']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?= $content ?>
</div>
</main>
</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;">
<div class="p-3 border-bottom border-secondary">
<h5 class="mb-0"><i class="bi bi-globe2"></i> BackPRO</h5>
<small class="text-secondary">Zarządzanie Zapleczem SEO</small>
<nav class="sidebar d-flex flex-column flex-shrink-0 min-vh-100" style="width: 260px;">
<div class="sidebar-header">
<div class="d-flex align-items-center">
<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>
<ul class="nav flex-column p-2 flex-grow-1">
<li class="nav-item">
<a class="nav-link text-white" href="/">
<i class="bi bi-speedometer2 me-2"></i>Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="/sites">
<i class="bi bi-wordpress me-2"></i>Strony
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="/seo/stats">
<i class="bi bi-graph-up-arrow me-2"></i>Statystyki SEO
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="/global-topics">
<i class="bi bi-bookmarks me-2"></i>Tematy
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="/articles">
<i class="bi bi-file-earmark-text me-2"></i>Artykuły
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="/logs">
<i class="bi bi-journal-text me-2"></i>Log
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="/installer">
<i class="bi bi-cloud-upload me-2"></i>Instalator WP
</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" href="/settings">
<i class="bi bi-gear me-2"></i>Ustawienia
</a>
</li>
</ul>
<div class="p-3 border-top border-secondary">
<small class="text-secondary">Zalogowano jako:</small><br>
<span class="text-white"><?= htmlspecialchars(\App\Core\Auth::user()['username'] ?? '') ?></span>
<a href="/change-password" class="d-block text-secondary small mt-1"><i class="bi bi-key me-1"></i>Zmień hasło</a>
<div class="flex-grow-1 py-4 overflow-y-auto">
<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>
<ul class="nav flex-column mb-4">
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/') ? 'active' : '' ?>" href="/">
<i class="bi bi-grid-1x2-fill"></i>Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/sites') ? 'active' : '' ?>" href="/sites">
<i class="bi bi-browser-safari"></i>Strony WP
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/seo/stats') ? 'active' : '' ?>" href="/seo/stats">
<i class="bi bi-bar-chart-line-fill"></i>Statystyki SEO
</a>
</li>
</ul>
<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>
<ul class="nav flex-column mb-4">
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/global-topics') ? 'active' : '' ?>" href="/global-topics">
<i class="bi bi-tags-fill"></i>Biblioteka Tematów
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/articles') ? 'active' : '' ?>" href="/articles">
<i class="bi bi-journal-richtext"></i>Artykuły
</a>
</li>
</ul>
<div class="px-4 mb-3 text-uppercase text-secondary small fw-bold" style="font-size: 0.65rem; letter-spacing: 0.1em;">Administracja</div>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/installer') ? 'active' : '' ?>" href="/installer">
<i class="bi bi-magic"></i>Instalator WP
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= \App\Core\Router::isCurrent('/settings') ? 'active' : '' ?>" href="/settings">
<i class="bi bi-sliders"></i>Ustawienia API
</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>
</nav>