first commit

This commit is contained in:
2026-01-29 21:08:01 +01:00
commit 4e4dfe66c6
28 changed files with 2509 additions and 0 deletions

9
.htaccess Normal file
View File

@@ -0,0 +1,9 @@
RewriteEngine On
RewriteBase /
# Nie przepisuj istniejących plików i katalogów
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Przepisz wszystko na index.php
RewriteRule ^(.*)$ index.php [QSA,L]

17
.vscode/ftp-kr.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"host": "host117523.hostido.net.pl",
"username": "www@pagedev.pl",
"password": "dP9frFL3nw469pXJJ5vG",
"remotePath": "/public_html/",
"protocol": "ftp",
"port": 21,
"fileNameEncoding": "utf8",
"autoUpload": true,
"autoDelete": false,
"autoDownload": false,
"ignoreRemoteModification": true,
"ignore": [
".git",
"/.vscode"
]
}

16
.vscode/ftp-kr.sync.cache.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"ftp://host117523.hostido.net.pl:21@www@pagedev.pl": {
"public_html": {
"cgi-bin": {},
"models": {
"Database.php": {
"type": "-",
"size": 1094,
"lmtime": 1769712478164,
"modified": false
}
}
}
},
"$version": 1
}

12
.vscode/sftp.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "host117523.hostido.net.pl",
"host": "host117523.hostido.net.pl",
"protocol": "ftp",
"port": 21,
"username": "www@pagedev.pl",
"password": "dP9frFL3nw469pXJJ5vG",
"remotePath": "/public_html/",
"uploadOnSave": false,
"useTempFile": false,
"openSsh": false
}

66
PROJECT_LOG.md Normal file
View File

@@ -0,0 +1,66 @@
# Projekt: System 2FA + Notatnik (PHP MVC)
## Cel projektu
- Prosta aplikacja logowania 2FA w PHP z notatnikiem użytkownika.
- MVC (kontrolery, modele, widoki). Bootstrap UI.
## Najważniejsze funkcje (stan na dziś)
- Logowanie użytkownika + weryfikacja kodem 2FA (testowy kod w konsoli).
- Panel użytkownika po zalogowaniu.
- Notatnik: lista, dodawanie, edycja, usuwanie z ładnym modalem potwierdzenia.
- Kalendarz: widok miesięczny, dodawanie/edycja/usuwanie wydarzeń.
## Dane testowe
- Login: projectpro
- Hasło: testowehaslo
## Routing (przyjazne URL)
- /logowanie
- /uwierzytelnianie (POST)
- /weryfikacja
- /weryfikuj-kod (POST)
- /panel lub /pulpit
- /notatnik
- /notatka/nowa
- /notatka/edytuj?id=ID
- /notatka/zapisz (POST)
- /notatka/usun (POST)
- /kalendarz
- /wydarzenie/nowe
- /wydarzenie/edytuj?id=ID
- /wydarzenie/zapisz (POST)
- /wydarzenie/usun (POST)
- /wyloguj-sie
- /inicjalizacja
## Baza danych (SQLite)
- users: użytkownicy (hasła haszowane)
- verification_codes: kody 2FA z wygasaniem
- notes: notatki per user (created_at/updated_at)
- calendar_events: wydarzenia per user (event_date, created_at/updated_at)
## Struktura MVC
- controllers: InitController, LoginController, DashboardController, NotesController
- controllers: InitController, LoginController, DashboardController, NotesController, CalendarController
- models: Database, User, Notes, CalendarEvent
- views: login, verify, dashboard, notes/index, notes/form, calendar/index, calendar/form
- layout wspólny: views/layout.php
## Wygląd UI
- Bootstrap 5
- Dodatkowe style: public/css/style.css, public/css/notes.css, public/css/calendar.css
- JS: public/js/app.js
## Ważne uwagi techniczne
- Layout obsługuje: $pageTitle, $bodyClass, $extraHead, $extraScript, $content.
- Widoki korzystają z layoutu i generują zawartość przez output buffering.
- Notatnik używa modala Bootstrap do potwierdzenia usunięcia.
## Pliki kluczowe
- index.php: router i bootstrap aplikacji
- .htaccess: przepisywanie URL
- models/Notes.php: inicjalizacja tabeli notatek przy użyciu
## Do dalszego pilnowania
- Utrzymywać spójność URL z routerem.
- Aktualizować ten plik po kolejnych zmianach.

77
README.md Normal file
View File

@@ -0,0 +1,77 @@
# System Logowania 2FA
Prosta aplikacja demonstracyjna logowania dwuskładnikowego w PHP.
## Wymagania
- PHP 7.4 lub nowszy
- SQLite3
- Serwer WWW (Apache/Nginx) lub wbudowany serwer PHP
## Instalacja
1. Skopiuj pliki na serwer WWW
2. Upewnij się, że katalog `database/` ma uprawnienia do zapisu
3. Przejdź do `/inicjalizacja` (lub `index.php?action=init`) aby zainicjalizować bazę danych
4. Zaloguj się używając testowego konta
## Przyjazne URLe
Aplikacja wspiera przyjazne URLe (friendly URLs):
- `/` lub `/logowanie` - ekran logowania
- `/uwierzytelnianie` - proces uwierzytelniania
- `/weryfikacja` - ekran weryfikacji 2FA
- `/weryfikuj-kod` - weryfikacja kodu
- `/panel` lub `/dashboard` - panel użytkownika
- `/wyloguj-sie` - wylogowanie
- `/inicjalizacja` - inicjalizacja bazy danych
Stare URLe z parametrem `?action=` nadal działają dla kompatybilności.
## Dane testowe
- **Login:** projectpro
- **Hasło:** testowehaslo
## Struktura projektu
```
projektphp/
├── controllers/ # Kontrolery MVC
│ ├── InitController.php
│ ├── LoginController.php
│ └── DashboardController.php
├── models/ # Modele danych
│ ├── Database.php
│ └── User.php
├── views/ # Widoki PHP
│ ├── layout.php
│ ├── login.php
│ ├── verify.php
│ └── dashboard.php
├── public/ # Publiczne zasoby
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── app.js
├── database/ # Baza danych SQLite
│ └── database.db
├── .htaccess # Konfiguracja Apache
└── index.php # Punkt wejścia
```
## Proces logowania
1. Użytkownik wprowadza login i hasło
2. System generuje 6-cyfrowy kod weryfikacyjny
3. Kod wyświetlany jest w konsoli przeglądarki (tryb testowy)
4. Po wprowadzeniu prawidłowego kodu użytkownik jest zalogowany
5. Kod wygasa po 15 minutach
## Uwagi
- W trybie testowym kod weryfikacyjny jest wyświetlany w konsoli przeglądarki (F12)
- W produkcji kod powinien być wysyłany emailem
- Hasła są hashowane przy użyciu `password_hash()`
- Sesje są bezpiecznie zarządzane

View File

@@ -0,0 +1,142 @@
<?php
class CalendarController
{
private $calendarModel;
public function __construct()
{
if (!isset($_SESSION['user_id'])) {
header('Location: /logowanie');
exit;
}
$this->calendarModel = new CalendarEvent();
}
public function index()
{
$userId = $_SESSION['user_id'];
$month = $_GET['month'] ?? date('Y-m');
$monthDate = DateTime::createFromFormat('Y-m', $month);
if (!$monthDate) {
$monthDate = new DateTime('first day of this month');
$month = $monthDate->format('Y-m');
}
$firstDay = (clone $monthDate)->modify('first day of this month');
$daysInMonth = (int)$firstDay->format('t');
$startWeekday = (int)$firstDay->format('N');
$prevMonth = (clone $monthDate)->modify('-1 month')->format('Y-m');
$nextMonth = (clone $monthDate)->modify('+1 month')->format('Y-m');
$monthLabel = $monthDate->format('F Y');
$selectedDate = $_GET['date'] ?? date('Y-m-d');
$events = $this->calendarModel->getByMonth($userId, $month);
$eventsByDate = [];
foreach ($events as $event) {
$eventsByDate[$event['event_date']][] = $event;
}
$eventsForSelected = $this->calendarModel->getByDate($userId, $selectedDate);
require_once __DIR__ . '/../views/calendar/index.php';
}
public function create()
{
$event = null;
$defaultDate = $_GET['date'] ?? date('Y-m-d');
$returnMonth = $_GET['month'] ?? date('Y-m');
require_once __DIR__ . '/../views/calendar/form.php';
}
public function edit()
{
$eventId = $_GET['id'] ?? null;
if (!$eventId) {
$_SESSION['error'] = 'Nie podano ID wydarzenia';
header('Location: /kalendarz');
exit;
}
$userId = $_SESSION['user_id'];
$event = $this->calendarModel->getById($eventId, $userId);
if (!$event) {
$_SESSION['error'] = 'Wydarzenie nie zostało znalezione';
header('Location: /kalendarz');
exit;
}
$defaultDate = $event['event_date'];
$returnMonth = $_GET['month'] ?? date('Y-m', strtotime($event['event_date']));
require_once __DIR__ . '/../views/calendar/form.php';
}
public function save()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /kalendarz');
exit;
}
$userId = $_SESSION['user_id'];
$eventId = $_POST['event_id'] ?? null;
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['content'] ?? '');
$eventDate = $_POST['event_date'] ?? date('Y-m-d');
$returnMonth = $_POST['return_month'] ?? date('Y-m');
if ($title === '') {
$_SESSION['error'] = 'Tytuł wydarzenia jest wymagany';
$redirect = $eventId ? "/wydarzenie/edytuj?id=$eventId&month=$returnMonth" : "/wydarzenie/nowe?date=$eventDate&month=$returnMonth";
header('Location: ' . $redirect);
exit;
}
if ($eventId) {
$this->calendarModel->update($eventId, $userId, $title, $content, $eventDate);
$_SESSION['success'] = 'Wydarzenie zostało zaktualizowane';
} else {
$this->calendarModel->create($userId, $title, $content, $eventDate);
$_SESSION['success'] = 'Wydarzenie zostało utworzone';
}
header('Location: /kalendarz?month=' . $returnMonth . '&date=' . $eventDate);
exit;
}
public function delete()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /kalendarz');
exit;
}
$eventId = $_POST['event_id'] ?? null;
$returnMonth = $_POST['return_month'] ?? date('Y-m');
$returnDate = $_POST['return_date'] ?? date('Y-m-d');
if (!$eventId) {
$_SESSION['error'] = 'Nie podano ID wydarzenia';
header('Location: /kalendarz');
exit;
}
$userId = $_SESSION['user_id'];
$result = $this->calendarModel->delete($eventId, $userId);
if ($result) {
$_SESSION['success'] = 'Wydarzenie zostało usunięte';
} else {
$_SESSION['error'] = 'Nie udało się usunąć wydarzenia';
}
header('Location: /kalendarz?month=' . $returnMonth . '&date=' . $returnDate);
exit;
}
}

View File

@@ -0,0 +1,27 @@
<?php
class DashboardController
{
public function index()
{
if (!isset($_SESSION['user_id'])) {
header('Location: /logowanie');
exit;
}
$userModel = new User();
$user = $userModel->getUserById($_SESSION['user_id']);
$calendarModel = new CalendarEvent();
$today = new DateTime('today');
$weekStart = (clone $today)->modify('monday this week');
$weekEnd = (clone $weekStart)->modify('+6 days');
$eventsThisWeek = $calendarModel->countByDateRange(
$_SESSION['user_id'],
$weekStart->format('Y-m-d'),
$weekEnd->format('Y-m-d')
);
require_once __DIR__ . '/../views/dashboard.php';
}
}

View File

@@ -0,0 +1,55 @@
<?php
class InitController
{
public function index()
{
try {
$db = Database::getInstance();
$db->initDatabase();
echo "<!DOCTYPE html>
<html lang='pl'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Inicjalizacja bazy danych</title>
<link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css' rel='stylesheet'>
</head>
<body class='bg-light'>
<div class='container mt-5'>
<div class='alert alert-success' role='alert'>
<h4 class='alert-heading'>Sukces!</h4>
<p>Baza danych została pomyślnie zainicjalizowana.</p>
<hr>
<p class='mb-0'>Testowy użytkownik:<br>
Login: <strong>projectpro</strong><br>
Hasło: <strong>testowehaslo</strong></p>
</div>
<a href='/logowanie' class='btn btn-primary'>Przejdź do logowania</a>
</div>
</body>
</html>";
} catch (Exception $e) {
echo "<!DOCTYPE html>
<html lang='pl'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Błąd inicjalizacji</title>
<link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css' rel='stylesheet'>
</head>
<body class='bg-light'>
<div class='container mt-5'>
<div class='alert alert-danger' role='alert'>
<h4 class='alert-heading'>Błąd!</h4>
<p>Nie udało się zainicjalizować bazy danych.</p>
<hr>
<p class='mb-0'>Szczegóły: " . htmlspecialchars($e->getMessage()) . "</p>
</div>
</div>
</body>
</html>";
}
}
}

View File

@@ -0,0 +1,98 @@
<?php
class LoginController
{
private $userModel;
public function __construct()
{
$this->userModel = new User();
}
public function index()
{
if (isset($_SESSION['user_id'])) {
header('Location: /panel');
exit;
}
require_once __DIR__ . '/../views/login.php';
}
public function authenticate()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /logowanie');
exit;
}
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$user = $this->userModel->authenticate($username, $password);
if ($user) {
// Generowanie kodu weryfikacyjnego
$code = $this->userModel->generateVerificationCode($user['id']);
// Zapisanie ID użytkownika w sesji tymczasowo
$_SESSION['pending_user_id'] = $user['id'];
$_SESSION['pending_username'] = $user['username'];
// W rzeczywistości tutaj wysłalibyśmy email
// Dla testów kod będzie wyświetlony w konsoli przeglądarki
$_SESSION['test_code'] = $code;
header('Location: /weryfikacja');
exit;
} else {
$_SESSION['error'] = 'Nieprawidłowy login lub hasło';
header('Location: /logowanie');
exit;
}
}
public function verify()
{
if (!isset($_SESSION['pending_user_id'])) {
header('Location: /logowanie');
exit;
}
require_once __DIR__ . '/../views/verify.php';
}
public function verifyCode()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /logowanie');
exit;
}
if (!isset($_SESSION['pending_user_id'])) {
header('Location: /logowanie');
exit;
}
$code = $_POST['code'] ?? '';
$userId = $_SESSION['pending_user_id'];
if ($this->userModel->verifyCode($userId, $code)) {
// Zalogowanie użytkownika
$_SESSION['user_id'] = $userId;
$_SESSION['username'] = $_SESSION['pending_username'];
// Czyszczenie danych tymczasowych
unset($_SESSION['pending_user_id']);
unset($_SESSION['pending_username']);
unset($_SESSION['test_code']);
header('Location: /panel');
exit;
} else {
$_SESSION['error'] = 'Nieprawidłowy kod weryfikacyjny lub kod wygasł';
header('Location: /weryfikacja');
exit;
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
class NotesController
{
private $notesModel;
public function __construct()
{
$this->notesModel = new Notes();
// Sprawdzenie czy użytkownik jest zalogowany
if (!isset($_SESSION['user_id'])) {
header('Location: /logowanie');
exit;
}
}
public function index()
{
$userId = $_SESSION['user_id'];
$notes = $this->notesModel->getAllByUser($userId);
$notesCount = $this->notesModel->getCount($userId);
require_once __DIR__ . '/../views/notes/index.php';
}
public function create()
{
$note = null; // Pusty formularz dla nowej notatki
require_once __DIR__ . '/../views/notes/form.php';
}
public function edit()
{
$noteId = $_GET['id'] ?? null;
if (!$noteId) {
$_SESSION['error'] = 'Nie podano ID notatki';
header('Location: /notatnik');
exit;
}
$userId = $_SESSION['user_id'];
$note = $this->notesModel->getById($noteId, $userId);
if (!$note) {
$_SESSION['error'] = 'Notatka nie została znaleziona';
header('Location: /notatnik');
exit;
}
require_once __DIR__ . '/../views/notes/form.php';
}
public function save()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /notatnik');
exit;
}
$userId = $_SESSION['user_id'];
$noteId = $_POST['note_id'] ?? null;
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['content'] ?? '');
if (empty($title)) {
$_SESSION['error'] = 'Tytuł notatki jest wymagany';
header('Location: ' . ($noteId ? "/notatka/edytuj?id=$noteId" : '/notatka/nowa'));
exit;
}
if ($noteId) {
// Aktualizacja istniejącej notatki
$result = $this->notesModel->update($noteId, $userId, $title, $content);
$_SESSION['success'] = 'Notatka została zaktualizowana';
} else {
// Tworzenie nowej notatki
$result = $this->notesModel->create($userId, $title, $content);
$_SESSION['success'] = 'Notatka została utworzona';
}
header('Location: /notatnik');
exit;
}
public function delete()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: /notatnik');
exit;
}
$noteId = $_POST['note_id'] ?? null;
if (!$noteId) {
$_SESSION['error'] = 'Nie podano ID notatki';
header('Location: /notatnik');
exit;
}
$userId = $_SESSION['user_id'];
$result = $this->notesModel->delete($noteId, $userId);
if ($result) {
$_SESSION['success'] = 'Notatka została usunięta';
} else {
$_SESSION['error'] = 'Nie udało się usunąć notatki';
}
header('Location: /notatnik');
exit;
}
}

157
index.php Normal file
View File

@@ -0,0 +1,157 @@
<?php
session_start();
// Autoloader dla klas
spl_autoload_register(function ($class) {
$paths = [
__DIR__ . '/controllers/' . $class . '.php',
__DIR__ . '/models/' . $class . '.php'
];
foreach ($paths as $path) {
if (file_exists($path)) {
require_once $path;
return;
}
}
});
// Pobieranie ścieżki z URL (odporne na root i podkatalog)
$requestUri = $_SERVER['REQUEST_URI'];
$uriPath = parse_url($requestUri, PHP_URL_PATH) ?? '/';
$basePath = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
if ($basePath && $basePath !== '.' && strpos($uriPath, $basePath) === 0) {
$uriPath = substr($uriPath, strlen($basePath));
}
$path = trim($uriPath, '/');
// Debug - usuń po naprawie
// error_log("REQUEST_URI: " . $requestUri);
// error_log("Base Path: " . $basePath);
// error_log("Path: " . $path);
// Mapowanie przyjaznych URLi na akcje
$routes = [
'' => 'login',
'logowanie' => 'login',
'uwierzytelnianie' => 'authenticate',
'weryfikacja' => 'verify',
'weryfikuj-kod' => 'verify-code',
'panel' => 'dashboard',
'dashboard' => 'dashboard',
'pulpit' => 'dashboard',
'wyloguj-sie' => 'logout',
'inicjalizacja' => 'init',
'notatnik' => 'notes',
'notatki' => 'notes',
'notatka/nowa' => 'note-create',
'notatka/edytuj' => 'note-edit',
'notatka/zapisz' => 'note-save',
'notatka/usun' => 'note-delete',
'kalendarz' => 'calendar',
'wydarzenie/nowe' => 'event-create',
'wydarzenie/edytuj' => 'event-edit',
'wydarzenie/zapisz' => 'event-save',
'wydarzenie/usun' => 'event-delete',
];
// Obsługa starych URLi z parametrem action dla kompatybilności
if (isset($_GET['action'])) {
$action = $_GET['action'];
} else {
$action = $routes[$path] ?? 'login';
}
switch ($action) {
case 'init':
$controller = new InitController();
$controller->index();
break;
case 'login':
$controller = new LoginController();
$controller->index();
break;
case 'authenticate':
$controller = new LoginController();
$controller->authenticate();
break;
case 'verify':
$controller = new LoginController();
$controller->verify();
break;
case 'verify-code':
$controller = new LoginController();
$controller->verifyCode();
break;
case 'dashboard':
$controller = new DashboardController();
$controller->index();
break;
case 'notes':
$controller = new NotesController();
$controller->index();
break;
case 'note-create':
$controller = new NotesController();
$controller->create();
break;
case 'note-edit':
$controller = new NotesController();
$controller->edit();
break;
case 'note-save':
$controller = new NotesController();
$controller->save();
break;
case 'note-delete':
$controller = new NotesController();
$controller->delete();
break;
case 'calendar':
$controller = new CalendarController();
$controller->index();
break;
case 'event-create':
$controller = new CalendarController();
$controller->create();
break;
case 'event-edit':
$controller = new CalendarController();
$controller->edit();
break;
case 'event-save':
$controller = new CalendarController();
$controller->save();
break;
case 'event-delete':
$controller = new CalendarController();
$controller->delete();
break;
case 'logout':
session_destroy();
header('Location: /logowanie');
exit;
break;
default:
header('Location: /logowanie');
exit;
}

104
models/CalendarEvent.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
class CalendarEvent
{
private $db;
public function __construct()
{
$this->db = Database::getInstance()->getConnection();
$this->initTable();
}
private function initTable()
{
$sql = "CREATE TABLE IF NOT EXISTS calendar_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
event_date DATE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)";
try {
$this->db->exec($sql);
} catch (PDOException $e) {
// Tabela już istnieje
}
}
public function getByMonth($userId, $month)
{
$stmt = $this->db->prepare("
SELECT * FROM calendar_events
WHERE user_id = ? AND strftime('%Y-%m', event_date) = ?
ORDER BY event_date ASC, id ASC
");
$stmt->execute([$userId, $month]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function getByDate($userId, $date)
{
$stmt = $this->db->prepare("
SELECT * FROM calendar_events
WHERE user_id = ? AND event_date = ?
ORDER BY id ASC
");
$stmt->execute([$userId, $date]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function getById($id, $userId)
{
$stmt = $this->db->prepare("
SELECT * FROM calendar_events
WHERE id = ? AND user_id = ?
");
$stmt->execute([$id, $userId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function create($userId, $title, $content, $eventDate)
{
$stmt = $this->db->prepare("
INSERT INTO calendar_events (user_id, title, content, event_date)
VALUES (?, ?, ?, ?)
");
return $stmt->execute([$userId, $title, $content, $eventDate]);
}
public function update($id, $userId, $title, $content, $eventDate)
{
$stmt = $this->db->prepare("
UPDATE calendar_events
SET title = ?, content = ?, event_date = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND user_id = ?
");
return $stmt->execute([$title, $content, $eventDate, $id, $userId]);
}
public function delete($id, $userId)
{
$stmt = $this->db->prepare("
DELETE FROM calendar_events
WHERE id = ? AND user_id = ?
");
return $stmt->execute([$id, $userId]);
}
public function countByDateRange($userId, $startDate, $endDate)
{
$stmt = $this->db->prepare("
SELECT COUNT(*) AS count
FROM calendar_events
WHERE user_id = ? AND event_date BETWEEN ? AND ?
");
$stmt->execute([$userId, $startDate, $endDate]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return (int)$result['count'];
}
}

73
models/Database.php Normal file
View File

@@ -0,0 +1,73 @@
<?php
class Database
{
private static $instance = null;
private $connection;
private $dbPath;
private function __construct()
{
$this->dbPath = __DIR__ . '/../database/database.db';
}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection()
{
if ($this->connection === null) {
try {
$this->connection = new PDO('sqlite:' . $this->dbPath);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('Connection failed: ' . $e->getMessage());
}
}
return $this->connection;
}
public function initDatabase()
{
$db = $this->getConnection();
// Tworzenie tabeli użytkowników
$sql = "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)";
$db->exec($sql);
// Tworzenie tabeli kodów weryfikacyjnych
$sql = "CREATE TABLE IF NOT EXISTS verification_codes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
code TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME NOT NULL,
used INTEGER DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id)
)";
$db->exec($sql);
// Dodawanie testowego użytkownika
$username = 'projectpro';
$password = password_hash('testowehaslo', PASSWORD_DEFAULT);
try {
$stmt = $db->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->execute([$username, $password]);
} catch (PDOException $e) {
// Użytkownik już istnieje
}
return true;
}
}

92
models/Notes.php Normal file
View File

@@ -0,0 +1,92 @@
<?php
class Notes
{
private $db;
public function __construct()
{
$this->db = Database::getInstance()->getConnection();
$this->initTable();
}
private function initTable()
{
// Tworzenie tabeli notatek jeśli nie istnieje
$sql = "CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)";
try {
$this->db->exec($sql);
} catch (PDOException $e) {
// Tabela już istnieje
}
}
public function getAllByUser($userId)
{
$stmt = $this->db->prepare("
SELECT * FROM notes
WHERE user_id = ?
ORDER BY updated_at DESC
");
$stmt->execute([$userId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function getById($id, $userId)
{
$stmt = $this->db->prepare("
SELECT * FROM notes
WHERE id = ? AND user_id = ?
");
$stmt->execute([$id, $userId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function create($userId, $title, $content)
{
$stmt = $this->db->prepare("
INSERT INTO notes (user_id, title, content)
VALUES (?, ?, ?)
");
return $stmt->execute([$userId, $title, $content]);
}
public function update($id, $userId, $title, $content)
{
$stmt = $this->db->prepare("
UPDATE notes
SET title = ?, content = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND user_id = ?
");
return $stmt->execute([$title, $content, $id, $userId]);
}
public function delete($id, $userId)
{
$stmt = $this->db->prepare("
DELETE FROM notes
WHERE id = ? AND user_id = ?
");
return $stmt->execute([$id, $userId]);
}
public function getCount($userId)
{
$stmt = $this->db->prepare("
SELECT COUNT(*) as count FROM notes
WHERE user_id = ?
");
$stmt->execute([$userId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['count'];
}
}

70
models/User.php Normal file
View File

@@ -0,0 +1,70 @@
<?php
class User
{
private $db;
public function __construct()
{
$this->db = Database::getInstance()->getConnection();
}
public function authenticate($username, $password)
{
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
return $user;
}
return false;
}
public function generateVerificationCode($userId)
{
// Generowanie 6-cyfrowego kodu
$code = sprintf('%06d', random_int(0, 999999));
// Ustawienie czasu wygaśnięcia (15 minut)
$expiresAt = date('Y-m-d H:i:s', strtotime('+15 minutes'));
// Usuwanie starych nieużytych kodów dla tego użytkownika
$stmt = $this->db->prepare("DELETE FROM verification_codes WHERE user_id = ? AND used = 0");
$stmt->execute([$userId]);
// Zapisywanie nowego kodu
$stmt = $this->db->prepare("INSERT INTO verification_codes (user_id, code, expires_at) VALUES (?, ?, ?)");
$stmt->execute([$userId, $code, $expiresAt]);
return $code;
}
public function verifyCode($userId, $code)
{
$stmt = $this->db->prepare("
SELECT * FROM verification_codes
WHERE user_id = ? AND code = ? AND used = 0 AND expires_at > datetime('now')
ORDER BY created_at DESC LIMIT 1
");
$stmt->execute([$userId, $code]);
$verification = $stmt->fetch(PDO::FETCH_ASSOC);
if ($verification) {
// Oznaczenie kodu jako użyty
$stmt = $this->db->prepare("UPDATE verification_codes SET used = 1 WHERE id = ?");
$stmt->execute([$verification['id']]);
return true;
}
return false;
}
public function getUserById($userId)
{
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}

81
public/css/calendar.css Normal file
View File

@@ -0,0 +1,81 @@
/* Calendar styles */
.calendar-title {
font-weight: 600;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 0.5rem;
}
.calendar-weekday {
text-align: center;
font-weight: 600;
color: #6c757d;
padding: 0.5rem 0;
}
.calendar-cell {
min-height: 110px;
border: 1px solid #e9ecef;
border-radius: 0.5rem;
padding: 0.5rem;
background: #fff;
position: relative;
}
.calendar-cell--disabled {
background: #f8f9fa;
color: #adb5bd;
}
.calendar-cell--selected {
border-color: #0d6efd;
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.2);
}
.calendar-day {
display: inline-block;
font-weight: 600;
color: #212529;
text-decoration: none;
}
.calendar-day:hover {
text-decoration: underline;
}
.calendar-events {
margin-top: 0.4rem;
font-size: 0.8rem;
}
.calendar-event {
display: flex;
align-items: center;
gap: 0.25rem;
color: #0d6efd;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.calendar-event i {
font-size: 0.5rem;
}
.calendar-event--more {
color: #6c757d;
}
@media (max-width: 768px) {
.calendar-grid {
grid-template-columns: repeat(2, 1fr);
}
.calendar-weekday {
display: none;
}
}

137
public/css/notes.css Normal file
View File

@@ -0,0 +1,137 @@
/* Notes styles */
.note-card {
transition: transform 0.2s, box-shadow 0.2s;
border-radius: 0.5rem;
}
.note-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.note-content {
min-height: 100px;
color: #666;
font-size: 0.9rem;
line-height: 1.6;
}
.note-meta {
border-top: 1px solid #eee;
padding-top: 0.5rem;
}
/* Gradient backgrounds */
.bg-gradient-info {
background: linear-gradient(135deg, #36d1dc 0%, #5b86e5 100%);
}
.bg-gradient-success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.hover-shadow {
transition: all 0.3s ease;
cursor: pointer;
}
.hover-shadow:hover {
transform: translateY(-5px);
box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.2) !important;
}
/* Modal animations */
.modal.fade .modal-dialog {
transition: transform 0.3s ease-out;
transform: scale(0.9);
}
.modal.show .modal-dialog {
transform: scale(1);
}
/* Navbar improvements */
.navbar {
box-shadow: 0 2px 4px rgba(0,0,0,.1);
}
.navbar-nav .nav-link {
transition: all 0.2s ease;
border-radius: 0.25rem;
margin: 0 0.25rem;
}
.navbar-nav .nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.navbar-nav .nav-link.active {
background-color: rgba(255, 255, 255, 0.15);
font-weight: 500;
}
/* Card improvements */
.card {
border: none;
border-radius: 0.75rem;
}
/* Button improvements */
.btn {
transition: all 0.2s ease;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.btn:active {
transform: translateY(0);
}
/* Empty state */
.fa-inbox {
opacity: 0.3;
}
/* Responsive textarea */
textarea {
resize: vertical;
min-height: 200px;
}
/* Delete modal styling */
#deleteModal .modal-header {
border-top-left-radius: 0.75rem;
border-top-right-radius: 0.75rem;
}
#deleteModal .modal-content {
border-radius: 0.75rem;
border: none;
}
/* Alert animations */
@keyframes slideIn {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.alert {
animation: slideIn 0.3s ease-out;
}
/* Note title truncation on cards */
.note-card .card-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

115
public/css/style.css Normal file
View File

@@ -0,0 +1,115 @@
/* Gradient Background */
.bg-gradient-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
/* Login/Verify Card Styles */
.card {
border-radius: 1rem;
}
.card-body {
padding: 2rem;
}
/* Form Styles */
.form-control-user {
font-size: 0.9rem;
border-radius: 10rem;
padding: 1.5rem 1rem;
}
.btn-user {
font-size: 0.9rem;
border-radius: 10rem;
padding: 0.75rem 1rem;
}
/* Background Images for Login/Verify Cards */
.bg-login-image {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-size: cover;
background-position: center;
border-radius: 1rem 0 0 1rem;
position: relative;
}
.bg-login-image::before {
content: "🔐";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 6rem;
opacity: 0.3;
}
.bg-verify-image {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
background-size: cover;
background-position: center;
border-radius: 1rem 0 0 1rem;
position: relative;
}
.bg-verify-image::before {
content: "🔑";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 6rem;
opacity: 0.3;
}
/* Navbar Styles */
.navbar-dark {
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
}
.navbar-brand {
font-weight: 700;
padding-left: 1rem;
}
/* Alert Styles */
.alert {
border-radius: 0.5rem;
}
/* Card Animations */
.card {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Code Input Special Style */
input[name="code"] {
font-size: 1.5rem;
letter-spacing: 0.5rem;
font-weight: bold;
}
/* Dashboard Cards */
.bg-light {
background-color: #f8f9fc !important;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.bg-login-image,
.bg-verify-image {
display: none !important;
}
}

50
public/js/app.js Normal file
View File

@@ -0,0 +1,50 @@
// Simple app.js for additional functionality
document.addEventListener('DOMContentLoaded', function() {
// Auto-dismiss alerts after 5 seconds
const alerts = document.querySelectorAll('.alert:not(.alert-info)');
alerts.forEach(function(alert) {
setTimeout(function() {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
}, 5000);
});
// Add loading state to forms
const forms = document.querySelectorAll('form');
forms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Przetwarzanie...';
}
});
});
// Code input - accept only numbers
const codeInput = document.getElementById('code');
if (codeInput) {
codeInput.addEventListener('keypress', function(e) {
// Allow only numbers
if (e.key < '0' || e.key > '9') {
e.preventDefault();
}
});
// Auto-submit when 6 digits entered
codeInput.addEventListener('input', function(e) {
if (this.value.length === 6) {
// Optional: auto-submit after 6 digits
// this.form.submit();
}
});
}
// Add smooth scroll behavior
document.documentElement.style.scrollBehavior = 'smooth';
});
// Console styling for verification code (if shown)
console.log('%cSystem Logowania 2FA', 'color: #667eea; font-size: 24px; font-weight: bold;');
console.log('%cAplikacja demonstracyjna', 'color: #666; font-size: 12px;');

152
views/calendar/form.php Normal file
View File

@@ -0,0 +1,152 @@
<?php
$pageTitle = (isset($event) ? 'Edycja' : 'Nowe') . ' wydarzenie - System 2FA';
$bodyClass = 'bg-light';
$extraHead = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">'
. '<link rel="stylesheet" href="/public/css/calendar.css">';
ob_start();
?>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/panel">
<i class="fas fa-shield-alt"></i> System 2FA
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/pulpit">
<i class="fas fa-home"></i> Pulpit
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/notatnik">
<i class="fas fa-sticky-note"></i> Notatnik
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/kalendarz">
<i class="fas fa-calendar-alt"></i> Kalendarz
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<span class="nav-link text-white">
<i class="fas fa-user"></i> <?php echo htmlspecialchars($_SESSION['username']); ?>
</span>
</li>
<li class="nav-item">
<a class="nav-link" href="/wyloguj-sie">
<i class="fas fa-sign-out-alt"></i> Wyloguj
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">
<i class="fas fa-calendar-plus"></i> <?php echo isset($event) ? 'Edycja wydarzenia' : 'Nowe wydarzenie'; ?>
</h4>
</div>
<div class="card-body">
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle"></i> <?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="/wydarzenie/zapisz">
<?php if (isset($event)): ?>
<input type="hidden" name="event_id" value="<?php echo $event['id']; ?>">
<?php endif; ?>
<input type="hidden" name="return_month" value="<?php echo htmlspecialchars($returnMonth); ?>">
<div class="mb-3">
<label for="title" class="form-label">
<i class="fas fa-heading"></i> Tytuł wydarzenia *
</label>
<input type="text"
class="form-control form-control-lg"
id="title"
name="title"
placeholder="Wpisz tytuł wydarzenia..."
value="<?php echo isset($event) ? htmlspecialchars($event['title']) : ''; ?>"
required
autofocus>
</div>
<div class="mb-3">
<label for="event_date" class="form-label">
<i class="fas fa-calendar-day"></i> Data wydarzenia *
</label>
<input type="date"
class="form-control"
id="event_date"
name="event_date"
value="<?php echo htmlspecialchars($defaultDate); ?>"
required>
</div>
<div class="mb-3">
<label for="content" class="form-label">
<i class="fas fa-align-left"></i> Treść wydarzenia
</label>
<textarea class="form-control"
id="content"
name="content"
rows="8"
placeholder="Wpisz treść wydarzenia..."><?php echo isset($event) ? htmlspecialchars($event['content']) : ''; ?></textarea>
<small class="form-text text-muted">
<i class="fas fa-info-circle"></i> Pole opcjonalne
</small>
</div>
<div class="d-flex justify-content-between align-items-center">
<a href="/kalendarz?month=<?php echo htmlspecialchars($returnMonth); ?>&date=<?php echo htmlspecialchars($defaultDate); ?>" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Powrót do kalendarza
</a>
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-save"></i> Zapisz wydarzenie
</button>
</div>
</form>
</div>
<?php if (isset($event)): ?>
<div class="card-footer text-muted small">
<div class="row">
<div class="col-md-6">
<i class="fas fa-calendar-plus"></i> Utworzono:
<?php
$date = new DateTime($event['created_at']);
echo $date->format('d.m.Y H:i');
?>
</div>
<div class="col-md-6 text-md-end">
<i class="fas fa-calendar-check"></i> Zaktualizowano:
<?php
$date = new DateTime($event['updated_at']);
echo $date->format('d.m.Y H:i');
?>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
require __DIR__ . '/../layout.php';
?>

221
views/calendar/index.php Normal file
View File

@@ -0,0 +1,221 @@
<?php
$pageTitle = 'Kalendarz - System 2FA';
$bodyClass = 'bg-light';
$extraHead = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">'
. '<link rel="stylesheet" href="/public/css/calendar.css">';
ob_start();
?>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/panel">
<i class="fas fa-shield-alt"></i> System 2FA
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/pulpit">
<i class="fas fa-home"></i> Pulpit
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/notatnik">
<i class="fas fa-sticky-note"></i> Notatnik
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/kalendarz">
<i class="fas fa-calendar-alt"></i> Kalendarz
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<span class="nav-link text-white">
<i class="fas fa-user"></i> <?php echo htmlspecialchars($_SESSION['username']); ?>
</span>
</li>
<li class="nav-item">
<a class="nav-link" href="/wyloguj-sie">
<i class="fas fa-sign-out-alt"></i> Wyloguj
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle"></i> <?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle"></i> <?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center gap-2">
<a class="btn btn-outline-secondary" href="/kalendarz?month=<?php echo $prevMonth; ?>">
<i class="fas fa-chevron-left"></i>
</a>
<h3 class="mb-0 calendar-title"><?php echo htmlspecialchars($monthLabel); ?></h3>
<a class="btn btn-outline-secondary" href="/kalendarz?month=<?php echo $nextMonth; ?>">
<i class="fas fa-chevron-right"></i>
</a>
</div>
<a href="/wydarzenie/nowe?date=<?php echo htmlspecialchars($selectedDate); ?>&month=<?php echo $month; ?>" class="btn btn-primary">
<i class="fas fa-plus"></i> Nowe wydarzenie
</a>
</div>
<div class="calendar-grid">
<div class="calendar-weekday">Pon</div>
<div class="calendar-weekday">Wto</div>
<div class="calendar-weekday">Śro</div>
<div class="calendar-weekday">Czw</div>
<div class="calendar-weekday">Pią</div>
<div class="calendar-weekday">Sob</div>
<div class="calendar-weekday">Ndz</div>
<?php
$dayCounter = 1;
$totalCells = 42; // 6 tygodni
for ($cell = 1; $cell <= $totalCells; $cell++):
$cellDay = $cell - $startWeekday + 1;
$isCurrentMonth = $cellDay >= 1 && $cellDay <= $daysInMonth;
$dateStr = $isCurrentMonth ? $monthDate->format('Y-m') . '-' . str_pad($cellDay, 2, '0', STR_PAD_LEFT) : '';
$isSelected = $isCurrentMonth && $dateStr === $selectedDate;
$eventsForDay = $isCurrentMonth && isset($eventsByDate[$dateStr]) ? $eventsByDate[$dateStr] : [];
?>
<div class="calendar-cell <?php echo $isCurrentMonth ? '' : 'calendar-cell--disabled'; ?> <?php echo $isSelected ? 'calendar-cell--selected' : ''; ?>">
<?php if ($isCurrentMonth): ?>
<a class="calendar-day" href="/kalendarz?month=<?php echo $month; ?>&date=<?php echo $dateStr; ?>">
<?php echo $cellDay; ?>
</a>
<?php if (!empty($eventsForDay)): ?>
<div class="calendar-events">
<?php foreach (array_slice($eventsForDay, 0, 2) as $event): ?>
<div class="calendar-event">
<i class="fas fa-circle"></i>
<?php echo htmlspecialchars($event['title']); ?>
</div>
<?php endforeach; ?>
<?php if (count($eventsForDay) > 2): ?>
<div class="calendar-event calendar-event--more">+<?php echo count($eventsForDay) - 2; ?> więcej</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php endfor; ?>
</div>
<div class="row mt-4">
<div class="col-lg-6">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<i class="fas fa-list"></i> Wydarzenia: <?php echo htmlspecialchars($selectedDate); ?>
</div>
<div class="card-body">
<?php if (empty($eventsForSelected)): ?>
<p class="text-muted mb-0">Brak wydarzeń dla wybranego dnia.</p>
<?php else: ?>
<div class="list-group">
<?php foreach ($eventsForSelected as $event): ?>
<div class="list-group-item list-group-item-action d-flex justify-content-between align-items-start">
<div class="me-3">
<h6 class="mb-1">
<i class="fas fa-calendar-check text-primary"></i>
<?php echo htmlspecialchars($event['title']); ?>
</h6>
<p class="mb-1 small text-muted">
<?php echo nl2br(htmlspecialchars($event['content'] ?? '')); ?>
</p>
</div>
<div class="btn-group">
<a class="btn btn-sm btn-outline-primary" href="/wydarzenie/edytuj?id=<?php echo $event['id']; ?>&month=<?php echo $month; ?>">
<i class="fas fa-edit"></i>
</a>
<button class="btn btn-sm btn-outline-danger" type="button"
onclick="confirmDeleteEvent(<?php echo $event['id']; ?>, '<?php echo htmlspecialchars(addslashes($event['title'])); ?>')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-lg-6 mt-3 mt-lg-0">
<div class="card shadow-sm">
<div class="card-header bg-light">
<i class="fas fa-info-circle"></i> Szybkie akcje
</div>
<div class="card-body">
<p class="mb-2">Dodaj wydarzenie dla wybranego dnia:</p>
<a class="btn btn-primary" href="/wydarzenie/nowe?date=<?php echo htmlspecialchars($selectedDate); ?>&month=<?php echo $month; ?>">
<i class="fas fa-plus"></i> Dodaj wydarzenie
</a>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="deleteEventModal" tabindex="-1" aria-labelledby="deleteEventModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteEventModalLabel">
<i class="fas fa-exclamation-triangle"></i> Potwierdź usunięcie
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Czy na pewno chcesz usunąć wydarzenie:</p>
<p class="fw-bold" id="eventTitle"></p>
<p class="text-muted small">Tej operacji nie można cofnąć.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> Anuluj
</button>
<form method="POST" action="/wydarzenie/usun">
<input type="hidden" name="event_id" id="deleteEventId">
<input type="hidden" name="return_month" value="<?php echo $month; ?>">
<input type="hidden" name="return_date" value="<?php echo htmlspecialchars($selectedDate); ?>">
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash"></i> Usuń wydarzenie
</button>
</form>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
$extraScript = '<script>
function confirmDeleteEvent(eventId, eventTitle) {
document.getElementById("deleteEventId").value = eventId;
document.getElementById("eventTitle").textContent = eventTitle;
const modal = new bootstrap.Modal(document.getElementById("deleteEventModal"));
modal.show();
}
</script>';
require __DIR__ . '/../layout.php';
?>

144
views/dashboard.php Normal file
View File

@@ -0,0 +1,144 @@
<?php
$pageTitle = 'Panel - System 2FA';
$bodyClass = 'bg-light';
$extraHead = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">';
ob_start();
?>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/panel">
<i class="fas fa-shield-alt"></i> System 2FA
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="/pulpit">
<i class="fas fa-home"></i> Pulpit
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/notatnik">
<i class="fas fa-sticky-note"></i> Notatnik
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/kalendarz">
<i class="fas fa-calendar-alt"></i> Kalendarz
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<span class="nav-link text-white">
<i class="fas fa-user"></i> <?php echo htmlspecialchars($_SESSION['username']); ?>
</span>
</li>
<li class="nav-item">
<a class="nav-link" href="/wyloguj-sie">
<i class="fas fa-sign-out-alt"></i> Wyloguj
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card shadow-lg">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">
<i class="fas fa-home"></i> Panel główny
</h4>
</div>
<div class="card-body">
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">
<i class="fas fa-user-check"></i> Witaj, <?php echo htmlspecialchars($user['username']); ?>! 🎉
</h4>
<p>Pomyślnie zalogowałeś się do systemu z uwierzytelnianiem dwuskładnikowym.</p>
<hr>
<p class="mb-0">
<i class="fas fa-calendar-alt"></i> <strong>Data rejestracji:</strong>
<?php echo date('d.m.Y H:i', strtotime($user['created_at'])); ?>
</p>
</div>
<h5 class="mt-4 mb-3"><i class="fas fa-th-large"></i> Szybki dostęp</h5>
<div class="row">
<div class="col-md-4 mb-3">
<a href="/notatnik" class="text-decoration-none">
<div class="card bg-gradient-info text-white h-100 hover-shadow">
<div class="card-body text-center py-4">
<i class="fas fa-sticky-note fa-3x mb-3"></i>
<h5 class="card-title">Notatnik</h5>
<p class="card-text">Zarządzaj swoimi notatkami</p>
</div>
</div>
</a>
</div>
<div class="col-md-4 mb-3">
<a href="/pulpit" class="text-decoration-none">
<div class="card bg-gradient-success text-white h-100 hover-shadow">
<div class="card-body text-center py-4">
<i class="fas fa-home fa-3x mb-3"></i>
<h5 class="card-title">Pulpit</h5>
<p class="card-text">Strona główna panelu</p>
</div>
</div>
</a>
</div>
<div class="col-md-4 mb-3">
<a href="/kalendarz" class="text-decoration-none">
<div class="card bg-primary text-white h-100 hover-shadow">
<div class="card-body text-center py-4">
<i class="fas fa-calendar-alt fa-3x mb-3"></i>
<h5 class="card-title">Kalendarz</h5>
<p class="card-text">Wydarzenia w tym tygodniu: <strong><?php echo (int)$eventsThisWeek; ?></strong></p>
</div>
</div>
</a>
</div>
</div>
<h5 class="mt-4 mb-3"><i class="fas fa-info-circle"></i> Bezpieczeństwo</h5>
<div class="row">
<div class="col-md-6 mb-3">
<div class="card bg-light h-100">
<div class="card-body">
<h6 class="card-title">
<i class="fas fa-check-circle text-success"></i> Bezpieczne logowanie
</h6>
<p class="card-text small">Twoje konto jest chronione uwierzytelnianiem dwuskładnikowym</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card bg-light h-100">
<div class="card-body">
<h6 class="card-title">
<i class="fas fa-lock text-primary"></i> Sesja aktywna
</h6>
<p class="card-text small">Twoja sesja jest bezpiecznie zarządzana przez system</p>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer text-muted text-center">
<small>System logowania dwuskładnikowego - Demo</small>
</div>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
require __DIR__ . '/layout.php';
?>

22
views/layout.php Normal file
View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $pageTitle ?? 'Aplikacja'; ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/public/css/style.css">
<?php if (isset($extraHead)): ?>
<?php echo $extraHead; ?>
<?php endif; ?>
</head>
<body class="<?php echo $bodyClass ?? 'bg-light'; ?>">
<?php echo $content ?? ''; ?>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/public/js/app.js"></script>
<?php if (isset($extraScript)): ?>
<?php echo $extraScript; ?>
<?php endif; ?>
</body>
</html>

63
views/login.php Normal file
View File

@@ -0,0 +1,63 @@
<?php
$pageTitle = 'Logowanie - System 2FA';
$bodyClass = 'bg-gradient-primary';
ob_start();
?>
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">System Logowania 2FA</h1>
</div>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?php
echo htmlspecialchars($_SESSION['error']);
unset($_SESSION['error']);
?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form class="user" method="POST" action="/uwierzytelnianie">
<div class="form-group mb-3">
<input type="text" class="form-control form-control-user"
id="username" name="username"
placeholder="Login" required autofocus>
</div>
<div class="form-group mb-3">
<input type="password" class="form-control form-control-user"
id="password" name="password"
placeholder="Hasło" required>
</div>
<button type="submit" class="btn btn-primary btn-user btn-block w-100">
Zaloguj się
</button>
</form>
<hr>
<div class="text-center">
<small class="text-muted">
Testowy użytkownik: <strong>projectpro</strong> / <strong>testowehaslo</strong>
</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
require __DIR__ . '/layout.php';
?>

147
views/notes/form.php Normal file
View File

@@ -0,0 +1,147 @@
<?php
$pageTitle = (isset($note) ? 'Edycja' : 'Nowa') . ' notatka - System 2FA';
$bodyClass = 'bg-light';
$extraHead = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">'
. '<link rel="stylesheet" href="/public/css/notes.css">';
ob_start();
?>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/panel">
<i class="fas fa-shield-alt"></i> System 2FA
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/pulpit">
<i class="fas fa-home"></i> Pulpit
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/notatnik">
<i class="fas fa-sticky-note"></i> Notatnik
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/kalendarz">
<i class="fas fa-calendar-alt"></i> Kalendarz
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<span class="nav-link text-white">
<i class="fas fa-user"></i> <?php echo htmlspecialchars($_SESSION['username']); ?>
</span>
</li>
<li class="nav-item">
<a class="nav-link" href="/wyloguj-sie">
<i class="fas fa-sign-out-alt"></i> Wyloguj
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">
<i class="fas fa-<?php echo isset($note) ? 'edit' : 'plus'; ?>"></i>
<?php echo isset($note) ? 'Edycja notatki' : 'Nowa notatka'; ?>
</h4>
</div>
<div class="card-body">
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle"></i> <?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="/notatka/zapisz">
<?php if (isset($note)): ?>
<input type="hidden" name="note_id" value="<?php echo $note['id']; ?>">
<?php endif; ?>
<div class="mb-3">
<label for="title" class="form-label">
<i class="fas fa-heading"></i> Tytuł notatki *
</label>
<input type="text"
class="form-control form-control-lg"
id="title"
name="title"
placeholder="Wpisz tytuł notatki..."
value="<?php echo isset($note) ? htmlspecialchars($note['title']) : ''; ?>"
required
autofocus>
</div>
<div class="mb-3">
<label for="content" class="form-label">
<i class="fas fa-align-left"></i> Treść notatki
</label>
<textarea class="form-control"
id="content"
name="content"
rows="12"
placeholder="Wpisz treść notatki..."><?php echo isset($note) ? htmlspecialchars($note['content']) : ''; ?></textarea>
<small class="form-text text-muted">
<i class="fas fa-info-circle"></i> Pole opcjonalne
</small>
</div>
<div class="d-flex justify-content-between align-items-center">
<a href="/notatnik" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Powrót do listy
</a>
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-save"></i> Zapisz notatkę
</button>
</div>
</form>
</div>
<?php if (isset($note)): ?>
<div class="card-footer text-muted small">
<div class="row">
<div class="col-md-6">
<i class="fas fa-calendar-plus"></i> Utworzono:
<?php
$date = new DateTime($note['created_at']);
echo $date->format('d.m.Y H:i');
?>
</div>
<div class="col-md-6 text-md-end">
<i class="fas fa-calendar-check"></i> Zaktualizowano:
<?php
$date = new DateTime($note['updated_at']);
echo $date->format('d.m.Y H:i');
?>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
$extraScript = '<script>
const textarea = document.getElementById("content");
textarea.addEventListener("input", function() {
this.style.height = "auto";
this.style.height = this.scrollHeight + "px";
});
</script>';
require __DIR__ . '/../layout.php';
?>

170
views/notes/index.php Normal file
View File

@@ -0,0 +1,170 @@
<?php
$pageTitle = 'Notatnik - System 2FA';
$bodyClass = 'bg-light';
$extraHead = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">'
. '<link rel="stylesheet" href="/public/css/notes.css">';
ob_start();
?>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/panel">
<i class="fas fa-shield-alt"></i> System 2FA
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/pulpit">
<i class="fas fa-home"></i> Pulpit
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/notatnik">
<i class="fas fa-sticky-note"></i> Notatnik
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/kalendarz">
<i class="fas fa-calendar-alt"></i> Kalendarz
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<span class="nav-link text-white">
<i class="fas fa-user"></i> <?php echo htmlspecialchars($_SESSION['username']); ?>
</span>
</li>
<li class="nav-item">
<a class="nav-link" href="/wyloguj-sie">
<i class="fas fa-sign-out-alt"></i> Wyloguj
</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle"></i> <?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle"></i> <?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-sticky-note text-primary"></i> Moje Notatki</h2>
<a href="/notatka/nowa" class="btn btn-primary">
<i class="fas fa-plus"></i> Nowa notatka
</a>
</div>
<?php if (empty($notes)): ?>
<div class="text-center py-5">
<i class="fas fa-inbox fa-5x text-muted mb-3"></i>
<h4 class="text-muted">Brak notatek</h4>
<p class="text-muted">Stwórz swoją pierwszą notatkę, aby zacząć!</p>
<a href="/notatka/nowa" class="btn btn-primary btn-lg mt-3">
<i class="fas fa-plus"></i> Dodaj pierwszą notatkę
</a>
</div>
<?php else: ?>
<div class="row">
<?php foreach ($notes as $note): ?>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card note-card h-100 shadow-sm">
<div class="card-body d-flex flex-column">
<h5 class="card-title">
<i class="fas fa-file-alt text-primary"></i>
<?php echo htmlspecialchars($note['title']); ?>
</h5>
<p class="card-text flex-grow-1 note-content">
<?php
$content = htmlspecialchars($note['content'] ?? '');
echo nl2br(mb_substr($content, 0, 150)) . (mb_strlen($content) > 150 ? '...' : '');
?>
</p>
<div class="note-meta text-muted small mb-3">
<i class="fas fa-clock"></i>
<?php
$date = new DateTime($note['updated_at']);
echo $date->format('d.m.Y H:i');
?>
</div>
<div class="btn-group" role="group">
<a href="/notatka/edytuj?id=<?php echo $note['id']; ?>"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit"></i> Edytuj
</a>
<button type="button"
class="btn btn-sm btn-outline-danger"
onclick="confirmDelete(<?php echo $note['id']; ?>, '<?php echo htmlspecialchars(addslashes($note['title'])); ?>')">
<i class="fas fa-trash"></i> Usuń
</button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="mt-4 text-center text-muted">
<p>Łącznie notatek: <strong><?php echo $notesCount; ?></strong></p>
</div>
<?php endif; ?>
</div>
<!-- Modal potwierdzenia usunięcia -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteModalLabel">
<i class="fas fa-exclamation-triangle"></i> Potwierdź usunięcie
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Czy na pewno chcesz usunąć notatkę:</p>
<p class="fw-bold" id="noteTitle"></p>
<p class="text-muted small">Tej operacji nie można cofnąć.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> Anuluj
</button>
<form method="POST" action="/notatka/usun" id="deleteForm">
<input type="hidden" name="note_id" id="deleteNoteId">
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash"></i> Usuń notatkę
</button>
</form>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
$extraScript = '<script>
function confirmDelete(noteId, noteTitle) {
document.getElementById("deleteNoteId").value = noteId;
document.getElementById("noteTitle").textContent = noteTitle;
const deleteModal = new bootstrap.Modal(document.getElementById("deleteModal"));
deleteModal.show();
}
</script>';
require __DIR__ . '/../layout.php';
?>

78
views/verify.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
$pageTitle = 'Weryfikacja 2FA - System 2FA';
$bodyClass = 'bg-gradient-primary';
$extraHead = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">';
ob_start();
?>
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-verify-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-2">Weryfikacja dwuskładnikowa</h1>
<p class="mb-4 text-muted">Wprowadź kod weryfikacyjny</p>
</div>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?php
echo htmlspecialchars($_SESSION['error']);
unset($_SESSION['error']);
?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="alert alert-info" role="alert">
<i class="fas fa-info-circle"></i>
<strong>Tryb testowy:</strong> Kod weryfikacyjny znajduje się w konsoli przeglądarki (F12).
</div>
<form class="user" method="POST" action="/weryfikuj-kod">
<div class="form-group mb-3">
<input type="text" class="form-control form-control-user text-center"
id="code" name="code"
placeholder="000000"
maxlength="6"
pattern="[0-9]{6}"
required autofocus>
<small class="form-text text-muted">Wprowadź 6-cyfrowy kod</small>
</div>
<button type="submit" class="btn btn-primary btn-user btn-block w-100">
Zweryfikuj
</button>
</form>
<hr>
<div class="text-center">
<a href="/logowanie" class="small">Powrót do logowania</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
$extraScript = '<script>
// Wyświetlanie kodu weryfikacyjnego w konsoli (tylko dla testów)
' . (isset($_SESSION['test_code']) ? "console.log('%c🔐 KOD WERYFIKACYJNY: " . $_SESSION['test_code'] . "', 'background: #222; color: #bada55; font-size: 20px; padding: 10px;');\n console.log('W produkcji kod zostałby wysłany emailem.');" : '') . '
// Auto-focus i formatowanie pola kodu
document.getElementById("code").addEventListener("input", function() {
this.value = this.value.replace(/[^0-9]/g, "");
});
</script>';
require __DIR__ . '/layout.php';
?>