fontawesome und jeeede menge reworks
All checks were successful
Build and push / Pulling repo on server (push) Successful in 20s

This commit is contained in:
Chris 2023-10-22 01:46:22 +02:00
parent fc9de5b15c
commit 004e38b3bb
72 changed files with 53087 additions and 83 deletions

5
web/css/fontawesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,194 @@
<?php
class Model {
public $data;
public $id = false;
public $redis;
function __construct()
{
//redis
if ($GLOBALS['redis'])
$this->redis = $GLOBALS['redis'];
}
/**
* Magic getter function
*
* @param $name Variable name
*
* @return mixed
*/
public function __get($name)
{
if (isset($this->data[$name]))
return $this->data[$name];
}
/**
* Magic setter function
*
* @return mixed
*/
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function getDBFields()
{
return $this->dbFields;
}
/**
* Returns all data fields but removes filtered datapoints like passwords, etc
*/
public function getDataFiltered()
{
$data = $this->data;
if(is_array($this->hidden))
foreach($this->hidden as $secretfield)
unset($data[$secretfield]);
foreach($this->dbFields as $field=>$options)
if($options['type']=='csv')
$data[$field] = ($data[$field]?explode(';',$data[$field]):[]);
return $data;
}
public function save()
{
if (!$this->validate())
return false;
foreach($this->dbFields as $field=>$options)
{
if(isset($this->data[$field]))
$this->redis->hset($this->dbTable.':'.$this->id,$field,$this->data[$field]);
}
}
public function load($id)
{
$this->id = $id;
if(!$this->redis->exists($this->dbTable.':'.$this->id))
return false;
$keys = array_keys($this->dbFields);
foreach($keys as $key)
{
$value = $this->redis->hget($this->dbTable.':'.$this->id,$key);
if($value!==NULL) //we'll leave null values
switch($this->dbFields[$key]['type'])
{
case 'int': $value = intval($value);break;
case 'bool': $value = boolval($value);break;
case 'float': $value = floatval($value);break;
case 'double': $value = doubleval($value);break;
}
$this->data[$key] = $value;
}
return true;
}
/**
* @param array $data
*/
private function validate()
{
if (!$this->dbFields)
return true;
$data = $this->data;
foreach ($this->dbFields as $key => $options) {
$type = null;
$required = false;
if(in_array('autoupdate',$options))
$this->data[$key] = NULL;
if (isset($data[$key]))
$value = $data[$key];
else
$value = null;
if (is_array($value))
continue;
if (isset($desc[0]))
$type = $desc[0];
if (in_array('required',$options))
$required = true;
if($value===null && $options['autoValMethod'])
{
if(method_exists($this,$options['autoValMethod']))
$value = $this->{$options['autoValMethod']}();
else if(function_exists($options['autoValMethod']))
$value = $options['autoValMethod']();
else
$value = null;
$this->data[$key] = $value;
}
if($options['default']!==null && $value===null)
$value = $options['default'];
if ($required && strlen($value) == 0) {
throw new Exception($this->dbTable . "." . $key . " is required");
}
if ($value == null)
continue;
switch ($type) {
case 'email':
$regexp = null;
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new Exception("$type validation failed");
}
break;
case 'csv':
case "text":
$regexp = null;
break;
case "int":
$regexp = "/^[0-9]*$/";
break;
case "double":
$regexp = "/^[0-9\.]*$/";
break;
case "bool":
$regexp = '/^(yes|no|0|1|true|false)$/i';
break;
case "datetime":
$regexp = "/^[0-9a-zA-Z -:]*$/";
break;
default:
$regexp = $type;
break;
}
if (!$regexp)
continue;
if (!preg_match($regexp, $value)) {
throw new Exception("$type validation failed");
}
}
return true;
}
function delete()
{
return $this->redis->del($this->dbTable.':'.$this->id);
}
function gen_shorthash(){
return substr(uniqid(),-7);
}
function getDateTime($time=false)
{
return date('Y-m-d H:i:s',($time?$time:time()));
}
}

View File

@ -48,7 +48,8 @@ class Page
function redirect($url)
{
header("Location: $url");
//header("Location: $url");
header("HX-Redirect: ". $url);
exit();
}

View File

@ -6,6 +6,8 @@ function autoload($className)
//one of the global classes?
if (file_exists(ROOT . DS . 'inc'. DS. 'classes' . DS . $className . '.class.php'))
require_once(ROOT . DS . 'inc'. DS. 'classes' . DS . $className . '.class.php');
else if (file_exists(ROOT . DS . 'models' . DS . $className . '.model.php'))
require_once(ROOT . DS . 'models' . DS . $className . '.model.php');
else if (file_exists(ROOT . DS . 'pages' . DS . strtolower($className) . DS . 'controller.php'))
require_once(ROOT . DS . 'pages' . DS . strtolower($className) . DS . 'controller.php');
}
@ -25,7 +27,22 @@ function includeManagement()
//DB
if(defined('REDIS_SERVER') && REDIS_SERVER !='')
$GLOBALS['redis'] = new Redis();
{
$redis = new Redis();
try{
$redis->pconnect(REDIS_SERVER, REDIS_PORT);
if (defined('REDIS_PASS') && REDIS_PASS)
$redis->auth(REDIS_PASS);
if (defined('REDIS_PREFIX') && REDIS_PREFIX)
$redis->setOption(Redis::OPT_PREFIX, REDIS_PREFIX);
if (defined('REDIS_DB') && REDIS_DB)
$redis->select(REDIS_DB);
$GLOBALS['redis'] = $redis;
}
catch (Exception $e) {
$GLOBALS['redis'] = false;
}
}
else
$GLOBALS['redis'] = false;
}
@ -78,4 +95,40 @@ function callHook($url)
return $response;
else
return $dispatch->renderPagecontent();
}
function getMenu()
{
//first let's find all possible menu items
$arr = array();
if ($handle = opendir(ROOT . DS . 'pages')) {
while (false !== ($file = readdir($handle))) {
if (file_exists(ROOT . DS . 'pages' . DS . $file.DS.'controller.php') && class_exists($file)) {
$instance = new $file($file, 'index', false);
$instance->setMenu();
if($instance->maySeeThisPage()===true)
{
$menu_text = $instance->menu_text;
$menu_priority = $instance->menu_priority;
if($menu_text)
{
while($arr[$menu_priority])
$menu_priority++;
$arr[$menu_priority] = array('text'=>$menu_text,'image'=>$instance->menu_image, 'url'=>$file,'menu_class'=>$instance->menu_class);
}
}
}
}
closedir($handle);
}
//sort the menu
ksort($arr);
$arr = array_values($arr);
return $arr;
}

View File

@ -3,5 +3,6 @@
define('REDIS_SERVER', 'ingress');
define('REDIS_PORT', 6379);
define('REDIS_DB', 1);
define('REDIS_PREFIX', 'dogstats:');
define('DEV',true);

View File

@ -195,4 +195,18 @@ function ulid_to_timestamp($ulid)
function escape($str)
{
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
function uuid4($data = null) {
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
$data = $data ?? random_bytes(16);
assert(strlen($data) == 16);
// Set version to 0100
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
// Set bits 6-7 to 10
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
// Output the 36 character UUID.
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

View File

@ -1,11 +1,8 @@
<?php
session_start();
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__));
if($_SERVER['HTTP_HX_REQUEST']!='true')
exit(file_get_contents(ROOT.DS.'templates'.DS.'mainpage.html'));
// if(file_exists(ROOT.DS.'inc'.DS.'config.inc.php'))
// include_once(ROOT.DS.'inc'.DS.'config.inc.php');
// else die('ERR: no config file found :(');
@ -13,6 +10,12 @@ if($_SERVER['HTTP_HX_REQUEST']!='true')
include_once(ROOT.DS.'inc'.DS.'core.php');
includeManagement();
if($_SERVER['HTTP_HX_REQUEST']!='true')
{
include(ROOT.DS.'templates'.DS.'mainpage.html');
die();
}
if($_GET['url'])
$url = explode('/',ltrim(parse_url($_GET['url'], PHP_URL_PATH),'/'));
else $url = array_filter(explode('/',ltrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),'/')));

57
web/models/User.model.php Normal file
View File

@ -0,0 +1,57 @@
<?php
class User extends Model {
protected $dbTable = "users";
protected $dbFields = Array (
'uuid' => ['type'=>'text','required','unique','autoValMethod'=>'gen_ulid'],
'password' => ['type'=>'text'],
'registered' => ['type'=>'datetime','required','unique','autoValMethod'=>'getDateTime'],
'email' => ['type'=>'email','unique'],
'firstname' => ['type'=>'text'],
'lastname' => ['type'=>'text'],
'last_login' => ['type'=>'datetime','required','unique','autoValMethod'=>'getDateTime'],
'token' => ['type'=>'text','required','unique','autoValMethod'=>'uuid4'],
'timezone' => ['type'=>'int'],
'active' => ['type'=>'int','default'=>0]
);
protected $hidden = ['password','token'];
function login()
{
if(!$this->id) return false;
$this->last_login = $this->getDateTime();
$this->save();
$_SESSION['user'] = $this;
$_SESSION['userid'] = $this->id;
}
function logout()
{
if(!$this->id) return false;
unset($_SESSION['user']);
}
function exists($id)
{
return $this->redis->exists($this->dbTable.':'.$id);
}
function getAll($filtered = true)
{
$keys = $this->redis->keys($this->dbTable.':*');
$users = [];
foreach($keys as $key)
{
$id = end(explode(':',$key));
$u = new User();
$u->load($id);
if($filtered===true)
$users[] = $u->getDataFiltered();
else
$users[] = $u->data;
}
return $users;
}
}

View File

@ -0,0 +1,74 @@
<?php
class Admin extends Page {
function loginas()
{
$user = $_REQUEST['email'];
$u = new User();
if($u->load($user))
{
$u->login();
$this->redirect('/');
}
else
{
$this->set('message', 'User '.escape($user).' not found');
$this->set('template', 'notfound.html');
}
}
function edituser()
{
$user = $_REQUEST['email'];
$u = new User();
if($u->load($user))
{
$data = $u->data;
$this->set('userdata', $data);
$this->set('userid', $user);
$this->set('template', 'edituser.html');
}
else
{
$this->set('message', 'User '.escape($user).' not found');
$this->set('template', 'notfound.html');
}
}
function edituserdata()
{
$user = $_REQUEST['email'];
$u = new User();
if(!$u->load($user))
{
$this->set('message', 'User '.escape($user).' not found');
$this->set('template', '/templates/notfound.html');
return;
}
foreach($_REQUEST as $key => $value)
{
if($key == 'email') continue;
$u->$key = $value;
}
try{
$u->save();
}
catch(Exception $e)
{
$this->set('message', $e->getMessage());
$this->set('template', '/templates/error.html');
return;
}
$this->set('message', 'Speichern erfolgreich');
$this->set('template', '/templates/success.html');
}
function maySeeThisPage(){return true;}
}

View File

@ -0,0 +1,37 @@
<form hx-post="/admin/edituserdata" hx-target="#response">
<div id="defaultModal" tabindex="-1" aria-hidden="true" class="z-50 w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<!-- Modal header -->
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
Edit user <?= $userid ?>
</h3>
</div>
<!-- Modal body -->
<div class="p-6 space-y-6">
<div class="flex flex-col">
<label for="id" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
ID
</label>
<input type="text" name="id" disabled id="id" value="<?= $userid ?>" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
</div>
<?php foreach (array_keys($userdata) as $key) : ?>
<div class="flex flex-col">
<label for="<?= $key ?>" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
<?= $key ?>
</label>
<input type="text" name="<?= $key ?>" id="<?= $key ?>" value="<?= $userdata[$key] ?>" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
</div>
<?php endforeach; ?>
</div>
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>
<div id="response"></div>
</div>
</div>
</div>
</div>
</form>

View File

@ -4,15 +4,17 @@ class Home extends Page
{
function setMenu()
{
$this->menu_text = 'home';
$this->menu_image = 'mdi-action-home';
$this->menu_text = 'Home';
$this->menu_image = 'far fa-home';
$this->menu_priority = 0;
}
function index()
{
$u = new User();
$this->set('userdata', $u->getAll());
$this->set('template', "home.html");
return $this->renderPagecontent();
//return $this->renderPagecontent();
}
function maySeeThisPage(){return true;}

View File

@ -1,9 +1,6 @@
<div class="hidden sm:mb-8 sm:flex sm:justify-center">
<div
class="relative rounded-full px-3 py-1 text-sm leading-6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
Announcing our next round of funding. <a href="#" class="font-semibold text-indigo-600"><span
class="absolute inset-0" aria-hidden="true"></span>Read more <span
aria-hidden="true">&rarr;</span></a>
<div class="relative rounded-full px-3 py-1 text-sm leading-6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
Currently logged in as: <?= $_SESSION['userid']?:'nobody' ?>
</div>
</div>
<div class="text-center">
@ -11,14 +8,14 @@
<p class="mt-6 text-lg leading-8 text-gray-600">Anim aute id magna aliqua ad ad non deserunt sunt.
Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.</p>
<div class="mt-10 flex items-center justify-center gap-x-6">
<a href="#"
class="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Get
<a href="#" class="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Get
started</a>
<a href="#" class="text-sm font-semibold leading-6 text-gray-900">Learn more <span
aria-hidden="true">→</span></a>
<a href="#" class="text-sm font-semibold leading-6 text-gray-900">Learn more <span aria-hidden="true"></span></a>
</div>
</div>
<h1 class="text-2xl p-6 text-center">Admin stuff</h1>
<div class="container flex justify-center mx-auto">
<div class="flex flex-col">
<div class="w-full">
@ -26,54 +23,37 @@
<table class="divide-y divide-gray-300 ">
<thead class="bg-gray-50">
<tr>
<?php foreach (array_keys($userdata[0]) as $key) : ?>
<th class="px-6 py-2 text-xs text-gray-500">
ID
<?= $key ?>
</th>
<?php endforeach; ?>
<th class="px-6 py-2 text-xs text-gray-500">
Name
</th>
<th class="px-6 py-2 text-xs text-gray-500">
Email
</th>
<th class="px-6 py-2 text-xs text-gray-500">
Created_at
Login
</th>
<th class="px-6 py-2 text-xs text-gray-500">
Edit
</th>
<th class="px-6 py-2 text-xs text-gray-500">
Delete
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-300">
<?php foreach ([
['id' => 1, 'name' => 'Max', 'email' => 'test@test.ts'],
['id' => 2, 'name' => 'Roxie', 'email' => 'loxie@test.ts'],
] as $user) : ?>
<?php foreach ($userdata as $user) : ?>
<tr class="whitespace-nowrap">
<td class="px-6 py-4 text-sm text-gray-500">
<?= $user['id'] ?>
</td>
<td class="px-6 py-4">
<div class="text-sm text-gray-900">
<?= $user['name'] ?>
</div>
</td>
<td class="px-6 py-4">
<div class="text-sm text-gray-500"><?= $user['email'] ?></div>
</td>
<td class="px-6 py-4 text-sm text-gray-500">
2021-1-12
</td>
<td class="px-6 py-4">
<a href="#" class="px-4 py-1 text-sm text-indigo-600 bg-indigo-200 rounded-full">Edit</a>
</td>
<td class="px-6 py-4">
<a href="#" class="px-4 py-1 text-sm text-red-400 bg-red-200 rounded-full">Delete</a>
</td>
</tr>
<tr class="whitespace-nowrap">
<?php foreach (array_keys($user) as $key) : ?>
<td class="px-6 py-4 text-sm text-gray-500">
<?= $user[$key] ?>
</td>
<?php endforeach; ?>
<td class="px-6 py-4">
<button name="email" value="<?= $user['email'] ?>" hx-post="/admin/loginas/" class="px-4 py-1 text-sm text-indigo-600 bg-indigo-200 rounded-full">Login as this user</button>
</td>
<td class="px-6 py-4">
<button name="email" value="<?= $user['email'] ?>" hx-post="/admin/edituser/" hx-target="#main" class="px-4 py-1 text-sm text-red-400 bg-red-200 rounded-full">Edit</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>

View File

@ -2,6 +2,16 @@
class Login extends Page {
function setMenu()
{
if($_SESSION['user'])
$this->menu_text = $_SESSION['userid'];
else
$this->menu_text = 'Login';
$this->menu_image = 'far fa-user';
$this->menu_priority = 99;
}
function index()
{
$this->set('hello','world');

View File

@ -31,7 +31,7 @@
Sign in
</button>
<p class="text-sm font-light text-gray-500 dark:text-gray-400">
Dont have an account yet? <a href="#" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
Dont have an account yet? <a href="#" hx-get="/register" hx-push-url="/register" hx-target="#main" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
</p>
</form>
</div>

View File

@ -0,0 +1,91 @@
<?php
class Register extends Page {
function index()
{
$this->set('template', 'register.html');
}
function validate()
{
$email = trim($_REQUEST['email']);
$password = trim($_REQUEST['password']);
$password2 = trim($_REQUEST['password2']);
$hash = password_hash($password, PASSWORD_DEFAULT);
$u = new User();
$err = false;
if(!$email)
$err = "Email is required";
else if(!filter_var($email, FILTER_VALIDATE_EMAIL))
$err = "Email is invalid";
else if(!$password)
$err = "Password is required";
else if($password != $password2)
$err = "Passwords do not match";
else if(strlen($password) < 8)
$err = "Password must be at least 8 characters long";
else if($u->exists($email))
$err = "Email already exists";
if($err)
{
$this->set('template', '/templates/error.html');
$this->set('title', 'Error');
$this->set('message', $err);
return;
}
else
{
$u->id = $email;
$u->email = $email;
$u->password = $hash;
$u->active = 0;
try
{
$u->save();
}
catch(Exception $e)
{
$this->set('template', '/templates/error.html');
$this->set('title', 'Error');
$this->set('message', $e->getMessage());
return;
}
//$this->redirect('/register/success');
return;
}
return print_r(['email'=>$email,'password'=>$password,'password2'=>$password2], true);
}
function success()
{
$this->set('template', '/templates/success.html');
$this->set('title', 'Success');
$this->set('message', 'You have successfully registered. Activate your account from the Link in your email');
}
function test()
{
$u = new User();
$u->id = 'chris@chris.ch';
$u->email = 'chris@chris.ch';
$u->password = '123456';
$u->save();
return nl2br(print_r($u, true));
}
function test2()
{
$u = new User();
$response = $u->load('chris@chasdris.ch');
return nl2br(print_r($response, true));
}
}

View File

@ -0,0 +1,30 @@
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto lg:py-0">
<div class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Register
</h1>
<div id="response" class="text-gray-900 dark:text-white">
<?= $hello ?>
</div>
<form class="space-y-4 md:space-y-6" hx-post="/register/validate" hx-target="#response">
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" required="">
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
<input type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
</div>
<div>
<label for="password2" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Repeat Password</label>
<input type="password" name="password2" id="password2" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
</div>
<button type="submit" class="bg-sky-500 hover:bg-sky-700 p-2 rounded">
Account erstellen
</button>
</form>
</div>
</div>
</div>

4
web/templates/error.html Normal file
View File

@ -0,0 +1,4 @@
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4" role="alert">
<p class="font-bold"><?= $title ?></p>
<p><?= $message ?></p>
</div>

4
web/templates/info.html Normal file
View File

@ -0,0 +1,4 @@
<div class="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4" role="alert">
<p class="font-bold"><?= $title ?></p>
<p><?= $message ?></p>
</div>

View File

@ -6,40 +6,17 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dogstats</title>
<link href="/css/output.css" rel="stylesheet">
<link href="/css/fontawesome.min.css" rel="stylesheet">
<script src="/js/htmx.min.js"></script>
</head>
<body>
<div class="bg-white">
<header class="inset-x-0 top-0 z-50">
<nav class="flex items-center justify-between p-6 lg:px-8" aria-label="Global">
<div class="flex lg:flex-1">
<a href="#" hx-get="/home" hx-push-url="/home" hx-target="#main" class="-m-1.5 p-1.5">
<span class="sr-only">Dogstats</span>
<img class="h-8 w-auto" src="/imgs/dogstats-50.png" alt="">
</a>
</div>
<div class="flex lg:hidden">
<button type="button" class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700">
<span class="sr-only">Open main menu</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
</div>
<div class="hidden lg:flex lg:gap-x-12">
<a href="#" hx-get="/templates/product.html" hx-target="#main" class="text-sm font-semibold leading-6 text-gray-900">Product</a>
<a href="#" hx-get="/features" hx-push-url="/features" hx-target="#main" class="text-sm font-semibold leading-6 text-gray-900">Features</a>
</div>
<div class="hidden lg:flex lg:flex-1 lg:justify-end">
<a href="#" hx-get="/login" hx-target="#main" hx-push-url="/login" class="text-sm font-semibold leading-6 text-gray-900">Log in <span aria-hidden="true">&rarr;</span></a>
</div>
</nav>
</header>
<div id="main" hx-get="/" hx-trigger="load">
<?php include(ROOT."/templates/menu.html") ?>
<div id="main" hx-get="/" hx-trigger="load" hx-indicator="#spinner">
<i id="spinner" class="fa-solid fa-spinner"></i>
</div>
</div>
</body>

15
web/templates/menu.html Normal file
View File

@ -0,0 +1,15 @@
<header class="text-gray-600 body-font">
<div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
<a class="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">
<img class="h-8 w-auto" src="/imgs/dogstats-50.png" alt="">
<span class="ml-3 text-xl">Dogstats</span>
</a>
<nav class="md:ml-auto flex flex-wrap items-center text-base justify-center">
<?php foreach(getMenu() as $item) : ?>
<a href="/<?= $item['url'] ?>" hx-push-url="/<?= $item['url'] ?>" hx-get="/<?= $item['url'] ?>" hx-target="#main" class="mr-5 hover:text-gray-900"><i class="<?= $item['image'] ?>"></i> <?= $item['text'] ?></a>
<?php endforeach; ?>
</nav>
</div>
</header>

View File

@ -0,0 +1,4 @@
<div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4" role="alert">
<p class="font-bold"><?= $title ?></p>
<p><?= $message ?></p>
</div>

View File

@ -0,0 +1,4 @@
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4" role="alert">
<p class="font-bold"><?= $title ?></p>
<p><?= $message ?></p>
</div>

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

12423
web/webfonts/fa-light-300.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.