i dont know whats in this

This commit is contained in:
Bye 2024-07-27 15:07:27 +03:00
parent 5174c30cec
commit 19c32f0a71
28 changed files with 1082 additions and 44 deletions

View file

@ -8,7 +8,7 @@ $routes = [
exit();
},
'i11n' => function () {
'i18n' => function () {
global $path;
return match ($path[2]) {
'languages' => [

View file

@ -33,7 +33,7 @@ function format_bcid ($bcid): string
$stripped_bcid = strtoupper($stripped_bcid);
if (!validate_bcid($stripped_bcid)) {
throw new Exception('Invalid BCID.');
return '999-9999';
}
return substr($stripped_bcid, 0, 3).'-'.substr($stripped_bcid, -4, 4);
@ -46,10 +46,6 @@ function get_user_by_id($bcid) {
function get_user_display_name($userId, $escape = true) {
global $user;
if (!$_SESSION['auth']) {
return '';
}
$target = array();
if ($userId == $user['id']) {
$target = $user;
@ -73,6 +69,40 @@ function get_user_display_name($userId, $escape = true) {
return $display_name;
}
function get_user_avatar($userId) {
global $user;
if (!$_SESSION['auth']) {
return 'https://cdn.id.byecorps.com/profile/default.png';
}
$target = array();
if ($userId == $user['id']) {
$target = $user;
} else {
$target = get_user_by_id($userId);
}
$avatar = db_execute('SELECT * FROM avatars JOIN neo_id.files f on f.id = avatars.file_id WHERE avatars.owner = ?',
[ $target['id'] ]);
if ($avatar) {
// echo '<pre>'; print_r($avatar); echo '</pre>';
return 'https://cdn.id.byecorps.com/' . $avatar['path'];
}
return 'https://cdn.id.byecorps.com/profile/default.png';
}
function set_user_language(string $lang_code, string $id): void
{
db_execute(
'UPDATE accounts SET language = ? WHERE id = ?',
[$lang_code, $id]
);
$_SESSION['lang'] = $lang_code;
}
function requires_auth($redirect = '/auth/login') {
global $path_raw;
@ -84,3 +114,13 @@ function requires_auth($redirect = '/auth/login') {
header('Location: '.$redirect.'?callback='.urlencode($path_raw));
exit();
}
function requires_admin() {
global $user;
if ($user['is_admin']) {
return true;
}
return false;
}

158
common/files.php Normal file
View file

@ -0,0 +1,158 @@
<?php
use kornrunner\Blurhash\Blurhash;
function mime_to_extension($mime) {
return match ($mime) {
'image/gif' => '.gif',
'image/jpeg' => '.jpg',
'image/png' => '.png',
'image/webp' => '.webp',
default => ''
};
}
/**
*
* Squishes an image into a 128x128 square, and converts it to JPEG
* if it is not a JPEG, PNG, WEBP or GIF.
*
* @param string $path
* @return array
*/
function turn_image_into_avatar(string $path): array
{
$manager = new \Intervention\Image\ImageManager(
new \Intervention\Image\Drivers\Gd\Driver()
);
$image = $manager->read($path);
// Get mimetype
$mime = mime_content_type($path);
$filetype = mime_to_extension($mime);
$enc = $image->resize(width: 128, height: 128);
if ($filetype=='') {
$enc = $image->encodeByMediaType('image/webp');
$filetype = '.webp';
} else {
$enc = $image->encodeByMediaType();
}
$image_data = (string) $enc;
return [
"data" => $image_data,
"mime" => $enc->mimetype(),
'filetype' => $filetype
];
}
function get_blurhash_for_image($image): string
{
// Copied shamelessly from https://github.com/kornrunner/php-blurhash
$width = imagesx($image);
$height = imagesy($image);
$pixels = [];
for ($y = 0; $y < $height; ++$y) {
$row = [];
for ($x = 0; $x < $width; ++$x) {
$index = imagecolorat($image, $x, $y);
$colors = imagecolorsforindex($image, $index);
$row[] = [$colors['red'], $colors['green'], $colors['blue']];
}
$pixels[] = $row;
}
$components_x = 4;
$components_y = 3;
return Blurhash::encode($pixels, $components_x, $components_y);
}
function upload_file($filename, $target, $owner=null) {
global $bunny_client;
$bunny_client->upload($filename, $target);
// Get file mime time
$mime = mime_content_type($filename);
$blurhash = '';
if (str_starts_with('image/', $mime)) {
$blurhash = get_blurhash_for_image(imagecreatefromstring(file_get_contents($filename)));
}
db_execute(
'INSERT INTO files (path, uploader, blurhash) VALUES (?, ?, ?)',
[$target, $owner, $blurhash]
);
}
function upload_raw_data($data, $target, $owner=null) {
$filename = '/tmp/'.uniqid(more_entropy: true);
file_put_contents($filename, $data);
upload_file($filename, $target, $owner);
unlink($filename);
}
/**
* @throws \Bunny\Storage\Exception
* @throws ImagickException
*/
function upload_avatar($data, $user): string
{
global $bunny_client;
$img = turn_image_into_avatar($data['tmp_name']);
$uuid = uniqid(prefix: 'avatar', more_entropy: true);
$remote_file_name = 'avatars/'.$uuid.$img['filetype'];
upload_raw_data($img['data'], $remote_file_name);
$file = db_execute('SELECT id FROM files WHERE path = ? LIMIT 1', [$remote_file_name]);
$existing_avatar = db_execute('SELECT * FROM avatars WHERE owner = ?', [$user['id']]);
if (empty($existing_avatar)) {
db_execute(
'INSERT INTO avatars (file_id, owner) VALUES (?, ?)',
[$file['id'], $user['id']]
);
} else {
db_execute(
'UPDATE avatars SET file_id = ? WHERE owner = ?',
[$file['id'], $user['id']]
);
}
return 'https://'.BUNNY_STORAGE_ZONE.'.b-cdn.net/'.$remote_file_name;
}
/**
* @param $id int
* @return void
*/
function delete_file_by_id(int $id): void
{
global $bunny_client;
// Get remote path
$file = db_execute('select * from files where id = ?', [$id]);
if (empty($file)) {
return;
}
// Remove reference to the file in the database
db_execute('delete from files where id = ?', [$id]);
// Then remove the file
$bunny_client->delete($file['path']);
}

17
common/misc.php Normal file
View file

@ -0,0 +1,17 @@
<?php
/**
* Redirects to $url.
*
* @param $url string
*
*/
function location(string $url):void
{
header('Location: '. $url);
exit();
}
function flash(string $text, string $type, array &$flash) {
$flash[] = ['text' => $text, 'type' => $type];
}

18
common/validation.php Normal file
View file

@ -0,0 +1,18 @@
<?php
function validate_email($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* Password rules:
* - At least 8 characters long
* That's it
*/
function validate_password($password) {
$password_min_length = 8;
if (strlen($password) >= $password_min_length) {
return $password;
}
return false;
}

View file

@ -1,5 +1,11 @@
{
"require": {
"bunnycdn/storage": "^3.3"
"bunnycdn/storage": "^3.3",
"kornrunner/blurhash": "^1.2",
"lbuchs/webauthn": "^2.2",
"intervention/image": "^3.7",
"ext-gd": "*",
"ext-imagick": "*",
"ext-fileinfo": "*"
}
}

231
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "959cf05207f5b0c9a0bc557d5387056c",
"content-hash": "2630884c327f232fb79f8fcefae92fa4",
"packages": [
{
"name": "bunnycdn/storage",
@ -381,6 +381,235 @@
],
"time": "2023-12-03T20:05:35+00:00"
},
{
"name": "intervention/gif",
"version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/Intervention/gif.git",
"reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/gif/zipball/3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
"reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"phpstan/phpstan": "^1",
"phpunit/phpunit": "^10.0",
"slevomat/coding-standard": "~8.0",
"squizlabs/php_codesniffer": "^3.8"
},
"type": "library",
"autoload": {
"psr-4": {
"Intervention\\Gif\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@intervention.io",
"homepage": "https://intervention.io/"
}
],
"description": "Native PHP GIF Encoder/Decoder",
"homepage": "https://github.com/intervention/gif",
"keywords": [
"animation",
"gd",
"gif",
"image"
],
"support": {
"issues": "https://github.com/Intervention/gif/issues",
"source": "https://github.com/Intervention/gif/tree/4.1.0"
},
"funding": [
{
"url": "https://paypal.me/interventionio",
"type": "custom"
},
{
"url": "https://github.com/Intervention",
"type": "github"
}
],
"time": "2024-03-26T17:23:47+00:00"
},
{
"name": "intervention/image",
"version": "3.7.2",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "5451ff9f909c2fc836722e5ed6831b9f9a6db68c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/5451ff9f909c2fc836722e5ed6831b9f9a6db68c",
"reference": "5451ff9f909c2fc836722e5ed6831b9f9a6db68c",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"intervention/gif": "^4.1",
"php": "^8.1"
},
"require-dev": {
"mockery/mockery": "^1.6",
"phpstan/phpstan": "^1",
"phpunit/phpunit": "^10.0",
"slevomat/coding-standard": "~8.0",
"squizlabs/php_codesniffer": "^3.8"
},
"suggest": {
"ext-exif": "Recommended to be able to read EXIF data properly."
},
"type": "library",
"autoload": {
"psr-4": {
"Intervention\\Image\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@intervention.io",
"homepage": "https://intervention.io/"
}
],
"description": "PHP image manipulation",
"homepage": "https://image.intervention.io/",
"keywords": [
"gd",
"image",
"imagick",
"resize",
"thumbnail",
"watermark"
],
"support": {
"issues": "https://github.com/Intervention/image/issues",
"source": "https://github.com/Intervention/image/tree/3.7.2"
},
"funding": [
{
"url": "https://paypal.me/interventionio",
"type": "custom"
},
{
"url": "https://github.com/Intervention",
"type": "github"
}
],
"time": "2024-07-05T13:35:01+00:00"
},
{
"name": "kornrunner/blurhash",
"version": "v1.2.2",
"source": {
"type": "git",
"url": "https://github.com/kornrunner/php-blurhash.git",
"reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kornrunner/php-blurhash/zipball/bc8a4596cb0a49874f0158696a382ab3933fefe4",
"reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4",
"shasum": ""
},
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
"ext-gd": "*",
"ocramius/package-versions": "^1.4|^2.0",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^9",
"vimeo/psalm": "^4.3"
},
"type": "library",
"autoload": {
"psr-4": {
"kornrunner\\Blurhash\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Boris Momčilović",
"email": "boris.momcilovic@gmail.com"
}
],
"description": "Pure PHP implementation of Blurhash",
"homepage": "https://github.com/kornrunner/php-blurhash",
"support": {
"issues": "https://github.com/kornrunner/php-blurhash/issues",
"source": "https://github.com/kornrunner/php-blurhash.git"
},
"time": "2022-07-13T19:38:39+00:00"
},
{
"name": "lbuchs/webauthn",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/lbuchs/WebAuthn.git",
"reference": "20adb4a240c3997bd8cac7dc4dde38ab0bea0ed1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lbuchs/WebAuthn/zipball/20adb4a240c3997bd8cac7dc4dde38ab0bea0ed1",
"reference": "20adb4a240c3997bd8cac7dc4dde38ab0bea0ed1",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"autoload": {
"psr-4": {
"lbuchs\\WebAuthn\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Lukas Buchs",
"role": "Developer"
}
],
"description": "A simple PHP WebAuthn (FIDO2) server library",
"homepage": "https://github.com/lbuchs/webauthn",
"keywords": [
"Authentication",
"webauthn"
],
"support": {
"issues": "https://github.com/lbuchs/WebAuthn/issues",
"source": "https://github.com/lbuchs/WebAuthn/tree/v2.2.0"
},
"time": "2024-07-04T07:17:40+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",

View file

@ -3,6 +3,7 @@
$DOC_ROOT = $_SERVER['DOCUMENT_ROOT'];
require_once $DOC_ROOT . '/vendor/autoload.php';
use Intervention\Image\ImageManager;
// Includes
try {
@ -19,11 +20,18 @@ try {
echo "<b>Critical error:</b> " . $e->getMessage() . "<br />Please contact the developers.";
}
$bunny_client = new \Bunny\Storage\Client(BUNNY_ACCESS_KEY, BUNNY_STORAGE_ZONE, \Bunny\Storage\Region::STOCKHOLM);
require_once 'strings/en.php'; // This ensures strings will fall back to English if one is missing.
require_once 'common/strings.php';
require_once 'common/account_utils.php';
require_once 'common/validation.php';
require_once 'common/database.php';
require_once 'common/account_utils.php';
require_once 'common/files.php';
require_once 'common/misc.php';
$flash = [];
// Starts the session
// TODO: write this to use the database to work across more than one server (e.g. don't use PHP sessions)
@ -42,6 +50,7 @@ $user = null;
if ($_SESSION['auth']) {
$user = get_user_by_id($_SESSION['id']);
$_SESSION['lang'] = $user['language'];
}
$uri_string = $_SERVER['REQUEST_URI']; // `/foo/bar?bar=foo&foo=bar`
@ -72,6 +81,7 @@ if (str_ends_with($path_raw, '/') && $path_raw != '/') {
// If there's a 'lang' query param, change the language!
if (array_key_exists('lang', $query)) {
$_SESSION['lang'] = $query['lang'];
location($path_raw);
}
patch_lang($_SESSION['lang']);
@ -79,6 +89,18 @@ patch_lang($_SESSION['lang']);
$routes = [
'' => function () { require 'views/home.php'; },
'admin' => function () {
global $path, $query, $DOC_ROOT, $flash;
requires_auth();
requires_admin();
switch ($path[2]) {
default: return 404;
case 'files':
require 'views/admin/files.php';
}
},
'api' => function () {
global $path, $query;
@ -88,15 +110,19 @@ $routes = [
require 'api.php'; /* Handoff further routing to API script. */
},
'auth' => function () {
global $path, $query;
global $path, $query, $flash;
if ($path[2] == 'signout') {
switch ($path[2]) {
case 'signout':
require 'views/signedout.php';
} else if ($path[2] == 'signup') {
break;
case 'signup':
require 'views/signup.php';
} else if ($path[2] == 'login') {
break;
case 'login':
require 'views/login.php';
} else {
break;
default:
return 404;
}
exit();
@ -119,6 +145,13 @@ $routes = [
if (isset($path[3])) {
return 404;
}
if ($path[2] == 'edit') {
requires_auth();
require 'views/profile_edit.php';
return 200;
}
$profile_owner = $path[2];
$profile_owner = get_user_by_id($profile_owner);
} else {
@ -129,8 +162,21 @@ $routes = [
return 200;
},
'settings' => function () {
global $path, $flash, $user;
if (isset($path[2])) {
switch ($path[2]) {
default: return 404;
case 'security':
require 'views/settings_security.php';
break;
case 'region':
require 'views/settings_region.php';
break;
}
} else {
require 'views/settings.php';
}
}
];
if (array_key_exists($path[1], $routes)) {

View file

@ -2,7 +2,7 @@
const select = document.createElement('select');
const script = document.scripts[document.scripts.length - 1];
const langs_req = fetch('/api/i11n/languages')
const langs_req = fetch('/api/i18n/languages')
.then(async data => {
return await data.json();
})

View file

View file

@ -0,0 +1,7 @@
const linksOfCurrentPage = document.querySelectorAll('a[href="'+window.location.pathname+'"]');
console.log(window.location.pathname)
for (let i = 0; i < linksOfCurrentPage.length; i++) {
console.log(linksOfCurrentPage[i])
linksOfCurrentPage[i].classList.add('selected');
}

@ -1 +1 @@
Subproject commit d9131afc20afc0bb4205990bb34f8c455ae81f8d
Subproject commit 66e3af2bab97bc80b247cf43944bcd5d6506719f

View file

@ -1,5 +1,7 @@
@import "colours.css";
@import 'ui.css';
@import 'extra_icons.css';
:root {
color-scheme: light dark;
@ -12,6 +14,8 @@ body {
display: flex;
flex-direction: column;
background: var(--page-bg);
min-height: 100vh;
}
@ -45,6 +49,20 @@ main {
margin: 16px;
}
.hero {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
justify-content: center;
padding: 1rem 1rem;
gap: 1rem;
}
.hero .logo {
height: 128px;
}
footer {
flex: 0;
@ -126,3 +144,11 @@ body > .errorbox {
.center {
text-align: center;
}
.align-vertically {
vertical-align: middle;
}
.spacer {
padding: 16px;
}

View file

@ -7,13 +7,17 @@
/* ByeCorps ID colour scheme. Use on this site. */
--black-bean: #330f0a;
--dark-slate-gray: #394f49;
--dark-slate-gray-dim: #536E65;
--fern-green: #65743a;
--flax: #efdd8d;
--flax-dim: #BFB26E;
--mindaro: #f4fdaf;
--white: #ffffff;
--gray-0: #f8f9fa;
--gray-1: #f1f3f5;
--grey-5: #adb5bd;
--gray-8: #343a40;
--gray-9: #212529;
--red-2: #ffc9c9;
@ -21,20 +25,35 @@
--red-7: #f03e3e;
--red-9: #c92a2a;
--ff-bg-black: #1a1a1a;
--page-bg: var(--white);
--link-fg: var(--dark-slate-gray);
--non-color-link-fg: var(--gray-9);
--hover-bg: var(--gray-1);
--selected-bg: var(--gray-0);
--input-bg: var(--gray-1);
--button-primary-bg: var(--flax);
--button-primary-hover-bg: var(--flax-dim);
--error-fg: var(--red-9);
}
@media screen and (prefers-color-scheme: dark) {
:root {
--page-bg: var(--ff-bg-black);
--link-fg: var(--flax);
--non-color-link-fg: var(--gray-0);
--hover-bg: var(--gray-9);
--hover-bg: var(--gray-8);
--selected-bg: var(--gray-9);
--error-fg: var(--red-3);
--input-bg: var(--gray-9);
--button-primary-bg: var(--dark-slate-gray);
--button-primary-hover-bg: var(--dark-slate-gray-dim);
}
}

View file

@ -16,16 +16,21 @@ ul > li > a {
text-decoration: none;
}
ul > li:has(.selected) {
background: var(--selected-bg);
}
ul > li:hover {
background: var(--hover-bg);
background: var(--hover-bg) !important;
}
.grid {
display: grid;
grid-gap: 1rem;
grid-template-columns: 1fr 2fr;
grid-template-columns: minmax(400px, 1fr) 3fr;
}
.id-card {
align-self: center;
background: var(--hover-bg);
@ -36,3 +41,40 @@ ul > li:hover {
.id-card > img {
border-radius: 0.5rem;
}
.settingsthingy h3 {
margin-bottom: 0;
}
.grid.halfandhalf {
grid-template-columns: 1fr 1fr;
}
.language-selector {
display: grid;
gap: 1rem;
margin-bottom: 1rem;
}
.language-selector label {
padding: 1rem;
border-radius: 1rem;
cursor: pointer;
background: var(--input-bg);
}
.language-selector label:hover {
background: var(--selected-bg);
}
@media screen and (max-width: 880px) {
.grid {
grid-template-columns: 1fr;
}
}
@media screen and (max-width: 700px) {
.grid.halfandhalf {
grid-template-columns: 1fr;
}
}

44
styles/extra_icons.css Normal file
View file

@ -0,0 +1,44 @@
.fluent--person-passkey-48-filled {
display: inline-block;
width: 1em;
height: 1em;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23000' d='M24 4c-5.523 0-10 4.477-10 10s4.477 10 10 10s10-4.477 10-10S29.523 4 24 4M12.25 28A4.25 4.25 0 0 0 8 32.249V33c0 3.755 1.942 6.567 4.92 8.38C15.85 43.163 19.786 44 24 44c3.716 0 7.216-.65 10-2.027v-7.489A9 9 0 0 1 30.055 28zm19.82 0A7 7 0 1 1 41 33.71V34l2.293 2.293a1 1 0 0 1 0 1.414L41 40l2.322 2.322a1 1 0 0 1 .03 1.384l-3.646 3.968a1 1 0 0 1-1.444.03l-1.97-1.969a1 1 0 0 1-.292-.707V33.326A7.01 7.01 0 0 1 32.07 28M41 26a2 2 0 1 0-4 0a2 2 0 0 0 4 0'/%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.fluent--person-passkey-32-filled {
display: inline-block;
width: 1em;
height: 1em;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%23000' d='M16 16a7 7 0 1 0 0-14a7 7 0 0 0 0 14m-8.5 2A3.5 3.5 0 0 0 4 21.5v.5c0 2.393 1.523 4.417 3.685 5.793C9.859 29.177 12.802 30 16 30c2.718 0 5.25-.594 7.285-1.622v-5.067A5.78 5.78 0 0 1 20.735 18zm17.07 9.626v-5.06a4.5 4.5 0 1 1 3.215.248V23l1.474 1.474a.643.643 0 0 1 0 .91l-1.474 1.473l1.493 1.493a.643.643 0 0 1 .019.89l-2.344 2.55a.643.643 0 0 1-.928.02l-1.266-1.266a.64.64 0 0 1-.188-.454zm3.055-10.25a1.125 1.125 0 1 0-2.25 0a1.125 1.125 0 0 0 2.25 0'/%3E%3C/svg%3E");
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
.icon-48 {
font-size: 48px;
}
.icon-32 {
font-size: 32px;
}
.icon-24 {
font-size: 24px;
}
.icon-16 {
font-size: 16px;
}

23
styles/login_form.css Normal file
View file

@ -0,0 +1,23 @@
.login-form {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 600px;
margin: auto;
}
.login-form > * {
margin: 0;
}
.login-form > *[type=submit] {
margin: 0 auto;
}
.passkey {
max-width: 600px;
margin: auto;
}

53
styles/ui.css Normal file
View file

@ -0,0 +1,53 @@
form.mini-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.input {
display: flex;
flex-direction: column;
background: var(--input-bg);
padding: 0.5rem;
border-radius: 0.5rem;
}
.input label {
font-size: 0.8rem;
float: right;
}
.input input {
all: unset;
}
.input:has([data-com-onepassword-filled='light']) {
background-color: hsl(210, 100%, 93%);
}
.input:has([data-com-onepassword-filled='dark']) {
background-color: rgb(36, 107, 179);
}
.input:has(:-internal-autofill-selected) {
background-color: light-dark(rgb(232, 240, 254), rgba(70, 90, 126, 0.4));
}
button, .button {
padding: 0.5rem;
margin: 0 0.25rem;
border-radius: 0.5rem;
border: rgba(136, 136, 136, 0.3) 1px solid;
font-family: "Montserrat", system-ui, sans-serif;
cursor: pointer;
}
button.primary, .button.primary {
background: var(--button-primary-bg);
}
button.primary:hover, .button.primary:hover {
background: var(--button-primary-hover-bg);
}

59
views/admin/files.php Normal file
View file

@ -0,0 +1,59 @@
<?php
if (!requires_admin()) {exit;} // failsafe in case this file is opened from "not the index".
if (isset($query['delete'])) {
delete_file_by_id($query['delete']);
}
$files = db_execute_all('SELECT * FROM files');
?>
<!doctype html>
<html>
<head>
<?php include $DOC_ROOT.'/views/partials/head.php' ?>
<title>[A] Files ~> ByeCorps ID</title>
</head>
<body>
<?php include $DOC_ROOT.'/views/partials/header.php' ?>
<main>
<h1>[ADMIN] Files</h1>
<p>There are <?= count($files) ?> files.</p>
<ul>
<?php
foreach ($files as $file) {
echo '<li>';
if ($file['blurhash']) {
echo '<img src="https://cdn.id.byecorps.com/'. $file['path'] .'" />';
}
echo '<p><a href="https://cdn.id.byecorps.com/'.$file['path'].'">ID: '.$file['id'].' ~~ '.$file['path'].'</a></p>';
if ($file['uploader']) {
echo '<p>Owned by <b>'. get_user_display_name($file['uploader']) .'</b></p>';
} else {
echo '<p>No owner on file</p>';
}
echo '<p>Uploaded on <b>'. $file['uploaded_date'] .'</b></p>';
$avatar = db_execute('select * from avatars where file_id = ?', [$file['id']]);
if (!empty($avatar)) {
echo '<p>Is ' . get_user_display_name($avatar['owner']) . '\'s avatar</p>';
}
echo '<p>Options: <a href="?delete='. $file['id'] .'">Delete</a></p>';
echo '</li>';
}
?>
</ul>
</main>
<?php include $DOC_ROOT.'/views/partials/footer.php' ?>
</body>
</html>

View file

@ -11,7 +11,7 @@
<h1><?= get_string('page.dashboard') ?></h1>
<div class="grid">
<div class="id-card">
<img src="https://cdn.id.byecorps.com/profile/281G3NV" alt="" />
<img src="<?= get_user_avatar($_SESSION['id']); ?>" alt="<?= get_user_display_name($_SESSION['id']) ?>'s avatar" />
<div class="info">
<div class="display_name"><?= get_user_display_name($_SESSION['id']) ?></div>
<div class="id"><?= format_bcid($_SESSION['id']) ?></div>
@ -31,12 +31,6 @@
<div class="label"><?= get_string('page.settings') ?></div>
</a>
</li>
<li>
<a href="/settings/apps" class="item">
<div class="icon"><span class="fa-fw fa-solid fa-lock"></span></div>
<div class="label"><?= get_string('page.manageAppAccess') ?></div>
</a>
</li>
<li>
<a href="/auth/signout" class="item">
<div class="icon"><span class="fa-fw fa-solid fa-right-to-bracket"></span></div>

View file

@ -8,7 +8,17 @@
<?php include 'partials/header.php'; ?>
<main>
<div class="hero">
<div class="hero-text">
<img src="https://cdn.id.byecorps.com/assets/bcid.svg" alt="ByeCorps ID Logo" class="logo">
<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="/auth/login" class="button primary"><?= get_string('auth.login') ?></a>
<a href="/auth/signup" class="button"><?= get_string('auth.signup') ?></a>
</div>
</div>
</main>
<?php include 'partials/footer.php'; ?>

View file

@ -10,6 +10,11 @@ if ($_SESSION['auth']) {
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Validate email address
if (!validate_email($_POST['email'])) {
$error_body = get_string('error.invalidEmail');
}
// Figure out if it's a user
$user_to_log_in_as = db_execute('SELECT id, email, password FROM accounts WHERE email = ?', [$_POST['email']]);
if (!$user_to_log_in_as) {
@ -28,6 +33,10 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
}
exit();
}
} else {
if (key_exists('callback', $query)) {
$subtitle = get_string('auth.logInToContinue');
}
}
skip:
@ -38,6 +47,7 @@ skip:
<html lang="en">
<head>
<?php include 'partials/head.php' ?>
<link rel="stylesheet" href="/styles/login_form.css" />
</head>
<body>
<?php include 'partials/header.php' ?>
@ -49,20 +59,39 @@ skip:
}
?>
<h1><?= get_string('page.login') ?></h1>
<div id="wrapper">
<h1 class="center"><?= get_string('page.login') ?></h1>
<p class="center">Don't have one? <a href="/auth/signup">Sign up</a>.</p>
<?php
if (isset($subtitle)) {
echo '<p class="subtitle center">'. $subtitle .'</p>';
}
?>
<?php
if (isset($error_body)) {
include 'partials/error.php';
}
?>
<form method="post">
<p><label for="email"><?= get_string("auth.email") ?></label>
<input type="email" name="email" id="email" /></p>
<p><label for="password"><?= get_string("auth.password") ?></label>
<input type="password" name="password" id="password" /></p>
<form class="login-form" method="post">
<div class="input"><label for="email"><?= get_string("auth.email") ?></label>
<input type="email" name="email" id="email" /></div>
<div class="input"><label for="password"><?= get_string("auth.password") ?></label>
<input type="password" name="password" id="password" /></div>
<button type="submit">Submit</button>
<button class="primary" type="submit"><?= get_string('auth.login') ?></button>
</form>
</div>
<div class="spacer"></div>
<div class="passkey center">
<h2><span class="icon icon-32 align-vertically fluent--person-passkey-32-filled"></span>
<span class="label"><?= get_string('auth.passkey') ?></span></h2>
<p><?= get_string('auth.logInWithPasskeyExplainer'); ?></p>
<p><button><?= get_string('auth.logInWithPasskey') ?></button></p>
</div>
</main>
<?php include 'partials/footer.php' ?>

View file

@ -0,0 +1,22 @@
<ul>
<li>
<a href="/settings/account" class="item">
<div class="icon"><span class="fa-fw fa-solid fa-user"></span></div>
<span class="label"><?= get_string('settings.account') ?></span>
</a>
</li>
<li>
<a href="/settings/region" class="item">
<div class="icon"><span class="fa-fw fa-solid fa-globe"></span></div>
<span class="label"><?= get_string('settings.region') ?></span>
</a>
</li>
<li>
<a href="/settings/security" class="item">
<div class="icon"><span class="fa-fw fa-solid fa-lock"></span></div>
<div class="label"><?= get_string('settings.security') ?></div>
</a>
</li>
</ul>
<script src="/scripts/settings_list_assister.js" async></script>

View file

@ -18,14 +18,22 @@ if (is_null($user)) {
<main>
<?php
if ($error) {
if (isset($error)) {
include 'partials/error.php';
include 'partials/footer.php';
exit();
}
?>
<p><?= $profile_owner['id'] ?></p>
<div id="profile-wrapper">
<img id="profile-image"
src="<?= get_user_avatar($profile_owner['id']) ?>"
alt="<?= get_user_display_name($profile_owner['id']) ?>'s avatar" />
<div id="profile-user-info">
<span id="profile-display-name"><?= get_user_display_name($profile_owner['id']) ?></span>
<span id="profile-bcid"><?= format_bcid($profile_owner['id']) ?></span>
</div>
</div>
</main>
<?php include 'partials/footer.php' ?>

46
views/profile_edit.php Normal file
View file

@ -0,0 +1,46 @@
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$mime_avatar = mime_content_type($_FILES['avatar']['tmp_name']);
if (str_contains($mime_avatar, 'image')) {
echo $mime_avatar;
$new_avatar = upload_avatar($_FILES['avatar'], $user);
}
// location('/profile');
}
?>
<!doctype html>
<html lang="en">
<head>
<?php include "partials/head.php" ?>
</head>
<body>
<?php include "partials/header.php" ?>
<main>
<h1>Editing profile</h1>
<form method="post" enctype="multipart/form-data">
<fieldset>
<legend>Images</legend>
<label for="avatar">Avatar</label>
<input type="file" accept="image/*" name="avatar" id="avatar" />
</fieldset>
<button type="submit"><?= get_string('button.submit') ?></button>
</form>
</main>
<?php include 'partials/footer.php' ?>
</body>
</html>

18
views/settings.php Normal file
View file

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<?php require 'partials/head.php'; ?>
<title><?= get_string('page.settings'); ?> ~> ByeCorps ID </title>
<link rel="stylesheet" href="/styles/dashboard.css" />
</head>
<body>
<?php include "partials/header.php" ?>
<main>
<h1><span class="fa-solid fa-fw fa-cog"></span> <?= get_string('page.settings'); ?></h1>
<?php include 'partials/settings_list.php' ?>
</main>
<?php include 'partials/footer.php' ?>
</body>
</html>

67
views/settings_region.php Normal file
View file

@ -0,0 +1,67 @@
<?php
function update_language(): void
{
global $user;
set_user_language($_POST['lang'], $user['id']);
location('/settings/region');
}
if (isset($path[3])) {
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
switch ($path[3]) {
case 'set_language':
update_language();
break;
default:
location('/settings/region');
exit;
}
} else {
location('/settings/region');
}
}
?>
<!doctype html>
<html>
<head>
<?php require 'partials/head.php'; ?>
<title><?= get_string('page.settings'); ?> ~> ByeCorps ID </title>
<link rel="stylesheet" href="/styles/dashboard.css" />
</head>
<body>
<?php include "partials/header.php" ?>
<main>
<h1><span class="fa-solid fa-fw fa-cog"></span> <?= get_string('page.settings'); ?></h1>
<div class="grid">
<?php include 'partials/settings_list.php' ?>
<div class="settingsthingy">
<h2><?= get_string('settings.region') ?></h2>
<p>Here you can set the language ByeCorps ID is displayed in.</p>
<form action="/settings/region/set_language" method="post">
<div class="language-selector">
<?php
foreach (LANGAUGES as $lang) {
$checked = '';
if ($lang['code'] == $_SESSION['lang']) {
$checked = 'checked="checked"';
}
echo '<label>
<input type="radio" name="lang" '.$checked.' id="lang" value="'. $lang['code'] . '" />
'. $lang['name'] .'
</label>';
}
?>
</div>
<button class='primary' type="submit"><?= get_string('button.submit') ?></button>
</form>
</div>
</div>
</main>
<?php include 'partials/footer.php' ?>
</body>
</html>

View file

@ -0,0 +1,57 @@
<?php
?>
<!doctype html>
<html>
<head>
<?php require 'partials/head.php'; ?>
<title><?= get_string('page.settings'); ?> ~> ByeCorps ID </title>
<link rel="stylesheet" href="/styles/dashboard.css" />
</head>
<body>
<?php include "partials/header.php" ?>
<main>
<h1><span class="fa-solid fa-fw fa-cog"></span> <?= get_string('page.settings'); ?></h1>
<div class="grid">
<?php include 'partials/settings_list.php' ?>
<div class="settingsthingy">
<h2><?= get_string('settings.security') ?></h2>
<h3><?= get_string('auth.password') ?></h3>
<form class="settings-grid mini-form" method="post">
<div class="input">
<label for="current-password"><?= get_string('auth.currentPassword') ?></label>
<input type="password" name="current-password" id="current-password" autocomplete="current-password" />
</div>
<div class="input">
<label for="new-password"><?= get_string('auth.newPassword') ?></label>
<input type="password" name="new-password" id="new-password" autocomplete="new-password" />
</div>
<div class="input">
<label for="confirm-password"><?= get_string('auth.confirmPassword') ?></label>
<input type="password" name="confirm-password" id="confirm-password" autocomplete="new-password" />
</div>
<button type="submit"><?= get_string('button.changePassword') ?></button>
</form>
<form action="/settings/security/passkey" method="post" class="settings-grid">
<h3><span class="icon icon-24 align-vertically fluent--person-passkey-32-filled center"></span> <?= get_string('auth.passkeyPlural') ?></h3>
<div class="grid halfandhalf">
<div class="item">
<p>Passkeys allow you to log in to ByeCorps ID using your device instead of a password.</p>
<button id="add_passkey">Add a passkey</button>
</div>
<div class="item">
<?= get_string('settings.passkeysCountNone') ?>
</div>
</div>
</form>
</div>
</div>
</main>
<?php include 'partials/footer.php' ?>
</body>
</html>