Compare commits
3 Commits
main
...
11108bdfcc
| Author | SHA1 | Date | |
|---|---|---|---|
| 11108bdfcc | |||
| e280fcfba8 | |||
| 3570f442f5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@
|
|||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
/docker-compose.yaml
|
||||||
|
|||||||
5
Makefile
5
Makefile
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
FRONTEND_DIR ?= .
|
FRONTEND_DIR ?= .
|
||||||
|
|
||||||
|
setup:
|
||||||
|
rm -f docker-compose.yaml
|
||||||
|
cp docker-compose.dev docker-compose.yaml
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
@cd $(FRONTEND_DIR) && \
|
@cd $(FRONTEND_DIR) && \
|
||||||
export QT_QPA_PLATFORM=offscreen && \
|
export QT_QPA_PLATFORM=offscreen && \
|
||||||
|
|||||||
32
app/Domains/Dashboard/Controllers/DashboardController.php
Normal file
32
app/Domains/Dashboard/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Dashboard\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\AuthCheckProvider;
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class DashboardController extends CommonController {
|
||||||
|
public function __invoke(Request $request) {
|
||||||
|
if ($this->checkAuth()) {
|
||||||
|
return $this->renderForLoggedInUser($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->intended('/login');
|
||||||
|
|
||||||
|
dd('U');
|
||||||
|
return $this->renderForGuest($request);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderForLoggedInUser(Request $request) {
|
||||||
|
$authCheckProvider = new AuthCheckProvider;
|
||||||
|
$inertiaProvider = new InertiaProvider('Dashboard/Dashboard', ['appName' => app('tenant')->name]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderForGuest(Request $request) {
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Domains/Dashboard/Views/Dashboard.vue
Normal file
42
app/Domains/Dashboard/Views/Dashboard.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
navbar: Object,
|
||||||
|
tenant: String,
|
||||||
|
user: Object,
|
||||||
|
currentPath: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title='Dashboard' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||||
|
<diV class="dashboard-widget-container">
|
||||||
|
<shadowed-box class="dashboard-widget-box" style="width: 60%;">
|
||||||
|
Meine Anmeldungen
|
||||||
|
</shadowed-box>
|
||||||
|
|
||||||
|
<shadowed-box class="dashboard-widget-box">
|
||||||
|
Meine Abrechnungen
|
||||||
|
</shadowed-box>
|
||||||
|
</diV>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dashboard-widget-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-widget-box {
|
||||||
|
flex-grow: 1; display: inline-block;
|
||||||
|
height: 150px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
29
app/Domains/Invoice/Views/CreateInvoice.vue
Normal file
29
app/Domains/Invoice/Views/CreateInvoice.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
navbar: Object,
|
||||||
|
tenant: String,
|
||||||
|
user: Object,
|
||||||
|
currentPath: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title='Neue Abrechnung' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||||
|
<!-- Alles hier wird in den Slot von AppLayout eingefügt -->
|
||||||
|
<h2>Dashboard Content</h2>
|
||||||
|
<p>Test 1!
|
||||||
|
Hier wird mal eine Rechnung erstellt.
|
||||||
|
Wenn es geht oder auch nicht</p>
|
||||||
|
{{props.tenant}}
|
||||||
|
|
||||||
|
<button @click="$toast.success('Hallo vom Dashboard!')">Test Toaster</button>
|
||||||
|
<button @click="$toast.error('Soe sieht ein Fehler aus')">Error Toaster</button>
|
||||||
|
</AppLayout>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\MessageSystem\Actions\SendMessage;
|
||||||
|
|
||||||
|
use App\Enumerations\MessageType;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
|
class SendMessageCommand {
|
||||||
|
private SendMessageRequest $request;
|
||||||
|
|
||||||
|
public function __construct(SendMessageRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send() : SendMessageResponse {
|
||||||
|
$response = new SendMessageResponse();
|
||||||
|
|
||||||
|
foreach ($this->request->messageTypes as $messageType) {
|
||||||
|
switch (true) {
|
||||||
|
case $messageType->value === MessageType::EMAIL->value:
|
||||||
|
$this->sendAsEmail();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case $messageType->value === MessageType::INTERNAL->value:
|
||||||
|
$this->sendAsInternalMessage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendAsEmail() {
|
||||||
|
foreach ($this->request->recipient->getEmailAddresses() as $emailAddress) {
|
||||||
|
Mail::html($this->request->message, function ($message) use ($emailAddress) {
|
||||||
|
$message
|
||||||
|
->to($emailAddress->getValue(), $this->request->recipient->getName())
|
||||||
|
->subject($this->request->subject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendAsInternalMessage() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\MessageSystem\Actions\SendMessage;
|
||||||
|
|
||||||
|
use App\Enumerations\MessageType;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
use App\ValueObjects\MessageRecipient;
|
||||||
|
|
||||||
|
class SendMessageRequest {
|
||||||
|
public string $message;
|
||||||
|
public string $subject;
|
||||||
|
|
||||||
|
public MessageRecipient $recipient;
|
||||||
|
|
||||||
|
/** @var MessageType[] */
|
||||||
|
public array $messageTypes;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $message,
|
||||||
|
string $subject,
|
||||||
|
MessageRecipient $recipient,
|
||||||
|
array $messageTypes
|
||||||
|
) {
|
||||||
|
$this->message = $message;
|
||||||
|
$this->recipient = $recipient;
|
||||||
|
$this->subject = $subject;
|
||||||
|
$this->messageTypes = $messageTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\MessageSystem\Actions\SendMessage;
|
||||||
|
|
||||||
|
class SendMessageResponse {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\GenerateActivationToken;
|
||||||
|
|
||||||
|
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageCommand;
|
||||||
|
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageRequest;
|
||||||
|
use App\Enumerations\MessageType;
|
||||||
|
use App\MessageTemplates\activationCodeTemplate;
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
use App\ValueObjects\MessageRecipient;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class GenerateActivationTokenCommand {
|
||||||
|
private GenerateActivationTokenRequest $request;
|
||||||
|
|
||||||
|
public function __construct(GenerateActivationTokenRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : GenerateActivationTokenResponse {
|
||||||
|
$response = new GenerateActivationTokenResponse;
|
||||||
|
|
||||||
|
$activationCode = Str::password(24,true,true,false,false);
|
||||||
|
$this->request->user->activation_token = $activationCode;
|
||||||
|
if (null !== $this->request->expirationDate) {
|
||||||
|
$this->request->user->activation_token_expires_at = $this->request->expirationDate;
|
||||||
|
}
|
||||||
|
$this->request->user->save();
|
||||||
|
|
||||||
|
$response->activationCode = $activationCode;
|
||||||
|
|
||||||
|
$activationMessage = new activationCodeTemplate();
|
||||||
|
$activationMessage->composeMessage(EmailAddress::fromString($this->request->user->email), $activationCode);
|
||||||
|
|
||||||
|
$recipient = new MessageRecipient();
|
||||||
|
$recipient->addEmailAddress(EmailAddress::fromString($this->request->user->email));
|
||||||
|
$recipient->setName($this->request->user->getFullname());
|
||||||
|
|
||||||
|
$message = activationCodeTemplate::createForUser(EmailAddress::fromString($this->request->user->email), $activationCode);
|
||||||
|
|
||||||
|
$userMessageRequests = new SendMessageRequest($message->getMessage(), $message->getSubject(), $recipient, [MessageType::EMAIL]);
|
||||||
|
|
||||||
|
$userMessageCommand = new SendMessageCommand($userMessageRequests);
|
||||||
|
$userMessageCommand->send();
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\GenerateActivationToken;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class GenerateActivationTokenRequest {
|
||||||
|
public User $user;
|
||||||
|
public ?\DateTime $expirationDate;
|
||||||
|
|
||||||
|
public function __construct(User $user, ?\DateTime $expirationDate = null) {
|
||||||
|
$this->user = $user;
|
||||||
|
$this->expirationDate = $expirationDate;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\GenerateActivationToken;
|
||||||
|
|
||||||
|
class GenerateActivationTokenResponse {
|
||||||
|
public string $activationCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||||
|
|
||||||
|
class UserActivationCommand {
|
||||||
|
private UserActivationRequest $request;
|
||||||
|
public function __construct(UserActivationRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UserActivationResponse {
|
||||||
|
$response = new UserActivationResponse();
|
||||||
|
$this->request->user->active = true;
|
||||||
|
$this->request->user->activation_token = null;
|
||||||
|
$this->request->user->activation_token_expires_at = null;
|
||||||
|
$this->request->user->save();
|
||||||
|
$response->success = true;
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class UserActivationRequest {
|
||||||
|
public User $user;
|
||||||
|
|
||||||
|
public function __construct(User $user) {
|
||||||
|
$this->user = $user;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||||
|
|
||||||
|
class UserActivationResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserChangePassword;
|
||||||
|
|
||||||
|
|
||||||
|
class UserChangePasswordCommand {
|
||||||
|
private UserChangePasswordRequest $request;
|
||||||
|
|
||||||
|
public function __construct(UserChangePasswordRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UserChangePasswordResponse {
|
||||||
|
$response = new UserChangePasswordResponse();
|
||||||
|
|
||||||
|
$this->request->user->password = $this->request->newPassword;
|
||||||
|
$this->request->user->save();
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserChangePassword;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class UserChangePasswordRequest {
|
||||||
|
public User $user;
|
||||||
|
public string $newPassword;
|
||||||
|
|
||||||
|
public function __construct(User $user, string $newPassword) {
|
||||||
|
$this->user = $user;
|
||||||
|
$this->newPassword = $newPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserChangePassword;
|
||||||
|
|
||||||
|
class UserChangePasswordResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||||
|
|
||||||
|
class UserDeactivationCommand {
|
||||||
|
private UserDeactivationRequest $request;
|
||||||
|
public function __construct(UserDeactivationRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UserDeactivationResponse {
|
||||||
|
$response = new UserDeactivationResponse();
|
||||||
|
$this->request->user->active = false;
|
||||||
|
$this->request->user->password = NULL;
|
||||||
|
$this->request->user->username = 'deleted-' . $this->request->user->username;
|
||||||
|
$this->request->user->email = 'null@example.com';
|
||||||
|
$this->request->user->save();
|
||||||
|
$response->success = true;
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class UserDeactivationRequest {
|
||||||
|
public User $user;
|
||||||
|
|
||||||
|
public function __construct(User $user) {
|
||||||
|
$this->user = $user;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||||
|
|
||||||
|
class UserDeactivationResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserRegistration;
|
||||||
|
|
||||||
|
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageCommand;
|
||||||
|
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageRequest;
|
||||||
|
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenRequest;
|
||||||
|
use App\Enumerations\MessageType;
|
||||||
|
use App\MessageTemplates\Registration\InformAdminAboutNewUserTemplate;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
use App\ValueObjects\MessageRecipient;
|
||||||
|
|
||||||
|
class UserRegistrationCommand {
|
||||||
|
private UserRegistrationRequest $request;
|
||||||
|
|
||||||
|
public function __construct(UserRegistrationRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UserRegistrationResponse {
|
||||||
|
$response = new UserRegistrationResponse;
|
||||||
|
|
||||||
|
$user = User::create([
|
||||||
|
'user_role_main' => $this->request->userRoleMain,
|
||||||
|
'user_role_local_group' => $this->request->userRoleLocalGroup,
|
||||||
|
'username' => $this->request->email->getValue(),
|
||||||
|
'local_group' => $this->request->localGroup,
|
||||||
|
'firstname' => $this->request->firstname,
|
||||||
|
'lastname' => $this->request->lastname,
|
||||||
|
'nickname' => $this->request->nickname !== '' ? $this->request->nickname : null,
|
||||||
|
'email' => $this->request->email->getValue(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$generateActivationCoedeRequest = new GenerateActivationTokenRequest($user);
|
||||||
|
$generateActivationCoedeDommand = new GenerateActivationTokenCommand($generateActivationCoedeRequest);
|
||||||
|
$result = $generateActivationCoedeDommand->execute();
|
||||||
|
|
||||||
|
$user->activation_token = $result->activationCode;
|
||||||
|
|
||||||
|
$siteAdmin = new MessageRecipient();
|
||||||
|
$siteAdmin->addEmailAddress(EmailAddress::fromString(env('APP_ADMIN_MAIL')));
|
||||||
|
$siteAdmin->setName(env('APP_ADMIN_NAME'));
|
||||||
|
|
||||||
|
$registrationMessage = InformAdminAboutNewUserTemplate::createNew($user);
|
||||||
|
|
||||||
|
$registrationMessageRequest = new SendMessageRequest($registrationMessage->getMessage(), $registrationMessage->getSubject(), $siteAdmin, [MessageType::EMAIL]);
|
||||||
|
$registrationMessageCommand = new SendMessageCommand($registrationMessageRequest);
|
||||||
|
$registrationMessageCommand->send();
|
||||||
|
|
||||||
|
$response->user = $user;
|
||||||
|
$response->success = true;
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserRegistration;
|
||||||
|
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
|
||||||
|
class UserRegistrationRequest {
|
||||||
|
public string $firstname;
|
||||||
|
public string $lastname;
|
||||||
|
public EmailAddress $email;
|
||||||
|
public string $nickname;
|
||||||
|
public string $userRoleMain;
|
||||||
|
public string $userRoleLocalGroup;
|
||||||
|
public string $localGroup;
|
||||||
|
|
||||||
|
public function __construct(string $firstname, string $lastname, string $nickname, EmailAddress $email, string $userRoleMain, string $userRoleLocalGroup, string $localGroup) {
|
||||||
|
$this->firstname = $firstname;
|
||||||
|
$this->lastname = $lastname;
|
||||||
|
$this->nickname = $nickname;
|
||||||
|
$this->email = $email;
|
||||||
|
$this->userRoleMain = $userRoleMain;
|
||||||
|
$this->userRoleLocalGroup = $userRoleLocalGroup;
|
||||||
|
$this->localGroup = $localGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Actions\UserRegistration;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class UserRegistrationResponse {
|
||||||
|
public ?User $user;
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->user = null;
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\UserManagement\Actions\UserActivation\UserActivationCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\UserActivation\UserActivationRequest;
|
||||||
|
use App\Domains\UserManagement\Actions\UserActivation\UserDeactivationCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\UserActivation\UserDeactivationRequest;
|
||||||
|
use App\Domains\UserManagement\Actions\UserChangePassword\UserChangePasswordCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\UserChangePassword\UserChangePasswordRequest;
|
||||||
|
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationRequest;
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
use Carbon\Traits\Date;
|
||||||
|
use DateTime;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EmailVerificationController extends CommonController
|
||||||
|
{
|
||||||
|
public function verifyEmailForm(Request $request) {
|
||||||
|
$inertiaProvider = new InertiaProvider('UserManagement/VerifyEmail', ['appName' => app('tenant')->name]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doVerification(Request $request) : JsonResponse
|
||||||
|
{
|
||||||
|
|
||||||
|
$user = $this->users->findByUsername($request->get('email'));
|
||||||
|
if ($user === null) {
|
||||||
|
return response()->json([
|
||||||
|
'error_types' => [
|
||||||
|
'email' => 'Die E-Mail-Adresse konnte nicht zugeordnet werden.',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new DateTime() > DateTime::createFromFormat('Y-m-d H:i:s', $user->activation_token_expires_at)) {
|
||||||
|
return response()->json([
|
||||||
|
'error_types' => [
|
||||||
|
'verificationToken' => 'Der Sicherheitsschlüssel ist abgelaufen.',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->users->checkVerificationToken($user, $request->get('verificationToken'))) {
|
||||||
|
return response()->json([
|
||||||
|
'error_types' => [
|
||||||
|
'verificationToken' => 'Der Sicherheitsschlüssel ist nicht korrekt',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userActivationRequest = new UserActivationRequest($user);
|
||||||
|
$userActivationCommand = new UserActivationCommand($userActivationRequest);
|
||||||
|
$activationResult = $userActivationCommand->execute();
|
||||||
|
|
||||||
|
|
||||||
|
if (!$activationResult->success) {
|
||||||
|
return response()->json([
|
||||||
|
'error_types' => [
|
||||||
|
'verificationToken' => 'Ein allgemeiner Fehler ist aufgetreten. Bitte versuche es später noch einmal.',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userPasswordResetRequest = new UserChangePasswordRequest($user, $request->get('password'));
|
||||||
|
$userPasswordResetCommand = new UserChangePasswordCommand($userPasswordResetRequest);
|
||||||
|
$userPasswordResetCommand->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Dein Account wurde aktiviert.Du kannst dich nun mit deinem neuen Passwort einloggen.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Domains/UserManagement/Controllers/LogOutController.php
Normal file
21
app/Domains/UserManagement/Controllers/LogOutController.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class LogOutController {
|
||||||
|
public function __invoke(Request $request) {
|
||||||
|
Auth::logout();
|
||||||
|
|
||||||
|
// Session invalidieren
|
||||||
|
$request->session()->invalidate();
|
||||||
|
|
||||||
|
// CSRF-Token regenerieren (für Sicherheit)
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
// Redirect z.B. zur Login-Seite
|
||||||
|
return redirect()->intended('/')->with('status', 'Erfolgreich abgemeldet!');
|
||||||
|
}
|
||||||
|
}
|
||||||
58
app/Domains/UserManagement/Controllers/LoginController.php
Normal file
58
app/Domains/UserManagement/Controllers/LoginController.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class LoginController extends CommonController {
|
||||||
|
public function loginForm(Request $request) {
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
if ($request->session()->has('errors')) {
|
||||||
|
$errors = $request->session()->get('errors')->getBag('default')->getMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$inertiaProvider = new InertiaProvider('UserManagement/Login', ['errors' => $errors, 'appName' => app('tenant')->name]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doLogin(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
$credentials = $request->validate([
|
||||||
|
'username' => ['required', 'string'],
|
||||||
|
'password' => ['required'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'username.required' => 'Bitte gib deinen Anmeldenamen ein.',
|
||||||
|
'username.string' => 'Der Anmeldename muss eine E-Mail-Adresse sein.',
|
||||||
|
'password.required' => 'Bitte gib dein Passwort ein.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $this->users->findByUsername($request->get('username'));
|
||||||
|
if ($user !== null && $user->password === null) {
|
||||||
|
return redirect()->intended('/register/verifyEmail');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#$credentials = ['username' => 'development', 'password' => 'development'];
|
||||||
|
|
||||||
|
if (!Auth::attempt($credentials)) {
|
||||||
|
return back()->withErrors([
|
||||||
|
'username' => 'Diese Zugangsdaten sind ungültig.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->session()->regenerate();
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
|
||||||
|
# dd($user->firstname . ' ' . $user->lastname);
|
||||||
|
|
||||||
|
return redirect()->intended('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageCommand;
|
||||||
|
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageRequest;
|
||||||
|
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationRequest;
|
||||||
|
use App\Enumerations\MessageType;
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
use App\ValueObjects\MessageRecipient;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class RegistrationController extends CommonController {
|
||||||
|
public function loginForm(Request $request) {
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
if ($request->session()->has('errors')) {
|
||||||
|
$errors = $request->session()->get('errors')->getBag('default')->getMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$inertiaProvider = new InertiaProvider('UserManagement/Registration', ['errors' => $errors, 'appName' => app('tenant')->name]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doRegistration(Request $request) : JsonResponse {
|
||||||
|
|
||||||
|
$user = $this->users->findByUsername($request->get('email'));
|
||||||
|
if ($user !== null) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Dieser Account existiert bereits.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = EmailAddress::fromString($request->get('email'));
|
||||||
|
$userRoleMain = UserRole::USER_ROLE_USER;
|
||||||
|
$userRoleLocalGroup = UserRole::USER_ROLE_USER;
|
||||||
|
|
||||||
|
$localGroup = app('tenant')->slug === 'lv' ? $request->get('localGroup') : app('tenant')->slug;
|
||||||
|
|
||||||
|
|
||||||
|
$registrationRequest = new UserRegistrationRequest(
|
||||||
|
$request->get('firstname'),
|
||||||
|
$request->get('lastname'),
|
||||||
|
$request->get('nickname'),
|
||||||
|
$email,
|
||||||
|
$userRoleMain,
|
||||||
|
$userRoleLocalGroup,
|
||||||
|
$localGroup
|
||||||
|
);
|
||||||
|
|
||||||
|
$registrationCommand = new UserRegistrationCommand($registrationRequest);
|
||||||
|
$result = $registrationCommand->execute();
|
||||||
|
|
||||||
|
if (!$result->success) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Beim Erstellen des Accounts ist ein Fehler aufgetreten.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Registrierung erfolgreich! Bitte prüfe nun dein E-Mail-Postfach'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\UserManagement\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenRequest;
|
||||||
|
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenResponse;
|
||||||
|
use App\Domains\UserManagement\Actions\UserActivation\UserActivationCommand;
|
||||||
|
use App\Domains\UserManagement\Actions\UserActivation\UserActivationRequest;
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ResetPasswordController extends CommonController {
|
||||||
|
public function resetPasswordForm() {
|
||||||
|
$inertiaProvider = new InertiaProvider('UserManagement/ResetPassword', []);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doResetPassword(Request $request) : JsonResponse {
|
||||||
|
$user = $this->users->findByUsername($request->get('email'));
|
||||||
|
|
||||||
|
if (null !== $user) {
|
||||||
|
$expirationDate = new \DateTime()->modify('+15 Minutes');
|
||||||
|
|
||||||
|
$resetAccountRequest = new GenerateActivationTokenRequest($user, $expirationDate);
|
||||||
|
$resetAccountCommand = new GenerateActivationTokenCommand($resetAccountRequest);
|
||||||
|
$resetAccountCommand->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Falls deine E-Mail-Adresse gefunden wurde, erhältst du nun eine E-Mail mit weiteren Schritten zum Zurücksetzen deines Passwortes.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Domains/UserManagement/Routes/api.php
Normal file
17
app/Domains/UserManagement/Routes/api.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||||
|
use App\Domains\UserManagement\Controllers\RegistrationController;
|
||||||
|
use App\Domains\UserManagement\Controllers\ResetPasswordController;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
|
||||||
|
Route::prefix('v1')
|
||||||
|
->group(function () {
|
||||||
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
|
Route::post('/register', [RegistrationController::class, 'doRegistration']);
|
||||||
|
Route::post('/register/confirmEmail', [EmailVerificationController::class, 'doVerification']);
|
||||||
|
Route::post('/reset-password', [ResetPasswordController::class, 'doResetPassword']);
|
||||||
|
});
|
||||||
|
});
|
||||||
31
app/Domains/UserManagement/Routes/web.php
Normal file
31
app/Domains/UserManagement/Routes/web.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||||
|
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||||
|
use App\Domains\UserManagement\Controllers\LoginController;
|
||||||
|
use App\Domains\UserManagement\Controllers\LogOutController;
|
||||||
|
use App\Domains\UserManagement\Controllers\RegistrationController;
|
||||||
|
use App\Domains\UserManagement\Controllers\ResetPasswordController;
|
||||||
|
use App\Http\Controllers\TestRenderInertiaProvider;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
|
||||||
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
|
Route::get('/register', [RegistrationController::class, 'loginForm']);
|
||||||
|
Route::get('/register/verifyEmail', [EmailVerificationController::class, 'verifyEmailForm']);
|
||||||
|
|
||||||
|
Route::get('/reset-password', [ResetPasswordController::class, 'resetPasswordForm']);
|
||||||
|
|
||||||
|
route::get('/logout', LogOutController::class);
|
||||||
|
route::post('/login', [LoginController::class, 'doLogin']);
|
||||||
|
route::get('/login', [LoginController::class, 'loginForm']);
|
||||||
|
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::post('/logout', [LogoutController::class, 'logout']);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
69
app/Domains/UserManagement/Views/Login.vue
Normal file
69
app/Domains/UserManagement/Views/Login.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||||
|
import {onMounted, ref} from 'vue'
|
||||||
|
import { toast } from 'vue3-toastify'
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
navbar: Object,
|
||||||
|
tenant: String,
|
||||||
|
user: Object,
|
||||||
|
currentPath: String,
|
||||||
|
errors: Object,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||||
|
toast.error(props.errors.username[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
//console.log(props.errors.password[0])
|
||||||
|
const username = ref('')
|
||||||
|
const password = ref('')
|
||||||
|
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
|
||||||
|
function resetPassword() {
|
||||||
|
window.location.href = '/reset-password';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title='Anmelden' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||||
|
<form method="POST" action="/login">
|
||||||
|
<input type="hidden" name="_token" :value="csrfToken" />
|
||||||
|
<shadowed-box style="width: 50%; margin: 150px auto; padding: 20px;">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Anmeldename</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="username" id="username"></input>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Passwort</th>
|
||||||
|
<td><input type="password" name="password" id="password"></input></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" value="Anmelden" style="margin-top: 20px;" />
|
||||||
|
<input type="button" @click="resetPassword" style="margin-top: 20px; margin-left: 20px;" value="Passwort vergessen" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</shadowed-box>
|
||||||
|
</form>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
th {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
166
app/Domains/UserManagement/Views/Registration.vue
Normal file
166
app/Domains/UserManagement/Views/Registration.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||||
|
import {computed, onMounted, reactive, ref} from 'vue'
|
||||||
|
import { toast } from 'vue3-toastify'
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
navbar: Object,
|
||||||
|
tenant: String,
|
||||||
|
user: Object,
|
||||||
|
currentPath: String,
|
||||||
|
errors: Object,
|
||||||
|
availableLocalGroups: Array,
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||||
|
toast.error(props.errors.username[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
//console.log(props.errors.password[0])
|
||||||
|
const username = ref('')
|
||||||
|
const password = ref('')
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
firstname: '',
|
||||||
|
lastname: '',
|
||||||
|
nickname: '',
|
||||||
|
email: '',
|
||||||
|
localGroup: '',
|
||||||
|
password: '',
|
||||||
|
password_confirmation: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const errors = reactive({})
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const isValid = computed(() => {
|
||||||
|
errors.firstname = ''
|
||||||
|
errors.lastname = ''
|
||||||
|
errors.localGroup = ''
|
||||||
|
errors.email = ''
|
||||||
|
|
||||||
|
|
||||||
|
if (!form.firstname) {
|
||||||
|
errors.firstname = 'Bitte gib deinen Vornamen ein'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.lastname) {
|
||||||
|
errors.lastname = 'Bitte gib deinen Nachnamen ein'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.localGroup) {
|
||||||
|
errors.localGroup = 'Bitte gib deinen Stamm an'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!form.email) {
|
||||||
|
errors.email = 'Bitte gib deine E-Mail-Adresse ein'
|
||||||
|
} else if (!/^\S+@\S+\.\S+$/.test(form.email)) {
|
||||||
|
errors.email = 'Ungültige E-Mail'
|
||||||
|
}
|
||||||
|
|
||||||
|
return !errors.password && !errors.firstname && !errors.lastname && !errors.localGroup
|
||||||
|
})
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
|
||||||
|
if (!isValid.value) return false
|
||||||
|
|
||||||
|
const data = await request("/api/v1/register", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
"firstname": form.firstname,
|
||||||
|
"lastname": form.lastname,
|
||||||
|
'nickname': form.nickname,
|
||||||
|
"email": form.email,
|
||||||
|
"localGroup": form.localGroup,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status === 'error') {
|
||||||
|
toast.error(data.message);
|
||||||
|
} else {
|
||||||
|
toast.success(data.message)
|
||||||
|
window.location.href = '/register/verifyEmail';
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title='Registrieren' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||||
|
<form method="POST" action="/register" @submit.prevent="submit">
|
||||||
|
<input type="hidden" name="_token" :value="csrfToken" />
|
||||||
|
<shadowed-box style="width: 75%; margin: 150px auto; padding: 20px;">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Vorname</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="firstname" id="firstname" v-model="form.firstname" />
|
||||||
|
<ErrorText :message="errors.firstname" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Nachname</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="lastname" id="lastname" v-model="form.lastname" />
|
||||||
|
<ErrorText :message="errors.lastname" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Pfadi-Name*<br />
|
||||||
|
<small>*Falls vorhanden</small>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="nickname" id="nickname" v-model="form.nickname" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>E-Mail-Adresse</th>
|
||||||
|
<td>
|
||||||
|
<input type="email" name="email" id="email" v-model="form.email" />
|
||||||
|
<ErrorText :message="errors.email" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-if="props.tenant.slug === 'lv'">
|
||||||
|
<th>Stamm</th>
|
||||||
|
<td>
|
||||||
|
<select name="localgroup" v-model="form.localGroup">
|
||||||
|
<option v-for="localGroup in props.availableLocalGroups" :value="localGroup.slug">{{localGroup.name}}</option>
|
||||||
|
</select>
|
||||||
|
<ErrorText :message="errors.localGroup" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" value="Registrieren" style="margin-top: 20px;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</shadowed-box>
|
||||||
|
</form>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
th {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
102
app/Domains/UserManagement/Views/ResetPassword.vue
Normal file
102
app/Domains/UserManagement/Views/ResetPassword.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||||
|
import {computed, onMounted, reactive, ref} from 'vue'
|
||||||
|
import { toast } from 'vue3-toastify'
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
navbar: Object,
|
||||||
|
tenant: String,
|
||||||
|
user: Object,
|
||||||
|
currentPath: String,
|
||||||
|
errors: Object,
|
||||||
|
availableLocalGroups: Array,
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||||
|
toast.error(props.errors.username[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const form = reactive({
|
||||||
|
email: '',
|
||||||
|
verificationToken: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const errors = reactive({})
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const isValid = computed(() => {
|
||||||
|
errors.email = ''
|
||||||
|
|
||||||
|
if (!form.email) {
|
||||||
|
errors.email = 'Bitte gib deine E-Mail-Adresse ein'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return !errors.email
|
||||||
|
})
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
|
||||||
|
if (!isValid.value) return false
|
||||||
|
|
||||||
|
const data = await request("/api/v1/reset-password", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
"email": form.email,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.error_types) {
|
||||||
|
Object.keys(data.error_types).forEach((key) => {
|
||||||
|
if (key in errors) {
|
||||||
|
errors[key] = data.error_types[key]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
window.location.href = '/register/verifyEmail';
|
||||||
|
toast.success(data.message)
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title='Passwort zurücksetzen' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||||
|
<form method="POST" action="/reset-password" @submit.prevent="submit">
|
||||||
|
<input type="hidden" name="_token" :value="csrfToken" />
|
||||||
|
<shadowed-box style="width: 75%; margin: 150px auto; padding: 20px;">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>E-Mail-Adresse</th>
|
||||||
|
<td>
|
||||||
|
<input type="email" name="email" id="email" v-model="form.email" />
|
||||||
|
<ErrorText :message="errors.email" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" value="Verifizierung starten" style="margin-top: 20px;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</shadowed-box>
|
||||||
|
</form>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
th {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
152
app/Domains/UserManagement/Views/VerifyEmail.vue
Normal file
152
app/Domains/UserManagement/Views/VerifyEmail.vue
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||||
|
import {computed, onMounted, reactive, ref} from 'vue'
|
||||||
|
import { toast } from 'vue3-toastify'
|
||||||
|
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||||
|
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||||
|
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
navbar: Object,
|
||||||
|
tenant: String,
|
||||||
|
user: Object,
|
||||||
|
currentPath: String,
|
||||||
|
errors: Object,
|
||||||
|
availableLocalGroups: Array,
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||||
|
toast.error(props.errors.username[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const form = reactive({
|
||||||
|
email: '',
|
||||||
|
verificationToken: '',
|
||||||
|
password: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const errors = reactive({})
|
||||||
|
const { request } = useAjax()
|
||||||
|
|
||||||
|
const isValid = computed(() => {
|
||||||
|
errors.email = '';
|
||||||
|
errors.verificationToken = '';
|
||||||
|
errors.password = '';
|
||||||
|
|
||||||
|
if (!form.email) {
|
||||||
|
errors.email = 'Bitte gib deine E-Mail-Adresse ein'
|
||||||
|
} else if (!/^\S+@\S+\.\S+$/.test(form.email)) {
|
||||||
|
errors.email = 'Ungültige E-Mail'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.verificationToken) {
|
||||||
|
errors.verificationToken = 'Bitte gib den Sicherheitsschlüssel, den du per E-Mail erhalten hast, ein'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.password) {
|
||||||
|
errors.password = 'Bitte gib ein Passwort ein'
|
||||||
|
} else if (form.password.length < 2) {
|
||||||
|
errors.password = 'Das Passwort muss mindestens 12 Zeichen lang sein'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.password !== form.password_confirmation) {
|
||||||
|
errors.password = 'Das Passwort und die Wiederholung stimmen nicht überein'
|
||||||
|
}
|
||||||
|
|
||||||
|
return !errors.password && !errors.email && !errors.verificationToken
|
||||||
|
})
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
|
||||||
|
if (!isValid.value) return false
|
||||||
|
|
||||||
|
const data = await request("/api/v1/register/confirmEmail", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
"email": form.email,
|
||||||
|
"verificationToken": form.verificationToken,
|
||||||
|
"password": form.password,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.error_types) {
|
||||||
|
Object.keys(data.error_types).forEach((key) => {
|
||||||
|
if (key in errors) {
|
||||||
|
errors[key] = data.error_types[key]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toast.success(data.message)
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPassword() {
|
||||||
|
window.location.href = '/reset-password';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AppLayout title='E-Mail bestätigen' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||||
|
<form method="POST" action="/register" @submit.prevent="submit">
|
||||||
|
<input type="hidden" name="_token" :value="csrfToken" />
|
||||||
|
<shadowed-box style="width: 75%; margin: 150px auto; padding: 20px;">
|
||||||
|
<p>
|
||||||
|
Bitte prüfe dein E-Mail-Postfach.<br />
|
||||||
|
Solltest du keinen Aktivierungsschlüssel erhalten haben, kontaktiere bitte einen Administrator.
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>E-Mail-Adresse</th>
|
||||||
|
<td>
|
||||||
|
<input type="email" name="email" id="email" v-model="form.email" />
|
||||||
|
<ErrorText :message="errors.email" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Sicherheitsschlüssel</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="verificationToken" id="verificationToken" v-model="form.verificationToken" />
|
||||||
|
<ErrorText :message="errors.verificationToken" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Dein neues Passwort</th>
|
||||||
|
<td><input type="password" name="password" id="password" v-model="form.password" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Passwort (wiederholen)</th>
|
||||||
|
<td>
|
||||||
|
<input type="password" name="password_retype" id="password_retype" v-model="form.password_confirmation" />
|
||||||
|
<ErrorText :message="errors.password" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" value="E-Mail validieren" style="margin-top: 20px;" />
|
||||||
|
<input type="button" @click="resetPassword" style="margin-top: 20px; margin-left: 20px;" value="Validierungscode neu zusenden" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</shadowed-box>
|
||||||
|
</form>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
th {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
21
app/Enumerations/CostUnitType.php
Normal file
21
app/Enumerations/CostUnitType.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $name
|
||||||
|
*/
|
||||||
|
class CostUnitType extends CommonModel
|
||||||
|
{
|
||||||
|
public const COST_UNIT_TYPE_EVENT = 'event';
|
||||||
|
public const COST_UNIT_TYPE_RUNNING_JOB = 'running_job';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
}
|
||||||
22
app/Enumerations/EatingHabit.php
Normal file
22
app/Enumerations/EatingHabit.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $name
|
||||||
|
*/
|
||||||
|
class EatingHabit extends CommonModel
|
||||||
|
{
|
||||||
|
public const string EATING_HABIT_OMNIVOR = 'EATING_HABIT_OMNIVOR';
|
||||||
|
public const string EATING_HABIT_VEGETARIAN = 'EATING_HABIT_VEGETARIAN';
|
||||||
|
public const string EATING_HABIT_VEGAN = 'EATING_HABIT_VEGAN';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
}
|
||||||
22
app/Enumerations/FirstAidPermission.php
Normal file
22
app/Enumerations/FirstAidPermission.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $name
|
||||||
|
*/
|
||||||
|
class FirstAidPermission extends CommonModel
|
||||||
|
{
|
||||||
|
public const string FIRST_AID_PERMISSION_ALLOWED = 'FIRST_AID_PERMISSION_ALLOWED';
|
||||||
|
public const string FIRST_AID_PERMISSION_DENIED = 'FIRST_AID_PERMISSION_DENIED';
|
||||||
|
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
'description'
|
||||||
|
];
|
||||||
|
}
|
||||||
8
app/Enumerations/MessageType.php
Normal file
8
app/Enumerations/MessageType.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
enum MessageType : string {
|
||||||
|
case INTERNAL = 'INTERNAL';
|
||||||
|
case EMAIL = 'EMAIL';
|
||||||
|
}
|
||||||
26
app/Enumerations/SwimmingPermission.php
Normal file
26
app/Enumerations/SwimmingPermission.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $name
|
||||||
|
*/
|
||||||
|
class SwimmingPermission extends CommonModel
|
||||||
|
{
|
||||||
|
public const string SWIMMING_PERMISSION_ALLOWED = 'SWIMMING_PERMISSION_ALLOWED';
|
||||||
|
public const string SWIMMING_PERMISSION_LIMITED = 'SWIMMING_PERMISSION_LIMITED';
|
||||||
|
public const string SWIMMING_PERMISSION_DENIED = 'SWIMMING_PERMISSION_DENIED';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
}
|
||||||
17
app/Enumerations/UserRole.php
Normal file
17
app/Enumerations/UserRole.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class UserRole extends CommonModel {
|
||||||
|
public const USER_ROLE_ADMIN = 'ROLE_ADMINISTRATOR';
|
||||||
|
public const USER_ROLE_GROUP_LEADER = 'ROLE_GROUP_LEADER';
|
||||||
|
public const USER_ROLE_USER = 'ROLE_USER';
|
||||||
|
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'slug'
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
abstract class Controller
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
13
app/Http/Controllers/TestRenderInertiaProvider.php
Normal file
13
app/Http/Controllers/TestRenderInertiaProvider.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Providers\InertiaProvider;
|
||||||
|
|
||||||
|
class TestRenderInertiaProvider
|
||||||
|
{
|
||||||
|
public function index() {
|
||||||
|
$inertiaProvider = new InertiaProvider('Invoice/CreateInvoice', ['appName' => app('tenant')->name]);
|
||||||
|
return $inertiaProvider->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
app/Installer/DevelopmentDataSeeder.php
Normal file
43
app/Installer/DevelopmentDataSeeder.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Installer;
|
||||||
|
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class DevelopmentDataSeeder {
|
||||||
|
public function execute() {
|
||||||
|
$this->installTenants();
|
||||||
|
$this->installUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private function installTenants() {
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'wilde-moehre',
|
||||||
|
'name' => 'Stamm Wilde Möhre',
|
||||||
|
'url' => 'wilde-moehre.mareike.local',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'email' => 'test@example1.com',
|
||||||
|
'city' => 'Halle (Saale)',
|
||||||
|
'postcode' => '06120',
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installUsers() {
|
||||||
|
User::create([
|
||||||
|
'firstname' => 'Development',
|
||||||
|
'lastname' => 'User',
|
||||||
|
'user_role_main' => UserRole::USER_ROLE_ADMIN,
|
||||||
|
'user_role_local_group' => UserRole::USER_ROLE_GROUP_LEADER,
|
||||||
|
'local_group' => 'wilde-moehre',
|
||||||
|
'email' => 'th.guenther@saale-mail.de',
|
||||||
|
'password' => bcrypt('development'),
|
||||||
|
'username' => 'development',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/Installer/ProductionDataSeeder.php
Normal file
71
app/Installer/ProductionDataSeeder.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Installer;
|
||||||
|
|
||||||
|
use App\Enumerations\CostUnitType;
|
||||||
|
use App\Enumerations\EatingHabit;
|
||||||
|
use App\Enumerations\FirstAidPermission;
|
||||||
|
use App\Enumerations\SwimmingPermission;
|
||||||
|
use App\Enumerations\UserRole;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
|
||||||
|
class ProductionDataSeeder {
|
||||||
|
public function execute() {
|
||||||
|
$this->installUserRoles();
|
||||||
|
$this->installCostUnitTypes();
|
||||||
|
$this->installSwimmingPermissions();
|
||||||
|
$this->installEatingHabits();
|
||||||
|
$this->installFirstAidPermissions();
|
||||||
|
$this->installTenants();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installUserRoles() {
|
||||||
|
UserRole::create(['name' => 'Administrator*in', 'slug' => UserRole::USER_ROLE_ADMIN]);
|
||||||
|
UserRole::create(['name' => 'Vorstandsmitglied', 'slug' => UserRole::USER_ROLE_GROUP_LEADER]);
|
||||||
|
UserRole::create(['name' => 'Benutzer*in', 'slug' => UserRole::USER_ROLE_USER]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installSwimmingPermissions() {
|
||||||
|
SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED]);
|
||||||
|
SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann NICHT schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_LIMITED]);
|
||||||
|
SwimmingPermission::create(['name' => 'Mein Kind darf nicht baden', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_DENIED]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installEatingHabits() {
|
||||||
|
EatingHabit::create(['name' => 'Vegan', 'slug' => EatingHabit::EATING_HABIT_VEGAN]);
|
||||||
|
EatingHabit::create(['name' => 'Vegetarisch', 'slug' => EatingHabit::EATING_HABIT_VEGETARIAN]);
|
||||||
|
EatingHabit::create(['name' => 'Omnivor', 'slug' => EatingHabit::EATING_HABIT_OMNIVOR]);
|
||||||
|
|
||||||
|
}
|
||||||
|
private function installFirstAidPermissions() {
|
||||||
|
FirstAidPermission::create([
|
||||||
|
'name' => 'Zugestimmt',
|
||||||
|
'description' => 'Ich STIMME der Anwendung von erweiteren Erste-Hilfe-Maßnahmen an meinem Kind explizit ZU.',
|
||||||
|
'slug' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED]);
|
||||||
|
|
||||||
|
FirstAidPermission::create([
|
||||||
|
'name' => 'Verweigert',
|
||||||
|
'description' => 'Ich LEHNE die Anwendung von erweiteren Erste-Hilfe-Maßnahmen an meinem Kind explizit AB.',
|
||||||
|
'slug' => FirstAidPermission::FIRST_AID_PERMISSION_DENIED]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installCostUnitTypes() {
|
||||||
|
CostUnitType::create(['slug' => CostUnitType::COST_UNIT_TYPE_EVENT, 'name' => 'Veranstaltung']);
|
||||||
|
CostUnitType::create(['slug' => CostUnitType::COST_UNIT_TYPE_RUNNING_JOB, 'name' => 'Laufende Tätigkeit']);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installTenants() {
|
||||||
|
Tenant::create([
|
||||||
|
'slug' => 'lv',
|
||||||
|
'name' => 'Landesunmittelbare Mitglieder',
|
||||||
|
'url' => 'mareike.local',
|
||||||
|
'account_iban' => 'DE12345678901234567890',
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
'city' => 'Lommatzsch',
|
||||||
|
'postcode' => '01623',
|
||||||
|
'is_active_local_group' => true,
|
||||||
|
'has_active_instance' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/MessageTemplates/MessageTemplate.php
Normal file
37
app/MessageTemplates/MessageTemplate.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\MessageTemplates;
|
||||||
|
|
||||||
|
use App\Enumerations\MessageType;
|
||||||
|
|
||||||
|
abstract class MessageTemplate {
|
||||||
|
protected string $subject;
|
||||||
|
protected string $message;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubject(): string
|
||||||
|
{
|
||||||
|
return $this->subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubject(string $subject): void
|
||||||
|
{
|
||||||
|
$this->subject = $subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage(): string
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMessage(string $message): void
|
||||||
|
{
|
||||||
|
$this->message = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\MessageTemplates\Registration;
|
||||||
|
|
||||||
|
use App\MessageTemplates\activationCodeTemplate;
|
||||||
|
use App\MessageTemplates\MessageTemplate;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
|
||||||
|
class InformAdminAboutNewUserTemplate extends MessageTemplate
|
||||||
|
{
|
||||||
|
public static function createNew(User $user) : InformAdminAboutNewUserTemplate {
|
||||||
|
$template = new InformAdminAboutNewUserTemplate();
|
||||||
|
$template->composeMessage($user);
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->subject = "Eine Person hat sich auf mareike registriert";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function composeMessage(User $user): void {
|
||||||
|
$this->message =
|
||||||
|
<<<HTML
|
||||||
|
|
||||||
|
<h1>Eine Person hat sich auf mareike angemeldet</h1>
|
||||||
|
<p>Soeben hat sich eine neue Person auf mareike angemeldet:</p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name:</th>
|
||||||
|
<td>%1\$s</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Email:</th>
|
||||||
|
<td>%2\$s</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Stamm:</th>
|
||||||
|
<td>%3\$s</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Freischaltcode:</th>
|
||||||
|
<td>%4\$s</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Sollte die Person unberechtigt angemeldet sein, lösche den Account.
|
||||||
|
</p>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
$this->message = sprintf($this->message,
|
||||||
|
$user->getFullname(),
|
||||||
|
$user->email,
|
||||||
|
$user->local_group,
|
||||||
|
$user->activation_token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/MessageTemplates/activationCodeTemplate.php
Normal file
23
app/MessageTemplates/activationCodeTemplate.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\MessageTemplates;
|
||||||
|
|
||||||
|
use App\ValueObjects\EmailAddress;
|
||||||
|
|
||||||
|
class activationCodeTemplate extends MessageTemplate {
|
||||||
|
|
||||||
|
public static function createForUser(EmailAddress $emailAddress, string $activationCode) : activationCodeTemplate {
|
||||||
|
$template = new activationCodeTemplate();
|
||||||
|
$template->composeMessage($emailAddress, $activationCode);
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->subject = "Dein Aktivierungscode";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function composeMessage(EmailAddress $emailAddress, string $activationCode): void {
|
||||||
|
$this->message = "Dein Aktivierungscode lautet: {$activationCode}" . PHP_EOL .
|
||||||
|
"Gib diesen zusammen mit der Mailadresse {$emailAddress->getValue()} ein.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Middleware;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Middleware;
|
use Inertia\Middleware;
|
||||||
24
app/Middleware/IdentifyTenant.php
Normal file
24
app/Middleware/IdentifyTenant.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Middleware;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
|
class IdentifyTenant
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
$host = $request->getHost();
|
||||||
|
$tenant = Tenant::where(['url' => $host, 'has_active_instance' => true])->first();
|
||||||
|
|
||||||
|
if (! $tenant) {
|
||||||
|
throw new NotFoundHttpException('Tenant not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
app()->instance('tenant', $tenant);
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Models/Tenant.php
Normal file
39
app/Models/Tenant.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $local_group
|
||||||
|
* @property string $email
|
||||||
|
* @property string $url
|
||||||
|
* @property string $account_iban
|
||||||
|
* @property string $city
|
||||||
|
* @property string $postcode
|
||||||
|
* @property string $gdpr_text
|
||||||
|
* @property string $impress_text
|
||||||
|
* @property string $url_participation_rules
|
||||||
|
* @property boolean $events_allowed
|
||||||
|
* @property boolean $has_active_instance
|
||||||
|
*/
|
||||||
|
class Tenant extends CommonModel
|
||||||
|
{
|
||||||
|
public const PRIMARY_TENANT_NAME = 'LV';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'url',
|
||||||
|
'account_iban',
|
||||||
|
'city',
|
||||||
|
'postcode',
|
||||||
|
'gdpr_text',
|
||||||
|
'impress_text',
|
||||||
|
'url_participation_rules',
|
||||||
|
'is_active_local_group',
|
||||||
|
'has_active_instance'
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -2,15 +2,40 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $username
|
||||||
|
* @property string $local_group
|
||||||
|
* @property string $firstname
|
||||||
|
* @property string $nickname
|
||||||
|
* @property string $lastname
|
||||||
|
* @property string $membership_id
|
||||||
|
* @property string $address_1
|
||||||
|
* @property string $address_2
|
||||||
|
* @property string $postcode
|
||||||
|
* @property string $city
|
||||||
|
* @property string $email
|
||||||
|
* @property string $phone
|
||||||
|
* @property string $birthday
|
||||||
|
* @property string $medications
|
||||||
|
* @property string $allergies
|
||||||
|
* @property string $intolerances
|
||||||
|
* @property string $eating_habits
|
||||||
|
* @property string $swimming_permission
|
||||||
|
* @property string $first_aid_permission
|
||||||
|
* @property string $bank_account_iban
|
||||||
|
* @property string $password
|
||||||
|
* @property boolean $active
|
||||||
|
* @property string $user_role_main
|
||||||
|
* @property string $user_role_local_group
|
||||||
|
* @property string $activation_token
|
||||||
|
* @property string $activation_token_expires_at
|
||||||
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
use Notifiable;
|
||||||
use HasFactory, Notifiable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@@ -18,9 +43,32 @@ class User extends Authenticatable
|
|||||||
* @var list<string>
|
* @var list<string>
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'user_role_main',
|
||||||
|
'user_role_local_group',
|
||||||
|
'activation_token',
|
||||||
|
'activation_token_expires_at',
|
||||||
|
'username',
|
||||||
|
'local_group',
|
||||||
|
'firstname',
|
||||||
|
'nickname',
|
||||||
|
'lastname',
|
||||||
|
'membership_id',
|
||||||
|
'address_1',
|
||||||
|
'address_2',
|
||||||
|
'postcode',
|
||||||
|
'city',
|
||||||
'email',
|
'email',
|
||||||
|
'phone',
|
||||||
|
'birthday',
|
||||||
|
'medications',
|
||||||
|
'allergies',
|
||||||
|
'intolerances',
|
||||||
|
'eating_habits',
|
||||||
|
'swimming_permission',
|
||||||
|
'first_aid_permission',
|
||||||
|
'bank_account_iban',
|
||||||
'password',
|
'password',
|
||||||
|
'active',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,7 +77,6 @@ class User extends Authenticatable
|
|||||||
* @var list<string>
|
* @var list<string>
|
||||||
*/
|
*/
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password',
|
|
||||||
'remember_token',
|
'remember_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -41,8 +88,19 @@ class User extends Authenticatable
|
|||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFullname() : string {
|
||||||
|
return sprintf('%1$1s %2$s %3$s',
|
||||||
|
$this->firstname,
|
||||||
|
$this->nickname !== '' ? '(' . $this->nickname . ')' : '',
|
||||||
|
$this->lastname
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNicename() : string {
|
||||||
|
return $this->nickname !== '' ? $this->nickname : $this->firstname;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@@ -19,6 +20,11 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
Auth::provider('tenant-users', function ($app, array $config) {
|
||||||
|
return new TenantUserProvider(
|
||||||
|
$app['hash'],
|
||||||
|
$config['model']
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
app/Providers/AuthCheckProvider.php
Normal file
32
app/Providers/AuthCheckProvider.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
class AuthCheckProvider {
|
||||||
|
public function checkLoggedIn() : bool {
|
||||||
|
if (!auth()->check()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = auth()->user();
|
||||||
|
$tenant = app('tenant');
|
||||||
|
if ($tenant->slug === 'lv') {
|
||||||
|
return $user->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $user->active && $tenant->slug === $user->tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserRole() : ?string {
|
||||||
|
if (!$this->checkLoggedIn()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app('tenant')->slug === 'lv') {
|
||||||
|
return auth()->user()->user_role_main;
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth()->user()->user_role_local_group;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
app/Providers/InertiaProvider.php
Normal file
55
app/Providers/InertiaProvider.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Models\User;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
final class InertiaProvider
|
||||||
|
{
|
||||||
|
private string $vueFile;
|
||||||
|
private array $props;
|
||||||
|
|
||||||
|
private ?User $user;
|
||||||
|
|
||||||
|
public function __construct(string $vueFile, array $props) {
|
||||||
|
$this->user = auth()->user();
|
||||||
|
$this->vueFile = $vueFile;
|
||||||
|
$this->props = $props;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() : Response {
|
||||||
|
$this->props['navbar'] = $this->generateNavbar();
|
||||||
|
$this->props['tenant'] = app('tenant');
|
||||||
|
$this->props['user'] = $this->user;
|
||||||
|
$this->props['currentPath'] = request()->path();
|
||||||
|
$this->props['availableLocalGroups'] = Tenant::where(['is_active_local_group' => true])->get();
|
||||||
|
|
||||||
|
return Inertia::render(
|
||||||
|
str_replace('/', '/Views/', $this->vueFile),
|
||||||
|
$this->props
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateNavbar() : array {
|
||||||
|
$navigation = [
|
||||||
|
'personal' => [],
|
||||||
|
'common' => [],
|
||||||
|
'costunits' => [],
|
||||||
|
'events' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
$navigation['personal'][] = ['url' => '/', 'display' => 'Home'];
|
||||||
|
if (null !== $this->user) {
|
||||||
|
$navigation['personal'][] = ['url' => '/personal-data', 'display' => 'Meine Daten'];
|
||||||
|
$navigation['personal'][] = ['url' => '/messages', 'display' => 'Meine Nachrichten'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$navigation['common'][] = ['url' => '/capture-invoice', 'display' => 'Neue Abrechnung'];
|
||||||
|
$navigation['common'][] = ['url' => '/available-events', 'display' => 'Verfügbare Veranstaltungen'];
|
||||||
|
|
||||||
|
return $navigation;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Providers/TenantUserProvider.php
Normal file
33
app/Providers/TenantUserProvider.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Auth\EloquentUserProvider;
|
||||||
|
|
||||||
|
class TenantUserProvider extends EloquentUserProvider
|
||||||
|
{
|
||||||
|
public function retrieveByCredentials(array $credentials)
|
||||||
|
{
|
||||||
|
$credentials['active'] = true;
|
||||||
|
|
||||||
|
$query = $this->createModel()->newQuery();
|
||||||
|
|
||||||
|
foreach ($credentials as $key => $value) {
|
||||||
|
if (! str_contains($key, 'password')) {
|
||||||
|
$query->where($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app('tenant')->slug === 'lv') {
|
||||||
|
return $query->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->where([
|
||||||
|
'local_group' => app('tenant')->slug,
|
||||||
|
'active' => true
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $query->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/Repositories/UserRepository.php
Normal file
20
app/Repositories/UserRepository.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class UserRepository {
|
||||||
|
public function findByUsername(string $username) : ?User {
|
||||||
|
return User::where(['username' => $username])->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkVerificationToken(User $user, string $token) : bool {
|
||||||
|
if (new DateTime() > DateTime::createFromFormat('Y-m-d H:i:s', $user->activation_token_expires_at)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $token === $user->activation_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Scopes/CommonController.php
Normal file
19
app/Scopes/CommonController.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Scopes;
|
||||||
|
|
||||||
|
use App\Providers\AuthCheckProvider;
|
||||||
|
use App\Repositories\UserRepository;
|
||||||
|
|
||||||
|
abstract class CommonController {
|
||||||
|
protected UserRepository $users;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->users = new UserRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkAuth() {
|
||||||
|
$authCheckProvider = new AuthCheckProvider;
|
||||||
|
return $authCheckProvider->checkLoggedIn();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/Scopes/CommonModel.php
Normal file
10
app/Scopes/CommonModel.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Scopes;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
abstract class CommonModel extends Model
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
13
app/Scopes/InstancedModel.php
Normal file
13
app/Scopes/InstancedModel.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Scopes;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
abstract class InstancedModel extends Model
|
||||||
|
{
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::addGlobalScope(new SiteScope());
|
||||||
|
}
|
||||||
|
}
|
||||||
14
app/Scopes/SiteScope.php
Normal file
14
app/Scopes/SiteScope.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Scopes;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Scope;
|
||||||
|
|
||||||
|
class SiteScope implements Scope
|
||||||
|
{
|
||||||
|
public function apply(Builder $builder, Model $model): void
|
||||||
|
{
|
||||||
|
$builder->where($model->getTable() . '.tenant', app('tenant')->slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/ValueObjects/EmailAddress.php
Normal file
26
app/ValueObjects/EmailAddress.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\ValueObjects;
|
||||||
|
|
||||||
|
class EmailAddress {
|
||||||
|
private string $value;
|
||||||
|
public static function fromString(string $value) : EmailAddress {
|
||||||
|
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
throw new \InvalidArgumentException("Invalid email address: $value");
|
||||||
|
}
|
||||||
|
|
||||||
|
$emailAddress = new EmailAddress();
|
||||||
|
$emailAddress->setValue($value);
|
||||||
|
return $emailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setValue(string $value): void
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/ValueObjects/MessageRecipient.php
Normal file
50
app/ValueObjects/MessageRecipient.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\ValueObjects;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class MessageRecipient {
|
||||||
|
private ?User $user;
|
||||||
|
/** @var EmailAddress[] */
|
||||||
|
private array $emailAddresses;
|
||||||
|
private string $name;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->user = null;
|
||||||
|
$this->emailAddresses = [];
|
||||||
|
$this->name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(?User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmailAddresses(): array
|
||||||
|
{
|
||||||
|
return $this->emailAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEmailAddress(EmailAddress $emailAddresses): void
|
||||||
|
{
|
||||||
|
$this->emailAddresses[] = $emailAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): void
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
10
app/Views/Components/ErrorText.vue
Normal file
10
app/Views/Components/ErrorText.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
message: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<small class="error_text" v-if="props.message">{{ props.message }}</small>
|
||||||
|
</template>
|
||||||
|
|
||||||
17
app/Views/Components/Icon.vue
Normal file
17
app/Views/Components/Icon.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script setup>
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import * as SolidIcons from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (SolidIcons[`fa${props.name.charAt(0).toUpperCase()}${props.name.slice(1)}`]) {
|
||||||
|
library.add(SolidIcons[`fa${props.name.charAt(0).toUpperCase()}${props.name.slice(1)}`])
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<font-awesome-icon :icon="name" />
|
||||||
|
</template>
|
||||||
23
app/Views/Components/ShadowedBox.vue
Normal file
23
app/Views/Components/ShadowedBox.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
style: { type: String},
|
||||||
|
class: { type: String},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(props.style)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="shadowed-box" :style=props.style :class=props.class>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.shadowed-box {
|
||||||
|
box-shadow: 2px 2px 5px #c0c0c0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
41
app/Views/Partials/GlobalWidgets/GlobalWidgets.vue
Normal file
41
app/Views/Partials/GlobalWidgets/GlobalWidgets.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import ShadowedBox from "../../Components/ShadowedBox.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<diV class="widget-container">
|
||||||
|
<shadowed-box class="widget-box">
|
||||||
|
Widget 1
|
||||||
|
</shadowed-box>
|
||||||
|
|
||||||
|
<shadowed-box class="widget-box">
|
||||||
|
Widget 2
|
||||||
|
</shadowed-box>
|
||||||
|
<shadowed-box class="widget-box">
|
||||||
|
Widget 3
|
||||||
|
</shadowed-box>
|
||||||
|
<shadowed-box class="widget-box">
|
||||||
|
Widget 4
|
||||||
|
</shadowed-box>
|
||||||
|
</diV>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.widget-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-box {
|
||||||
|
flex-grow: 1; display: inline-block;
|
||||||
|
width: calc(25% - 40px);
|
||||||
|
height: 150px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
@@ -8,10 +9,11 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
->withRouting(
|
->withRouting(
|
||||||
web: __DIR__.'/../routes/web.php',
|
web: __DIR__.'/../routes/web.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
api: __DIR__.'/../routes/api.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
//
|
$middleware->append(IdentifyTenant::class);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"keywords": ["laravel", "framework"],
|
"keywords": ["laravel", "framework"],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.5",
|
||||||
"inertiajs/inertia-laravel": "^2.0",
|
"inertiajs/inertia-laravel": "^2.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1"
|
"laravel/tinker": "^2.10.1"
|
||||||
|
|||||||
20
composer.lock
generated
20
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "0e42fe1a7066e7a110e956ae26703d94",
|
"content-hash": "587caff9de06de75c1e22cceac366334",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -2194,16 +2194,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nesbot/carbon",
|
"name": "nesbot/carbon",
|
||||||
"version": "3.11.0",
|
"version": "3.11.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/CarbonPHP/carbon.git",
|
"url": "https://github.com/CarbonPHP/carbon.git",
|
||||||
"reference": "bdb375400dcd162624531666db4799b36b64e4a1"
|
"reference": "f438fcc98f92babee98381d399c65336f3a3827f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1",
|
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f",
|
||||||
"reference": "bdb375400dcd162624531666db4799b36b64e4a1",
|
"reference": "f438fcc98f92babee98381d399c65336f3a3827f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2227,7 +2227,7 @@
|
|||||||
"phpstan/extension-installer": "^1.4.3",
|
"phpstan/extension-installer": "^1.4.3",
|
||||||
"phpstan/phpstan": "^2.1.22",
|
"phpstan/phpstan": "^2.1.22",
|
||||||
"phpunit/phpunit": "^10.5.53",
|
"phpunit/phpunit": "^10.5.53",
|
||||||
"squizlabs/php_codesniffer": "^3.13.4"
|
"squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0"
|
||||||
},
|
},
|
||||||
"bin": [
|
"bin": [
|
||||||
"bin/carbon"
|
"bin/carbon"
|
||||||
@@ -2270,14 +2270,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "An API extension for DateTime that supports 281 different languages.",
|
"description": "An API extension for DateTime that supports 281 different languages.",
|
||||||
"homepage": "https://carbon.nesbot.com",
|
"homepage": "https://carbonphp.github.io/carbon/",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"date",
|
"date",
|
||||||
"datetime",
|
"datetime",
|
||||||
"time"
|
"time"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"docs": "https://carbon.nesbot.com/docs",
|
"docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html",
|
||||||
"issues": "https://github.com/CarbonPHP/carbon/issues",
|
"issues": "https://github.com/CarbonPHP/carbon/issues",
|
||||||
"source": "https://github.com/CarbonPHP/carbon"
|
"source": "https://github.com/CarbonPHP/carbon"
|
||||||
},
|
},
|
||||||
@@ -2295,7 +2295,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-12-02T21:04:28+00:00"
|
"time": "2026-01-29T09:26:29+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nette/schema",
|
"name": "nette/schema",
|
||||||
@@ -8436,7 +8436,7 @@
|
|||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "^8.2"
|
"php": "^8.5"
|
||||||
},
|
},
|
||||||
"platform-dev": {},
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.9.0"
|
"plugin-api-version": "2.9.0"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'Europe/Berlin',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ return [
|
|||||||
|
|
||||||
'providers' => [
|
'providers' => [
|
||||||
'users' => [
|
'users' => [
|
||||||
'driver' => 'eloquent',
|
'driver' => 'tenant-users',
|
||||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
'model' => App\Models\User::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
// 'users' => [
|
// 'users' => [
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'lifetime' => (int) env('SESSION_LIFETIME', 120),
|
'lifetime' => (int) env('SESSION_LIFETIME', 43200), // 30 days
|
||||||
|
|
||||||
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Factories;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
|
||||||
*/
|
|
||||||
class UserFactory extends Factory
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The current password being used by the factory.
|
|
||||||
*/
|
|
||||||
protected static ?string $password;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the model's default state.
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function definition(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => fake()->name(),
|
|
||||||
'email' => fake()->unique()->safeEmail(),
|
|
||||||
'email_verified_at' => now(),
|
|
||||||
'password' => static::$password ??= Hash::make('password'),
|
|
||||||
'remember_token' => Str::random(10),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model's email address should be unverified.
|
|
||||||
*/
|
|
||||||
public function unverified(): static
|
|
||||||
{
|
|
||||||
return $this->state(fn (array $attributes) => [
|
|
||||||
'email_verified_at' => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('users', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->string('name');
|
|
||||||
$table->string('email')->unique();
|
|
||||||
$table->timestamp('email_verified_at')->nullable();
|
|
||||||
$table->string('password');
|
|
||||||
$table->rememberToken();
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
|
|
||||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
|
||||||
$table->string('email')->primary();
|
|
||||||
$table->string('token');
|
|
||||||
$table->timestamp('created_at')->nullable();
|
|
||||||
});
|
|
||||||
|
|
||||||
Schema::create('sessions', function (Blueprint $table) {
|
|
||||||
$table->string('id')->primary();
|
|
||||||
$table->foreignId('user_id')->nullable()->index();
|
|
||||||
$table->string('ip_address', 45)->nullable();
|
|
||||||
$table->text('user_agent')->nullable();
|
|
||||||
$table->longText('payload');
|
|
||||||
$table->integer('last_activity')->index();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('users');
|
|
||||||
Schema::dropIfExists('password_reset_tokens');
|
|
||||||
Schema::dropIfExists('sessions');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
32
database/migrations/2026_01_30_140001_create_tenants.php
Normal file
32
database/migrations/2026_01_30_140001_create_tenants.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tenants', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('slug')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email');
|
||||||
|
$table->string('url');
|
||||||
|
$table->string('account_iban');
|
||||||
|
$table->string('city');
|
||||||
|
$table->string('postcode');
|
||||||
|
$table->string('gdpr_text')-> nullable();
|
||||||
|
$table->string('impress_text')->nullable();
|
||||||
|
$table->string('url_participation_rules')->nullable();
|
||||||
|
$table->boolean('has_active_instance')->default(true);
|
||||||
|
$table->boolean('is_active_local_group')->default(true);
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tenants');
|
||||||
|
}
|
||||||
|
};
|
||||||
108
database/migrations/2026_01_30_140002_create_users_table.php
Normal file
108
database/migrations/2026_01_30_140002_create_users_table.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('user_roles', function (Blueprint $table) {
|
||||||
|
$table->string('slug')->unique()->primary();
|
||||||
|
$table->string('name');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('eating_habits', function (Blueprint $table) {
|
||||||
|
$table->string('slug')->unique()->primary();
|
||||||
|
$table->string('name');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('swimming_permissions', function (Blueprint $table) {
|
||||||
|
$table->string('slug')->unique()->primary();
|
||||||
|
$table->string('name');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('first_aid_permissions', function (Blueprint $table) {
|
||||||
|
$table->string('slug')->unique()->primary();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('description');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('users', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('user_role_main');
|
||||||
|
$table->string('user_role_local_group');
|
||||||
|
$table->string('username')->unique();
|
||||||
|
$table->string('password')->nullable();
|
||||||
|
$table->string('local_group');
|
||||||
|
$table->string('firstname');
|
||||||
|
$table->string('nickname')->nullable();
|
||||||
|
$table->string('lastname');
|
||||||
|
$table->string('membership_id')->nullable();
|
||||||
|
$table->string('address_1')->nullable();
|
||||||
|
$table->string('address_2')->nullable();
|
||||||
|
$table->string('postcode')->nullable();
|
||||||
|
$table->string('city')->nullable();
|
||||||
|
$table->string('email')->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
|
$table->date('birthday')->nullable();
|
||||||
|
$table->string('medications')->nullable();
|
||||||
|
$table->string('allergies')->nullable();
|
||||||
|
$table->string('intolerances')->nullable();
|
||||||
|
$table->string('eating_habits')->nullable();
|
||||||
|
$table->string('swimming_permission')->nullable();
|
||||||
|
$table->string('first_aid_permission')->nullable();
|
||||||
|
$table->string('bank_account_iban')->nullable();
|
||||||
|
$table->string('activation_token')->nullable();
|
||||||
|
$table->dateTime('activation_token_expires_at')->nullable()->default(date('Y-m-d H:i:s', strtotime('+3 days')));
|
||||||
|
$table->boolean('active')->default(false);
|
||||||
|
|
||||||
|
$table->foreign('local_group')->references('slug')->on('tenants')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->foreign('user_role_main')->references('slug')->on('user_roles')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->foreign('user_role_local_group')->references('slug')->on('user_roles')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->foreign('swimming_permission')->references('slug')->on('swimming_permissions')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->foreign('eating_habits')->references('slug')->on('eating_habits')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->foreign('first_aid_permission')->references('slug')->on('first_aid_permissions')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
|
||||||
|
$table->rememberToken();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||||
|
$table->string('email')->primary();
|
||||||
|
$table->string('token');
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('sessions', function (Blueprint $table) {
|
||||||
|
$table->string('id')->primary();
|
||||||
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
|
$table->string('ip_address', 45)->nullable();
|
||||||
|
$table->text('user_agent')->nullable();
|
||||||
|
$table->longText('payload');
|
||||||
|
$table->integer('last_activity')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('users');
|
||||||
|
Schema::dropIfExists('password_reset_tokens');
|
||||||
|
Schema::dropIfExists('sessions');
|
||||||
|
Schema::dropIfExists('user_roles');
|
||||||
|
Schema::dropIfExists('eating_habits');
|
||||||
|
Schema::dropIfExists('swimming_permissions');
|
||||||
|
Schema::dropIfExists('first_aid_permissions');
|
||||||
|
}
|
||||||
|
};
|
||||||
43
database/migrations/2026_01_30_140010_create_cost_units.php
Normal file
43
database/migrations/2026_01_30_140010_create_cost_units.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('cost_unit_types', function (Blueprint $table) {
|
||||||
|
$table->string('slug')->primary();
|
||||||
|
$table->string('name');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Schema::create('cost_units', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('tenant');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('type');
|
||||||
|
$table->dateTime('billing_deadline');
|
||||||
|
$table->float('distance_allowance');
|
||||||
|
$table->boolean('mail_on_new')->default(true);
|
||||||
|
$table->boolean('allow_new')->default(true);
|
||||||
|
$table->boolean('archived')->default(false);
|
||||||
|
$table->string('treasurers');
|
||||||
|
|
||||||
|
$table->foreign('tenant')->references('slug')->on('tenants')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->foreign('type')->references('slug')->on('cost_unit_types')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('cost_units');
|
||||||
|
Schema::dropIfExists('cost_unit_types');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Installer\DevelopmentDataSeeder;
|
||||||
|
use App\Installer\ProductionData;
|
||||||
|
use App\Installer\ProductionDataSeeder;
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
@@ -15,11 +17,12 @@ class DatabaseSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
$productionSeeeder = new ProductionDataSeeder();
|
||||||
|
$productionSeeeder->execute();
|
||||||
|
|
||||||
User::factory()->create([
|
if (str_ends_with(env('APP_URL'), 'mareike.local')) {
|
||||||
'name' => 'Test User',
|
$deveopmentDataSeeder = new DevelopmentDataSeeder();
|
||||||
'email' => 'test@example.com',
|
$deveopmentDataSeeder->execute();
|
||||||
]);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ services:
|
|||||||
|
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.mareike.rule=Host(`mareike.local`)"
|
- "traefik.http.routers.mareike.rule=Host(`mareike.local`) || Host(`admin.mareike.local`) || Host(`wilde-moehre.mareike.local`) || Host(`fennek.mareike.local`)"
|
||||||
- "traefik.http.routers.mareike.entrypoints=websecure"
|
- "traefik.http.routers.mareike.entrypoints=websecure"
|
||||||
- "traefik.http.routers.mareike.tls=true"
|
- "traefik.http.routers.mareike.tls=true"
|
||||||
- "traefik.http.services.mareike.loadbalancer.server.port=80"
|
- "traefik.http.services.mareike.loadbalancer.server.port=80"
|
||||||
|
|
||||||
- "traefik.http.routers.mareike-http.rule=Host(`mareike.local`)"
|
- "traefik.http.routers.mareike-http.rule=Host(`mareike.local`) || Host(`admin.mareike.local`) || Host(`wilde-moehre.mareike.local`)"
|
||||||
- "traefik.http.routers.mareike-http.entrypoints=web"
|
- "traefik.http.routers.mareike-http.entrypoints=web"
|
||||||
- "traefik.http.routers.mareike-http.middlewares=redirect-to-https"
|
- "traefik.http.routers.mareike-http.middlewares=redirect-to-https"
|
||||||
|
|
||||||
@@ -46,8 +46,6 @@ services:
|
|||||||
- ./:/var/www/html
|
- ./:/var/www/html
|
||||||
command: >
|
command: >
|
||||||
sh -c "
|
sh -c "
|
||||||
npm install &&
|
|
||||||
npm install vue3-toastify && npm install @inertiajs/progress && npm install @inertiajs/progress &&
|
|
||||||
while true; do
|
while true; do
|
||||||
npm run build
|
npm run build
|
||||||
echo 'Vite Dev-Server beendet. Neustart in 3 Sekunden...'
|
echo 'Vite Dev-Server beendet. Neustart in 3 Sekunden...'
|
||||||
@@ -30,5 +30,16 @@ server {
|
|||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# In der Nginx-Konfiguration von mareike.local
|
||||||
|
location /build/assets/ {
|
||||||
|
# Erlaubt explizit deine Subdomain
|
||||||
|
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Content-Type, X-Inertia' always;
|
||||||
|
|
||||||
|
# Falls du alle Subdomains von mareike.local erlauben willst:
|
||||||
|
#
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
218
package-lock.json
generated
218
package-lock.json
generated
@@ -5,6 +5,9 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.1.3",
|
||||||
"@inertiajs/progress": "^0.2.7",
|
"@inertiajs/progress": "^0.2.7",
|
||||||
"@inertiajs/vue3": "^2.3.12",
|
"@inertiajs/vue3": "^2.3.12",
|
||||||
"vue": "^3.5.27",
|
"vue": "^3.5.27",
|
||||||
@@ -13,10 +16,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.3",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
|
"autoprefixer": "^10.4.24",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"postcss": "^8.5.6",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
"vite": "^7.0.7"
|
"vite": "^7.0.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -508,6 +513,49 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==",
|
||||||
|
"license": "(CC-BY-4.0 AND MIT)",
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/vue-fontawesome": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-OHHUTLPEzdwP8kcYIzhioUdUOjZ4zzmi+midwa4bqscza4OJCOvTKJEHkXNz8PgZ23kWci1HkKVX0bm8f9t9gQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7",
|
||||||
|
"vue": ">= 3.0.0 < 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@inertiajs/core": {
|
"node_modules/@inertiajs/core": {
|
||||||
"version": "2.3.12",
|
"version": "2.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.3.12.tgz",
|
||||||
@@ -1421,6 +1469,43 @@
|
|||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/autoprefixer": {
|
||||||
|
"version": "10.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz",
|
||||||
|
"integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"browserslist": "^4.28.1",
|
||||||
|
"caniuse-lite": "^1.0.30001766",
|
||||||
|
"fraction.js": "^5.3.4",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"postcss-value-parser": "^4.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"autoprefixer": "bin/autoprefixer"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.13.4",
|
"version": "1.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
|
||||||
@@ -1432,6 +1517,50 @@
|
|||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/baseline-browser-mapping": {
|
||||||
|
"version": "2.9.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
||||||
|
"integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/browserslist": {
|
||||||
|
"version": "4.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
||||||
|
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
|
"caniuse-lite": "^1.0.30001759",
|
||||||
|
"electron-to-chromium": "^1.5.263",
|
||||||
|
"node-releases": "^2.0.27",
|
||||||
|
"update-browserslist-db": "^1.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"browserslist": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
@@ -1461,6 +1590,27 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/caniuse-lite": {
|
||||||
|
"version": "1.0.30001766",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
|
||||||
|
"integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@@ -1612,6 +1762,13 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/electron-to-chromium": {
|
||||||
|
"version": "1.5.283",
|
||||||
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz",
|
||||||
|
"integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
@@ -1802,6 +1959,20 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fraction.js": {
|
||||||
|
"version": "5.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||||
|
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/rawify"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -2315,6 +2486,13 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-releases": {
|
||||||
|
"version": "2.0.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||||
|
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/nprogress": {
|
"node_modules/nprogress": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
|
||||||
@@ -2380,6 +2558,13 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
@@ -2659,6 +2844,37 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/update-browserslist-db": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"escalade": "^3.2.0",
|
||||||
|
"picocolors": "^1.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"update-browserslist-db": "cli.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"browserslist": ">= 4.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||||
|
|||||||
@@ -10,13 +10,18 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.3",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
|
"autoprefixer": "^10.4.24",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"postcss": "^8.5.6",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
"vite": "^7.0.7"
|
"vite": "^7.0.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.1.3",
|
||||||
"@inertiajs/progress": "^0.2.7",
|
"@inertiajs/progress": "^0.2.7",
|
||||||
"@inertiajs/vue3": "^2.3.12",
|
"@inertiajs/vue3": "^2.3.12",
|
||||||
"vue": "^3.5.27",
|
"vue": "^3.5.27",
|
||||||
|
|||||||
126
public/css/app.css
Normal file
126
public/css/app.css
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||||
|
@source '../../storage/framework/views/*.php';
|
||||||
|
@source '../**/*.blade.php';
|
||||||
|
@source '../**/*.js';
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: #FAFAFB !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 50px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 20px 54px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 0 10px 0 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
height: 80px; /* Höhe anpassen */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .anonymous-actions {
|
||||||
|
position: relative;
|
||||||
|
right: calc(-100% + 330px);
|
||||||
|
width: 350px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50px 0 0 50px;
|
||||||
|
background-color: #FAFAFB !important;
|
||||||
|
text-align: right;
|
||||||
|
padding: 10px;
|
||||||
|
top: -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .user-info {
|
||||||
|
position: relative;
|
||||||
|
right: calc(-100% + 275px);
|
||||||
|
width: 295px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50px 0 0 50px;
|
||||||
|
background-color: #FAFAFB !important;
|
||||||
|
text-align: right;
|
||||||
|
padding: 10px;
|
||||||
|
top: -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .left-side {
|
||||||
|
height: 75px;
|
||||||
|
background: #ffffff;
|
||||||
|
align-content: center;
|
||||||
|
padding: 0 50px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
width: 100%;
|
||||||
|
margin-top: -5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexbox {
|
||||||
|
display: flex;
|
||||||
|
background-color: #FAFAFB;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
gap: 1px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
.app-layout {
|
||||||
|
display: flex;
|
||||||
|
height: 95vh;
|
||||||
|
margin: 20px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
flex-basis:275px;
|
||||||
|
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img {
|
||||||
|
width: 135px !important;
|
||||||
|
height: 70px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
height: 40px;
|
||||||
|
background: #666666;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding-right: 1em;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:after {
|
||||||
|
content: ":";
|
||||||
|
}
|
||||||
62
public/css/elements.css
Normal file
62
public/css/elements.css
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/* Toaster */
|
||||||
|
.toaster {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: #4caf50;
|
||||||
|
color: #fff;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"],
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 13pt;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #656363;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: calc(100% + 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus,
|
||||||
|
input[type="email"]:focus,
|
||||||
|
input[type="password"]:focus,
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1d4899;
|
||||||
|
color: #1d4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="button"],
|
||||||
|
input[type="submit"] {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #809dd5 !important;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="button"]:hover,
|
||||||
|
input[type="submit"]:hover {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error_text {
|
||||||
|
color: red;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
BIN
public/images/logo.png
Normal file
BIN
public/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
@@ -1,11 +0,0 @@
|
|||||||
@import 'tailwindcss';
|
|
||||||
|
|
||||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
|
||||||
@source '../../storage/framework/views/*.php';
|
|
||||||
@source '../**/*.blade.php';
|
|
||||||
@source '../**/*.js';
|
|
||||||
|
|
||||||
@theme {
|
|
||||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
||||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
|
||||||
}
|
|
||||||
@@ -5,43 +5,57 @@ import { InertiaProgress } from '@inertiajs/progress'
|
|||||||
import Vue3Toastify, { toast } from 'vue3-toastify'
|
import Vue3Toastify, { toast } from 'vue3-toastify'
|
||||||
import 'vue3-toastify/dist/index.css'
|
import 'vue3-toastify/dist/index.css'
|
||||||
|
|
||||||
// Optional: Lade-Balken für Inertia
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
|
// Icons importieren
|
||||||
|
import { faUser, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(faUser, faTrash, faCheck)
|
||||||
|
|
||||||
|
|
||||||
InertiaProgress.init()
|
InertiaProgress.init()
|
||||||
|
|
||||||
// Inertia App starten
|
|
||||||
createInertiaApp({
|
createInertiaApp({
|
||||||
// Alle Pages in app/Views/Pages/**/*.vue werden automatisch importiert
|
|
||||||
resolve: name => {
|
resolve: name => {
|
||||||
// Vite scannt die Pages dynamisch
|
const pages = import.meta.glob('@domains/**/*.vue')
|
||||||
const pages = import.meta.glob('@views/**/*.vue')
|
|
||||||
|
|
||||||
// Suche nach der richtigen Page-Datei
|
|
||||||
const key = Object.keys(pages).find(k =>
|
const key = Object.keys(pages).find(k =>
|
||||||
k.endsWith(`/${name}.vue`) || k.endsWith(`/${name}/index.vue`)
|
k.endsWith(`/${name}.vue`) || k.endsWith(`/${name}/index.vue`)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!key) throw new Error(`Page not found: ${name}`)
|
if (!key) throw new Error(`Page not found: ${name}`)
|
||||||
|
|
||||||
// Unterstützt sowohl <script setup> als auch klassische Exports
|
|
||||||
return pages[key]()
|
return pages[key]()
|
||||||
},
|
},
|
||||||
|
|
||||||
// Setup der App
|
|
||||||
setup({ el, App, props, plugin }) {
|
setup({ el, App, props, plugin }) {
|
||||||
const vueApp = createApp({ render: () => h(App, props) })
|
const vueApp = createApp({ render: () => h(App, props) })
|
||||||
|
|
||||||
// Inertia Plugin
|
|
||||||
vueApp.use(plugin)
|
vueApp.use(plugin)
|
||||||
|
|
||||||
// Toastify global verfügbar machen
|
vueApp.component('font-awesome-icon', FontAwesomeIcon)
|
||||||
vueApp.use(Vue3Toastify, {
|
|
||||||
autoClose: 3000,
|
|
||||||
position: 'top-right',
|
|
||||||
pauseOnHover: true,
|
|
||||||
})
|
|
||||||
vueApp.config.globalProperties.$toast = toast
|
|
||||||
|
|
||||||
// Mounten auf das DOM
|
vueApp.use(Vue3Toastify, {
|
||||||
|
autoClose: 10000,
|
||||||
|
position: 'bottom-right',
|
||||||
|
pauseOnHover: true,
|
||||||
|
hideProgressBar: false, // Progressbar anzeigen
|
||||||
|
toastDefaults: {
|
||||||
|
success: {
|
||||||
|
style: {background: '#4caf50', color: '#fff'}, // grün
|
||||||
|
progressStyle: {background: '#2e7d32', height: '4px'},
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
style: {background: '#f44336', color: '#fff'}, // rot
|
||||||
|
progressStyle: {background: '#c62828', height: '4px'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
vueApp.config.globalProperties.$toast = toast
|
||||||
vueApp.mount(el)
|
vueApp.mount(el)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
80
resources/js/components/ajaxHandler.js
Normal file
80
resources/js/components/ajaxHandler.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
|
export function useAjax() {
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref(null)
|
||||||
|
const data = ref(null)
|
||||||
|
|
||||||
|
async function request(url, options = {}) {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
data.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: options.method || "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
body: options.body ? JSON.stringify(options.body) : null,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
||||||
|
const result = await response.json()
|
||||||
|
data.value = result
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err
|
||||||
|
console.error("AJAX Error:", err)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function download(url, options = {}) {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: options.method || "GET",
|
||||||
|
headers: {
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
body: options.body ? JSON.stringify(options.body) : null,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
const filename =
|
||||||
|
options.filename ||
|
||||||
|
response.headers
|
||||||
|
.get("Content-Disposition")
|
||||||
|
?.split("filename=")[1]
|
||||||
|
?.replace(/["']/g, "") ||
|
||||||
|
"download.bin"
|
||||||
|
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement("a")
|
||||||
|
a.href = downloadUrl
|
||||||
|
a.download = filename
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
a.remove()
|
||||||
|
window.URL.revokeObjectURL(downloadUrl)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err
|
||||||
|
console.error("Download Error:", err)
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {data, loading, error, request, download}
|
||||||
|
}
|
||||||
195
resources/js/layouts/AppLayout.vue
Normal file
195
resources/js/layouts/AppLayout.vue
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<script setup>
|
||||||
|
import Icon from "../../../app/Views/Components/Icon.vue";
|
||||||
|
import GlobalWidgets from "../../../app/Views/Partials/GlobalWidgets/GlobalWidgets.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: { type: String, default: 'App' },
|
||||||
|
user: { type: Object, },
|
||||||
|
flash: { type: Object, default: () => ({}) },
|
||||||
|
navbar: { type: Object },
|
||||||
|
tenant: String,
|
||||||
|
currentPath: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(props.currentPath)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app-layout">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<!-- Main content -->
|
||||||
|
<div class="main">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flexbox">
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="../../../public/images/logo.png" alt="Logo" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<nav class="nav">
|
||||||
|
<ul class="nav-links" v-if="props.navbar.personal.length > 0">
|
||||||
|
<li v-for="navlink in props.navbar.personal">
|
||||||
|
<a
|
||||||
|
:class="{ navlink_active: navlink.url.endsWith(props.currentPath) }"
|
||||||
|
:href="navlink.url"
|
||||||
|
>{{navlink.display}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="nav-links" v-if="props.navbar.common.length > 0">
|
||||||
|
<li v-for="navlink in props.navbar.common">
|
||||||
|
<a
|
||||||
|
:class="{ navlink_active: navlink.url.endsWith(props.currentPath) }"
|
||||||
|
:href="navlink.url"
|
||||||
|
>{{navlink.display}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav-links" v-if="props.navbar.costunits.length > 0">
|
||||||
|
<li v-for="navlink in props.navbar.costunits">
|
||||||
|
<a
|
||||||
|
:class="{ navlink_active: navlink.url.endsWith(props.currentPath) }"
|
||||||
|
:href="navlink.url"
|
||||||
|
>{{navlink.display}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav-links" v-if="props.navbar.events.length > 0">
|
||||||
|
<li v-for="navlink in props.navbar.events">
|
||||||
|
<a
|
||||||
|
:class="{ navlink_active: navlink.url.endsWith(props.currentPath) }"
|
||||||
|
:href="navlink.url"
|
||||||
|
>{{navlink.display}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<div class="header">
|
||||||
|
<div class="left-side"><h1>{{ props.title }}</h1></div>
|
||||||
|
<div class="user-info" v-if="props.user !== null">
|
||||||
|
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
|
||||||
|
<Icon name="envelope" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/profile" class="header-link-anonymous" title="Mein Profil">
|
||||||
|
<Icon name="user" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/logout" class="header-link-anonymous-logout" title="Abmelden">
|
||||||
|
<Icon name="lock" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="anonymous-actions" v-else>
|
||||||
|
<a href="/register" class="header-link-anonymous">Registrieren</a>
|
||||||
|
<a href="/login" class="header-link-anonymous"> Anmelden </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<global-widgets :user="props.user" :tenant="props.tenant" v-if="props.user !== null" />
|
||||||
|
<div class="content">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer">
|
||||||
|
© 2026 Your Company
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toaster -->
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="flash.message" class="toaster">
|
||||||
|
{{ flash.message }}
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.header-link-anonymous,
|
||||||
|
.header-link-anonymous-logout {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 10px 30px;
|
||||||
|
margin: 0 -20px 0 30px;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous-logout {
|
||||||
|
padding-right: 35px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous:hover {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-link-anonymous-logout:hover {
|
||||||
|
background-color: #ff0000;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
width: 285px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li a {
|
||||||
|
color: #b6b6b6;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px 25px;
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
width: calc(100% - 50px);
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: -10px;
|
||||||
|
width: 275px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav li {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
padding: 8px 12px;
|
||||||
|
display: block;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a:hover {
|
||||||
|
background-color: #1d4899;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navlink_active {
|
||||||
|
background-color: #fae39c !important;
|
||||||
|
color: #1d4899 !important; /* Dunklere Schrift beim Hover */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,10 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
|
<link rel="stylesheet" href="/css/app.css" />
|
||||||
|
<link rel="stylesheet" href="/css/elements.css" />
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Base href für relative Assets -->
|
||||||
|
<base href="/">
|
||||||
|
|
||||||
|
<!-- Vite Assets -->
|
||||||
@vite('resources/js/app.js')
|
@vite('resources/js/app.js')
|
||||||
|
|
||||||
@inertiaHead
|
@inertiaHead
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
3
routes/api.php
Normal file
3
routes/api.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__.'/../app/Domains/UserManagement/Routes/api.php';
|
||||||
@@ -1,16 +1,37 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||||
|
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||||
|
use App\Domains\UserManagement\Controllers\LoginController;
|
||||||
|
use App\Domains\UserManagement\Controllers\LogOutController;
|
||||||
|
use App\Domains\UserManagement\Controllers\RegistrationController;
|
||||||
|
use App\Domains\UserManagement\Controllers\ResetPasswordController;
|
||||||
|
use App\Http\Controllers\TestRenderInertiaProvider;
|
||||||
|
use App\Middleware\IdentifyTenant;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../app/Domains/UserManagement/Routes/web.php';
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
return view('welcome');
|
Route::get('/', DashboardController::class);
|
||||||
|
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
|
||||||
|
Route::get('/messages', fn () => inertia('Messages'));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/messages', [TestRenderInertiaProvider::class, 'index']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Route::get('/inertia', function () {
|
|
||||||
return Inertia::render('Pages/Home', [
|
|
||||||
'appName' => config('app.name'),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import laravel from 'laravel-vite-plugin'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: '',
|
||||||
plugins: [
|
plugins: [
|
||||||
laravel({
|
laravel({
|
||||||
input: 'resources/js/app.js',
|
input: 'resources/js/app.js',
|
||||||
@@ -15,6 +16,12 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'resources/js'),
|
'@': path.resolve(__dirname, 'resources/js'),
|
||||||
'@views': path.resolve(__dirname, 'app/Views'),
|
'@views': path.resolve(__dirname, 'app/Views'),
|
||||||
|
'@domains': path.resolve(__dirname, 'app/Domains'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
host: true, // Dev-Server auch über Subdomains erreichbar
|
||||||
|
strictPort: true, // verhindert zufällige Portwechsel
|
||||||
|
cors: true, // erlaubt Dev-HMR über Subdomains
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user