diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..358fc5b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+config/config.php
+.DS_Store
+Thumbs.db
diff --git a/README.md b/README.md
index 65a599a..e69de29 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +0,0 @@
-# Habit Tracker
-
-## Projektziel
-
-Der Habit Tracker ist eine Webanwendung, mit der Benutzer eigene Gewohnheiten anlegen, verwalten und täglich abhaken können. Ziel ist es, Fortschritte sichtbar zu machen und Benutzer beim Aufbau guter Routinen zu unterstützen.
-
-## Geplante Hauptfunktionen
-
-- Registrierung und Login
-- Dashboard mit heutigen Habits
-- Habits erstellen, bearbeiten und löschen
-- Habits täglich abhaken
-- Übersicht aller Habits mit Filter und Sortierung
-- Detailansicht mit Verlauf, Erfolgsquote und Streak
-- Kategorien für Habits
-- Responsive Oberfläche für Desktop und Smartphone
-
-## Geplante Datenbanktabellen
-
-- users
-- habits
-- habit_logs
-- categories
-- reminders
-
-## Technische Anforderungen
-
-- MVC-Struktur
-- PHP, HTML, CSS, SQL
-- Prepared Statements zum Schutz vor SQL-Injections
-- htmlspecialchars zum Schutz vor XSS
-- Passwortspeicherung mit password_hash
-- Session-basierter Login
-- Optional: Fetch API zum Abhaken ohne Neuladen der Seite
\ No newline at end of file
diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php
new file mode 100755
index 0000000..35f4c4a
--- /dev/null
+++ b/app/Controllers/AuthController.php
@@ -0,0 +1,160 @@
+ ['email' => ''],
+ 'errors' => [],
+ ]);
+ }
+
+ // Prueft die Anmeldedaten und startet die Sitzung
+ public function login(): void
+ {
+ verify_csrf_token();
+
+ $email = $this->postString('email');
+ $password = $this->postPassword();
+ $errors = [];
+
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email) > 190) {
+ $errors['email'] = 'Bitte gib eine gueltige E-Mail-Adresse ein.';
+ }
+
+ if ($password === '') {
+ $errors['password'] = 'Bitte gib dein Passwort ein.';
+ }
+
+ if (!empty($errors)) {
+ $this->renderLogin($email, $errors);
+ return;
+ }
+
+ try {
+ $user = User::findByEmail($email);
+ } catch (PDOException $exception) {
+ $this->renderLogin($email, ['form' => 'Die Anmeldung ist gerade nicht verfuegbar. Bitte versuche es spaeter erneut.']);
+ return;
+ }
+
+ if (!$user || !password_verify($password, $user['password_hash'])) {
+ $this->renderLogin($email, ['form' => 'E-Mail oder Passwort ist nicht korrekt.']);
+ return;
+ }
+
+ session_regenerate_id(true);
+ $_SESSION['user_id'] = (int) $user['id'];
+ $_SESSION['user_name'] = $user['name'];
+ unset($_SESSION['csrf_token']);
+ redirect('dashboard');
+ }
+
+ // Zeigt das Registrierungsformular
+ public function showRegister(): void
+ {
+ View::render('auth/register', [
+ 'values' => ['name' => '', 'email' => ''],
+ 'errors' => [],
+ ]);
+ }
+
+ // Legt ein neues Konto an
+ public function register(): void
+ {
+ verify_csrf_token();
+
+ $name = $this->postString('name');
+ $email = $this->postString('email');
+ $password = $this->postPassword();
+ $errors = [];
+
+ if ($name === '') {
+ $errors['name'] = 'Bitte gib deinen Namen ein.';
+ } elseif (mb_strlen($name) > 100) {
+ $errors['name'] = 'Der Name darf maximal 100 Zeichen lang sein.';
+ }
+
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $errors['email'] = 'Bitte gib eine gueltige E-Mail-Adresse ein.';
+ } elseif (mb_strlen($email) > 190) {
+ $errors['email'] = 'Die E-Mail-Adresse darf maximal 190 Zeichen lang sein.';
+ }
+
+ if (strlen($password) < 8) {
+ $errors['password'] = 'Das Passwort muss mindestens 8 Zeichen lang sein.';
+ }
+
+ if (!empty($errors)) {
+ $this->renderRegister($name, $email, $errors);
+ return;
+ }
+
+ try {
+ if (User::findByEmail($email)) {
+ $this->renderRegister($name, $email, ['email' => 'Diese E-Mail ist bereits registriert.']);
+ return;
+ }
+
+ $created = User::create($name, $email, $password);
+ } catch (PDOException $exception) {
+ $this->renderRegister($name, $email, ['form' => 'Das Konto konnte nicht erstellt werden. Bitte versuche es spaeter erneut.']);
+ return;
+ }
+
+ if (!$created) {
+ $this->renderRegister($name, $email, ['form' => 'Das Konto konnte nicht erstellt werden. Bitte versuche es spaeter erneut.']);
+ return;
+ }
+
+ flash('success', 'Registrierung erfolgreich. Du kannst dich jetzt anmelden.');
+ redirect('login');
+ }
+
+ // Beendet die Sitzung
+ public function logout(): void
+ {
+ verify_csrf_token();
+
+ $_SESSION = [];
+
+ if (ini_get('session.use_cookies')) {
+ $params = session_get_cookie_params();
+ setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
+ }
+ session_destroy();
+ redirect('login');
+ }
+
+ private function renderLogin(string $email, array $errors): void
+ {
+ View::render('auth/login', [
+ 'values' => ['email' => $email],
+ 'errors' => $errors,
+ ]);
+ }
+
+ private function renderRegister(string $name, string $email, array $errors): void
+ {
+ View::render('auth/register', [
+ 'values' => ['name' => $name, 'email' => $email],
+ 'errors' => $errors,
+ ]);
+ }
+
+ private function postString(string $key): string
+ {
+ $value = $_POST[$key] ?? '';
+
+ return is_string($value) ? trim($value) : '';
+ }
+
+ private function postPassword(): string
+ {
+ $value = $_POST['password'] ?? '';
+
+ return is_string($value) ? $value : '';
+ }
+}
\ No newline at end of file
diff --git a/app/Controllers/HabitController.php b/app/Controllers/HabitController.php
new file mode 100755
index 0000000..4ac4a25
--- /dev/null
+++ b/app/Controllers/HabitController.php
@@ -0,0 +1,162 @@
+ $_GET['category_id'] ?? '',
+ 'status' => $_GET['status'] ?? '',
+ ];
+
+ $remindersByHabit = [];
+ foreach (Reminder::allForUser($userId) as $reminder) {
+ $remindersByHabit[(int) $reminder['habit_id']][] = $reminder;
+ }
+
+ View::render('habits/dashboard', [
+ 'habits' => Habit::allForUser($userId, $filters),
+ 'categories' => Category::all(),
+ 'filters' => $filters,
+ 'remindersByHabit' => $remindersByHabit,
+ ]);
+ }
+
+ // Zeigt das leere Formular zum Anlegen
+ public function create(): void
+ {
+ require_login();
+
+ View::render('habits/form', [
+ 'habit' => null,
+ 'categories' => Category::all(),
+ 'errors' => [],
+ ]);
+ }
+
+ // Speichert einen neuen Habit
+ public function store(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ $data = $this->validatedData();
+
+ if (!empty($data['errors'])) {
+ View::render('habits/form', [
+ 'habit' => $_POST,
+ 'categories' => Category::all(),
+ 'errors' => $data['errors'],
+ ]);
+ return;
+ }
+
+ Habit::create(current_user_id(), $data);
+ flash('success', 'Habit wurde erstellt.');
+ redirect('dashboard');
+ }
+
+ // Zeigt das Formular zum Bearbeiten
+ public function edit(): void
+ {
+ require_login();
+
+ $habit = Habit::findForUser((int) ($_GET['id'] ?? 0), current_user_id());
+
+ if (!$habit) {
+ flash('error', 'Habit wurde nicht gefunden.');
+ redirect('dashboard');
+ }
+
+ View::render('habits/form', [
+ 'habit' => $habit,
+ 'categories' => Category::all(),
+ 'errors' => [],
+ ]);
+ }
+
+ // Uebernimmt die Aenderungen eines bestehenden Habits
+ public function update(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ $id = (int) ($_POST['id'] ?? 0);
+ $data = $this->validatedData();
+
+ if (!empty($data['errors'])) {
+ // ID mitgeben, sonst zeigt das Formular auf keinen Datensatz mehr
+ View::render('habits/form', [
+ 'habit' => array_merge($_POST, ['id' => $id]),
+ 'categories' => Category::all(),
+ 'errors' => $data['errors'],
+ ]);
+ return;
+ }
+
+ Habit::update($id, current_user_id(), $data);
+ flash('success', 'Habit wurde gespeichert.');
+ redirect('dashboard');
+ }
+
+ // Loescht einen Habit
+ public function delete(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ Habit::delete((int) ($_POST['id'] ?? 0), current_user_id());
+ flash('success', 'Habit wurde geloescht.');
+ redirect('dashboard');
+ }
+
+ public function toggle(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ Habit::toggleToday((int) ($_POST['id'] ?? 0), current_user_id());
+ redirect('dashboard');
+ }
+
+ private function validatedData(): array
+ {
+ $title = trim($_POST['title'] ?? '');
+ $description = trim($_POST['description'] ?? '');
+ $categoryId = $_POST['category_id'] ?? '';
+ $targetPerWeek = (int) ($_POST['target_per_week'] ?? 0);
+ $errors = [];
+
+ if ($title === '') {
+ $errors['title'] = 'Bitte gib einen Namen fuer den Habit ein.';
+ } elseif (mb_strlen($title) > 120) {
+ $errors['title'] = 'Der Name darf maximal 120 Zeichen lang sein.';
+ }
+
+ if (mb_strlen($description) > 1000) {
+ $errors['description'] = 'Die Beschreibung darf maximal 1000 Zeichen lang sein.';
+ }
+
+ // Gegen die Datenbank pruefen, damit keine erfundene ID durchkommt
+ if ($categoryId !== '' && !Category::exists((int) $categoryId)) {
+ $errors['category_id'] = 'Bitte waehle eine gueltige Kategorie aus.';
+ }
+
+ if ($targetPerWeek < 1 || $targetPerWeek > 7) {
+ $errors['target_per_week'] = 'Bitte waehle einen Wert zwischen 1 und 7.';
+ }
+
+ return [
+ 'title' => $title,
+ 'description' => $description,
+ 'category_id' => $categoryId,
+ 'target_per_week' => $targetPerWeek,
+ 'errors' => $errors,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/app/Controllers/ReminderController.php b/app/Controllers/ReminderController.php
new file mode 100755
index 0000000..a717d6f
--- /dev/null
+++ b/app/Controllers/ReminderController.php
@@ -0,0 +1,193 @@
+queryId('habit_id'), current_user_id());
+
+ if (!$habit) {
+ flash('error', 'Der Habit fuer die Erinnerung wurde nicht gefunden.');
+ redirect('dashboard');
+ }
+
+ View::render('reminders/form', [
+ 'habit' => $habit,
+ 'reminder' => ['habit_id' => $habit['id'], 'is_active' => 1],
+ 'errors' => [],
+ ]);
+ }
+
+ // Speichert eine neue Erinnerung
+ public function store(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ $habit = Habit::findForUser($this->postId('habit_id'), current_user_id());
+
+ if (!$habit) {
+ flash('error', 'Der Habit fuer die Erinnerung wurde nicht gefunden.');
+ redirect('dashboard');
+ }
+
+ $data = $this->validatedData();
+
+ if (!empty($data['errors'])) {
+ View::render('reminders/form', [
+ 'habit' => $habit,
+ 'reminder' => array_merge($_POST, $data),
+ 'errors' => $data['errors'],
+ ]);
+ return;
+ }
+
+ if (!Reminder::createForHabit((int) $habit['id'], current_user_id(), $data)) {
+ flash('error', 'Die Erinnerung konnte nicht gespeichert werden.');
+ redirect('dashboard');
+ }
+
+ flash('success', 'Erinnerung wurde erstellt.');
+ redirect('dashboard');
+ }
+
+ // Zeigt das Formular zum Bearbeiten
+ public function edit(): void
+ {
+ require_login();
+
+ $reminder = Reminder::findForUser($this->queryId('id'), current_user_id());
+
+ if (!$reminder) {
+ flash('error', 'Erinnerung wurde nicht gefunden.');
+ redirect('dashboard');
+ }
+
+ $habit = Habit::findForUser((int) $reminder['habit_id'], current_user_id());
+
+ View::render('reminders/form', [
+ 'habit' => $habit,
+ 'reminder' => $reminder,
+ 'errors' => [],
+ ]);
+ }
+
+ public function update(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ $reminder = Reminder::findForUser($this->postId('id'), current_user_id());
+
+ if (!$reminder) {
+ flash('error', 'Erinnerung wurde nicht gefunden.');
+ redirect('dashboard');
+ }
+
+ $habit = Habit::findForUser((int) $reminder['habit_id'], current_user_id());
+ $data = $this->validatedData();
+
+ if (!empty($data['errors'])) {
+ View::render('reminders/form', [
+ 'habit' => $habit,
+ 'reminder' => array_merge($reminder, $data),
+ 'errors' => $data['errors'],
+ ]);
+ return;
+ }
+
+ if (!Reminder::update((int) $reminder['id'], current_user_id(), $data)) {
+ flash('error', 'Die Erinnerung konnte nicht gespeichert werden.');
+ redirect('dashboard');
+ }
+
+ flash('success', 'Erinnerung wurde gespeichert.');
+ redirect('dashboard');
+ }
+
+ // Loescht eine Erinnerung
+ public function delete(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ $reminder = Reminder::findForUser($this->postId('id'), current_user_id());
+
+ if (!$reminder || !Reminder::delete((int) $reminder['id'], current_user_id())) {
+ flash('error', 'Die Erinnerung konnte nicht geloescht werden.');
+ redirect('dashboard');
+ }
+
+ flash('success', 'Erinnerung wurde geloescht.');
+ redirect('dashboard');
+ }
+
+ public function toggle(): void
+ {
+ require_login();
+ verify_csrf_token();
+
+ $reminder = Reminder::findForUser($this->postId('id'), current_user_id());
+
+ if (!$reminder || !Reminder::toggle((int) $reminder['id'], current_user_id())) {
+ flash('error', 'Der Status der Erinnerung konnte nicht geaendert werden.');
+ redirect('dashboard');
+ }
+
+ flash('success', $reminder['is_active'] ? 'Erinnerung pausiert.' : 'Erinnerung aktiviert.');
+ redirect('dashboard');
+ }
+
+ private function validatedData(): array
+ {
+ $time = $this->postString('reminder_time');
+ $weekdayValue = $this->postString('weekday');
+ $activeValue = $this->postString('is_active');
+ $errors = [];
+
+ // Uhrzeit muss dem Format HH:MM zwischen 00:00 und 23:59
+ if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $time)) {
+ $errors['reminder_time'] = 'Bitte gib eine Uhrzeit im Format HH:MM ein.';
+ }
+
+ // Leerer Wochentag ist erlaubt und bedeutet taeglich
+ if ($weekdayValue !== '' && !preg_match('/^[1-7]$/', $weekdayValue)) {
+ $errors['weekday'] = 'Bitte waehle einen gueltigen Wochentag aus.';
+ }
+
+ if (!in_array($activeValue, ['0', '1'], true)) {
+ $errors['is_active'] = 'Bitte waehle einen gueltigen Status aus.';
+ }
+
+ return [
+ 'reminder_time' => $time,
+ 'weekday' => $weekdayValue === '' ? null : (int) $weekdayValue,
+ 'is_active' => $activeValue === '0' ? 0 : 1,
+ 'errors' => $errors,
+ ];
+ }
+
+ private function queryId(string $key): int
+ {
+ $value = $_GET[$key] ?? '';
+
+ return is_string($value) && preg_match('/^[1-9]\d*$/', $value) ? (int) $value : 0;
+ }
+
+ private function postId(string $key): int
+ {
+ $value = $_POST[$key] ?? '';
+
+ return is_string($value) && preg_match('/^[1-9]\d*$/', $value) ? (int) $value : 0;
+ }
+
+ private function postString(string $key): string
+ {
+ $value = $_POST[$key] ?? '';
+
+ return is_string($value) ? trim($value) : '';
+ }
+}
\ No newline at end of file
diff --git a/app/Core/Database.php b/app/Core/Database.php
new file mode 100755
index 0000000..f7e7f49
--- /dev/null
+++ b/app/Core/Database.php
@@ -0,0 +1,34 @@
+ PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ ]);
+ } catch (PDOException $exception) {
+ throw new PDOException('Datenbankverbindung fehlgeschlagen.');
+ }
+
+ return self::$connection;
+ }
+}
\ No newline at end of file
diff --git a/app/Core/View.php b/app/Core/View.php
new file mode 100755
index 0000000..5b0fe82
--- /dev/null
+++ b/app/Core/View.php
@@ -0,0 +1,15 @@
+query('SELECT id, name, color FROM categories ORDER BY name')
+ ->fetchAll();
+ }
+
+ public static function exists(int $id): bool
+ {
+ $statement = Database::connection()->prepare('SELECT id FROM categories WHERE id = :id LIMIT 1');
+ $statement->execute(['id' => $id]);
+
+ return (bool) $statement->fetch();
+ }
+}
diff --git a/app/Models/Habit.php b/app/Models/Habit.php
new file mode 100755
index 0000000..f867d45
--- /dev/null
+++ b/app/Models/Habit.php
@@ -0,0 +1,115 @@
+ $userId];
+
+ if (!empty($filters['category_id'])) {
+ $where[] = 'h.category_id = :category_id';
+ $params['category_id'] = (int) $filters['category_id'];
+ }
+
+ if (($filters['status'] ?? '') === 'done') {
+ $where[] = 'hl.id IS NOT NULL';
+ } elseif (($filters['status'] ?? '') === 'open') {
+ $where[] = 'hl.id IS NULL';
+ }
+
+ $sql = 'SELECT h.*, c.name AS category_name, c.color AS category_color,
+ CASE WHEN hl.id IS NULL THEN 0 ELSE 1 END AS done_today,
+ (SELECT COUNT(*)
+ FROM habit_logs hwl
+ WHERE hwl.habit_id = h.id
+ AND YEARWEEK(hwl.completed_on, 1) = YEARWEEK(CURRENT_DATE, 1)) AS completed_this_week
+ FROM habits h
+ LEFT JOIN categories c ON c.id = h.category_id
+ LEFT JOIN habit_logs hl
+ ON hl.habit_id = h.id
+ AND hl.completed_on = CURRENT_DATE
+ WHERE ' . implode(' AND ', $where) . '
+ ORDER BY h.created_at DESC';
+
+ $statement = Database::connection()->prepare($sql);
+ $statement->execute($params);
+
+ return $statement->fetchAll();
+ }
+
+ public static function findForUser(int $id, int $userId): ?array
+ {
+ $statement = Database::connection()->prepare(
+ 'SELECT * FROM habits WHERE id = :id AND user_id = :user_id LIMIT 1'
+ );
+ $statement->execute(['id' => $id, 'user_id' => $userId]);
+
+ $habit = $statement->fetch();
+ return $habit ?: null;
+ }
+
+ // Legt einen neuen Habit an
+ public static function create(int $userId, array $data): bool
+ {
+ $sql = 'INSERT INTO habits (user_id, category_id, title, description, target_per_week)
+ VALUES (:user_id, :category_id, :title, :description, :target_per_week)';
+
+ return Database::connection()->prepare($sql)->execute([
+ 'user_id' => $userId,
+ 'category_id' => $data['category_id'] ?: null,
+ 'title' => $data['title'],
+ 'description' => $data['description'],
+ 'target_per_week' => (int) $data['target_per_week'],
+ ]);
+ }
+
+ public static function update(int $id, int $userId, array $data): bool
+ {
+ $sql = 'UPDATE habits
+ SET category_id = :category_id,
+ title = :title,
+ description = :description,
+ target_per_week = :target_per_week
+ WHERE id = :id AND user_id = :user_id';
+
+ return Database::connection()->prepare($sql)->execute([
+ 'id' => $id,
+ 'user_id' => $userId,
+ 'category_id' => $data['category_id'] ?: null,
+ 'title' => $data['title'],
+ 'description' => $data['description'],
+ 'target_per_week' => (int) $data['target_per_week'],
+ ]);
+ }
+
+ public static function delete(int $id, int $userId): bool
+ {
+ $statement = Database::connection()->prepare('DELETE FROM habits WHERE id = :id AND user_id = :user_id');
+
+ return $statement->execute(['id' => $id, 'user_id' => $userId]);
+ }
+
+ public static function toggleToday(int $id, int $userId): bool
+ {
+ $habit = self::findForUser($id, $userId);
+
+ if (!$habit) {
+ return false;
+ }
+
+ $db = Database::connection();
+ $check = $db->prepare(
+ 'SELECT id FROM habit_logs WHERE habit_id = :habit_id AND completed_on = CURRENT_DATE LIMIT 1'
+ );
+ $check->execute(['habit_id' => $id]);
+ $log = $check->fetch();
+
+ if ($log) {
+ return $db->prepare('DELETE FROM habit_logs WHERE id = :id')->execute(['id' => $log['id']]);
+ }
+
+ $insert = $db->prepare('INSERT INTO habit_logs (habit_id, completed_on) VALUES (:habit_id, CURRENT_DATE)');
+ return $insert->execute(['habit_id' => $id]);
+ }
+}
\ No newline at end of file
diff --git a/app/Models/Reminder.php b/app/Models/Reminder.php
new file mode 100755
index 0000000..a35111a
--- /dev/null
+++ b/app/Models/Reminder.php
@@ -0,0 +1,99 @@
+prepare(
+ 'SELECT r.*, h.title AS habit_title
+ FROM reminders r
+ INNER JOIN habits h ON h.id = r.habit_id
+ WHERE r.id = :id AND h.user_id = :user_id
+ LIMIT 1'
+ );
+ $statement->execute(['id' => $id, 'user_id' => $userId]);
+
+ $reminder = $statement->fetch();
+ return $reminder ?: null;
+ }
+
+ public static function allForUser(int $userId): array
+ {
+ $statement = Database::connection()->prepare(
+ 'SELECT r.*, h.title AS habit_title
+ FROM reminders r
+ INNER JOIN habits h ON h.id = r.habit_id
+ WHERE h.user_id = :user_id
+ ORDER BY h.title ASC, r.is_active DESC, r.weekday ASC, r.reminder_time ASC'
+ );
+ $statement->execute(['user_id' => $userId]);
+
+ return $statement->fetchAll();
+ }
+
+ // Legt eine Erinnerung an
+ public static function createForHabit(int $habitId, int $userId, array $data): bool
+ {
+ $statement = Database::connection()->prepare(
+ 'INSERT INTO reminders (habit_id, reminder_time, weekday, is_active)
+ SELECT id, :reminder_time, :weekday, :is_active
+ FROM habits
+ WHERE id = :habit_id AND user_id = :user_id'
+ );
+ $statement->execute([
+ 'habit_id' => $habitId,
+ 'user_id' => $userId,
+ 'reminder_time' => $data['reminder_time'],
+ 'weekday' => $data['weekday'],
+ 'is_active' => $data['is_active'],
+ ]);
+
+ return $statement->rowCount() === 1;
+ }
+
+ // Aktualisiert eine Erinnerung
+ public static function update(int $id, int $userId, array $data): bool
+ {
+ $statement = Database::connection()->prepare(
+ 'UPDATE reminders r
+ INNER JOIN habits h ON h.id = r.habit_id
+ SET r.reminder_time = :reminder_time,
+ r.weekday = :weekday,
+ r.is_active = :is_active
+ WHERE r.id = :id AND h.user_id = :user_id'
+ );
+
+ return $statement->execute([
+ 'id' => $id,
+ 'user_id' => $userId,
+ 'reminder_time' => $data['reminder_time'],
+ 'weekday' => $data['weekday'],
+ 'is_active' => $data['is_active'],
+ ]);
+ }
+
+ public static function delete(int $id, int $userId): bool
+ {
+ $statement = Database::connection()->prepare(
+ 'DELETE r
+ FROM reminders r
+ INNER JOIN habits h ON h.id = r.habit_id
+ WHERE r.id = :id AND h.user_id = :user_id'
+ );
+
+ return $statement->execute(['id' => $id, 'user_id' => $userId]);
+ }
+
+ // Schaltet zwischen aktiv und pausiert um
+ public static function toggle(int $id, int $userId): bool
+ {
+ $statement = Database::connection()->prepare(
+ 'UPDATE reminders r
+ INNER JOIN habits h ON h.id = r.habit_id
+ SET r.is_active = IF(r.is_active = 1, 0, 1)
+ WHERE r.id = :id AND h.user_id = :user_id'
+ );
+
+ return $statement->execute(['id' => $id, 'user_id' => $userId]);
+ }
+}
\ No newline at end of file
diff --git a/app/Models/User.php b/app/Models/User.php
new file mode 100755
index 0000000..eaa8bd5
--- /dev/null
+++ b/app/Models/User.php
@@ -0,0 +1,27 @@
+prepare($sql);
+
+ return $statement->execute([
+ 'name' => $name,
+ 'email' => $email,
+ 'password_hash' => password_hash($password, PASSWORD_DEFAULT),
+ ]);
+ }
+
+ // Sucht einen Benutzer anhand der E-Mail
+ public static function findByEmail(string $email): ?array
+ {
+ $statement = Database::connection()->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
+ $statement->execute(['email' => $email]);
+
+ $user = $statement->fetch();
+ return $user ?: null;
+ }
+}
\ No newline at end of file
diff --git a/app/Views/auth/login.php b/app/Views/auth/login.php
new file mode 100644
index 0000000..b49bc84
--- /dev/null
+++ b/app/Views/auth/login.php
@@ -0,0 +1,48 @@
+ ''];
+$errors = $errors ?? [];
+?>
+
+
+
diff --git a/app/Views/auth/register.php b/app/Views/auth/register.php
new file mode 100644
index 0000000..14cf9e1
--- /dev/null
+++ b/app/Views/auth/register.php
@@ -0,0 +1,66 @@
+ '', 'email' => ''];
+$errors = $errors ?? [];
+?>
+
+
+ Registrieren
+
+ = e($errors['form']) ?>
+
+
+ Schon registriert? Zum Login
+
+
diff --git a/app/Views/habits/dashboard.php b/app/Views/habits/dashboard.php
new file mode 100755
index 0000000..116dc51
--- /dev/null
+++ b/app/Views/habits/dashboard.php
@@ -0,0 +1,120 @@
+ 'Montag',
+ 2 => 'Dienstag',
+ 3 => 'Mittwoch',
+ 4 => 'Donnerstag',
+ 5 => 'Freitag',
+ 6 => 'Samstag',
+ 7 => 'Sonntag',
+];
+?>
+
+
+
+
+
+
+ Noch keine Habits gefunden. Erstelle deinen ersten Habit.
+
+
+
+
+
+
+
+
+
+
= e($habit['title']) ?>
+
= e($habit['description']) ?>
+
+ = e($habit['category_name'] ?? 'Ohne Kategorie') ?> |
+ Woche: = (int) $habit['completed_this_week'] ?> / = (int) $habit['target_per_week'] ?> erledigt
+
+
+
+
+
+
+
+
+
+
+ Noch keine Erinnerung eingerichtet.
+
+
+
+
+ -
+ = e(substr($reminder['reminder_time'], 0, 5)) ?>
+ = e($weekday) ?>
+ = $isActive ? 'Aktiv' : 'Pausiert' ?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/habits/form.php b/app/Views/habits/form.php
new file mode 100755
index 0000000..e32f02b
--- /dev/null
+++ b/app/Views/habits/form.php
@@ -0,0 +1,64 @@
+
+
+
diff --git a/app/Views/layout/footer.php b/app/Views/layout/footer.php
new file mode 100644
index 0000000..9765cc0
--- /dev/null
+++ b/app/Views/layout/footer.php
@@ -0,0 +1,3 @@
+
+