diff --git a/account.php b/account.php new file mode 100644 index 0000000000000000000000000000000000000000..4812057ebbca5dcb4b604e478dae5fa9506db761 --- /dev/null +++ b/account.php @@ -0,0 +1,132 @@ +<?php + +if (!isset($_SESSION['auth'])) { + header('Location: /signin?callback=/account'); + exit; +} + +function get_gravatar_url( $email ) { + // Trim leading and trailing whitespace from + // an email address and force all characters + // to lower case + $address = strtolower( trim( $email ) ); + + // Create an SHA256 hash of the final string + $hash = hash( 'sha256', $address ); + + // Grab the actual image URL + return 'https://www.gravatar.com/avatar/' . $hash; + } + +$stmt = $pdo->prepare('SELECT * FROM accounts WHERE id = ? LIMIT 1'); +$stmt->execute([$_SESSION['id']]); +$user = $stmt->fetch(); + + +if ($_SERVER['REQUEST_METHOD'] == "POST") { + + if (isset($_POST["old_password"]) && $_POST["old_password"] != "") { + // means password reset is wanted. + + if (!password_verify($_POST["old_password"], $user["password"])) { + $password_error = "Incorrect password. (Error 901)"; + } + + if (password_verify($_POST['new_password'], $user["password"])) { + $password_error = "New password may not be same as old password. (Error 902)"; + } + + if ($_POST['new_password'] != $_POST['repeat_new_password']) { + $password_error = "The passwords must match. (Error 900)"; + } + + + if (isset($password_error)) { + $message = $password_error; + goto skip_submit; + } + + $new_password = password_hash($_POST["new_password"], PASSWORD_DEFAULT); + + $sql = "UPDATE accounts SET password = ? WHERE id = ?"; + $pdo->prepare($sql)->execute([$new_password, $user["id"]]); + } + + if (isset($_POST["display_name"])) { + $sql = "UPDATE accounts SET display_name = ? WHERE id = ?"; + $pdo->prepare($sql)->execute([$_POST["display_name"], $user["id"]]); + } + + $message = "Updated sucessfully. Changes might take a few minutes to take effect."; + +} + +skip_submit: + +?> + +<h1>Your account</h1> + +<?php +if (isset($message )) { + echo "<div class='flash'>".$message."</div>"; + } +?> + +<div id="profile"> + <img src="<?= get_gravatar_url($user['email']) ?>"> + <div class="details"> + <span class="displayname"><?= $user['display_name'] ?></span> + <span class="bcid"><?= format_bcid($user['id']); ?></span> + <time datetime="<?= $user["created_date"] ?>">Since <?= $user["created_date"]; ?></time> + </div> +</div> + +<form method="post"> + <fieldset> + <legend>Profile</legend> + <div class="container"> + <label>BCID</label> + <input type="text" disabled value="<?= format_bcid($user['id']) ?>"> + </div> + + <div class="container"> + <input type="checkbox" disabled checked="<?= $user['verified'] ?>" > + <label> Verified email</label> + </div> + + <div class="container"> + <label for="email">Email address</label> + <input type="email" name="email" id="email" value="<?= $user['email'] ?>"> + </div> + + <div class="container"> + <label for="display_name">Display name</label> + <input type="text" name="display_name" id="display_name" value="<?= $user['display_name'] ?>"> + </div> + </fieldset> + <fieldset> + <legend>Password</legend> + <p>You only need to insert values here if you're resetting your password.</p> + <div class="container"> + <label for="old_password">Current password</label> + <input type="password" name="old_password" id="old_password"> + </div> + <div class="container"> + <label for="new_password">New password</label> + <input type="password" name="new_password" id="new_password"> + </div> + <div class="container"> + <label for="repeat_new_password">Repeat new password</label> + <input type="password" name="repeat_new_password" id="repeat_new_password"> + </div> + </fieldset> + + <button class="primary" type="submit"><i class="fa-fw fa-solid fa-floppy-disk"></i> Save</button> +</form> + +<div class="dangerzone"> + <h2>Danger Zone</h2> + <p><a href="/signout" class="button"><i class="fa-fw fa-solid fa-person-through-window"></i> Sign out</a> <a href="/dangerous/delete_account" class="button danger">Delete account</a></p> +</div> + diff --git a/admin_accounts.php b/admin_accounts.php new file mode 100644 index 0000000000000000000000000000000000000000..483fcf797020092e202304882888ac826d391334 --- /dev/null +++ b/admin_accounts.php @@ -0,0 +1,36 @@ +<?php + +if ($_SESSION['id'] != "281G3NV") { + http_response_code(401); + die("<img src='https://http.cat/401.jpg'>"); +} + +$sql = "SELECT * FROM accounts"; +$result = $pdo-> query($sql); +if (!$result) { + http_response_code(500); + die("<img src='https://http.cat/500.jpg'>"); +} + + +$count_req = $pdo->query("SELECT COUNT(*) FROM accounts"); +$count = $count_req->fetchColumn(); + + +?> + +<h2 class="subheading">Admin</h2> +<h1>Accounts</h1> + +<p>There is currently <?= $count ?> accounts registered.</p> + + +<ul> + <?php + foreach ($result as $row) { + echo "<li><pre>"; + print_r($row); + echo "</pre><p><a href='/admin/signinas?id=".$row['id']."'>Sign in as ".$row['display_name']."</a></li>"; + } + ?> +</ul> diff --git a/admin_initdatabase.php b/admin_initdatabase.php new file mode 100644 index 0000000000000000000000000000000000000000..4ff12208baf9ec976bed918abcb121c0d9870824 --- /dev/null +++ b/admin_initdatabase.php @@ -0,0 +1,51 @@ +<?php + +if ($_SESSION['id'] != "281G3NV") { + http_response_code(401); + die("<img src='https://http.cat/401.jpg'>"); +} + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + if ($_POST['init'] == 'Init') { + echo("<p>Initialising DB..."); + $pdo = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD, PDO_OPTIONS); + echo "<p>Create table `accounts`"; + $stmt = $pdo->prepare('CREATE TABLE `accounts` ( + `id` tinytext NOT NULL, + `email` text NOT NULL,, + `display_name` text NULL, + `password` text NOT NULL, + `verified` tinyint(1) NOT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;'); + + try { + $stmt->execute(); + } catch (PDOException $e) { + echo('<p>An error occurred: '. $e->getMessage() .'. Will skip. (Most likely the table already exists.)'); + } + + echo '<p>Set indexes for table `accounts`'; + $stmt = $pdo->prepare('ALTER TABLE `accounts` + ADD PRIMARY KEY (`id`(7)), + ADD UNIQUE KEY `email` (`email`) USING HASH;'); + + try { + $stmt->execute(); + } catch (PDOException $e) { + echo('<p>An error occurred: '. $e->getMessage() .'. Most likely this is already set.'); + } + + echo "<p>Database initialised.</p>"; + } +} + +?> + +<h2 class="subheading">Admin</h2> +<h1>Init database</h1> + +<p>Assuming you have the database config configured, you can click this button to create the tables required for this thing to function.</p> + +<form method="post"> + <button name="init" value="Init" class="primary">Init DB</button> +</form> diff --git a/db.php b/db.php new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/forgot_password.php b/forgot_password.php new file mode 100644 index 0000000000000000000000000000000000000000..15ced1abb242fdacca355a64165772889875ee87 --- /dev/null +++ b/forgot_password.php @@ -0,0 +1,31 @@ + +<?php + +if (isset($_SESSION['auth'])) { + header('Location: /account'); +} + +if ($_SERVER['REQUEST_METHOD'] == "POST") { + $message = "We've sent an email to that inbox if we find an associated account."; + $sql = "SELECT * FROM accounts WHERE email = ?"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$_POST['email']]); + $user = $stmt->fetch(); + + if ($user != null) { // account exists + mail($user['email'], "ByeCorps ID Password Reset Confirmation", "The email was sent!"); + } +} + +?> + +<h1>Forgot password</h1> + +<?php if(isset($message)) echo "<p>".$message."</p>"; ?> + +<p>Forgot your password? We'll send you an email to reset it.</p> + +<form method="post"> + <input placeholder="a.dent@squornshellous.cloud" name="email" id="email" type="email"> + <button type="submit">Request password reset</button> +</form> \ No newline at end of file diff --git a/header.php b/header.php index c9ea57e19ab999acd8db9cd591e9de433b54dd00..7cae72e9a1c9d2c932df49c5f5795bee60002ad0 100644 --- a/header.php +++ b/header.php @@ -1,5 +1,25 @@ <!-- This is a testing file for the header used on BCID. Copy of header on ByeCorps.com --> +<?php + +if (!isset($_SESSION['auth'])) goto skip_auth; + + +if ($_SESSION['auth']) { + $sql = "SELECT display_name FROM accounts WHERE id = ?"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$_SESSION['id']]); + $name = $stmt->fetchColumn(); +} + +if ($name == '') { + $name = '<code class=bcid>'.format_bcid($_SESSION['id']).'</code>'; +} + +skip_auth: + +?> + <link rel="stylesheet" href="./styles/global.css"> <link rel="stylesheet" href="./fontawesome/css/all.css"> @@ -8,8 +28,12 @@ <a href="/" id="sitetitle"><span class="bc-1">Bye</span><span class="bc-2">Corps</span><span class="bc-3"> ID</span></a></div> <div class="end"> + + <?php if (!isset($_SESSION['auth'])) goto signed_out; ?> <div class="loggedin"> - <a href="/account" class="account">Hey there, Bye! <i class="fa-solid fa-fw fa-angle-down"></i></a> + <a href="/account" class="account">Hey there, <?= $name ?>! <i class="fa-solid fa-fw fa-angle-right"></i></a> </div> + <?php signed_out: ?> + </div> </header> \ No newline at end of file diff --git a/id.sql b/id.sql index c1a24cdef2fd436283f7baf1eeae844118f00146..86a603af03743f362854ccc5c4654d98682e1231 100644 --- a/id.sql +++ b/id.sql @@ -30,7 +30,8 @@ SET time_zone = "+00:00"; CREATE TABLE `accounts` ( `id` tinytext NOT NULL COMMENT 'BCID', `email` text NOT NULL, - `password` text NOT NULL COMMENT 'Hashed!!!', + `display_name` text NULL, + `password` text NOT NULL, `verified` tinyint(1) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/id_handler.php b/id_handler.php index 01615cd0fd3cb0019c44e00be1c77f7e547aa0fc..ac66b356e542c865bfadaf1c2e8abf0b6adb0c2f 100644 --- a/id_handler.php +++ b/id_handler.php @@ -1,6 +1,6 @@ <?php -function ganerate_bcid() { +function generate_bcid() { $CHARS = str_split("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); return $CHARS[array_rand($CHARS)].$CHARS[array_rand($CHARS)].$CHARS[array_rand($CHARS)].$CHARS[array_rand($CHARS)].$CHARS[array_rand($CHARS)].$CHARS[array_rand($CHARS)].$CHARS[array_rand($CHARS)]; } @@ -16,19 +16,17 @@ function validate_bcid($bcid) { return 0; // fail condition } -$BCID = ganerate_bcid(); +function format_bcid ($bcid) { // Formats to XXX-XXXX + $stripped_bcid = str_replace([' ','-'], '', $bcid); + $stripped_bcid = strtoupper($stripped_bcid); -echo "<pre>"; -echo "Random BCID (unformatted): $BCID -"; -echo "Check if BCID is valid: ".validate_bcid($BCID)." -"; + if (!validate_bcid($stripped_bcid)) { + throw new Exception('Invalid BCID.'); + } -if ($query['bcid']) { - echo "BCID provided in the query: ".$query['bcid']." -"; - echo "Checking the BCID provided in the query: ".validate_bcid($query['bcid'])." -"; + return substr($stripped_bcid, 0, 3).'-'.substr($stripped_bcid, -4, 4); } + +$BCID = generate_bcid(); ?> \ No newline at end of file diff --git a/index.php b/index.php index f2f3bed174c7903bdef3df1bad2d3ce9451b4e91..5bb9cdaa21b077f0b7da3014dbdfb5309153d63f 100644 --- a/index.php +++ b/index.php @@ -3,12 +3,22 @@ session_start(); include("config.php"); +include("id_handler.php"); +include("time_handler.php"); + +function does_variable_exists( $variable ) { + return (isset($$variable)) ? "true" : "false"; +} $host_string = $_SERVER['HTTP_HOST']; $host = explode('.', $host_string); $uri_string = $_SERVER['REQUEST_URI']; $query_string = explode('?', $uri_string); $path = $query_string[0]; +if (str_ends_with($path,'/') && $path != "/") { + header('Location: '.substr($path,0, -1)); + exit; +} $uri = array_values(array_filter(explode('/', $uri_string))); if(isset($query_string[1])) { @@ -21,30 +31,33 @@ if(isset($query_string[1])) { } } else { - $query = array(); + $query = array(); } -$include = "404.html"; +$pdo = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD, PDO_OPTIONS); +$include = "404.html"; // routing -if (!$uri) { - // empty array means index - $include = "landing.html"; -} -else if ($path == "/signin") { - $doc_title = "Sign in"; - include("signin.php"); - exit; -} -else if ($path == "/register") { - $doc_title = "Register"; - include("register.php"); - exit; -} -else if ($path == "/tests/id") { - include("id_handler.php"); - exit; + +$paths = array( + "/" => ["landing.php"], + "/admin/init/database" => ["admin_initdatabase.php"], + "/admin/accounts" => ["admin_accounts.php"], + "/account" => ["account.php", "Your account"], + "/signin" => ["signin.php", "Sign in"], + "/signup" => ["signup.php", "Sign up"], + "/signout" => ["signout.php", "Signed out"], + "/forgot_password" => ["forgot_password.php", "Forgot password"], + "/admin/signinas" => ["signinas.php"] +); + +if (isset($paths[$path])) { + $include = $paths[$path][0]; + if (isset($paths[$path][1])) { + $doc_title = $paths[$path][1]; + } } + else { $doc_title = "404"; http_response_code(404); @@ -60,7 +73,14 @@ else { <body> <?php include("header.php"); ?> <main> - <?php include($include); ?> + <?php + + if ($uri[0] == "admin" && $_SESSION['id'] != "281G3NV") { + http_response_code(401); + die("<img src='https://http.cat/401.jpg'>"); + } + + include($include); ?> </main> <?php include("footer.php"); ?> </body> diff --git a/landing.html b/landing.php similarity index 55% rename from landing.html rename to landing.php index 6ad77296ac6d82582180342c3b919b30e4921bae..c60788c252d7307f596c10918f74e57fa000f71a 100644 --- a/landing.html +++ b/landing.php @@ -3,7 +3,11 @@ <h1><span class="bc-1">Bye</span><span class="bc-2">Corps</span><span class="bc-3"> ID</span></h1> <p>Log into ByeCorps and beyond with a single ID.</p> <!-- <p><input type="email" name="loginEmail" id="loginEmail" placeholder="Email" /></p> --> - <a href="/signin" class="button primary">Sign in</a> - <a href="/register" class="button">Create an account</a> + + <?php + if ( $_SESSION['auth']) { echo "<a href='/account' class='button primary'>Manage account</a>"; } + else { echo "<a href='/signin' class='button primary'>Sign in</a><a href='/signup' class='button'>Create an account</a>"; } + ?> + </div> </div> \ No newline at end of file diff --git a/register.php b/register.php deleted file mode 100644 index eb8ce98fc7e6618426d63a2aa2dfa0654dff1d71..0000000000000000000000000000000000000000 --- a/register.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php - - -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $DB_SERVER = DB_ADDRESS; - $DB_USER = DB_USERNAME; - $DB_PASSWD = DB_PASSWORD; - $DB_BASE = DB_DATABASE; - - $email = $_POST['email']; - $password = password_hash($_POST['password'], PASSWORD_DEFAULT); - - try { - $conn = new PDO("mysql:host=$DB_SERVER;dbname=$DB_BASE", $DB_USER, $DB_PASSWD); - // set the PDO error mode to exception - $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $sql = "INSERT INTO `accounts` (`email`, `password`, `verified`) VALUES ('$email', '$password', '0')"; - try{ - $stmt = $conn->prepare($sql); - $stmt->execute($query); - $result = $stmt->fetch(); - echo "Failed successfully: $result"; - } catch (PDOException $e) { - http_response_code(500); - die("An error occured: $e"); - } - } - catch(PDOException $e) { - die ("Connection failed: " . $e->getMessage()); - } - echo '<pre>'; - print_r($_POST); - - exit; -} - -?> - -<!DOCTYPE html> -<html lang="en"> -<head> - <?php include("head.php"); ?> -</head> -<body> - <?php include("header.php"); ?> - <main> - <h2>Sign in</h2> - <form action="#" method="post"> - <input type="email" name="email" id="email" placeholder="Email"> - <input type="password" name="password" id="password" placeholder="Password"> - <button type="submit">Submit</button> - </form> - </main> - <?php include("footer.php"); ?> -</body> -</html> \ No newline at end of file diff --git a/signin.php b/signin.php new file mode 100644 index 0000000000000000000000000000000000000000..03614f1a8e31aa8199e57f96314a456bceda8b8b --- /dev/null +++ b/signin.php @@ -0,0 +1,56 @@ +<?php + +if ($_SESSION['auth']) { + header('Location: /account'); +} + +if (isset($query['callback'])) { + $message = "You must sign in to continue."; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $email = $_POST['email']; + $password = $_POST['password']; + + $sql = "SELECT * FROM accounts WHERE email = :email"; + try { + $stmt = $pdo->prepare($sql); + $stmt->execute(array("email"=> $email)); + $user = $stmt->fetch(); + } + catch (PDOException $e) { + die ("Something happened: ". $e->getMessage()); + } + + if (password_verify($password, $user["password"])) { + $_SESSION["id"] = $user["id"]; + $_SESSION["auth"] = true; + if (isset($query['callback'])) { + header("Location: ".$query['callback']); + } else { + header("Location: /account"); + } + + exit; + } else { + $message = "Email or password incorrect."; + } +} + +?> + + +<h2>Sign in to ByeCorps ID</h2> +<?php +if (isset($message)) { + echo "<div class='flash'>$message</div>"; +}?> +<form method="post"> + <input type="email" name="email" id="email" placeholder="Email"> + <input type="password" name="password" id="password" placeholder="Password"> + <button type="submit">Sign in</button> +</form> + +<p class="center"> + <!--<a href="/forgot_password">Forgot password?</a> ยท--> New? <a href="/register">Register</a> for a ByeCorps ID. +</p> \ No newline at end of file diff --git a/signinas.php b/signinas.php new file mode 100644 index 0000000000000000000000000000000000000000..d31fbfc904cf80b266e21cdbe8c318a56a5ef875 --- /dev/null +++ b/signinas.php @@ -0,0 +1,12 @@ +<?php + +if ($_SESSION['id'] != "281G3NV") { + http_response_code(401); + die("<img src='https://http.cat/401.jpg'>"); +} + +$_SESSION['id'] = $query['id']; + +header ('Location: /account'); + +?> \ No newline at end of file diff --git a/signout.php b/signout.php new file mode 100644 index 0000000000000000000000000000000000000000..fb565e57360646f122fa4d7f6a11fbaf650d7245 --- /dev/null +++ b/signout.php @@ -0,0 +1,8 @@ +<?php + +session_destroy(); + +?> + +<p>You've been signed out successfully. You may close the page.</p> +<p><a href="/signin">Sign back in</a> ~ <a href="/">Go to home</a></p> diff --git a/signup.php b/signup.php new file mode 100644 index 0000000000000000000000000000000000000000..9d70fca52e8cec08577080b9854f6416f6faa2cc --- /dev/null +++ b/signup.php @@ -0,0 +1,46 @@ +<?php + + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $DB_SERVER = DB_ADDRESS; + $DB_USER = DB_USERNAME; + $DB_PASSWD = DB_PASSWORD; + $DB_BASE = DB_DATABASE; + + $email = $_POST['email']; + $password = password_hash($_POST['password'], PASSWORD_DEFAULT); + $BCID = generate_bcid(); + if (!validate_bcid($BCID)) { + die("Server-side error with your BCID #. Try again."); + } + + try { + $sql = "INSERT INTO `accounts` (`id`, `email`, `password`, `verified`) VALUES (?, ?, ?, ?)"; + try{ + $stmt = $pdo->prepare($sql); + $stmt->execute([$BCID, $email, $password, 0]); + $result = $stmt->fetch(); + echo "Failed successfully: $result"; + } catch (PDOException $e) { + http_response_code(500); + die("An error occured: $e"); + } + } + catch(PDOException $e) { + die ("Connection failed: " . $e->getMessage()); + } + + $_SESSION["auth"] = true; + $_SESSION["id"] = $BCID; + + exit; +} + +?> + +<h2>Sign up for ByeCorps ID</h2> +<form method="post"> + <input type="email" name="email" id="email" placeholder="Email"> + <input type="password" name="password" id="password" placeholder="Password"> + <button type="submit">Sign up</button> +</form> \ No newline at end of file diff --git a/strings.php b/strings.php new file mode 100644 index 0000000000000000000000000000000000000000..c0cc8c06c1a3e1b3fa5e7f7a22da10b1e275ded3 --- /dev/null +++ b/strings.php @@ -0,0 +1,16 @@ +<?php + +// This file contains strings inserted by PHP, designed for easy editing and localisation. + +$errors = [ + // "error_code" => "Message" + + // XX errors are generic messages + + // 9XX errors are user error + "900" => "Sorry, those passwords don't match. Please try again.", + "901" => "Incorrect password. Please check your spelling and try again." + +] + +?> \ No newline at end of file diff --git a/styles/colours.css b/styles/colours.css index 2d9116719711283dc463c6115ae6f2b51be19730..61ee27517e8a278c46ab60795184abbb0d9c2022 100644 --- a/styles/colours.css +++ b/styles/colours.css @@ -13,14 +13,31 @@ --flax: #efdd8d; --mindaro: #f4fdaf; + /* open colors: used for debugging */ + --red-5: #ff6b6b; + --red-5-transparent: #ff6b6b3a; + --red-8: #e03131; + --green-5: #51cf66; + --green-8: #2f9e44; + color-scheme: light dark; } +button, .button { + background-color: #1f302b40; + color: var(--white); +} + button.primary, .button.primary { color: var(--black-bean); background-color: var(--flax); } +button.danger, .button.danger { + color: var(--white); + background-color: var(--red-5); +} + header { background-color: var(--flax); color: var(--dark-slate-gray); @@ -30,6 +47,49 @@ header a { color: var(--dark-slate-gray); } +input { + all: unset; + padding: 1em; + text-align: start; + + border-radius: 1em; + + background-color: #c0c0c077; +} + +input[data-com-onepassword-filled="light"] { + background-color: var(--byecorps-white) !important; +} + +input[data-com-onepassword-filled="dark"] { + background-color: var(--byecorps-blue) !important; +} + +.icon-true { + color: var(--green-8); +} + +.icon-false { + color: var(--red-8); +} + +.dangerzone { + background-color: var(--red-5-transparent); + color: var(--white); + + padding: 0.5rem 1em; + border-radius: 1em; + +} + +.dangerzone h2 { + margin: 0; +} + +.dangerzone p { + margin: 0; +} + @media screen and (prefers-color-scheme: dark) { button.primary, .button.primary { color: var(--flax); @@ -44,4 +104,21 @@ header a { header a { color: var(--flax); } + + input { + + background-color: #2c2c2c77; + } + + .icon-true { + color: var(--green-5); + } + + .icon-false { + color: var(--red-5); + } + + a, a:visited, a:link { + color: var(--flax); + } } \ No newline at end of file diff --git a/styles/design.css b/styles/design.css index 7de58d3401a273e29dc9d9bcdb82dbf5beec41e4..27cc92ba8293d0b6cc5407bb65952ec6bd90d492 100644 --- a/styles/design.css +++ b/styles/design.css @@ -28,3 +28,20 @@ input { border-radius: 1em; } + +input[type="checkbox"] { + -webkit-appearance: checkbox; + -moz-appearance: checkbox; + -ms-appearance: checkbox; + -o-appearance: checkbox; + appearance: checkbox; + + width: 1em; + height: 1em; + margin: 0 0.5em 0 0; +} + +input:disabled { + opacity: 0.75; + cursor: not-allowed; +} diff --git a/styles/global.css b/styles/global.css index 59f2674e65db971a74bb0ad242fd21936a0e2c53..379b7641486b53724242fb2c22df8adfe4719dae 100644 --- a/styles/global.css +++ b/styles/global.css @@ -3,10 +3,8 @@ @import url(./layout.css); @import url(./colours.css); -:root { - color-scheme: light dark; -} * { box-sizing: border-box; + } diff --git a/styles/layout.css b/styles/layout.css index 7d0829c4e41c09e48c051e8629eb6640553fcbaa..6e4f2b445b7cde423835c6a36ac0150132191352 100644 --- a/styles/layout.css +++ b/styles/layout.css @@ -33,6 +33,7 @@ header .end { } main { + height: 100%; flex: 1; padding: 1rem 1rem; } @@ -47,10 +48,69 @@ footer { gap: 1rem; } +fieldset { + + border: #c0c0c0c0 1px solid; + border-radius: calc(1em + 10px); + + padding: 10px 10px 5px 10px; +} + +legend { + font-size: 1.25rem; + font-weight: 600; +} + +form:has(fieldset) { + + /* fit two fieldsets side by side */ + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + + +} + +form:has(fieldset) > button[type="submit"] { + /* align the button to the right */ + grid-column: span 2; +} + +form .container { + /* contains a label and an input */ + display: flex; + flex-direction: column; + gap: 0.25rem; + padding-bottom: 5px; +} + +form .container:has(input[type="checkbox"]) { + flex-direction: row; +} + +form .container label { + font-size: 0.9rem; + opacity: 0.5; +} + +form .container:has(input[type="checkbox"]) label { + margin-left: 0.5em; + opacity: 1; + font-size: 1rem; +} + footer h2 { margin: 0; } +form { + display: flex; + flex-direction: column; + gap: 1rem; +} + + + .hero { display: flex; flex-direction: column; @@ -66,4 +126,5 @@ footer h2 { .accountnav { display: flex; gap: 1rem; -} \ No newline at end of file +} + diff --git a/styles/types.css b/styles/types.css index 6fce88f7215b165f0cb87eeed19f977f1eda151b..83ae2a8d44b8905ae76e919f3df2d17293854305 100644 --- a/styles/types.css +++ b/styles/types.css @@ -1,6 +1,8 @@ /* This file deals with font types and font families. */ @import url(https://fonts.bunny.net/css?family=montserrat:400,400i,600,600i,700,700i,900,900i); +@import url(https://fonts.bunny.net/css2?family=courier+prime:wght@400;700&display=swap); /* for BCIDs */ + @import url(/fontawesome/css/all.css); html { @@ -10,6 +12,16 @@ html { -moz-osx-font-smoothing: grayscale; } +h2.subheading { + font-weight: 500; + font-size: 1.5rem; + margin-bottom: 0; +} + +h2.subheading + h1 { + margin-top: 0; +} + .bc-1 { font-weight: 700; } @@ -22,6 +34,18 @@ html { font-weight: 400; } +.bcid { + font-family: 'Courier Prime', monospace; +} + .center { text-align: center; } + +.icon-true::before { + content: "\f00c"; +} + +.icon-false::before { + content: "\f00d"; +} diff --git a/time_handler.php b/time_handler.php new file mode 100644 index 0000000000000000000000000000000000000000..cae9b566816a65a6e968b819d0e82914e5ec46b2 --- /dev/null +++ b/time_handler.php @@ -0,0 +1,47 @@ +<?php +function time2str($ts) +{ + if(!ctype_digit($ts)) + $ts = strtotime($ts); + + $diff = time() - $ts; + if($diff == 0) + return 'now'; + elseif($diff > 0) + { + $day_diff = floor($diff / 86400); + if($day_diff == 0) + { + if($diff < 60) return 'just now'; + if($diff < 120) return '1 minute ago'; + if($diff < 3600) return floor($diff / 60) . ' minutes ago'; + if($diff < 7200) return '1 hour ago'; + if($diff < 86400) return floor($diff / 3600) . ' hours ago'; + } + if($day_diff == 1) return 'Yesterday'; + if($day_diff < 7) return $day_diff . ' days ago'; + if($day_diff < 31) return ceil($day_diff / 7) . ' weeks ago'; + if($day_diff < 60) return 'last month'; + return date('F Y', $ts); + } + else + { + $diff = abs($diff); + $day_diff = floor($diff / 86400); + if($day_diff == 0) + { + if($diff < 120) return 'in a minute'; + if($diff < 3600) return 'in ' . floor($diff / 60) . ' minutes'; + if($diff < 7200) return 'in an hour'; + if($diff < 86400) return 'in ' . floor($diff / 3600) . ' hours'; + } + if($day_diff == 1) return 'Tomorrow'; + if($day_diff < 4) return date('l', $ts); + if($day_diff < 7 + (7 - date('w'))) return 'next week'; + if(ceil($day_diff / 7) < 4) return 'in ' . ceil($day_diff / 7) . ' weeks'; + if(date('n', $ts) == date('n') + 1) return 'next month'; + return date('F Y', $ts); + } +} + +?> \ No newline at end of file