From e96413d1a04419add7b899d7ce7c9def5071e0ec Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode Date: Fri, 21 Nov 2025 14:23:59 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Add=20courses=20and=20lecturers?= =?UTF-8?q?=20management=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Course.php | 6 +- app/Models/Lecturer.php | 6 +- composer.json | 1 + composer.lock | 110 +++++- .../components/layouts/app/sidebar.blade.php | 15 + .../views/livewire/courses/create.blade.php | 126 +++++++ .../views/livewire/courses/edit.blade.php | 195 ++++++++++ .../views/livewire/courses/index.blade.php | 127 +++++++ .../livewire/courses/landingpage.blade.php | 164 +++++++++ .../views/livewire/lecturers/create.blade.php | 202 +++++++++++ .../views/livewire/lecturers/edit.blade.php | 278 +++++++++++++++ .../views/livewire/lecturers/index.blade.php | 136 +++++++ .../meetups/create-edit-events.blade.php | 2 +- .../views/livewire/meetups/create.blade.php | 337 ++++++++++++++++++ .../views/livewire/meetups/edit.blade.php | 10 +- .../views/livewire/meetups/index.blade.php | 11 +- .../livewire/meetups/landingpage.blade.php | 15 + routes/web.php | 16 +- 18 files changed, 1740 insertions(+), 17 deletions(-) create mode 100644 resources/views/livewire/courses/create.blade.php create mode 100644 resources/views/livewire/courses/edit.blade.php create mode 100644 resources/views/livewire/courses/index.blade.php create mode 100644 resources/views/livewire/courses/landingpage.blade.php create mode 100644 resources/views/livewire/lecturers/create.blade.php create mode 100644 resources/views/livewire/lecturers/edit.blade.php create mode 100644 resources/views/livewire/lecturers/index.blade.php create mode 100644 resources/views/livewire/meetups/create.blade.php diff --git a/app/Models/Course.php b/app/Models/Course.php index e1919a0..92030d1 100644 --- a/app/Models/Course.php +++ b/app/Models/Course.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; -use Spatie\Image\Manipulations; +use Spatie\Image\Enums\Fit; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; use Spatie\MediaLibrary\MediaCollections\Models\Media; @@ -49,10 +49,10 @@ class Course extends Model implements HasMedia { $this ->addMediaConversion('preview') - ->fit(Manipulations::FIT_CROP, 300, 300) + ->fit(Fit::Crop, 300, 300) ->nonQueued(); $this->addMediaConversion('thumb') - ->fit(Manipulations::FIT_CROP, 130, 130) + ->fit(Fit::Crop, 130, 130) ->width(130) ->height(130); } diff --git a/app/Models/Lecturer.php b/app/Models/Lecturer.php index aeed16f..d981727 100644 --- a/app/Models/Lecturer.php +++ b/app/Models/Lecturer.php @@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Support\Facades\Cookie; -use Spatie\Image\Manipulations; +use Spatie\Image\Enums\Fit; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; use Spatie\MediaLibrary\MediaCollections\Models\Media; @@ -49,10 +49,10 @@ class Lecturer extends Model implements HasMedia { $this ->addMediaConversion('preview') - ->fit(Manipulations::FIT_CROP, 300, 300) + ->fit(Fit::Crop, 300, 300) ->nonQueued(); $this->addMediaConversion('thumb') - ->fit(Manipulations::FIT_CROP, 130, 130) + ->fit(Fit::Crop, 130, 130) ->width(130) ->height(130); } diff --git a/composer.json b/composer.json index d2971ae..31b3335 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "spatie/laravel-permission": "^6.20", "spatie/laravel-sluggable": "^3.7", "spatie/laravel-tags": "^4.10", + "staudenmeir/eloquent-has-many-deep": "^1.21", "woodsandwalker/laravel-countries": "^1.5" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 04df5c7..58dcc7b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9b98697732c4305356365667814db4be", + "content-hash": "e2d34fa17a2f68c2fc315cab9b402b42", "packages": [ { "name": "akuechler/laravel-geoly", @@ -5329,6 +5329,114 @@ ], "time": "2025-01-13T13:04:43+00:00" }, + { + "name": "staudenmeir/eloquent-has-many-deep", + "version": "v1.21.2", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/eloquent-has-many-deep.git", + "reference": "b0a3041c44237ebcd0d1005e475a4c736cba482c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep/zipball/b0a3041c44237ebcd0d1005e475a4c736cba482c", + "reference": "b0a3041c44237ebcd0d1005e475a4c736cba482c", + "shasum": "" + }, + "require": { + "illuminate/database": "^12.0", + "php": "^8.2", + "staudenmeir/eloquent-has-many-deep-contracts": "^1.3" + }, + "require-dev": { + "awobaz/compoships": "^2.3", + "barryvdh/laravel-ide-helper": "^3.0", + "korridor/laravel-has-many-merged": "^1.2", + "larastan/larastan": "^3.0", + "laravel/framework": "^12.0", + "mockery/mockery": "^1.6", + "orchestra/testbench-core": "^10.0", + "phpunit/phpunit": "^11.0", + "staudenmeir/eloquent-json-relations": "^1.14", + "staudenmeir/laravel-adjacency-list": "^1.24" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Staudenmeir\\EloquentHasManyDeep\\IdeHelperServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Staudenmeir\\EloquentHasManyDeep\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Laravel Eloquent HasManyThrough relationships with unlimited levels", + "support": { + "issues": "https://github.com/staudenmeir/eloquent-has-many-deep/issues", + "source": "https://github.com/staudenmeir/eloquent-has-many-deep/tree/v1.21.2" + }, + "funding": [ + { + "url": "https://paypal.me/JonasStaudenmeir", + "type": "custom" + } + ], + "time": "2025-11-08T08:44:24+00:00" + }, + { + "name": "staudenmeir/eloquent-has-many-deep-contracts", + "version": "v1.3", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts.git", + "reference": "37ce351e4db919b3af606bc8ca0e62e2e4939cde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep-contracts/zipball/37ce351e4db919b3af606bc8ca0e62e2e4939cde", + "reference": "37ce351e4db919b3af606bc8ca0e62e2e4939cde", + "shasum": "" + }, + "require": { + "illuminate/database": "^12.0", + "php": "^8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Staudenmeir\\EloquentHasManyDeepContracts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Contracts for staudenmeir/eloquent-has-many-deep", + "support": { + "issues": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/issues", + "source": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/tree/v1.3" + }, + "time": "2025-02-15T17:11:01+00:00" + }, { "name": "symfony/clock", "version": "v7.3.0", diff --git a/resources/views/components/layouts/app/sidebar.blade.php b/resources/views/components/layouts/app/sidebar.blade.php index 3e139d5..ab30583 100644 --- a/resources/views/components/layouts/app/sidebar.blade.php +++ b/resources/views/components/layouts/app/sidebar.blade.php @@ -32,6 +32,21 @@ :current="request()->routeIs('meetups.map')" wire:navigate>{{ __('Karte') }} + + + + {{ __('Kurse') }} + + + {{ __('Dozenten') }} + + {{-- --}} diff --git a/resources/views/livewire/courses/create.blade.php b/resources/views/livewire/courses/create.blade.php new file mode 100644 index 0000000..78994e7 --- /dev/null +++ b/resources/views/livewire/courses/create.blade.php @@ -0,0 +1,126 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'lecturer_id' => ['required', 'exists:lecturers,id'], + 'description' => ['nullable', 'string'], + ]); + + $course = Course::create($validated); + + if ($this->logo) { + $course + ->addMedia($this->logo->getRealPath()) + ->usingName($course->name) + ->toMediaCollection('logo'); + } + + session()->flash('status', __('Kurs erfolgreich erstellt!')); + + $this->redirect(route_with_country('courses.edit', ['course' => $course]), navigate: true); + } + + public function with(): array + { + return [ + 'lecturers' => Lecturer::query()->orderBy('name')->get(), + ]; + } +}; ?> + +
+ {{ __('Neuen Kurs erstellen') }} + +
+ + + + {{ __('Grundlegende Informationen') }} + +
+ + + +
+ + @if($logo) + Logo + @else + + + @endif + + +
+ +
+
+
+ + + {{ __('Name') }} * + + {{ __('Der Anzeigename für diesen Kurs') }} + + + + + {{ __('Dozent') }} * + + + + + @foreach($lecturers as $lecturer) + {{ $lecturer->name }} + + @endforeach + + {{ __('Der Dozent, der diesen Kurs leitet') }} + + +
+ + + {{ __('Beschreibung') }} + + {{ __('Ausführliche Beschreibung des Kurses') }} + + +
+ + +
+ + {{ __('Abbrechen') }} + + + + {{ __('Kurs erstellen') }} + +
+
+
diff --git a/resources/views/livewire/courses/edit.blade.php b/resources/views/livewire/courses/edit.blade.php new file mode 100644 index 0000000..ebef00b --- /dev/null +++ b/resources/views/livewire/courses/edit.blade.php @@ -0,0 +1,195 @@ +course->load('media'); + + // Basic Information + $this->name = $this->course->name ?? ''; + $this->lecturer_id = $this->course->lecturer_id; + $this->description = $this->course->description; + + // System fields + $this->created_by = $this->course->created_by; + $this->created_at = $this->course->created_at?->format('Y-m-d H:i:s'); + $this->updated_at = $this->course->updated_at?->format('Y-m-d H:i:s'); + } + + public function updateCourse(): void + { + $validated = $this->validate([ + 'name' => ['required', 'string', 'max:255'], + 'lecturer_id' => ['required', 'exists:lecturers,id'], + 'description' => ['nullable', 'string'], + ]); + + $this->course->update($validated); + + if ($this->logo) { + $this->course->clearMediaCollection('logo'); + $this->course + ->addMedia($this->logo->getRealPath()) + ->usingName($this->course->name) + ->toMediaCollection('logo'); + $this->logo = null; + $this->course->load('media'); + } + + $this->dispatch('course-updated', name: $this->course->name); + + session()->flash('status', __('Kurs erfolgreich aktualisiert!')); + } + + public function with(): array + { + return [ + 'lecturers' => Lecturer::query()->orderBy('name')->get(), + ]; + } +}; ?> + +
+ {{ __('Kurs bearbeiten') }}: {{ $course->name }} + +
+ + + + {{ __('Grundlegende Informationen') }} + +
+ + + +
+ + @if (!$logo && $course->getFirstMedia('logo')) + Logo + @elseif($logo) + Logo + @else + + + @endif + + +
+ +
+
+
+ + + {{ __('ID') }} + + {{ __('System-generierte ID (nur lesbar)') }} + + + + {{ __('Name') }} * + + {{ __('Der Anzeigename für diesen Kurs') }} + + + + + {{ __('Dozent') }} * + + + + + @foreach($lecturers as $lecturer) + {{ $lecturer->name }} + + @endforeach + + {{ __('Der Dozent, der diesen Kurs leitet') }} + + +
+ + + {{ __('Beschreibung') }} + + {{ __('Ausführliche Beschreibung des Kurses') }} + + +
+ + + + {{ __('Systeminformationen') }} + +
+ + {{ __('Erstellt von') }} + + {{ __('Ersteller des Kurses') }} + + + + {{ __('Erstellt am') }} + + {{ __('Wann dieser Kurs erstellt wurde') }} + + + + {{ __('Aktualisiert am') }} + + {{ __('Letzte Änderungszeit') }} + +
+
+ + +
+ + {{ __('Abbrechen') }} + + +
+ @if (session('status')) + + {{ session('status') }} + + @endif + + + {{ __('Kurs aktualisieren') }} + +
+
+
+
diff --git a/resources/views/livewire/courses/index.blade.php b/resources/views/livewire/courses/index.blade.php new file mode 100644 index 0000000..0ca6cb6 --- /dev/null +++ b/resources/views/livewire/courses/index.blade.php @@ -0,0 +1,127 @@ +country = request()->route('country'); + } + + public function with(): array + { + return [ + 'courses' => Course::with(['lecturer', 'createdBy']) + ->withExists([ + 'courseEvents as has_future_events' => fn($query) => $query->where('from', '>=', now()) + ]) + ->when($this->search, fn($query) + => $query + ->where('name', 'ilike', '%'.$this->search.'%') + ->orWhere('description', 'ilike', '%'.$this->search.'%'), + ) + ->orderByDesc('has_future_events') + ->paginate(15), + ]; + } +}; ?> + +
+
+ {{ __('Kurse') }} +
+
+ +
+ {{ __('Neuer Kurs') }} +
+
+ + + + + {{ __('Name') }} + + + {{ __('Dozent') }} + + {{ __('Nächster Termin') }} + {{ __('Aktionen') }} + + + + @foreach ($courses as $course) + + + + + + + + @if($course->lecturer) +
+ + {{ $course->lecturer->name }} +
+ @endif +
+ + + @php + $nextEvent = $course->courseEvents() + ->where('from', '>=', now()) + ->orderBy('from', 'asc') + ->first(); + @endphp + @if($nextEvent) + + {{ $nextEvent->from->format('d.m.Y H:i') }} + + @endif + + + + + {{ __('Bearbeiten') }} + + + {{ __('Neues Event erstellen') }} + + +
+ @endforeach +
+
+
diff --git a/resources/views/livewire/courses/landingpage.blade.php b/resources/views/livewire/courses/landingpage.blade.php new file mode 100644 index 0000000..e504e64 --- /dev/null +++ b/resources/views/livewire/courses/landingpage.blade.php @@ -0,0 +1,164 @@ +country = request()->route('country'); + } + + public function with(): array + { + return [ + 'course' => $this->course->load('lecturer'), + 'events' => $this->course + ->courseEvents() + ->where('from', '>=', now()) + ->orderBy('from', 'asc') + ->get(), + ]; + } +}; ?> + +
+
+ +
+
+ +
+ {{ $course->name }} + @if($course->lecturer) + + + {{ $course->lecturer->name }} + + @endif +
+
+ + @if($course->description) +
+ {{ __('Über den Kurs') }} + {!! $course->description !!} +
+ @endif + + @if($course->lecturer) +
+ {{ __('Über den Dozenten') }} + +
+ +
+ {{ $course->lecturer->name }} + @if($course->lecturer->subtitle) + {{ $course->lecturer->subtitle }} + @endif + @if($course->lecturer->intro) + {!! $course->lecturer->intro !!} + @endif + + +
+ @if($course->lecturer->website) + + + Website + + @endif + + @if($course->lecturer->twitter_username) + + + + + Twitter + + @endif + + @if($course->lecturer->nostr) + + + Nostr + + @endif +
+
+
+
+ @endif +
+ + +
+ @if($course->lecturer && $course->lecturer->getFirstMedia('avatar')) +
+ {{ __('Dozent') }} + {{ $course->lecturer->name }} +
+ @endif +
+
+ + + @if($events->isNotEmpty()) +
+ {{ __('Kommende Veranstaltungen') }} + +
+ @foreach($events as $event) + + + {{ $event->from->format('d.m.Y') }} + + + + + {{ $event->from->format('H:i') }} - {{ $event->to->format('H:i') }} Uhr + + + @if($event->venue) + + + {{ $event->venue->name }} + + @endif + +
+ + {{ __('Details/Anmelden') }} + + @if($course->created_by === auth()->id()) + + {{ __('Bearbeiten') }} + + @endif +
+
+ @endforeach +
+
+ @endif +
diff --git a/resources/views/livewire/lecturers/create.blade.php b/resources/views/livewire/lecturers/create.blade.php new file mode 100644 index 0000000..8ecf334 --- /dev/null +++ b/resources/views/livewire/lecturers/create.blade.php @@ -0,0 +1,202 @@ +validate([ + 'name' => ['required', 'string', 'max:255', 'unique:lecturers,name'], + 'subtitle' => ['nullable', 'string'], + 'intro' => ['nullable', 'string'], + 'description' => ['nullable', 'string'], + 'active' => ['boolean'], + 'website' => ['nullable', 'url', 'max:255'], + 'twitter_username' => ['nullable', 'string', 'max:255'], + 'nostr' => ['nullable', 'string', 'max:255'], + 'lightning_address' => ['nullable', 'string'], + 'lnurl' => ['nullable', 'string'], + 'node_id' => ['nullable', 'string', 'max:255'], + 'paynym' => ['nullable', 'string'], + ]); + + $lecturer = Lecturer::create($validated); + + if ($this->avatar) { + $lecturer + ->addMedia($this->avatar->getRealPath()) + ->usingName($lecturer->name) + ->toMediaCollection('avatar'); + } + + session()->flash('status', __('Dozent erfolgreich erstellt!')); + + $this->redirect(route_with_country('lecturers.edit', ['lecturer' => $lecturer]), navigate: true); + } +}; ?> + +
+ {{ __('Neuen Dozenten erstellen') }} + +
+ + + + {{ __('Grundlegende Informationen') }} + +
+ + + +
+ @if($avatar) + Avatar + @else + + @endif + +
+ +
+
+
+ + + {{ __('Name') }} * + + {{ __('Vollständiger Name des Dozenten') }} + + + + + {{ __('Untertitel') }} + + {{ __('Kurze Berufsbezeichnung oder Rolle') }} + + + + + {{ __('Status') }} + + {{ __('Ist dieser Dozent aktiv?') }} + +
+ + + {{ __('Einführung') }} + + {{ __('Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)') }} + + + + + {{ __('Beschreibung') }} + + {{ __('Ausführliche Beschreibung und Biografie') }} + + +
+ + + + {{ __('Links & Soziale Medien') }} + +
+ + {{ __('Webseite') }} + + {{ __('Persönliche Webseite oder Portfolio') }} + + + + + {{ __('Twitter Benutzername') }} + + {{ __('Twitter-Handle ohne @ Symbol') }} + + + + + {{ __('Nostr') }} + + {{ __('Nostr öffentlicher Schlüssel') }} + + +
+
+ + + + {{ __('Zahlungsinformationen') }} + +
+ + {{ __('Lightning Adresse') }} + + {{ __('Lightning-Adresse für Zahlungen') }} + + + + + {{ __('LNURL') }} + + {{ __('LNURL für Lightning-Zahlungen') }} + + + + + {{ __('Node ID') }} + + {{ __('Lightning Node ID') }} + + + + + {{ __('PayNym') }} + + {{ __('PayNym für Bitcoin-Zahlungen') }} + + +
+
+ + +
+ + {{ __('Abbrechen') }} + + + + {{ __('Dozenten erstellen') }} + +
+
+
diff --git a/resources/views/livewire/lecturers/edit.blade.php b/resources/views/livewire/lecturers/edit.blade.php new file mode 100644 index 0000000..cab4a12 --- /dev/null +++ b/resources/views/livewire/lecturers/edit.blade.php @@ -0,0 +1,278 @@ +lecturer->load('media'); + + $this->name = $this->lecturer->name ?? ''; + $this->subtitle = $this->lecturer->subtitle; + $this->intro = $this->lecturer->intro; + $this->description = $this->lecturer->description; + $this->active = (bool) $this->lecturer->active; + + $this->website = $this->lecturer->website; + $this->twitter_username = $this->lecturer->twitter_username; + $this->nostr = $this->lecturer->nostr; + $this->lightning_address = $this->lecturer->lightning_address; + $this->lnurl = $this->lecturer->lnurl; + $this->node_id = $this->lecturer->node_id; + $this->paynym = $this->lecturer->paynym; + + $this->created_by = $this->lecturer->created_by; + $this->created_at = $this->lecturer->created_at?->format('Y-m-d H:i:s'); + $this->updated_at = $this->lecturer->updated_at?->format('Y-m-d H:i:s'); + } + + public function updateLecturer(): void + { + $validated = $this->validate([ + 'name' => ['required', 'string', 'max:255', Rule::unique('lecturers')->ignore($this->lecturer->id)], + 'subtitle' => ['nullable', 'string'], + 'intro' => ['nullable', 'string'], + 'description' => ['nullable', 'string'], + 'active' => ['boolean'], + 'website' => ['nullable', 'url', 'max:255'], + 'twitter_username' => ['nullable', 'string', 'max:255'], + 'nostr' => ['nullable', 'string', 'max:255'], + 'lightning_address' => ['nullable', 'string'], + 'lnurl' => ['nullable', 'string'], + 'node_id' => ['nullable', 'string', 'max:255'], + 'paynym' => ['nullable', 'string'], + ]); + + $this->lecturer->update($validated); + + if ($this->avatar) { + $this->lecturer->clearMediaCollection('avatar'); + $this->lecturer + ->addMedia($this->avatar->getRealPath()) + ->usingName($this->lecturer->name) + ->toMediaCollection('avatar'); + $this->avatar = null; + $this->lecturer->load('media'); + } + + $this->dispatch('lecturer-updated', name: $this->lecturer->name); + + session()->flash('status', __('Dozent erfolgreich aktualisiert!')); + } +}; ?> + +
+ {{ __('Dozent bearbeiten') }}: {{ $lecturer->name }} + +
+ + + + {{ __('Grundlegende Informationen') }} + +
+ + + +
+ @if (!$avatar && $lecturer->getFirstMedia('avatar')) + Avatar + @elseif($avatar) + Avatar + @else + + @endif + +
+ +
+
+
+ + + {{ __('ID') }} + + {{ __('System-generierte ID (nur lesbar)') }} + + + + {{ __('Name') }} * + + {{ __('Vollständiger Name des Dozenten') }} + + + + + {{ __('Untertitel') }} + + {{ __('Kurze Berufsbezeichnung oder Rolle') }} + + + + + {{ __('Status') }} + + {{ __('Ist dieser Dozent aktiv?') }} + +
+ + + {{ __('Einführung') }} + + {{ __('Kurze Vorstellung (wird auf Kurs-Seiten angezeigt)') }} + + + + + {{ __('Beschreibung') }} + + {{ __('Ausführliche Beschreibung und Biografie') }} + + +
+ + + + {{ __('Links & Soziale Medien') }} + +
+ + {{ __('Webseite') }} + + {{ __('Persönliche Webseite oder Portfolio') }} + + + + + {{ __('Twitter Benutzername') }} + + {{ __('Twitter-Handle ohne @ Symbol') }} + + + + + {{ __('Nostr') }} + + {{ __('Nostr öffentlicher Schlüssel') }} + + +
+
+ + + + {{ __('Zahlungsinformationen') }} + +
+ + {{ __('Lightning Adresse') }} + + {{ __('Lightning-Adresse für Zahlungen') }} + + + + + {{ __('LNURL') }} + + {{ __('LNURL für Lightning-Zahlungen') }} + + + + + {{ __('Node ID') }} + + {{ __('Lightning Node ID') }} + + + + + {{ __('PayNym') }} + + {{ __('PayNym für Bitcoin-Zahlungen') }} + + +
+
+ + + + {{ __('Systeminformationen') }} + +
+ + {{ __('Erstellt von') }} + + {{ __('Ersteller des Dozenten') }} + + + + {{ __('Erstellt am') }} + + {{ __('Wann dieser Dozent erstellt wurde') }} + + + + {{ __('Aktualisiert am') }} + + {{ __('Letzte Änderungszeit') }} + +
+
+ + +
+ + {{ __('Abbrechen') }} + + +
+ @if (session('status')) + + {{ session('status') }} + + @endif + + + {{ __('Dozent aktualisieren') }} + +
+
+
+
diff --git a/resources/views/livewire/lecturers/index.blade.php b/resources/views/livewire/lecturers/index.blade.php new file mode 100644 index 0000000..7df4a88 --- /dev/null +++ b/resources/views/livewire/lecturers/index.blade.php @@ -0,0 +1,136 @@ +country = request()->route('country'); + } + + public function sort($column) + { + if ($this->sortBy === $column) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $column; + $this->sortDirection = 'asc'; + } + } + + public function with(): array + { + return [ + 'lecturers' => Lecturer::with(['createdBy']) + ->when($this->search, fn($query) + => $query->where('name', 'ilike', '%'.$this->search.'%') + ->orWhere('description', 'ilike', '%'.$this->search.'%') + ->orWhere('subtitle', 'ilike', '%'.$this->search.'%'), + ) + ->orderBy($this->sortBy, $this->sortDirection) + ->paginate(15), + ]; + } +}; ?> + +
+
+ {{ __('Dozenten') }} +
+ + @auth + + {{ __('Dozenten anlegen') }} + + @endauth +
+
+ + + + {{ __('Name') }} + + {{ __('Untertitel') }} + {{ __('Kurse') }} + {{ __('Links') }} + {{ __('Aktionen') }} + + + + @foreach ($lecturers as $lecturer) + + + +
+
{{ $lecturer->name }}
+ @if($lecturer->active) + {{ __('Aktiv') }} + @else + {{ __('Inaktiv') }} + @endif +
+
+ + + @if($lecturer->subtitle) +
+ {{ Str::limit($lecturer->subtitle, 50) }} +
+ @endif +
+ + + {{ $lecturer->courses()->count() }} {{ __('Kurse') }} + + + +
+ @if($lecturer->website) + + + + @endif + @if($lecturer->twitter_username) + + + + @endif + @if($lecturer->nostr) + + + + @endif +
+
+ + + + {{ __('Bearbeiten') }} + + +
+ @endforeach +
+
+
diff --git a/resources/views/livewire/meetups/create-edit-events.blade.php b/resources/views/livewire/meetups/create-edit-events.blade.php index f1f9d99..caa8a17 100644 --- a/resources/views/livewire/meetups/create-edit-events.blade.php +++ b/resources/views/livewire/meetups/create-edit-events.blade.php @@ -64,7 +64,7 @@ new class extends Component { if ($this->event) { $this->event->delete(); session()->flash('status', __('Event erfolgreich gelöscht!')); - $this->redirect(route_with_country('meetups.edit', ['meetup' => $this->meetup]), navigate: true); + $this->redirect(route('meetups.landingpage', ['meetup' => $this->meetup, 'country' => $this->country]), navigate: true); } } }; ?> diff --git a/resources/views/livewire/meetups/create.blade.php b/resources/views/livewire/meetups/create.blade.php new file mode 100644 index 0000000..c331d31 --- /dev/null +++ b/resources/views/livewire/meetups/create.blade.php @@ -0,0 +1,337 @@ +validate([ + 'newCityName' => ['required', 'string', 'max:255', 'unique:cities,name'], + 'newCityCountryId' => ['required', 'exists:countries,id'], + 'newCityLatitude' => ['required', 'numeric'], + 'newCityLongitude' => ['required', 'numeric'], + ]); + + $city = City::create([ + 'name' => $validated['newCityName'], + 'country_id' => $validated['newCityCountryId'], + 'latitude' => $validated['newCityLatitude'], + 'longitude' => $validated['newCityLongitude'], + 'slug' => str($validated['newCityName'])->slug(), + 'created_by' => auth()->id(), + ]); + + $this->city_id = $city->id; + $this->reset(['newCityName', 'newCityCountryId', 'newCityLatitude', 'newCityLongitude']); + + \Flux\Flux::modal('add-city')->close(); + } + + public function createMeetup(): void + { + $validated = $this->validate([ + 'name' => ['required', 'string', 'max:255', 'unique:meetups,name'], + 'city_id' => ['required', 'exists:cities,id'], + 'intro' => ['nullable', 'string'], + 'telegram_link' => ['nullable', 'url', 'max:255'], + 'webpage' => ['nullable', 'url', 'max:255'], + 'twitter_username' => ['nullable', 'string', 'max:255'], + 'matrix_group' => ['nullable', 'string', 'max:255'], + 'nostr' => ['nullable', 'string', 'max:255'], + 'simplex' => ['nullable', 'string', 'max:255'], + 'signal' => ['nullable', 'string', 'max:510'], + 'community' => ['nullable', 'string', 'max:255'], + 'visible_on_map' => ['boolean'], + ]); + + $meetup = Meetup::create($validated); + + if ($this->logo) { + $meetup + ->addMedia($this->logo->getRealPath()) + ->usingName($meetup->name) + ->toMediaCollection('logo'); + } + + session()->flash('status', __('Meetup erfolgreich erstellt!')); + + $this->redirect(route_with_country('meetups.edit', ['meetup' => $meetup]), navigate: true); + } + + public function with(): array + { + return [ + 'cities' => City::query()->orderBy('name')->get(), + 'countries' => Country::query()->orderBy('countries.name')->get(), + ]; + } +}; ?> + +
+ {{ __('Neues Meetup erstellen') }} + +
+ + + + {{ __('Grundlegende Informationen') }} + +
+ + + +
+ @if($logo) + Logo + @else + + + @endif + + +
+ +
+
+
+ + + {{ __('Name') }} * + + {{ __('Der Anzeigename für dieses Meetup') }} + + + + +
+ {{ __('Stadt') }} * + + + {{ __('Stadt hinzufügen') }} + + +
+ + + + + @foreach($cities as $city) + {{ $city->name }} ({{ $city->country->name }}) + + @endforeach + + {{ __('Die nächstgrößte Stadt oder Ort') }} + +
+ + + {{ __('Auf Karte sichtbar') }} + + {{ __('Soll dieses Meetup auf der Karte angezeigt werden?') }} + +
+ + + {{ __('Einführung') }} + + {{ __('Kurze Beschreibung des Meetups') }} + + +
+ + + + {{ __('Links & Soziale Medien') }} + + +
+ + {{ __('Webseite') }} + + {{ __('Offizielle Webseite oder Landingpage') }} + + + + + {{ __('Telegram Link') }} + + {{ __('Link zur Telegram-Gruppe oder zum Kanal') }} + + +
+ + +
+ + {{ __('Twitter Benutzername') }} + + {{ __('Twitter-Handle ohne @ Symbol') }} + + + + + {{ __('Matrix Gruppe') }} + + {{ __('Matrix-Raum Bezeichner oder Link') }} + + +
+ + +
+ + {{ __('Nostr') }} + + {{ __('Nostr öffentlicher Schlüssel oder Bezeichner') }} + + +
+ + +
+ + {{ __('SimpleX') }} + + {{ __('SimpleX Chat Kontaktinformationen') }} + + + + + {{ __('Signal') }} + + {{ __('Signal Kontakt- oder Gruppeninformationen') }} + + +
+
+ + + + {{ __('Zusätzliche Informationen') }} + +
+ + {{ __('Gemeinschaft') }} + + {{ __('Keine') }} + einundzwanzig + bitcoin + + {{ __('Gemeinschafts- oder Organisationsname') }} + + +
+
+ + +
+ + {{ __('Abbrechen') }} + + + + {{ __('Meetup erstellen') }} + +
+
+ + + +
+
+ {{ __('Stadt hinzufügen') }} + {{ __('Füge eine neue Stadt zur Datenbank hinzu.') }} +
+ + + {{ __('Stadtname') }} * + + + + + + {{ __('Land') }} * + + @foreach($countries as $country) + +
+ {{ str($country->code)->lower() }} + {{ $country->name }} +
+
+ @endforeach +
+ +
+ +
+ + {{ __('Breitengrad') }} * + + + + + + {{ __('Längengrad') }} * + + + +
+ +
+ + + + {{ __('Abbrechen') }} + + + {{ __('Stadt erstellen') }} +
+
+
+
diff --git a/resources/views/livewire/meetups/edit.blade.php b/resources/views/livewire/meetups/edit.blade.php index 77b689d..e807299 100644 --- a/resources/views/livewire/meetups/edit.blade.php +++ b/resources/views/livewire/meetups/edit.blade.php @@ -212,7 +212,7 @@ new class extends Component {
{{ __('Stadt') }} - + {{ __('Stadt hinzufügen') }} @@ -350,7 +350,7 @@ new class extends Component {
- + {{ __('Abbrechen') }} @@ -361,7 +361,7 @@ new class extends Component { @endif - + {{ __('Meetup aktualisieren') }}
@@ -420,10 +420,10 @@ new class extends Component { - {{ __('Abbrechen') }} + {{ __('Abbrechen') }} - {{ __('Stadt erstellen') }} + {{ __('Stadt erstellen') }}
diff --git a/resources/views/livewire/meetups/index.blade.php b/resources/views/livewire/meetups/index.blade.php index 377b523..420a9b7 100644 --- a/resources/views/livewire/meetups/index.blade.php +++ b/resources/views/livewire/meetups/index.blade.php @@ -48,15 +48,20 @@ new class extends Component { }; ?>
-
+
{{ __('Meetups') }} - {{ __('Kalender-Stream-URL kopieren') }} -
+
+ {{ __('Kalender-Stream-URL kopieren') }} + @auth + + {{ __('Meetup erstellen') }} + + @endauth
diff --git a/resources/views/livewire/meetups/landingpage.blade.php b/resources/views/livewire/meetups/landingpage.blade.php index 20bb322..1e02332 100644 --- a/resources/views/livewire/meetups/landingpage.blade.php +++ b/resources/views/livewire/meetups/landingpage.blade.php @@ -193,6 +193,11 @@ new class extends Component {
{{ __('Kommende Veranstaltungen') }} + @if(auth()->user()->meetups()->find($meetup->id)?->exists) + + {{ __('Neues Event erstellen') }} + + @endif {{ __('Kalender-Stream-URL kopieren') }}
@@ -251,5 +256,15 @@ new class extends Component { @endforeach
+ @else +
+
+ @if(auth()->user()->meetups()->find($meetup->id)?->exists) + + {{ __('Neues Event erstellen') }} + + @endif +
+
@endif
diff --git a/routes/web.php b/routes/web.php index 7047928..f658050 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,20 +13,34 @@ Route::get('stream-calendar', \App\Http\Controllers\DownloadMeetupCalendar::clas Route::middleware([]) ->prefix('/{country:code}') ->group(function () { - Volt::route('meetups', 'meetups.index')->name('meetups.index'); Volt::route('map', 'meetups.map')->name('meetups.map'); Volt::route('meetup/{meetup:slug}', 'meetups.landingpage')->name('meetups.landingpage'); Volt::route('meetup/{meetup:slug}/event/{event}', 'meetups.landingpage-event')->name('meetups.landingpage-event'); + + Volt::route('courses', 'courses.index')->name('courses.index'); + Volt::route('course/{course}', 'courses.landingpage')->name('courses.landingpage'); + Volt::route('course/{course}/event/{event}', 'courses.landingpage-event')->name('courses.landingpage-event'); + + Volt::route('lecturers', 'lecturers.index')->name('lecturers.index'); }); Route::middleware(['auth']) ->prefix('/{country:code}') ->group(function () { Volt::route('dashboard', 'dashboard')->name('dashboard'); + Volt::route('meetup-create', 'meetups.create')->name('meetups.create'); Volt::route('meetup-edit/{meetup}', 'meetups.edit')->name('meetups.edit'); Volt::route('meetup/{meetup}/events/create', 'meetups.create-edit-events')->name('meetups.events.create'); Volt::route('meetup/{meetup}/events/{event}/edit', 'meetups.create-edit-events')->name('meetups.events.edit'); + + Volt::route('course-create', 'courses.create')->name('courses.create'); + Volt::route('course-edit/{course}', 'courses.edit')->name('courses.edit'); + Volt::route('course/{course}/events/create', 'courses.create-edit-events')->name('courses.events.create'); + Volt::route('course/{course}/events/{event}/edit', 'courses.create-edit-events')->name('courses.events.edit'); + + Volt::route('lecturer-create', 'lecturers.create')->name('lecturers.create'); + Volt::route('lecturer-edit/{lecturer}', 'lecturers.edit')->name('lecturers.edit'); }); Route::middleware(['auth'])