implement community basics
Some checks failed
rvr-nextgen/pipeline/pr-master There was a failure building this commit

This commit is contained in:
Bence Pőcze 2023-04-16 03:31:40 +02:00
parent 4dc08dffc9
commit 96b9ef6a72
Signed by: bence
GPG Key ID: DC5BD6E95A333E6D
21 changed files with 870 additions and 15 deletions

View File

@ -0,0 +1,20 @@
CREATE TABLE `communities` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`currency` varchar(3) NOT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `community_members` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`community_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned DEFAULT NULL,
`owner` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_for_community` (`community_id`, `user_id`),
KEY `community_id` (`community_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `community_members_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`),
CONSTRAINT `community_members_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

View File

@ -88,10 +88,14 @@ sub {
} }
hr { hr {
border: solid #bbbbbb 1px; border: solid #bbbbcc 1px;
margin: 10px 0; margin: 10px 0;
} }
.normal {
font-weight: 400;
}
.bold { .bold {
font-weight: 700; font-weight: 700;
} }
@ -418,13 +422,53 @@ div.buttonContainer>button {
div.box { div.box {
width: 576px; width: 576px;
background-color: #eeeeee; background-color: #eeeef4;
border-radius: 3px; border-radius: 3px;
margin: 10px auto; margin: 10px auto;
padding: 10px; padding: 10px;
box-sizing: border-box; box-sizing: border-box;
} }
div.gridContainer {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
grid-gap: 10px;
}
div.gridContainer > div {
background-color: #eeeef4;
border-radius: 3px;
padding: 5px 10px;
}
table {
border-collapse: separate;
border-spacing: 0;
}
table.fullWidth {
width: 100%;
}
table th {
font-weight: bold;
}
table th, table td {
padding: 2px 0;
vertical-align: middle;
}
.choices__inner {
box-sizing: border-box;
}
@media screen and (max-width: 424px) {
div.gridContainer {
grid-template-columns: auto;
}
}
@media screen and (max-width: 599px) { @media screen and (max-width: 599px) {
header h1 span { header h1 span {
display: none; display: none;

View File

@ -0,0 +1,62 @@
(function () {
const element = document.getElementById('new_member_user_id');
const choices = new Choices(element, {
noResultsText: 'No users found',
noChoicesText: 'Start typing to search users'
});
element.addEventListener('search', RVR.debounce(async function (e) {
RVR.httpRequest('GET', `/searchUser?q=${encodeURIComponent(e.detail.value)}`, function () {
choices.setChoices(this.response.results, 'value', 'label', true);
});
}));
element.addEventListener('choice', function () {
choices.setChoices([], 'value', 'label', true);
document.getElementById('new_member_button').disabled = false;
});
document.getElementById('new_member_button').addEventListener('click', function () {
document.getElementById('loading').style.visibility = 'visible';
let data = new FormData();
data.append('user_id', document.getElementById('new_member_user_id').value);
RVR.httpRequest('POST', newMemberUrl, function () {
window.location.reload();
}, data);
});
const ownerCheckboxesButtons = document.getElementsByClassName('member_owner');
for (const ownerCheckboxesButton of ownerCheckboxesButtons) {
ownerCheckboxesButton.addEventListener('change', function () {
document.getElementById('loading').style.visibility = 'visible';
let data = new FormData();
data.append('community_member_id', this.dataset.id);
data.append('owner', this.checked ? 1 : 0);
RVR.httpRequest('POST', editMemberUrl, function () {
document.getElementById('loading').style.visibility = 'hidden';
if (!this.response.success) {
ownerCheckboxesButton.checked = !ownerCheckboxesButton.checked;
}
}, data);
});
};
const deleteButtons = document.getElementsByClassName('delete_member');
for (const deleteButton of deleteButtons) {
deleteButton.addEventListener('click', function () {
document.getElementById('loading').style.visibility = 'visible';
let data = new FormData();
data.append('community_member_id', this.dataset.id);
RVR.httpRequest('POST', deleteMemberUrl, function () {
window.location.reload();
}, data);
});
};
})();

View File

@ -193,6 +193,14 @@ var RVR = {
form.onreset = function () { form.onreset = function () {
form.elements.submit.disabled = true; form.elements.submit.disabled = true;
} }
},
debounce: function(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
} }
}; };

57
public/static/package-lock.json generated Normal file
View File

@ -0,0 +1,57 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@babel/runtime": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
},
"choices.js": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/choices.js/-/choices.js-10.2.0.tgz",
"integrity": "sha512-8PKy6wq7BMjNwDTZwr3+Zry6G2+opJaAJDDA/j3yxvqSCnvkKe7ZIFfIyOhoc7htIWFhsfzF9tJpGUATcpUtPg==",
"requires": {
"deepmerge": "^4.2.2",
"fuse.js": "^6.6.2",
"redux": "^4.2.0"
}
},
"deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
},
"fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="
},
"leaflet": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz",
"integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ=="
},
"leaflet.markercluster": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA=="
},
"redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
}
}
}

View File

@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"leaflet": "^1.6.0", "leaflet": "^1.6.0",
"leaflet.markercluster": "^1.4.1" "leaflet.markercluster": "^1.4.1",
"choices.js": "^10.2.0"
} }
} }

View File

@ -0,0 +1,249 @@
<?php namespace RVR\Controller;
use DateTime;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember;
use RVR\Repository\CommunityRepository;
use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CurrencyRepository;
use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Request\IRequest;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\PersistentData\PersistentDataManager;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
class CommunityController implements ISecured
{
private IRequest $request;
private PersistentDataManager $pdm;
private UserRepository $userRepository;
private CommunityRepository $communityRepository;
private CommunityMemberRepository $communityMemberRepository;
private CurrencyRepository $currencyRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->userRepository = new UserRepository();
$this->communityRepository = new CommunityRepository();
$this->communityMemberRepository = new CommunityMemberRepository();
$this->currencyRepository = new CurrencyRepository();
}
public function authorize(): bool
{
return $this->request->user() !== null;
}
public function getCommunityHome(): ?IContent
{
$community = $this->communityRepository->getById($this->request->query('communityId'));
if ($community === null) {
return null;
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null) {
return null;
}
/*[$community, $ownCommunityMember] = $this->checkPermission($this->request->query('communityId'));
if (!$community) {
return null;
}*/
$currencies = $this->currencyRepository->getAllByCommunity($community);
$currencyNames = [];
foreach ($currencies as $currency) {
$currencyNames[] = $currency->getCurrency();
}
return new HtmlContent('communities/community', [
'community' => $community,
'members' => $this->getMembers($community),
'currencyNames' => $currencyNames,
'upcomingEvents' => [],
'editPermission' => $ownCommunityMember->getOwner()
]);
}
public function getCommunityNew(): IContent
{
return new HtmlContent('communities/community_edit');
}
public function getCommunityEdit(): IContent
{
$community = $this->communityRepository->getById($this->request->query('communityId'));
if ($community === null) {
return null;
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null || !$ownCommunityMember->getOwner()) {
return null;
}
return new HtmlContent('communities/community_edit', [
'community' => $community
]);
}
public function getMembersEdit(): ?IContent
{
$community = $this->communityRepository->getById($this->request->query('communityId'));
if ($community === null) {
return null;
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null || !$ownCommunityMember->getOwner()) {
return null;
}
return new HtmlContent('communities/community_members', [
'community' => $community,
'members' => $this->getMembers($community)
]);
}
private function getMembers(Community $community): array
{
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true));
usort($members, function($a, $b) {
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
});
return $members;
}
public function newMember(): ?IContent
{
$community = $this->communityRepository->getById($this->request->query('communityId'));
if ($community === null) {
return null;
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null || !$ownCommunityMember->getOwner()) {
return null;
}
$user = $this->userRepository->getById($this->request->post('user_id'));
$communityMember = new CommunityMember();
$communityMember->setCommunity($community);
$communityMember->setUser($user);
$this->pdm->saveToDb($communityMember);
return new JsonContent(['success' => true]);
}
public function editMember(): ?IContent
{
$community = $this->communityRepository->getById($this->request->query('communityId'));
if ($community === null) {
return null;
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null || !$ownCommunityMember->getOwner()) {
return null;
}
$communityMember = $this->communityMemberRepository->getById($this->request->post('community_member_id'));
if ($communityMember->getUserId() === $this->request->user()->getUniqueId()) {
return new JsonContent([
'error' => ['errorText' => 'Own user cannot be edited.']
]);
}
return new JsonContent(['success' => false]);
$communityMember->setOwner($this->request->post('owner'));
$this->pdm->saveToDb($communityMember);
return new JsonContent(['success' => true]);
}
public function deleteMember(): ?IContent
{
$community = $this->communityRepository->getById($this->request->query('communityId'));
if ($community === null) {
return null;
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null || !$ownCommunityMember->getOwner()) {
return null;
}
$communityMember = $this->communityMemberRepository->getById($this->request->post('community_member_id'));
if ($communityMember->getUserId() === $this->request->user()->getUniqueId()) {
return new JsonContent([
'error' => ['errorText' => 'Own user cannot be deleted.']
]);
}
$this->pdm->deleteFromDb($communityMember);
return new JsonContent(['success' => true]);
}
public function saveCommunity(): ?IContent
{
$communityId = $this->request->query('communityId');
if ($communityId){
$community = $this->communityRepository->getById($this->request->query('communityId'));
if ($community === null) {
return null;
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null || !$ownCommunityMember->getOwner()) {
return null;
}
} else {
$community = new Community();
}
$name = $this->request->post('name');
$currency = $this->request->post('currency');
if (strlen($name) === 0 || strlen($currency) === 0 || strlen($currency) > 3) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$community->setName($name);
$community->setCurrency($currency);
if (!$communityId) {
$community->setCreatedDate(new DateTime());
}
$this->pdm->saveToDb($community);
if (!$communityId) {
$communityMember = new CommunityMember();
$communityMember->setCommunity($community);
$communityMember->setUser($this->request->user());
$communityMember->setOwner(true);
$this->pdm->saveToDb($communityMember);
}
return new JsonContent([
'redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()])]
]);
}
private function checkPermission(int $communityId): array
{
$community = $this->communityRepository->getById($communityId);
if ($community === null) {
return [null, null];
}
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $this->request->user());
if ($ownCommunityMember === null) {
return [null, null];
}
return [$community, $ownCommunityMember];
}
}

View File

@ -1,18 +1,21 @@
<?php namespace RVR\Controller; <?php namespace RVR\Controller;
use RVR\Repository\CommunityMemberRepository;
use SokoWeb\Interfaces\Authorization\ISecured; use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Request\IRequest; use SokoWeb\Interfaces\Request\IRequest;
use SokoWeb\Interfaces\Response\IContent; use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\HtmlContent; use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
class HomeController implements ISecured class HomeController implements ISecured
{ {
private IRequest $request; private IRequest $request;
private CommunityMemberRepository $communityMemberRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
$this->communityMemberRepository = new CommunityMemberRepository();
} }
public function authorize(): bool public function authorize(): bool
@ -20,8 +23,20 @@ class HomeController implements ISecured
return $this->request->user() !== null; return $this->request->user() !== null;
} }
public function getIndex(): IContent public function getHome(): IContent
{ {
return new HtmlContent('index'); $ownCommunityMembers = $this->communityMemberRepository->getAllByUser($this->request->user(), true);
$communities = [];
foreach ($ownCommunityMembers as $ownCommunityMember) {
$communities[] = $ownCommunityMember->getCommunity();
}
usort($communities, function($a, $b) {
return strnatcmp($a->getName(), $b->getName());
});
return new HtmlContent('home', [
'communities' => $communities,
'upcomingEvents' => []
]);
} }
} }

View File

@ -0,0 +1,42 @@
<?php namespace RVR\Controller;
use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Request\IRequest;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\JsonContent;
class UserSearchController implements ISecured
{
private IRequest $request;
private UserRepository $userRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->userRepository = new UserRepository();
}
public function authorize(): bool
{
return $this->request->user() !== null;
}
public function searchUser(): IContent
{
$users = iterator_to_array($this->userRepository->searchByName($this->request->query('q')));
usort($users, function($a, $b) {
return strnatcmp($a->getDisplayName(), $b->getDisplayName());
});
$results = [];
foreach ($users as $user) {
$results[] = ['value' => $user->getId(), 'label' => $user->getFullDisplayName()];
}
return new JsonContent([
'results' => $results
]);
}
}

View File

@ -0,0 +1,57 @@
<?php namespace RVR\PersistentData\Model;
use DateTime;
use SokoWeb\PersistentData\Model\Model;
class Community extends Model
{
protected static string $table = 'communities';
protected static array $fields = ['name', 'currency', 'created'];
private string $name = '';
private string $currency = '';
private DateTime $created;
public function setName(string $name): void
{
$this->name = $name;
}
public function setCurrency(string $currency): void
{
$this->currency = $currency;
}
public function setCreatedDate(DateTime $created): void
{
$this->created = $created;
}
public function setCreated(string $created): void
{
$this->created = new DateTime($created);
}
public function getName(): string
{
return $this->name;
}
public function getCurrency(): string
{
return $this->currency;
}
public function getCreatedDate(): DateTime
{
return $this->created;
}
public function getCreated(): string
{
return $this->created->format('Y-m-d H:i:s');
}
}

View File

@ -0,0 +1,72 @@
<?php namespace RVR\PersistentData\Model;
use SokoWeb\PersistentData\Model\Model;
class CommunityMember extends Model
{
protected static string $table = 'community_members';
protected static array $fields = ['community_id', 'user_id', 'owner'];
protected static array $relations = ['community' => Community::class, 'user' => User::class];
private ?Community $community = null;
private ?int $communityId = null;
private ?User $user = null;
private ?int $userId = null;
private bool $owner = false;
public function setCommunity(Community $community): void
{
$this->community = $community;
}
public function setCommunityId(int $communityId): void
{
$this->communityId = $communityId;
}
public function setUser(User $user): void
{
$this->user = $user;
}
public function setUserId(int $userId): void
{
$this->userId = $userId;
}
public function setOwner(bool $owner): void
{
$this->owner = $owner;
}
public function getCommunity(): ?Community
{
return $this->community;
}
public function getCommunityId(): ?int
{
return $this->communityId;
}
public function getUser(): ?User
{
return $this->user;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function getOwner(): bool
{
return $this->owner;
}
}

View File

@ -171,6 +171,11 @@ class User extends Model implements IUser
return $this->nickname ?: $this->fullName; return $this->nickname ?: $this->fullName;
} }
public function getFullDisplayName(): string
{
return $this->nickname ? $this->nickname . ' (' . $this->fullName . ')' : $this->fullName;
}
public function checkPassword(string $password): bool public function checkPassword(string $password): bool
{ {
if ($this->password === null) { if ($this->password === null) {

View File

@ -0,0 +1,48 @@
<?php namespace RVR\Repository;
use Generator;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\User;
use SokoWeb\Database\Query\Select;
use SokoWeb\PersistentData\PersistentDataManager;
class CommunityMemberRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getById(int $id): ?CommunityMember
{
return $this->pdm->selectFromDbById($id, CommunityMember::class);
}
public function getAllByCommunity(Community $community, bool $useRelations = false): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
yield from $this->pdm->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
}
public function getAllByUser(User $user, bool $useRelations = false): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
yield from $this->pdm->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
}
public function getByCommunityAndUser(Community $community, User $user) : ?CommunityMember
{
$select = new Select(\Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
$select->where('user_id', '=', $user->getId());
return $this->pdm->selectFromDb($select, CommunityMember::class);
}
}

View File

@ -0,0 +1,19 @@
<?php namespace RVR\Repository;
use RVR\PersistentData\Model\Community;
use SokoWeb\PersistentData\PersistentDataManager;
class CommunityRepository
{
private PersistentDataManager $pdm;
public function __construct()
{
$this->pdm = new PersistentDataManager();
}
public function getById(int $id): ?Community
{
return $this->pdm->selectFromDbById($id, Community::class);
}
}

View File

@ -1,5 +1,6 @@
<?php namespace RVR\Repository; <?php namespace RVR\Repository;
use Generator;
use SokoWeb\Interfaces\Repository\IUserRepository; use SokoWeb\Interfaces\Repository\IUserRepository;
use SokoWeb\Database\Query\Select; use SokoWeb\Database\Query\Select;
use RVR\PersistentData\Model\User; use RVR\PersistentData\Model\User;
@ -51,4 +52,14 @@ class UserRepository implements IUserRepository
return $this->pdm->selectFromDb($select, User::class); return $this->pdm->selectFromDb($select, User::class);
} }
public function searchByName(string $name): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('full_name', 'LIKE', '%' . $name . '%');
$select->orWhere('nickname', 'LIKE', '%' . $name . '%');
$select->limit(10);
yield from $this->pdm->selectMultipleFromDb($select, User::class);
}
} }

View File

@ -0,0 +1,53 @@
@extends(templates/layout_normal)
@section(main)
<h2><?= $community->getName() ?> <span class="small">[<a href="/communities/<?= $community->getId() ?>/edit">edit</a>]</span></h2>
<div class="gridContainer marginTop">
<div>
<h3 class="marginBottom">Members</h3>
<?php foreach ($members as $member): ?>
<p><?= $member->getUser()->getDisplayName() ?></p>
<?php endforeach; ?>
<?php if ($editPermission): ?>
<hr>
<p><a href="/communities/<?= $community->getId() ?>/members">Edit members</a></p>
<?php endif; ?>
</div>
<div>
<h3 class="marginBottom">Currencies</h3>
<p class="marginBottom">Main currency: <b><?= $community->getCurrency() ?></b></p>
<p>Further currencies: <b><?= implode(', ', $currencyNames) ?></b></p>
</div>
<div>
<h3 class="marginBottom">Upcoming events</h3>
<?php if (count($upcomingEvents) > 0): ?>
<?php foreach ($upcomingEvents as $event): ?>
<p><a href="/events/<?= $event->getId() ?>"><?= $event->getName() ?></a></p>
<?php endforeach; ?>
<?php else: ?>
<p>There is no upcoming event.</p>
<?php endif; ?>
<!--<hr>
<p><a href="/events?communityId=<?= $community->getId() ?>">All events</a></p>
<p><a href="/communities/<?= $community->getId() ?>/newEvent">New event</a></p>-->
</div>
<div>
<h3 class="marginBottom">Finances</h3>
<table class="fullWidth">
<tr>
<td>You owe</td>
<td style="text-align: right; color: red;">0 <?= $community->getCurrency() ?></td>
</tr>
<tr>
<td>You're owed</td>
<td style="text-align: right; color: green;">0 <?= $community->getCurrency() ?></td>
</tr>
<tr>
<td>Your balance</td>
<td style="text-align: right;">0 <?= $community->getCurrency() ?></td>
</tr>
</table>
</div>
</div>
@endsection

View File

@ -0,0 +1,15 @@
@extends(templates/layout_normal)
@section(main)
<h2><?= isset($community) ? $community->getName() . ' - Edit' : 'New community' ?></h2>
<div class="box">
<form id="communityForm" action="/communities/<?= isset($community) ? $community->getId() . '/edit' : 'new' ?>" method="post" data-redirect-on-success="true">
<input type="text" class="text big fullWidth" name="name" placeholder="Name" value="<?= isset($community) ? $community->getName() : '' ?>" required>
<input type="text" class="text big fullWidth marginTop" name="currency" value="<?= isset($community) ? $community->getCurrency() : '' ?>" placeholder="Default currency" maxlength="3" required>
<p id="accountFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit" name="submit"><?= isset($community) ? 'Save' : 'Create' ?></button>
</div>
</form>
</div>
@endsection

View File

@ -0,0 +1,41 @@
@css(node_modules/choices.js/public/assets/styles/choices.min.css)
@js(node_modules/choices.js/public/assets/scripts/choices.js)
@js(js/communities/community_members.js)
@extends(templates/layout_normal)
@section(main)
<h2><?= $community->getName() ?> - Edit members</h2>
<div class="box">
<table class="fullWidth">
<thead>
<tr>
<th style="width: 50%;"></th>
<th style="width: 25%; text-align: center;">Owner</th>
<th style="width: 25%;"></th>
</tr>
</thead>
<?php foreach ($members as $member): ?>
<?php $editable = $member->getUserId() !== Container::$request->user()->getUniqueId(); ?>
<tr>
<td><?= $member->getUser()->getDisplayName() ?></td>
<td style="text-align: center;"><input type="checkbox" class="member_owner" data-id="<?= $member->getId() ?>" <?= $member->getOwner() ? 'checked' : '' ?> <?= !$editable ? 'disabled' : '' ?> /></td>
<td style="text-align: right;"><button type="button" class="small red delete_member" data-id="<?= $member->getId() ?>" <?= !$editable ? 'disabled' : '' ?>>Delete</button></td>
</tr>
<?php endforeach; ?>
<tr>
<td><select type="text" id="new_member_user_id"></td>
<td></td>
<td style="text-align: right;"><button type="button" class="small" id="new_member_button" disabled>Add</button></td>
</tr>
</table>
</div>
@endsection
@section(pageScript)
<script>
var newMemberUrl = '/<?= Container::$routeCollection->getRoute('community-members-new')->generateLink(['communityId' => $community->getId()]) ?>';
var editMemberUrl = '/<?= Container::$routeCollection->getRoute('community-members-edit')->generateLink(['communityId' => $community->getId()]) ?>';
var deleteMemberUrl = '/<?= Container::$routeCollection->getRoute('community-members-delete')->generateLink(['communityId' => $community->getId()]) ?>';
</script>
@endsection

30
views/home.php Normal file
View File

@ -0,0 +1,30 @@
@extends(templates/layout_normal)
@section(main)
<div class="gridContainer">
<div>
<h3 class="marginBottom">Communities</h3>
<?php if (count($communities) > 0): ?>
<?php foreach ($communities as $community): ?>
<p><a href="/communities/<?= $community->getId() ?>"><?= $community->getName() ?></a></p>
<?php endforeach; ?>
<?php else: ?>
<p>You have no community.</p>
<?php endif; ?>
<hr>
<p><a href="/communities/new">New community</a></p>
</div>
<div>
<h3 class="marginBottom">Upcoming events</h3>
<?php if (count($upcomingEvents) > 0): ?>
<?php foreach ($upcomingEvents as $event): ?>
<p><a href="/events/<?= $event->getId() ?>"><?= $event->getName() ?></a></p>
<?php endforeach; ?>
<?php else: ?>
<p>There is no upcoming event.</p>
<?php endif; ?>
<!--<hr>
<p><a href="/events">All events</a></p>-->
</div>
</div>
@endsection

View File

@ -1,8 +0,0 @@
@extends(templates/layout_normal)
@section(main)
<h2><?= $_ENV['APP_NAME'] ?></h2>
<div class="box">
<p>This is the new <?= $_ENV['APP_NAME'] ?>.</p>
</div>
@endsection

16
web.php
View File

@ -12,7 +12,7 @@ if (!empty($_ENV['DEV'])) {
Container::$routeCollection = new SokoWeb\Routing\RouteCollection(); Container::$routeCollection = new SokoWeb\Routing\RouteCollection();
Container::$routeCollection->get('index', '', [RVR\Controller\HomeController::class, 'getIndex']); Container::$routeCollection->get('home', '', [RVR\Controller\HomeController::class, 'getHome']);
Container::$routeCollection->get('startSession', 'startSession.json', [RVR\Controller\HomeController::class, 'startSession']); Container::$routeCollection->get('startSession', 'startSession.json', [RVR\Controller\HomeController::class, 'startSession']);
Container::$routeCollection->group('login', function (SokoWeb\Routing\RouteCollection $routeCollection) { Container::$routeCollection->group('login', function (SokoWeb\Routing\RouteCollection $routeCollection) {
$routeCollection->get('login', '', [RVR\Controller\LoginController::class, 'getLoginForm']); $routeCollection->get('login', '', [RVR\Controller\LoginController::class, 'getLoginForm']);
@ -41,6 +41,20 @@ Container::$routeCollection->group('account', function (SokoWeb\Routing\RouteCol
$routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [RVR\Controller\UserController::class, 'getGoogleAuthenticateRedirect']); $routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [RVR\Controller\UserController::class, 'getGoogleAuthenticateRedirect']);
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [RVR\Controller\UserController::class, 'authenticateWithGoogle']); $routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [RVR\Controller\UserController::class, 'authenticateWithGoogle']);
}); });
Container::$routeCollection->get('searchUser', 'searchUser', [RVR\Controller\UserSearchController::class, 'searchUser']);
Container::$routeCollection->group('communities', function (SokoWeb\Routing\RouteCollection $routeCollection) {
$routeCollection->get('community-new', 'new', [RVR\Controller\CommunityController::class, 'getCommunityNew']);
$routeCollection->post('community-new-action', 'new', [RVR\Controller\CommunityController::class, 'saveCommunity']);
$routeCollection->group('{communityId}', function (SokoWeb\Routing\RouteCollection $routeCollection) {
$routeCollection->get('community', '', [RVR\Controller\CommunityController::class, 'getCommunityHome']);
$routeCollection->get('community-edit', 'edit', [RVR\Controller\CommunityController::class, 'getCommunityEdit']);
$routeCollection->post('community-edit-action', 'edit', [RVR\Controller\CommunityController::class, 'saveCommunity']);
$routeCollection->get('community-members', 'members', [RVR\Controller\CommunityController::class, 'getMembersEdit']);
$routeCollection->post('community-members-new', 'newMember', [RVR\Controller\CommunityController::class, 'newMember']);
$routeCollection->post('community-members-edit', 'editMember', [RVR\Controller\CommunityController::class, 'editMember']);
$routeCollection->post('community-members-delete', 'deleteMember', [RVR\Controller\CommunityController::class, 'deleteMember']);
});
});
Container::$sessionHandler = new SokoWeb\Session\DatabaseSessionHandler(); Container::$sessionHandler = new SokoWeb\Session\DatabaseSessionHandler();