Implemented account system
This commit is contained in:
parent
51c20b55a0
commit
ace0de4063
25 changed files with 1543 additions and 40 deletions
1025
package-lock.json
generated
1025
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -13,12 +13,15 @@
|
|||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"bootstrap": "^5.3.3"
|
||||
"bootstrap": "^5.3.3",
|
||||
"datatables.net-bs5": "^2.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jquery": "^3.5.30",
|
||||
"assets-webpack-plugin": "^7.1.1",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.77.5",
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"index":{"js":["js/runtime.js","js/index.js"],"css":"css/common.css"}}
|
||||
{"index":{"js":["js/runtime.js","js/vendors.js","js/index.js"],"css":["css/vendors.css","css/common.css"]},"admin/accounts":{"js":["js/runtime.js","js/vendors.js","js/admin/accounts.js"],"css":["css/vendors.css","css/common.css"]}}
|
102
src/js/pages/admin/accounts.ts
Normal file
102
src/js/pages/admin/accounts.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import '../../../css/common/index.scss';
|
||||
import 'datatables.net-bs5/css/dataTables.bootstrap5.css';
|
||||
import '../../common/index';
|
||||
|
||||
import DataTable from 'datatables.net-bs5';
|
||||
import {Modal} from "bootstrap";
|
||||
|
||||
const DT_SERVERSIDE_PROCESSING_URL = '/api/dt/accounts';
|
||||
const TABLE = document.querySelector('#user-table');
|
||||
|
||||
function displayEdit(data: any) {
|
||||
|
||||
const modalElem = document.querySelector('#edit-modal');
|
||||
// @ts-ignore
|
||||
modalElem.querySelector('.name-input').textContent = data.name;
|
||||
// @ts-ignore
|
||||
modalElem.querySelector('.login-method').textContent = data.loginMethod;
|
||||
// @ts-ignore
|
||||
modalElem.querySelector('.permission-editor').value = data.permissionIndex;
|
||||
|
||||
// @ts-ignore
|
||||
modalElem?.querySelector('.js--save').dataset.userid = data.userid;
|
||||
|
||||
Modal.getOrCreateInstance('#edit-modal').show();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const dt = new DataTable('#user-table', {
|
||||
ajax: {
|
||||
url: DT_SERVERSIDE_PROCESSING_URL,
|
||||
},
|
||||
serverSide: true,
|
||||
columns: [
|
||||
{
|
||||
data: 'profilePictureUrl',
|
||||
render(data, type) {
|
||||
if (type !== 'display') {
|
||||
return data;
|
||||
}
|
||||
|
||||
return `<img src="${ data }" alt="Profile Picture" class="ratio-1 rounded-circle w-100" />`
|
||||
},
|
||||
orderable: false,
|
||||
searchable: false
|
||||
},
|
||||
{
|
||||
name: 'Name',
|
||||
data: 'name'
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
data: 'permission'
|
||||
},
|
||||
{
|
||||
name: 'Login-Method',
|
||||
data: 'loginMethod'
|
||||
}
|
||||
],
|
||||
order: [ [1, 'asc'] ],
|
||||
drawCallback: function (settings) {
|
||||
const api = new DataTable.Api(settings);
|
||||
api.rows().every(function (row) {
|
||||
const node = this.node();
|
||||
const data = this.data();
|
||||
|
||||
node.addEventListener('click', (e) => {
|
||||
displayEdit(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const button = <HTMLButtonElement>document.querySelector('#edit-modal .js--save');
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.addEventListener('click', async (e) => {
|
||||
const permissionEditor = <HTMLSelectElement>document.querySelector('#edit-modal .permission-editor');
|
||||
if (!permissionEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.set('permission', permissionEditor.value);
|
||||
|
||||
const response = await fetch(
|
||||
`/api/web/users/${button.dataset.userid}`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
Modal.getOrCreateInstance('#edit-modal').hide();
|
||||
dt.draw();
|
||||
})
|
||||
})
|
|
@ -13,4 +13,11 @@ enum LoginMethod: int
|
|||
self::DISCORD => 'fa-discord',
|
||||
};
|
||||
}
|
||||
|
||||
public function getHumanReadableName(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DISCORD => 'Discord',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,21 @@ namespace GamesShop\Login;
|
|||
|
||||
enum UserPermission : int
|
||||
{
|
||||
case NONE = 0;
|
||||
case VIEWER = 1;
|
||||
case PROVIDER = 10;
|
||||
case ADMIN = 100;
|
||||
|
||||
public function hasLevel(UserPermission $userPermission): bool {
|
||||
return $this->value >= $userPermission->value;
|
||||
}
|
||||
|
||||
public function getHumanReadableName() {
|
||||
return match ($this) {
|
||||
self::VIEWER => "Claimer",
|
||||
self::PROVIDER => "Provider",
|
||||
self::ADMIN => "Admin",
|
||||
default => "None",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
49
src/php/Routing/AdminAccountConfigRoute.php
Normal file
49
src/php/Routing/AdminAccountConfigRoute.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing;
|
||||
|
||||
use GamesShop\ContainerHandler;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Login\UserPermission;
|
||||
use GamesShop\Routing\Responses\TemplateResponse;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\RedirectResponse;
|
||||
use League\Route\Http\Exception\ForbiddenException;
|
||||
use League\Route\Http\Exception\UnauthorizedException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class AdminAccountConfigRoute
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoginHandler $loginHandler
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ForbiddenException
|
||||
* @throws UnauthorizedException
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!$this->loginHandler->isLoggedIn()) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
$user = $this->loginHandler->getCurrentUser();
|
||||
if ($user->getPermission()->value < UserPermission::ADMIN->value) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
|
||||
return new TemplateResponse('admin/accounts');
|
||||
}
|
||||
|
||||
public static function applyRoutes(\League\Route\Router $router) {
|
||||
$router->get('/accounts', self::class);
|
||||
}
|
||||
|
||||
}
|
16
src/php/Routing/Api/APIRoutes.php
Normal file
16
src/php/Routing/Api/APIRoutes.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Api;
|
||||
|
||||
use GamesShop\Routing\Api\DataTables\DataTablesAPIRoutes;
|
||||
use GamesShop\Routing\Api\Web\WebAPIRoutes;
|
||||
use League\Route\Router;
|
||||
|
||||
final class APIRoutes
|
||||
{
|
||||
public static function applyRoutes(Router $router) {
|
||||
$router->group('/api/dt', DataTablesAPIRoutes::setupRoutes(...));
|
||||
$router->group('/api/web', WebAPIRoutes::applyRoutes(...));
|
||||
}
|
||||
}
|
80
src/php/Routing/Api/DataTables/AccountsEndpoint.php
Normal file
80
src/php/Routing/Api/DataTables/AccountsEndpoint.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Api\DataTables;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use GamesShop\Entities\Account\User;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Login\UserPermission;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use League\Route\Http\Exception\ForbiddenException;
|
||||
use League\Route\Http\Exception\UnauthorizedException;
|
||||
use League\Route\RouteGroup;
|
||||
use League\Route\Router;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class AccountsEndpoint
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoginHandler $loginHandler,
|
||||
private readonly EntityManager $entityManager,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!$this->loginHandler->isLoggedIn()) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
$user = $this->loginHandler->getCurrentUser();
|
||||
if (!$user->getPermission()->hasLevel(UserPermission::ADMIN)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
|
||||
$params = $request->getQueryParams();
|
||||
$draw = $params['draw'];
|
||||
$start = $params['start'];
|
||||
$length = $params['length'];
|
||||
|
||||
$searchValue = $params['search']['value'];
|
||||
|
||||
$repo = $this->entityManager->getRepository(User::class);
|
||||
$total = $repo->count();
|
||||
|
||||
$criteria = Criteria::create();
|
||||
$criteria->where(Criteria::expr()->contains('name', $searchValue));
|
||||
$criteria->setFirstResult((int)$start);
|
||||
$criteria->setMaxResults((int)$length);
|
||||
|
||||
$values = $repo->matching($criteria);
|
||||
$filteredCount = $values->count();
|
||||
|
||||
return new JsonResponse([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => $total,
|
||||
'recordsFiltered' => $filteredCount,
|
||||
'data' =>
|
||||
$values->map(function (User $user) {
|
||||
return [
|
||||
'userid' => $user->getId(),
|
||||
'name' => $user->getName(),
|
||||
'profilePictureUrl' => $user->getProfilePictureUrl(),
|
||||
'permission' => $user->getPermission()->getHumanReadableName(),
|
||||
'permissionIndex' => $user->getPermission()->value,
|
||||
'loginMethod' => $user->getLoginMethod()->getHumanReadableName(),
|
||||
];
|
||||
})->toArray()
|
||||
]);
|
||||
}
|
||||
|
||||
public static function applyRoutes(RouteGroup $router) {
|
||||
$router->get('/accounts', AccountsEndpoint::class);
|
||||
}
|
||||
}
|
14
src/php/Routing/Api/DataTables/DataTablesAPIRoutes.php
Normal file
14
src/php/Routing/Api/DataTables/DataTablesAPIRoutes.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Api\DataTables;
|
||||
|
||||
use League\Route\RouteGroup;
|
||||
use League\Route\Router;
|
||||
|
||||
final class DataTablesAPIRoutes
|
||||
{
|
||||
public static function setupRoutes(RouteGroup $group): void {
|
||||
AccountsEndpoint::applyRoutes($group);
|
||||
}
|
||||
}
|
46
src/php/Routing/Api/Web/UserModifyRoute.php
Normal file
46
src/php/Routing/Api/Web/UserModifyRoute.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Api\Web;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use GamesShop\Entities\Account\User;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Login\UserPermission;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use League\Route\Http\Exception\ForbiddenException;
|
||||
use League\Route\Http\Exception\UnauthorizedException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final readonly class UserModifyRoute
|
||||
{
|
||||
public function __construct(
|
||||
private LoginHandler $loginHandler,
|
||||
private EntityManager $entityManager,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, array $args): ResponseInterface
|
||||
{
|
||||
if (!$this->loginHandler->isLoggedIn()) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
$user = $this->loginHandler->getCurrentUser();
|
||||
if (!$user->getPermission()->hasLevel(UserPermission::ADMIN)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
$permissions = $request->getParsedBody()['permission'];
|
||||
|
||||
$toChangeUser = $this->entityManager->getRepository(User::class)->find((int)$args['id']);
|
||||
$toChangeUser->setPermission(UserPermission::from((int)$permissions));
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new EmptyResponse(200);
|
||||
}
|
||||
}
|
13
src/php/Routing/Api/Web/WebAPIRoutes.php
Normal file
13
src/php/Routing/Api/Web/WebAPIRoutes.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Api\Web;
|
||||
|
||||
use League\Route\RouteGroup;
|
||||
|
||||
final class WebAPIRoutes
|
||||
{
|
||||
public static function applyRoutes(RouteGroup $group): void {
|
||||
$group->post('/users/{id:number}', UserModifyRoute::class);
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ use Psr\Http\Message\ResponseInterface;
|
|||
final class ErrorRoute
|
||||
{
|
||||
public function renderErrorPage(int $errorCode): ResponseInterface {
|
||||
$pageContent = ContainerHandler::get(TemplateEngine::class)->renderPage('error', [ 'errorCode' => $errorCode ]);
|
||||
$pageContent = ContainerHandler::get(TemplateEngine::class)->renderErrorPage($errorCode);
|
||||
|
||||
$response = new Response;
|
||||
$response->getBody()->write($pageContent);
|
||||
|
|
|
@ -9,6 +9,7 @@ use Doctrine\ORM\OptimisticLockException;
|
|||
use GamesShop\ContainerHandler;
|
||||
use GamesShop\Environment\EnvironmentHandler;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Routing\Responses\TemplateResponse;
|
||||
use GamesShop\Templates\TemplateEngine;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
@ -24,16 +25,10 @@ final class LoginRoutes
|
|||
|
||||
public function login(ServerRequestInterface $request) {
|
||||
$discordEnv = ContainerHandler::get(EnvironmentHandler::class)->getDiscordEnvironment();
|
||||
$pageContent = ContainerHandler::get(TemplateEngine::class)->renderPage(
|
||||
'login',
|
||||
[
|
||||
'discordUrl' => $discordEnv->loginUrl
|
||||
]
|
||||
);
|
||||
|
||||
$response = new Response;
|
||||
$response->getBody()->write($pageContent);
|
||||
return $response;
|
||||
return new TemplateResponse('login', [
|
||||
'discordUrl' => $discordEnv->loginUrl
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace GamesShop\Routing;
|
|||
use GamesShop\ContainerHandler;
|
||||
use GamesShop\Paths;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\Uri;
|
||||
use Mimey\MimeTypes;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class ResourceRoute
|
||||
{
|
||||
|
@ -19,9 +19,20 @@ final class ResourceRoute
|
|||
'gif', 'svg', 'png', 'jpg'
|
||||
];
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, array $args): ResponseInterface {
|
||||
public function isValid(Uri $uri): bool {
|
||||
$path = $uri->getPath();
|
||||
foreach (self::RESOURCE_EXTENSIONS as $extension) {
|
||||
if (!str_ends_with($path, $extension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filePath = Paths::PUBLIC_PATH . $request->getUri()->getPath();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getResponse(Uri $uri): ResponseInterface {
|
||||
$filePath = Paths::PUBLIC_PATH . $uri->getPath();
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
$response = new Response(status: 404);
|
||||
|
@ -41,11 +52,4 @@ final class ResourceRoute
|
|||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function addRouteEntry(\League\Route\Router $router): void {
|
||||
$joinedResourceExtensions = implode('|', self::RESOURCE_EXTENSIONS);
|
||||
$router->addPatternMatcher('resource', ".+[{$joinedResourceExtensions}]");
|
||||
|
||||
$router->get('/{resource:resource}', self::class);
|
||||
}
|
||||
}
|
20
src/php/Routing/Responses/TemplateResponse.php
Normal file
20
src/php/Routing/Responses/TemplateResponse.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Responses;
|
||||
|
||||
use GamesShop\ContainerHandler;
|
||||
use GamesShop\Templates\TemplateEngine;
|
||||
use Laminas\Diactoros\Response;
|
||||
|
||||
final class TemplateResponse extends Response
|
||||
{
|
||||
public function __construct(string $templateName, array $data = [], array $headers = [])
|
||||
{
|
||||
parent::__construct('php://memory', 200, $headers);
|
||||
|
||||
$templateEngine = ContainerHandler::get(TemplateEngine::class);
|
||||
$body = $templateEngine->renderPage($templateName, $data);
|
||||
$this->getBody()->write($body);
|
||||
}
|
||||
}
|
|
@ -5,22 +5,36 @@ namespace GamesShop\Routing;
|
|||
|
||||
use GamesShop\ContainerHandler;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Routing\Api\APIRoutes;
|
||||
use GamesShop\Templates\TemplateEngine;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use League\Container\Container;
|
||||
use League\Route\Http\Exception\BadRequestException;
|
||||
use League\Route\Http\Exception\ForbiddenException;
|
||||
use League\Route\Http\Exception\NotFoundException;
|
||||
use League\Route\Http\Exception\UnauthorizedException;
|
||||
use League\Route\Strategy\ApplicationStrategy;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class Router
|
||||
{
|
||||
public function __construct(
|
||||
private ResourceRoute $resourceRoute
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function route(): ResponseInterface
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals(
|
||||
$_SERVER, $_GET, $_POST, $_COOKIE, $_FILES
|
||||
);
|
||||
if ($this->resourceRoute->isValid($request->getUri())) {
|
||||
return $this->resourceRoute->getResponse($request->getUri());
|
||||
}
|
||||
|
||||
$router = new \League\Route\Router;
|
||||
$strategy = (new ApplicationStrategy)->setContainer(ContainerHandler::getInstance());
|
||||
$router->setStrategy($strategy);
|
||||
|
@ -28,12 +42,18 @@ final class Router
|
|||
IndexRoute::applyRoutes($router);
|
||||
LoginRoutes::addRoutes($router);
|
||||
SetupRoute::applyRoutes($router);
|
||||
ResourceRoute::addRouteEntry($router);
|
||||
AdminAccountConfigRoute::applyRoutes($router);
|
||||
|
||||
APIRoutes::applyRoutes($router);
|
||||
|
||||
try {
|
||||
return $router->dispatch($request);
|
||||
} catch (NotFoundException $e) {
|
||||
return (new ErrorRoute())->renderErrorPage(404);
|
||||
} catch (UnauthorizedException) {
|
||||
return (new ErrorRoute())->renderErrorPage(401);
|
||||
} catch (ForbiddenException) {
|
||||
return (new ErrorRoute())->renderErrorPage(403);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ final class SetupRoute
|
|||
|
||||
$attribute = $repo->find('ADMIN_SETUP_COMPLETED');
|
||||
if ($attribute) {
|
||||
|
||||
return new RedirectResponse('/');
|
||||
}
|
||||
|
||||
|
|
17
src/php/Templates/NavigationHeader.php
Normal file
17
src/php/Templates/NavigationHeader.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Templates;
|
||||
|
||||
use GamesShop\Login\UserPermission;
|
||||
|
||||
final class NavigationHeader
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $title,
|
||||
public readonly string $link,
|
||||
public readonly UserPermission $minimumPermission,
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace GamesShop\Templates;
|
||||
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Paths;
|
||||
use League\Plates\Engine;
|
||||
|
||||
|
@ -12,11 +13,13 @@ final class TemplateEngine extends Engine
|
|||
|
||||
public function __construct(
|
||||
private ResourceIndex $resourceIndex,
|
||||
LoginHandler $loginHandler,
|
||||
)
|
||||
{
|
||||
parent::__construct(self::TEMPLATES_PATH, 'php');
|
||||
$this->addData([
|
||||
'resources' => $this->resourceIndex,
|
||||
'activeUser' => $loginHandler->isLoggedIn() ? $loginHandler->getCurrentUser() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -24,4 +27,8 @@ final class TemplateEngine extends Engine
|
|||
{
|
||||
return parent::render("pages/$page", $data);
|
||||
}
|
||||
|
||||
public function renderErrorPage(int $error) {
|
||||
return self::renderPage('error', [ 'errorCode' => $error ]);
|
||||
}
|
||||
}
|
|
@ -1,31 +1,29 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use GamesShop\ContainerHandler;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Entities\Account\User;
|
||||
|
||||
/** @var User|null $activeUser */
|
||||
|
||||
$loginHandler = ContainerHandler::get(LoginHandler::class);
|
||||
?>
|
||||
|
||||
<?php if ($loginHandler->isLoggedIn()):
|
||||
$user = $loginHandler->getCurrentUser();
|
||||
?>
|
||||
<?php if ($activeUser !== null): ?>
|
||||
<div class="d-flex avatar justify-content-center">
|
||||
|
||||
<div class="avatar-icon h-100 position-relative me-2 ratio-1">
|
||||
<img src="<?= $user->getProfilePictureUrl(); ?>" class="rounded-circle h-100" alt="User Profile Picture" />
|
||||
<img src="<?= $activeUser->getProfilePictureUrl(); ?>" class="rounded-circle h-100" alt="User Profile Picture" />
|
||||
<div class="position-absolute bottom-0 end-0 ratio-1 d-flex align-items-center z-1 avatar-login-method">
|
||||
<i class="fa-brands <?= $user->getLoginMethod()->getIconClass() ?>"></i>
|
||||
<i class="fa-brands <?= $activeUser->getLoginMethod()->getIconClass() ?>"></i>
|
||||
<span class="position-absolute w-100 h-100 bottom-0 end-0 ratio-1 bg-body rounded-circle z-n1 avatar-login-method-icon"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column">
|
||||
<span class="me-2 h-3">
|
||||
<?= $user->getName() ?>
|
||||
<?= $activeUser->getName() ?>
|
||||
</span>
|
||||
<small class="text-muted">
|
||||
<?= $user->getPermission()->getHumanReadableName() ?>
|
||||
<?= $activeUser->getPermission()->getHumanReadableName() ?>
|
||||
</small>
|
||||
</div>
|
||||
<div class="h-100 d-flex align-items-center ms-2">
|
||||
|
|
|
@ -39,5 +39,7 @@ $resource = $resources->getResource($resourceEntry);
|
|||
<?= $this->section('content'); ?>
|
||||
</main>
|
||||
|
||||
<?= $this->section('modal') ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use GamesShop\ContainerHandler;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Entities\Account\User;
|
||||
use GamesShop\Login\UserPermission;
|
||||
use GamesShop\Templates\NavigationHeader;
|
||||
|
||||
ContainerHandler::get(LoginHandler::class);
|
||||
$headers = [
|
||||
new NavigationHeader('My Keys', '/keys', UserPermission::PROVIDER),
|
||||
new NavigationHeader('Accounts', '/accounts', UserPermission::ADMIN)
|
||||
];
|
||||
|
||||
/** @var User|null $activeUser */
|
||||
$currentPermission = $activeUser === null ? UserPermission::NONE : $activeUser->getPermission();
|
||||
|
||||
?>
|
||||
|
||||
|
@ -15,7 +22,19 @@ ContainerHandler::get(LoginHandler::class);
|
|||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbar-content">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0"></ul>
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<?php foreach ($headers as $header):
|
||||
if (!$currentPermission->hasLevel($header->minimumPermission)) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
<li class="nav-link">
|
||||
<a href="<?= $header->link ?>" class="nav-link"><?= $header->title ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<?= $this->insert('layout/accountDisplay'); ?>
|
||||
</div>
|
||||
|
|
57
src/templates/pages/admin/accounts.php
Normal file
57
src/templates/pages/admin/accounts.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use GamesShop\Login\UserPermission;
|
||||
|
||||
$this->layout('layout/main', [ 'resourceEntry' => 'admin/accounts' ]);
|
||||
?>
|
||||
|
||||
<h1>Users</h1>
|
||||
|
||||
<table id="user-table" class="table table-striped w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="2.4rem"></th>
|
||||
<th>Name</th>
|
||||
<th>Permission</th>
|
||||
<th>Login-Method</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<?php $this->start('modal') ?>
|
||||
<div class="modal" id="edit-modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title h3">
|
||||
Edit User
|
||||
</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-2">
|
||||
Name: <span class="name-input"></span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
Login Method: <span class="login-method"></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="permissions">Permissions:</label>
|
||||
<select name="" id="permissions" class="form-select permission-editor">
|
||||
<?php foreach (UserPermission::cases() as $userPermission):?>
|
||||
<option value="<?= $userPermission->value ?>"><?= $userPermission->getHumanReadableName() ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary js--save">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php $this->end() ?>
|
|
@ -2,6 +2,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'),
|
|||
Path = require('path'),
|
||||
AssetsPlugin = require('assets-webpack-plugin'),
|
||||
CopyPlugin = require('copy-webpack-plugin');
|
||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||
|
||||
const PUBLIC_FOLDER = Path.resolve(__dirname, 'public'),
|
||||
SOURCE_FOLDER = Path.resolve(__dirname, 'src'),
|
||||
|
@ -38,6 +39,10 @@ module.exports = {
|
|||
devtool: 'source-map',
|
||||
optimization: {
|
||||
runtimeChunk: "single",
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new CssMinimizerPlugin()
|
||||
],
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
|
@ -86,7 +91,8 @@ module.exports = {
|
|||
extensions: ['.js', '.ts'],
|
||||
},
|
||||
entry: {
|
||||
index: JS_FOLDER + "/pages/index"
|
||||
index: JS_FOLDER + "/pages/index",
|
||||
'admin/accounts': JS_FOLDER + "/pages/admin/accounts",
|
||||
},
|
||||
output: {
|
||||
path: PUBLIC_FOLDER,
|
||||
|
|
Loading…
Reference in a new issue