Add Livewire Flux components and new tests for project proposal and editing forms

This commit is contained in:
HolgerHatGarKeineNode
2026-01-18 15:19:00 +01:00
parent 30e78711c9
commit 0694a2d837
19 changed files with 816 additions and 444 deletions

View File

@@ -78,11 +78,16 @@ new class extends Component {
{{ $election['year'] }}
</div>
<div class="shadow-lg rounded-lg overflow-hidden">
<x-textarea wire:model="elections.{{ $loop->index }}.candidates" rows="25"
label="candidates" placeholder=""/>
<flux:field>
<flux:label>Kandidaten</flux:label>
<flux:textarea wire:model="elections.{{ $loop->index }}.candidates" rows="25" placeholder="Kandidaten..."/>
<flux:error name="elections.{{ $loop->index }}.candidates" />
</flux:field>
</div>
<div class="py-2">
<x-button label="Speichern" wire:click="saveElection({{ $loop->index }})" wire:loading.attr="disabled"/>
<flux:button wire:click="saveElection({{ $loop->index }})" wire:loading.attr="disabled">
Speichern
</flux:button>
</div>
</div>
@endforeach

View File

@@ -475,19 +475,19 @@ new class extends Component {
<div
class="flex items-center justify-between before:absolute before:inset-0 before:backdrop-blur-md before:bg-gray-50/90 dark:before:bg-[#1B1B1B]/90 before:-z-10 border-b border-gray-200 dark:border-gray-700/60 px-4 sm:px-6 md:px-5 h-16">
<div
class="flex flex-col space-y-2 sm:space-y-0 sm:flex-row justify-between items-center w-full">
<div>
@if($isNotClosed)
<x-badge success
label="Die Wahl ist geöffnet bis zum {{ $election->end_time?->timezone('Europe/Berlin')->format('d.m.Y H:i') }}"/>
@else
<x-badge negative label="Die Wahl ist geschlossen"/>
@endif
</div>
<div>
<x-button secondary
:href="route('association.election.admin', ['election' => $election])"
label="Wahl-Admin"/>
class="flex flex-col space-y-2 sm:space-y-0 sm:flex-row justify-between items-center w-full">
<div>
@if($isNotClosed)
<flux:badge color="success" label="Die Wahl ist geöffnet bis zum {{ $election->end_time?->timezone('Europe/Berlin')->format('d.m.Y H:i') }}"/>
@else
<flux:badge color="danger" label="Die Wahl ist geschlossen"/>
@endif
</div>
<div>
<flux:button secondary
:href="route('association.election.admin', ['election' => $election])"
label="Wahl-Admin">
</flux:button>
</div>
</div>
</div>

View File

@@ -202,19 +202,22 @@ class extends Component {
</div>
</div>
<div class="mt-2 flex justify-end w-full space-x-2">
<x-button
<flux:button
xs
target="_blank"
:href="url()->temporarySignedRoute('dl', now()->addMinutes(30), ['media' => $post->getFirstMedia('pdf')])"
label="Öffnen"
primary icon="cloud-arrow-down"/>
icon="cloud-arrow-down">
Öffnen
</flux:button>
@if($canEdit)
<x-button
<flux:button
xs
negative
wire:click="delete({{ $post->id }})"
wire:loading.attr="disabled"
label="Löschen"
negative icon="trash"/>
icon="trash">
Löschen
</flux:button>
@endif
</div>
</article>
@@ -253,27 +256,40 @@ class extends Component {
@enderror
</div>
<div wire:dirty>
<x-native-select
wire:model="form.category"
label="Kategorie"
placeholder="Wähle Kategorie"
:options="\App\Enums\NewsCategory::selectOptions()"
option-label="label" option-value="value"
/>
<flux:field>
<flux:label>Kategorie</flux:label>
<flux:select
wire:model="form.category"
placeholder="Wähle Kategorie"
>
@foreach(\App\Enums\NewsCategory::selectOptions() as $category)
<flux:select.option
:label="$category['label']"
:value="$category['value']"
/>
@endforeach
</flux:select>
<flux:error name="form.category" />
</flux:field>
</div>
<div wire:dirty>
<x-input label="Titel" wire:model="form.name"/>
<flux:field>
<flux:label>Titel</flux:label>
<flux:input wire:model="form.name" placeholder="News-Titel" />
<flux:error name="form.name" />
</flux:field>
</div>
<div wire:dirty>
<x-textarea
description="optional"
label="Beschreibung" wire:model="form.description"/>
<flux:field>
<flux:label>Beschreibung</flux:label>
<flux:description>optional</flux:description>
<flux:textarea wire:model="form.description" rows="4" placeholder="Beschreibung..." />
<flux:error name="form.description" />
</flux:field>
</div>
<button
wire:click="save"
class="btn-sm w-full bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-gray-800 dark:text-gray-300">
<flux:button wire:click="save" class="w-full">
Hinzufügen
</button>
</flux:button>
</div>
</div>
@endif

View File

@@ -503,19 +503,22 @@ new class extends Component {
sich bitte direkt an den Vorstand.</a>
</h4>
<div class="sm:flex sm:items-center space-y-4 sm:space-y-0 sm:space-x-4 mt-5">
<div class="sm:w-1/2 flex flex-col space-y-2">
<div class="flex items-center space-x-2">
<div wire:dirty>
<x-checkbox wire:model="form.check"
label="Ich stimme den Vereins-Statuten zu"/>
</div>
<div class="sm:w-1/2 flex flex-col space-y-2">
<div class="flex items-center space-x-2">
<div wire:dirty>
<flux:field variant="inline">
<flux:checkbox wire:model="form.check" label="Ich stimme den Vereins-Statuten zu"/>
<flux:error name="form.check" />
</flux:field>
</div>
<div>
<a href="https://einundzwanzig.space/verein/" target="_blank"
class="text-amber-500">Statuten</a>
</div>
</div>
<x-button label="Mit deinem aktuellen Nostr-Profil Mitglied werden"
wire:click="save({{ AssociationStatus::PASSIVE() }})"/>
<flux:button wire:click="save({{ AssociationStatus::PASSIVE() }})">
Mit deinem aktuellen Nostr-Profil Mitglied werden
</flux:button>
</div>
</div>
@endif
@@ -543,25 +546,35 @@ new class extends Component {
diese Adresse AES-256 verschlüsselt in der Datenbank ab.
</div>
<div
class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 text-amber-500">
<x-toggle xl warning
wire:model.live="no"
wire:dirty
label="NEIN">
<x-slot name="description">
<span class="py-2 text-amber-500">Ich informiere mich selbst in der News Sektion und gebe keine E-Mail Adresse raus.</span>
</x-slot>
</x-toggle>
class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 text-amber-500">
<flux:field variant="inline" xl>
<flux:label>NEIN</flux:label>
<flux:description>Ich informiere mich selbst in der News Sektion und gebe keine E-Mail Adresse raus.</flux:description>
<flux:switch
wire:model.live="no"
wire:dirty
/>
<flux:error name="no" />
</flux:field>
</div>
@if($showEmail)
<div wire:key="showEmail"
class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2">
<x-input wire:model.live.debounce="fax" wire:dirty label="Fax-Nummer"/>
<x-input wire:model.live.debounce="email" wire:dirty
label="E-Mail Adresse"/>
<flux:field>
<flux:label>Fax-Nummer</flux:label>
<flux:input wire:model.live.debounce="fax" wire:dirty placeholder="Fax-Nummer"/>
<flux:error name="fax" />
</flux:field>
<flux:field>
<flux:label>E-Mail Adresse</flux:label>
<flux:input type="email" wire:model.live.debounce="email" wire:dirty placeholder="E-Mail Adresse"/>
<flux:error name="email" />
</flux:field>
</div>
<div wire:key="showSave" class="flex space-x-2 mt-2">
<x-button wire:click="saveEmail" label="Speichern"/>
<flux:button wire:click="saveEmail" wire:loading.attr="disabled">
Speichern
</flux:button>
</div>
@endif
</div>

View File

@@ -70,23 +70,22 @@ class extends Component {
</h2>
<div class="space-y-4">
<div wire:dirty>
<x-input label="Name" wire:model="form.name"/>
@error('form.name')
<span class="text-red-500">{{ $message }}</span>
@enderror
<flux:field>
<flux:label>Name</flux:label>
<flux:input wire:model="form.name" placeholder="Projektname" />
<flux:error name="form.name" />
</flux:field>
</div>
<div wire:dirty>
<x-textarea label="Beschreibung" wire:model="form.description"/>
@error('form.description')
<span class="text-red-500">{{ $message }}</span>
@enderror
<flux:field>
<flux:label>Beschreibung</flux:label>
<flux:textarea wire:model="form.description" rows="6" placeholder="Projektbeschreibung..." />
<flux:error name="form.description" />
</flux:field>
</div>
<button
wire:click="save"
wire:loading.attr="disabled"
class="w-full btn-sm bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-gray-800 dark:text-gray-300">
<flux:button wire:click="save" wire:loading.attr="disabled" variant="primary" class="w-full">
Speichern
</button>
</flux:button>
</div>
</div>
</div>

View File

@@ -27,7 +27,13 @@ class extends Component {
$currentPubkey = NostrAuth::pubkey();
$currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $currentPubkey)->first();
if ($currentPleb && $currentPleb->id === $project->einundzwanzig_pleb_id) {
if (
(
$currentPleb
&& $currentPleb->id === $project->einundzwanzig_pleb_id
)
|| in_array($currentPleb->npub, config('einundzwanzig.config.current_board'))
) {
$this->isAllowed = true;
$this->form = [
'name' => $project->name,
@@ -76,23 +82,22 @@ class extends Component {
</h2>
<div class="space-y-4">
<div wire:dirty>
<x-input label="Name" wire:model="form.name"/>
@error('form.name')
<span class="text-red-500">{{ $message }}</span>
@enderror
<flux:field>
<flux:label>Name</flux:label>
<flux:input wire:model="form.name" placeholder="Projektname" />
<flux:error name="form.name" />
</flux:field>
</div>
<div wire:dirty>
<x-textarea label="Beschreibung" wire:model="form.description"/>
@error('form.description')
<span class="text-red-500">{{ $message }}</span>
@enderror
<flux:field>
<flux:label>Beschreibung</flux:label>
<flux:textarea wire:model="form.description" rows="6" placeholder="Projektbeschreibung..." />
<flux:error name="form.description" />
</flux:field>
</div>
<button
wire:click="update"
wire:loading.attr="disabled"
class="w-full btn-sm bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-gray-800 dark:text-gray-300">
<flux:button wire:click="update" wire:loading.attr="disabled" variant="primary" class="w-full">
Speichern
</button>
</flux:button>
</div>
</div>
</div>

View File

@@ -118,14 +118,15 @@ new class extends Component {
<!-- Search form -->
<form class="relative">
<x-input type="search" wire:model.live.debounce="search"
placeholder="Suche"/>
<flux:input type="search" wire:model.live.debounce="search"
placeholder="Suche" icon="magnifying-glass"/>
</form>
<!-- Add meetup button -->
@if($currentPleb && $currentPleb->association_status->value > 1 && $currentPleb->paymentEvents()->where('year', date('Y'))->where('paid', true)->exists())
<x-button :href="route('association.projectSupport.create')" icon="plus"
label="Projekt einreichen"/>
<flux:button :href="route('association.projectSupport.create')" icon="plus" variant="primary">
Projekt einreichen
</flux:button>
@endif
</div>