Compare commits
	
		
			17 Commits
		
	
	
		
			43960ddcb9
			...
			last_habit
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| eff5be49c4 | |||
| d13c490efb | |||
| d98c208df9 | |||
| 85af85b1ee | |||
| 1ff7fc454f | |||
| e0dfc7120f | |||
| 9c90710bf3 | |||
| 4b8ee90d8a | |||
| 59246decd7 | |||
| 147e3b1499 | |||
| daec4ec1c9 | |||
| 3c6ecfb5e2 | |||
| b4e08f28ca | |||
| c4366edb29 | |||
| d9f632da26 | |||
| 2847231376 | |||
| d33d233f0f | 
							
								
								
									
										53
									
								
								.docker/nginx.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								.docker/nginx.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| # User and worker process settings | ||||
| user www-data; | ||||
| worker_processes auto; | ||||
| pid /run/nginx.pid; | ||||
|  | ||||
| events { | ||||
|     worker_connections 1024; | ||||
| } | ||||
|  | ||||
| http { | ||||
|     # General settings | ||||
|     sendfile on; | ||||
|     tcp_nopush on; | ||||
|     tcp_nodelay on; | ||||
|     keepalive_timeout 65; | ||||
|     types_hash_max_size 2048; | ||||
|  | ||||
|     include /etc/nginx/mime.types; | ||||
|     default_type application/octet-stream; | ||||
|  | ||||
|     # Logging | ||||
|     access_log /var/log/nginx/access.log; | ||||
|     error_log /var/log/nginx/error.log; | ||||
|  | ||||
|     # Gzip compression | ||||
|     gzip on; | ||||
|     gzip_disable "msie6"; | ||||
|  | ||||
|     # Server configuration | ||||
|     server { | ||||
|         listen 80; | ||||
|         server_name localhost; | ||||
|  | ||||
|         root /var/www/html/public; | ||||
|         index index.php index.html; | ||||
|  | ||||
|         location / { | ||||
|             try_files $uri $uri/ /index.php?$query_string; | ||||
|         } | ||||
|  | ||||
|         location ~ \\.php$ { | ||||
|             include fastcgi_params; | ||||
|             fastcgi_pass 127.0.0.1:9000; | ||||
|             fastcgi_index index.php; | ||||
|             fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | ||||
|         } | ||||
|  | ||||
|         location ~ /\.ht { | ||||
|             deny all; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										22
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| .git | ||||
| .gitignore | ||||
|  | ||||
| storage/logs/* | ||||
| !storage/logs/.gitignore | ||||
|  | ||||
| Dockerfile | ||||
| docker-compose.yaml | ||||
|  | ||||
| *.swp | ||||
| *.swo | ||||
| *.idea/ | ||||
| *.vscode/ | ||||
| *.DS_Store | ||||
|  | ||||
| # Exclude sensitive config files (uncomment if you don't want to include environment config) | ||||
| # config/environment.php | ||||
|  | ||||
| README.md | ||||
| TODO.md | ||||
| LICENSE | ||||
|  | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| *.swp | ||||
							
								
								
									
										18
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| FROM php:8.2-fpm-alpine | ||||
|  | ||||
| WORKDIR /var/www/html | ||||
|  | ||||
| RUN apk add --no-cache nginx curl \ | ||||
|   && docker-php-ext-install mysqli | ||||
|  | ||||
| COPY . /var/www/html | ||||
|  | ||||
| RUN chown -R www-data:www-data /var/www/html \ | ||||
|   && chmod -R 755 /var/www/html | ||||
|  | ||||
| COPY .docker/nginx.conf /etc/nginx/nginx.conf | ||||
|  | ||||
| EXPOSE 80 | ||||
|  | ||||
| CMD php-fpm & nginx -g "daemon off;" | ||||
|  | ||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -8,6 +8,18 @@ An app for tracking habits and motivation to achieve personal goals | ||||
| - **Database:** MariaDB | ||||
|  | ||||
| ## How to build | ||||
|  | ||||
| ### Build using docker | ||||
| Run the container using docker-compose | ||||
| ```bash | ||||
| docker-compose up | ||||
| ``` | ||||
|  | ||||
| The app should be available at http://localhost:8000 | ||||
|  | ||||
| PhpMyAdmin should be available at http://localhost:8080 | ||||
|  | ||||
| ### Build manually | ||||
| 1. Clone the repo | ||||
| ```bash | ||||
|    git clone https://git.filiprojek.cz/fr/habit-tracker.git | ||||
| @@ -28,7 +40,7 @@ define('DB_NAME', 'your db name'); | ||||
| 3. Start an local web server | ||||
| - You can use php's integrated server by running this: | ||||
| ```bash | ||||
| php -S localhost:8000 | ||||
| php -S localhost:8000 -t ./public | ||||
| ``` | ||||
| - You can use any host and any port you want. | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								TODO.md
									
									
									
									
									
								
							| @@ -6,6 +6,13 @@ | ||||
| - [ ] edit user data - change password, mail... | ||||
|  | ||||
| ## Core of the app | ||||
| - [ ] Habits list | ||||
| - [ ] header and navbar | ||||
| - [ ] dashboard | ||||
|   - [x] css | ||||
|   - [ ] its just plain | ||||
|   - [ ] graphs | ||||
| - [x] Habits list | ||||
|   - [ ] css | ||||
| - [ ] Habits create | ||||
|   - [ ] validate cron input | ||||
| - [ ] Habits track | ||||
|   | ||||
| @@ -25,10 +25,10 @@ class AuthController extends Controller  { | ||||
|             if($result === true) { | ||||
|                 $this->redirect('/dashboard'); | ||||
|             } else { | ||||
|                 $this->view('auth/signin', ['error' => $result]); | ||||
|                 $this->view('auth/signin', ['error' => $result], 'noheader'); | ||||
|             } | ||||
|         } else { | ||||
|             $this->view('auth/signin', ['title' => 'Log In']); | ||||
|             $this->view('auth/signin', ['title' => 'Log In'], 'noheader'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -54,7 +54,7 @@ class AuthController extends Controller  { | ||||
|                 $this->view('auth/signup', [ | ||||
|                     'error' => 'Please correct the errors below.', | ||||
|                     'validationErrors' => $validator->errors() ?: [], | ||||
|                 ]); | ||||
|                 ], 'noheader'); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
| @@ -67,13 +67,13 @@ class AuthController extends Controller  { | ||||
|                 $this->view('auth/signup', [ | ||||
|                     'error' => $result, | ||||
|                     'validationErrors' => [], | ||||
|                 ]); | ||||
|                 ], 'noheader'); | ||||
|             } | ||||
|         } else { | ||||
|             $this->view('auth/signup', [ | ||||
|                 'title' => 'Register', | ||||
|                 'validationErrors' => [], | ||||
|             ]); | ||||
|             ], 'noheader'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								app/controllers/DashboardController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/controllers/DashboardController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <?php | ||||
| class DashboardController extends Controller { | ||||
|     public function index() { | ||||
|         $habit = new Habit(); | ||||
|         $habits = $habit->getHabitsByUser($_SESSION['user']['id']); | ||||
|  | ||||
|         $this->view('dashboard/index', [ | ||||
|             'title' => 'Dashboard', | ||||
|             'habits' => $habits, | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function reroute(){ | ||||
|         $this->redirect('/dashboard'); | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,9 @@ | ||||
|  | ||||
| class HabitController extends Controller { | ||||
|     public function index() { | ||||
|         // Display the list of habits (to be implemented later) | ||||
|         $habit = new Habit(); | ||||
|         $habits = $habit->getHabitsByUser($_SESSION['user']['id']); | ||||
|         $this->view('habits/index', ['title' => 'Habits', 'habits' => $habits]); | ||||
|     } | ||||
|  | ||||
|     public function create() { | ||||
| @@ -17,15 +19,12 @@ class HabitController extends Controller { | ||||
|             } | ||||
|  | ||||
|             if ($frequency === 'Custom') { | ||||
|                 var_dump($_POST); | ||||
|                 $daysOfWeek = $_POST['days_of_week'] ?? []; | ||||
|                 $daysOfMonth = $_POST['days_of_month'] ?? '*'; | ||||
|                 $months = $_POST['months'] ?? '*'; | ||||
|  | ||||
|                 // Combine into crontab-like string | ||||
|                 $customFrequency = implode(',', $daysOfWeek) . " $daysOfMonth $months"; | ||||
|                 var_dump($customFrequency); | ||||
|  | ||||
|             } | ||||
|  | ||||
|             $habit = new Habit(); | ||||
| @@ -38,7 +37,7 @@ class HabitController extends Controller { | ||||
|             ]); | ||||
|  | ||||
|             if ($result) { | ||||
|                 //$this->redirect('/habits'); | ||||
|                 $this->redirect('/habits'); | ||||
|             } else { | ||||
|                 $this->view('habits/create', ['error' => 'Failed to create habit.']); | ||||
|             } | ||||
|   | ||||
| @@ -29,4 +29,18 @@ class Habit { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function getHabitsByUser($userId) { | ||||
|         $stmt = $this->db->prepare("SELECT id, title, frequency, custom_frequency, reward_points, created_at FROM habits WHERE user_id = ?"); | ||||
|         $stmt->bind_param("i", $userId); | ||||
|         $stmt->execute(); | ||||
|         $result = $stmt->get_result(); | ||||
|  | ||||
|         $habits = []; | ||||
|         while ($row = $result->fetch_assoc()) { | ||||
|             $habits[] = $row; | ||||
|         } | ||||
|          | ||||
|         return $habits; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -60,6 +60,6 @@ class User { | ||||
|             }  | ||||
|         }  | ||||
|  | ||||
|         return "Invalid email or password."; | ||||
|         return "Incorrect username or password."; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,11 @@ | ||||
| <section class="signin"> | ||||
| <link rel="stylesheet" href="/css/login.css"> | ||||
| <link rel="stylesheet" href="/css/form.css"> | ||||
| <section class="form signin"> | ||||
|     <div class="header-form"> | ||||
|         <img src="/img/logo.jpg" alt="Habit Tracker Logo"> | ||||
|         <h1>Sign in to Habit Tracker</h1> | ||||
|     </div> | ||||
|  | ||||
|     <?php if ($this->get('error')): ?> | ||||
|         <div class="error"><?= $this->get('error') ?></div> | ||||
|     <?php endif; ?> | ||||
| @@ -18,4 +25,9 @@ | ||||
|  | ||||
|         <input type="submit" value="Sign In"> | ||||
|     </form> | ||||
|  | ||||
|     <div class="bordered"> | ||||
|         <p>New to Habit Tracker?</p> | ||||
|         <a href="/auth/signup">Create an account</a> | ||||
|     </div> | ||||
| </section> | ||||
|   | ||||
| @@ -1,4 +1,11 @@ | ||||
| <section class="signup"> | ||||
| <link rel="stylesheet" href="/css/login.css"> | ||||
| <link rel="stylesheet" href="/css/form.css"> | ||||
| <section class="form signup"> | ||||
|     <div class="header-form"> | ||||
|         <img src="/img/logo.jpg" alt="Habit Tracker Logo"> | ||||
|         <h1>Sign up to Habit Tracker</h1> | ||||
|     </div> | ||||
|  | ||||
|     <?php if ($this->get('error')): ?> | ||||
|         <div class="error"><?= $this->get('error') ?></div> | ||||
|     <?php endif; ?> | ||||
| @@ -30,4 +37,9 @@ | ||||
|  | ||||
|         <input type="submit" value="Sign Up"> | ||||
|     </form> | ||||
|  | ||||
|     <div class="bordered"> | ||||
|         <p>Already have an account?</p> | ||||
|         <a href="/auth/signin">Sign in</a> | ||||
|     </div> | ||||
| </section> | ||||
|   | ||||
| @@ -1,3 +1,47 @@ | ||||
| <h1>Welcome <?= $_SESSION['user']['username']?>!</h1> | ||||
| <link rel="stylesheet" href="/css/dashboard.css"> | ||||
|  | ||||
| <a href="/habits/create">Create new Habit</a> | ||||
| <section class="dashboard"> | ||||
|     <h1>Welcome, <?= htmlspecialchars($_SESSION['user']['username']) ?>!</h1> | ||||
|     <div> | ||||
|         <a href="/habits/create" class="btn-green">Create new habit!</a> | ||||
|         <a href="/habits" class="btn-primary">List all habits</a> | ||||
|     </div> | ||||
|     <div class="card-wrapper"> | ||||
|         <section class="card upcoming"> | ||||
|             <h2>Upcoming</h2> | ||||
|             <div class="habit"> | ||||
|                 <b>Habit Title</b> | ||||
|                 <p>Frequency</p> | ||||
|                 <p>Reward points</p> | ||||
|             </div> | ||||
|         </section> | ||||
|  | ||||
|         <section class="card recent"> | ||||
|             <h2>Recent</h2> | ||||
|             <div class="habit"> | ||||
|                 <b>Habit Title</b> | ||||
|                 <p>Frequency</p> | ||||
|                 <p>Reward points</p> | ||||
|             </div> | ||||
|         </section> | ||||
|  | ||||
|         <section class="card missed"> | ||||
|             <h2>Missed</h2> | ||||
|             <div class="habit"> | ||||
|                 <b>Habit Title</b> | ||||
|                 <p>Frequency</p> | ||||
|                 <p>Reward points</p> | ||||
|             </div> | ||||
|         </section> | ||||
|  | ||||
|         <section class="card history-graph"> | ||||
|             <h2>Graph of History</h2> | ||||
|         </section> | ||||
|  | ||||
|         <section class="card streak"> | ||||
|             <h2>Streak</h2> | ||||
|             <p>You're current streak is 123 days</p> | ||||
|             <p>Good job!</p> | ||||
|         </section> | ||||
|     </div> | ||||
| </section> | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| <section class="habit-create"> | ||||
|     <h1><?= $this->get('title', 'Create Habit') ?></h1> | ||||
| <link rel="stylesheet" href="/css/form.css"> | ||||
| <link rel="stylesheet" href="/css/habits_create.css"> | ||||
| <section class="form habit-create"> | ||||
|     <h1 class="header-form"><?= $this->get('title', 'Create Habit') ?></h1> | ||||
|  | ||||
|     <?php if ($this->get('error')): ?> | ||||
|         <div class="error" style="color: red; margin-bottom: 1rem;"> | ||||
| @@ -8,10 +10,10 @@ | ||||
|     <?php endif; ?> | ||||
|  | ||||
|     <form method="POST" action="/habits/create"> | ||||
|         <label for="name">Habit Name:</label> | ||||
|         <label for="name">Habit Name</label> | ||||
|         <input type="text" name="name" id="name" required value="<?= htmlspecialchars($_POST['name'] ?? '') ?>"> | ||||
|  | ||||
|         <label for="frequency">Frequency:</label> | ||||
|         <label for="frequency">Frequency</label> | ||||
|         <select name="frequency" id="frequency" onchange="toggleCustomFrequency(this.value)"> | ||||
|             <option value="Daily">Daily</option> | ||||
|             <option value="Weekly">Weekly</option> | ||||
| @@ -19,29 +21,50 @@ | ||||
|         </select> | ||||
|  | ||||
|         <div id="custom-frequency" style="display: none;"> | ||||
|             <label>Days of the Week:</label> | ||||
|             <input type="checkbox" name="days_of_week[]" value="1"> Monday | ||||
|             <input type="checkbox" name="days_of_week[]" value="2"> Tuesday | ||||
|             <input type="checkbox" name="days_of_week[]" value="3"> Wednesday | ||||
|             <input type="checkbox" name="days_of_week[]" value="4"> Thursday | ||||
|             <input type="checkbox" name="days_of_week[]" value="5"> Friday | ||||
|             <input type="checkbox" name="days_of_week[]" value="6"> Saturday | ||||
|             <input type="checkbox" name="days_of_week[]" value="7"> Sunday | ||||
|             <label id="lbl_dow">Days of the Week</label> | ||||
|             <div class="dow_chb_wrapper"> | ||||
|                 <label for="dow_mon">Monday</label> | ||||
|                 <input type="checkbox" name="days_of_week[]" id="dow_mon" value="1"> | ||||
|             </div> | ||||
|             <div class="dow_chb_wrapper"> | ||||
|                 <label for="dow_tue">Tuesday</label> | ||||
|                 <input type="checkbox" name="days_of_week[]" id="dow_tue" value="2"> | ||||
|             </div> | ||||
|             <div class="dow_chb_wrapper"> | ||||
|                 <label for="dow_wed">Wednesday</label> | ||||
|                 <input type="checkbox" name="days_of_week[]" id="dow_wed" value="3"> | ||||
|             </div> | ||||
|             <div class="dow_chb_wrapper"> | ||||
|                 <label for="dow_thu">Thursday</label> | ||||
|                 <input type="checkbox" name="days_of_week[]" id="dow_thu" value="4"> | ||||
|             </div> | ||||
|             <div class="dow_chb_wrapper"> | ||||
|                 <label for="dow_fri">Friday</label> | ||||
|                 <input type="checkbox" name="days_of_week[]" id="dow_fri" value="5"> | ||||
|             </div> | ||||
|             <div class="dow_chb_wrapper"> | ||||
|                 <label for="dow_sat">Saturday</label> | ||||
|                 <input type="checkbox" name="days_of_week[]" id="dow_sat" value="6"> | ||||
|             </div> | ||||
|             <div class="dow_chb_wrapper"> | ||||
|                 <label for="dow_sun">Sunday</label> | ||||
|                 <input type="checkbox" name="days_of_week[]" id="dow_sun" value="7"> | ||||
|             </div> | ||||
|  | ||||
|             <label for="days_of_month">Days of the Month:</label> | ||||
|             <label for="days_of_month" id="lbl_dom">Days of the Month</label> | ||||
|             <input type="text" name="days_of_month" id="days_of_month" placeholder="1,15 (comma-separated)"> | ||||
|  | ||||
|             <label for="months">Months:</label> | ||||
|             <label for="months">Months</label> | ||||
|             <input type="text" name="months" id="months" placeholder="1,7,12 (comma-separated)"> | ||||
|         </div> | ||||
|  | ||||
|         <button type="submit">Create Habit</button> | ||||
|         <input type="submit" value="Create Habit"> | ||||
|     </form> | ||||
| </section> | ||||
|  | ||||
| <script> | ||||
| function toggleCustomFrequency(value) { | ||||
|     const customFrequencyDiv = document.getElementById('custom-frequency'); | ||||
|     customFrequencyDiv.style.display = value === 'Custom' ? 'block' : 'none'; | ||||
|     customFrequencyDiv.style.display = value === 'Custom' ? 'flex' : 'none'; | ||||
| } | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										24
									
								
								app/views/habits/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/views/habits/index.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| <link rel="stylesheet" href="/css/habits_dashboard.css"> | ||||
| <section class="habits"> | ||||
|     <?php if (empty($this->get('habits'))): ?> | ||||
|         <p>No habits yet. <a href="/habits/create">Create your first habit</a>.</p> | ||||
|     <?php else: ?> | ||||
|         <div class="habits-wrapper"> | ||||
|         <?php foreach ($this->get('habits') as $habit): ?> | ||||
|             <div class="habit bordered"> | ||||
|                 <b><?= htmlspecialchars($habit['title']) ?></b> | ||||
|                 <p>Frequency: <?= htmlspecialchars($habit['frequency']) ?></p> | ||||
|                 <?php if (isset($habit['custom_frequency'])): ?> | ||||
|                     <p><?= htmlspecialchars($habit['custom_frequency'] ?? 'N/A') ?></p> | ||||
|                 <?php endif; ?> | ||||
|                     <p><?= htmlspecialchars($habit['reward_points']) ?></p> | ||||
|                     <p><?= htmlspecialchars($habit['created_at']) ?></p> | ||||
|                     <a href="/habits/done">Mark as done</a> | | ||||
|                     <a href="/habits/edit?id=<?= $habit['id'] ?>">Edit</a> | | ||||
|                     <a href="/habits/delete?id=<?= $habit['id'] ?>" onclick="return confirm('Are you sure you want to delete this habit?')">Delete</a> | ||||
|             </div> | ||||
|         <?php endforeach; ?> | ||||
|         </div> | ||||
|         <a href="/habits/create" class="btn-green">Create new habit!</a> | ||||
|     <?php endif; ?> | ||||
| </section> | ||||
| @@ -1,17 +1,35 @@ | ||||
| <!doctype html> | ||||
| <html> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta name="author" content="Filip Rojek | http://filiprojek.cz"> | ||||
|     <meta name="email" content="webmaster(@)fofrweb.com"> | ||||
|     <meta name="copyright" content="(c) filiprojek.cz"> | ||||
|     <title>Habit Tracker | <?= $data['title'] ?></title> | ||||
|     <link rel="stylesheet" href="/css/main.css"> | ||||
|     <link rel="stylesheet" href="/css/global.css"> | ||||
|     <link rel="stylesheet" href="/css/vars.css"> | ||||
|     <link rel="stylesheet" href="/css/header.css"> | ||||
|     <link rel="icon" type="image/x-icon" href="/img/favicon.ico"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <header> | ||||
|       <a href="/auth/signin">Log In</a> | ||||
|       <a href="/auth/signup">Sign Up</a> | ||||
|       <a href="/auth/logout">Log Out</a> | ||||
|       <div id="hd-left"> | ||||
|         <a href="/"><img src="/img/logo.jpg" alt="home"></a> | ||||
|         <label><?= $data['title'] ?></label> | ||||
|       </div> | ||||
|       <div id="hd-right"> | ||||
|         <?php if (!isset($_SESSION['user'])): ?> | ||||
|         <a href="/auth/signin">Log In</a> | ||||
|         <a href="/auth/signup">Sign Up</a> | ||||
|         <?php else: ?> | ||||
|         <a href="/auth/logout" class="btn-secondary">Sign out</a> | ||||
|         <?php endif; ?> | ||||
|       </div> | ||||
|     </header> | ||||
|     <section class="content"> | ||||
|     <main class="content"> | ||||
|       <?= $content ?> | ||||
|     </section> | ||||
|     </main> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										20
									
								
								app/views/layouts/noheader.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/views/layouts/noheader.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <!doctype html> | ||||
| <html> | ||||
|   <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta name="author" content="Filip Rojek | http://filiprojek.cz"> | ||||
|     <meta name="email" content="webmaster(@)fofrweb.com"> | ||||
|     <meta name="copyright" content="(c) filiprojek.cz"> | ||||
|     <title>Habit Tracker | <?= $data['title'] ?></title> | ||||
|     <link rel="stylesheet" href="/css/main.css"> | ||||
|     <link rel="stylesheet" href="/css/global.css"> | ||||
|     <link rel="stylesheet" href="/css/vars.css"> | ||||
|     <link rel="icon" type="image/x-icon" href="/img/favicon.ico"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <main class="content"> | ||||
|       <?= $content ?> | ||||
|     </main> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										6
									
								
								config/environment.php.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								config/environment.php.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?php | ||||
|  | ||||
| define('DB_HOST', '0.0.0.0'); | ||||
| define('DB_NAME', 'habit_tracker');  | ||||
| define('DB_USER', 'username');  | ||||
| define('DB_PASS', 'password'); | ||||
| @@ -16,8 +16,8 @@ class Controller { | ||||
|      * @param string $viewName | ||||
|      * @param array $data | ||||
|      */ | ||||
|     public function view($viewName, $data = []) { | ||||
|     public function view($viewName, $data = [], $layout = "base") { | ||||
|         $view = new View(); | ||||
|         $view->render($viewName, $data); | ||||
|         $view->render($viewName, $data, $layout); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -78,7 +78,7 @@ class Router { | ||||
|         } else { | ||||
|             http_response_code(404); | ||||
|             $view = new View(); | ||||
|             $view->render('errors/404'); | ||||
|             $view->render('errors/404', [], 'noheader'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,16 +1,31 @@ | ||||
| services: | ||||
|   mariadb: | ||||
|     image: mariadb:11.4 # LTS at 25. 12. 2025 | ||||
|     restart: always | ||||
|     restart: on-failure:2 | ||||
|     environment: | ||||
|       MARIADB_ROOT_PASSWORD: root | ||||
|     ports: | ||||
|       - 3306:3306 | ||||
|     profiles: ["prod", "dev"] | ||||
|  | ||||
|   phpmyadmin: | ||||
|     image: phpmyadmin | ||||
|     restart: always | ||||
|     restart: on-failure:2 | ||||
|     ports: | ||||
|       - 8080:80 | ||||
|     environment: | ||||
|       - PMA_ARBITRARY=1 | ||||
|     profiles: ["dev"] | ||||
|  | ||||
|   habittracker: | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: Dockerfile | ||||
|     volumes: | ||||
|       - .:/var/www/html | ||||
|     ports: | ||||
|       - 8000:80 | ||||
|     depends_on: | ||||
|       - mariadb | ||||
|     restart: on-failure:2 | ||||
|     profiles: ["prod"] | ||||
|   | ||||
							
								
								
									
										21
									
								
								public/css/dashboard.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								public/css/dashboard.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| .dashboard { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| .card-wrapper { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   gap: 1rem; | ||||
|   justify-content: center; | ||||
|   margin-top: 2rem; | ||||
| } | ||||
|  | ||||
| .card { | ||||
|   background-color: var(--clr-secondary); | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
|   min-width: 17rem; | ||||
|   padding: 1rem; | ||||
| } | ||||
							
								
								
									
										78
									
								
								public/css/form.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								public/css/form.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| .form { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| } | ||||
|  | ||||
| body { | ||||
|   background-color: var(--clr-secondary); | ||||
| } | ||||
|  | ||||
| .form .header-form { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   flex-direction: column; | ||||
| } | ||||
|  | ||||
| .form .header-form img { | ||||
|   height: 5rem; | ||||
| } | ||||
|  | ||||
| .form form { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   padding: 1rem; | ||||
|   background-color: var(--clr-tertiary); | ||||
|   gap: .5rem; | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
| } | ||||
|  | ||||
| .form form input, | ||||
| select { | ||||
|   background-color: var(--clr-secondary); | ||||
|   caret-color: white; | ||||
|   color: white; | ||||
|   padding: .3rem; | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
|   width: 15rem; | ||||
| } | ||||
|  | ||||
| .form form input[type="submit"] { | ||||
|   background-color: var(--clr-green); | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| .form .error { | ||||
|   width: 17rem; | ||||
|   padding: 1rem; | ||||
|   background-color: var(--clr-danger-muted); | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border-danger); | ||||
|   margin-bottom: 1rem; | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| .form small.error { | ||||
|   width: 15rem; | ||||
| } | ||||
|  | ||||
| .form .bordered { | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
|   width: 17rem; | ||||
|   padding: 1rem; | ||||
|   margin-top: 1rem; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .form .bordered a { | ||||
|   text-decoration: none; | ||||
|   color: var(--clr-link-blue); | ||||
| } | ||||
|  | ||||
| .form .bordered a:hover { | ||||
|   text-decoration: underline; | ||||
| } | ||||
							
								
								
									
										47
									
								
								public/css/global.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								public/css/global.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| @import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap"); | ||||
|  | ||||
| body { | ||||
| 	font-family: "Open Sans", serif; | ||||
| 	background-color: var(--clr-primary); | ||||
| 	color: white; | ||||
| 	font-size: 14px; | ||||
| } | ||||
|  | ||||
| a { | ||||
| 	color: white; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
| 	margin-top: .5rem; | ||||
| 	margin-bottom: 1rem; | ||||
| } | ||||
|  | ||||
| .btn-primary, | ||||
| .btn-secondary, | ||||
| .btn-tertiary, | ||||
| .btn-green, | ||||
| .btn-danger { | ||||
| 	background-color: var(--clr-primary); | ||||
| 	padding: .5rem; | ||||
| 	text-decoration: none; | ||||
| 	cursor: pointer; | ||||
| 	border-radius: var(--border-radious); | ||||
| 	border: var(--borderWidth-thin) solid var(--clr-border); | ||||
| } | ||||
|  | ||||
| .btn-secondary { | ||||
| 	background-color: var(--clr-secondary); | ||||
| } | ||||
|  | ||||
| .btn-tertiary { | ||||
| 	background-color: var(--clr-tertiary); | ||||
| } | ||||
|  | ||||
| .btn-green { | ||||
| 	background-color: var(--clr-green); | ||||
| } | ||||
|  | ||||
| .btn-danger { | ||||
| 	background-color: var(--clr-danger-muted); | ||||
| 	border: var(--borderWidth-thin) solid var(--clr-border-danger); | ||||
| } | ||||
							
								
								
									
										21
									
								
								public/css/habits_create.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								public/css/habits_create.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| .form form .dow_chb_wrapper input[type="checkbox"] { | ||||
|   width: 1rem; | ||||
| } | ||||
|  | ||||
| .form form .dow_chb_wrapper { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
| } | ||||
|  | ||||
| #lbl_dow { | ||||
|   margin-bottom: .5rem; | ||||
| } | ||||
|  | ||||
| #lbl_dom { | ||||
|   margin-top: .5rem; | ||||
| } | ||||
|  | ||||
| #custom-frequency { | ||||
|   flex-direction: column; | ||||
|   justify-content: space-between; | ||||
| } | ||||
							
								
								
									
										15
									
								
								public/css/habits_dashboard.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								public/css/habits_dashboard.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| .habits-wrapper { | ||||
|   display: flex; | ||||
|   gap: 1rem; | ||||
|   flex-wrap: wrap; | ||||
|   justify-content: center; | ||||
| } | ||||
|  | ||||
| .habits .bordered { | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
|   width: 17rem; | ||||
|   padding: 1rem; | ||||
|   margin-top: 1rem; | ||||
|   text-align: center; | ||||
| } | ||||
							
								
								
									
										26
									
								
								public/css/header.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								public/css/header.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| header { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
|   padding: 0 2rem; | ||||
|   width: 100vw; | ||||
|   height: 3rem; | ||||
|   background: var(--clr-secondary); | ||||
|   border-radius: var(--border-radious); | ||||
|   border-bottom: var(--borderWidth-thin) solid var(--clr-border); | ||||
| } | ||||
|  | ||||
| #hd-left, | ||||
| #hd-right { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 1rem; | ||||
| } | ||||
|  | ||||
| header a img { | ||||
|   height: 2rem; | ||||
| } | ||||
|  | ||||
| header a { | ||||
|   text-decoration: none; | ||||
| } | ||||
							
								
								
									
										13
									
								
								public/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								public/css/main.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| * { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| main { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   margin-top: 2rem; | ||||
|   margin-bottom: 2rem; | ||||
|   padding: 0 var(--container-size); | ||||
| } | ||||
							
								
								
									
										20
									
								
								public/css/vars.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								public/css/vars.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| :root { | ||||
|   --container-size: 5vw; | ||||
|   --clr-primary: #010409; | ||||
|   --clr-secondary: #0d1117; | ||||
|   --clr-tertiary: #151b23; | ||||
|   --clr-green: #238636; | ||||
|   --clr-danger-muted: #f851491a; | ||||
|  | ||||
|   --clr-link-blue: #4493f8; | ||||
|   --clr-light-blue: #39a2ae; | ||||
|   --clr-light-green: #71f79f; | ||||
|   --clr-red: #9b1d20; | ||||
|   --clr-orange: #e08e45; | ||||
|   --clr-gray-blue: #627c85; | ||||
|  | ||||
|   --border-radious: 5px; | ||||
|   --borderWidth-thin: max(1px, 0.0625rem); | ||||
|   --clr-border: #3d444db3; | ||||
|   --clr-border-danger: #f8514966; | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								public/img/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/logo.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/logo.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 45 KiB | 
| @@ -24,8 +24,11 @@ require_once models . 'Habit.php'; | ||||
|  | ||||
| // Initialize router | ||||
| $router = new Router(); | ||||
| $router->add('/', 'HomeController@index'); | ||||
| $router->add('/home', 'HomeController@home'); | ||||
| if(!isset($_SESSION['user'])) { | ||||
|     $router->add('/', 'HomeController@index'); | ||||
| } else { | ||||
|     $router->add('/', 'DashboardController@reroute', ['RequireAuth']); | ||||
| } | ||||
|  | ||||
| // auth routes | ||||
| $router->group('/auth', [], function ($router) { | ||||
| @@ -35,7 +38,7 @@ $router->group('/auth', [], function ($router) { | ||||
| }); | ||||
|  | ||||
| // dashboard route | ||||
| $router->add('/dashboard', 'HomeController@dashboard', ['RequireAuth']); | ||||
| $router->add('/dashboard', 'DashboardController@index', ['RequireAuth']); | ||||
|  | ||||
| // habits routes | ||||
| $router->group('/habits', ['RequireAuth'], function ($router) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user