Basic design created

This commit is contained in:
2026-02-03 09:33:18 +01:00
parent 3570f442f5
commit e280fcfba8
29 changed files with 1055 additions and 28 deletions

View 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) {
}
}

View 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>

View 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!');
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Domains\UserManagement\Controllers;
use App\Providers\InertiaProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController {
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.',
]);
#$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('/');
}
}

View File

@@ -0,0 +1,64 @@
<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')
</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;" />
</td>
</tr>
</table>
</shadowed-box>
</form>
</AppLayout>
</template>
<style>
th {
width: 100px;
}
</style>

View 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();
}
}

View File

@@ -38,6 +38,7 @@ class User extends Authenticatable
'first_aid_permission',
'bank_account_iban',
'password',
'active',
];
/**

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Providers;
class AuthCheckProvider {
public function checkLoggedIn() : bool {
if (!auth()->check()) {
return false;
}
$user = auth()->user();
$tenant = app('tenant');
return $user->active && $tenant->slug === $user->tenant;
}
public function getUserRole() : ?string {
if (!$this->checkLoggedIn()) {
return null;
}
return auth()->user()->user_role;
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Providers;
use App\Models\User;
use Inertia\Inertia;
use Inertia\Response;
@@ -10,17 +11,43 @@ 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')->local_group_name;
$this->props['user'] = $this->user;
$this->props['currentPath'] = request()->path();
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;
}
}

View File

@@ -16,7 +16,11 @@ class TenantUserProvider extends EloquentUserProvider
}
}
$query->where('tenant', app('tenant')->slug);
$query->where([
'tenant' => app('tenant')->slug,
'active' => true
]);
return $query->first();
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Scopes;
use App\Providers\AuthCheckProvider;
abstract class CommonController {
protected function checkAuth() {
$authCheckProvider = new AuthCheckProvider;
return $authCheckProvider->checkLoggedIn();
}
}

View 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>

View 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>

View 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>