🎨 feat(project): enhance project support form with image upload and validation features.

🗃️ refactor(project): rename project support route for clarity and consistency.
🗑️ chore(project): implement delete confirmation for project proposals in the index view.
🔧 fix(editor): adjust initialization delay for SimpleMDE editor to improve responsiveness.
📸 fix(media): update fallback image URL in ProjectProposal model for better asset management.
This commit is contained in:
fsociety
2024-10-24 18:05:37 +02:00
parent 6db38d08f5
commit ef1d0fdf17
5 changed files with 184 additions and 53 deletions

View File

@@ -64,7 +64,7 @@ class ProjectProposal extends Model implements HasMedia
$this $this
->addMediaCollection('main') ->addMediaCollection('main')
->singleFile() ->singleFile()
->useFallbackUrl(asset('img/einundzwanzig.png')); ->useFallbackUrl(asset('einundzwanzig-alpha.jpg'));
} }
public function einundzwanzigPleb(): BelongsTo public function einundzwanzigPleb(): BelongsTo

View File

@@ -1,55 +1,43 @@
@props(['model'])
<div <div
wire:ignore wire:ignore
x-data="{ x-data="{
value: $wire.entangle('{{ $model }}'), value: $wire.entangle('{{ $model }}'),
init() { init() {
let editor = new EasyMDE({ this.$nextTick(() => {
element: this.$refs.editor, setTimeout(() => {
lineNumbers: true, let editor = new EasyMDE({
uploadImage: false, element: this.$refs.editor,
spellChecker: false, lineNumbers: true,
{{-- imageMaxSize: 1024 * 1024 * 10,--}} uploadImage: false,
{{-- imageUploadFunction: (file, onSuccess, onError) => {--}} spellChecker: false,
{{-- @this.upload('images', file, (uploadedFilename) => {--}} showIcons: [
{{-- const currentImage = @this.get('currentImage');--}} 'heading',
{{-- const temporaryUrls = @this.get('temporaryUrls');--}} 'heading-smaller',
{{-- onSuccess(temporaryUrls[currentImage]);--}} 'heading-bigger',
{{-- @this.set('currentImage', currentImage + 1)--}} 'heading-1',
{{-- }, () => {--}} 'heading-2',
{{-- // Error callback.--}} 'heading-3',
{{-- }, (event) => {--}} 'code',
{{-- // Progress callback.--}} 'table',
{{-- // event.detail.progress contains a number between 1 and 100 as the upload progresses.--}} 'quote',
{{-- })--}} 'strikethrough',
{{-- },--}} 'unordered-list',
showIcons: [ 'ordered-list',
'heading', 'clean-block',
'heading-smaller', 'horizontal-rule',
'heading-bigger', 'undo',
'heading-1', 'redo',
'heading-2', //'upload-image',
'heading-3', ],
'code', });
'table',
'quote',
'strikethrough',
'unordered-list',
'ordered-list',
'clean-block',
'horizontal-rule',
'undo',
'redo',
//'upload-image',
],
})
editor.value(this.value) editor.value(this.value);
editor.codemirror.on('change', () => { editor.codemirror.on('change', () => {
this.value = editor.value() this.value = editor.value();
}) });
}, 100); // Adjust the delay as needed
});
}, },
}" }"
class="w-full" class="w-full"

View File

@@ -1,5 +1,6 @@
<?php <?php
use App\Livewire\Forms\ProjectProposalForm;
use Livewire\Volt\Component; use Livewire\Volt\Component;
use swentel\nostr\Filter\Filter; use swentel\nostr\Filter\Filter;
use swentel\nostr\Key\Key; use swentel\nostr\Key\Key;
@@ -10,20 +11,122 @@ use swentel\nostr\Subscription\Subscription;
use function Laravel\Folio\{middleware}; use function Laravel\Folio\{middleware};
use function Laravel\Folio\name; use function Laravel\Folio\name;
use function Livewire\Volt\{state, mount, on, computed}; use function Livewire\Volt\{state, mount, on, computed, form, usesFileUploads};
name('association.projectSupport.form'); name('association.projectSupport.edit');
form(ProjectProposalForm::class);
state([ state([
'projectProposal' => fn() => $projectProposal, 'projectProposal' => fn() => $projectProposal,
'image',
'isAllowed' => false,
'currentPubkey' => null,
'currentPleb' => null,
]); ]);
mount(function ($projectProposal) {
$this->form->fill($projectProposal->toArray());
$this->image = $projectProposal->getFirstMedia('main');
});
usesFileUploads();
on([
'nostrLoggedIn' => function ($pubkey) {
$this->currentPubkey = $pubkey;
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
if ($this->currentPleb->association_status->value < 3) {
return $this->js('alert("Du bist hierzu nicht berechtigt.")');
}
$this->isAllowed = true;
},
'nostrLoggedOut' => function () {
$this->isAllowed = false;
$this->currentPubkey = null;
$this->currentPleb = null;
},
]);
$save = function () {
$this->form->validate();
$this->projectProposal->update([
...$this->form->except('id', 'slug'),
'einundzwanzig_pleb_id' => $this->currentPleb->id,
]);
return redirect()->route('association.projectSupport');
};
?> ?>
<x-layouts.app title="Welcome"> <x-layouts.app title="{{ $projectProposal->name }}">
@volt @volt
<div> <div x-cloak x-show="isAllowed" class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto"
@dd($projectProposal) x-data="nostrDefault(@this)">
<form class="space-y-8 divide-y divide-gray-700 pb-24">
<div class="space-y-8 divide-y divide-gray-700 sm:space-y-5">
<div class="mt-6 sm:mt-5 space-y-6 sm:space-y-5">
<x-input.group :for=" md5('image')" :label="__('Bild')">
<div class="py-4">
@if ($image && str($image->getMimeType())->contains(['image/jpeg','image/jpg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp']))
<div class="text-gray-200">{{ __('Preview') }}:</div>
<img class="h-48 object-contain" src="{{ $image->temporaryUrl() }}">
@endif
@if (isset($projectProposal) && $projectProposal->getFirstMediaUrl('main'))
<div class="text-gray-200">{{ __('Current picture') }}:</div>
<img class="h-48 object-contain" src="{{ $projectProposal->getFirstMediaUrl('main') }}">
@endif
</div>
<input class="text-gray-200" type="file" wire:model="image">
@error('image') <span class="text-red-500">{{ $message }}</span> @enderror
</x-input.group>
<x-input.group :for="md5('form.name')" :label="__('Name')">
<x-input autocomplete="off" wire:model.debounce="form.name"
:placeholder="__('Name')"/>
</x-input.group>
<x-input.group :for="md5('form.website')" :label="__('Webseite des Projekts')">
<x-input autocomplete="off" wire:model.debounce="form.website"
:placeholder="__('Website')"/>
</x-input.group>
<x-input.group :for="md5('form.name')" :label="__('Beabsichtigte Unterstützung in Sats')">
<x-input type="number" autocomplete="off" wire:model.debounce="form.support_in_sats"
:placeholder="__('Beabsichtigte Unterstützung in Sats')"/>
</x-input.group>
<x-input.group :for="md5('form.description')">
<x-slot name="label">
<div>
{{ __('Beschreibung') }}
</div>
<div
class="text-amber-500 text-xs py-2">{{ __('Bitte verfasse einen ausführlichen und verständlichen Antragstext, damit die Abstimmung über eine mögliche Förderung erfolgen kann.') }}</div>
</x-slot>
<div
class="text-amber-500 text-xs py-2">{{ __('Für Bilder in Markdown verwende bitte z.B. Imgur oder einen anderen Anbieter.') }}</div>
<x-input.simple-mde model="form.description"/>
@error('form.description') <span
class="text-red-500 py-2">{{ $message }}</span> @enderror
</x-input.group>
<x-input.group :for="md5('save')" label="">
<x-button secondary :href="route('association.projectSupport')">
<i class="fa fa-thin fa-arrow-left"></i>
{{ __('Zurück') }}
</x-button>
<x-button primary wire:click="save">
<i class="fa fa-thin fa-save"></i>
{{ __('Speichern') }}
</x-button>
</x-input.group>
</div>
</div>
</form>
</div> </div>
@endvolt @endvolt
</x-layouts.app> </x-layouts.app>

View File

@@ -55,7 +55,7 @@ $save = function () {
?> ?>
<x-layouts.app title="Welcome"> <x-layouts.app title="Neuer Vorschlag für eine Unterstützung">
@volt @volt
<div x-cloak x-show="isAllowed" class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" <div x-cloak x-show="isAllowed" class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto"
x-data="nostrDefault(@this)"> x-data="nostrDefault(@this)">
@@ -109,6 +109,10 @@ $save = function () {
</x-input.group> </x-input.group>
<x-input.group :for="md5('save')" label=""> <x-input.group :for="md5('save')" label="">
<x-button secondary :href="route('association.projectSupport')">
<i class="fa fa-thin fa-arrow-left"></i>
{{ __('Zurück') }}
</x-button>
<x-button primary wire:click="save"> <x-button primary wire:click="save">
<i class="fa fa-thin fa-save"></i> <i class="fa fa-thin fa-save"></i>
{{ __('Save') }} {{ __('Save') }}

View File

@@ -7,6 +7,7 @@ use swentel\nostr\Message\RequestMessage;
use swentel\nostr\Relay\Relay; use swentel\nostr\Relay\Relay;
use swentel\nostr\Request\Request; use swentel\nostr\Request\Request;
use swentel\nostr\Subscription\Subscription; use swentel\nostr\Subscription\Subscription;
use WireUi\Actions\Notification;
use function Laravel\Folio\{middleware}; use function Laravel\Folio\{middleware};
use function Laravel\Folio\name; use function Laravel\Folio\name;
@@ -44,11 +45,35 @@ on([
}, },
]); ]);
$confirmDelete = function ($id) {
$notification = new Notification($this);
$notification->confirm([
'title' => 'Projektunterstützung löschen',
'message' => 'Bist du sicher, dass du diese Projektunterstützung löschen möchtest?',
'accept' => [
'label' => 'Ja, löschen',
'method' => 'delete',
'params' => $id,
],
]);
};
$delete = function ($id) {
\App\Models\ProjectProposal::query()->findOrFail($id)->delete();
$this->projects = \App\Models\ProjectProposal::query()
->with([
'einundzwanzigPleb.profile',
'votes',
])
->get();
};
?> ?>
<x-layouts.app title="Projekt Unterstützungen"> <x-layouts.app title="Projekt Unterstützungen">
@volt @volt
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" x-data="nostrDefault(@this)" x-cloak x-show="isAllowed"> <div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" x-data="nostrDefault(@this)" x-cloak
x-show="isAllowed">
<!-- Page header --> <!-- Page header -->
<div class="sm:flex sm:justify-between sm:items-center mb-5"> <div class="sm:flex sm:justify-between sm:items-center mb-5">
@@ -149,6 +174,17 @@ on([
</div> </div>
@endif @endif
</div> </div>
<div class="flex justify-between items-center mt-3">
@if($currentPleb && $currentPleb->id === $project->einundzwanzig_pleb_id)
<x-button
negative
wire:click="confirmDelete({{ $project->id }})"
label="Löschen"/>
<x-button
:href="route('association.projectSupport.edit', ['projectProposal' => $project])"
label="Editieren"/>
@endif
</div>
</div> </div>
</article> </article>
@endforeach @endforeach