🛠️ Refactor delete confirmation logic with projectToDelete property, enhance project voting features in Livewire, and update Blade templates for consistency and improved UX.

This commit is contained in:
HolgerHatGarKeineNode
2026-01-19 23:40:42 +01:00
parent 39835c3a24
commit 714de411a6
4 changed files with 228 additions and 128 deletions

View File

@@ -81,33 +81,31 @@
</div> </div>
@endif @endif
</div> </div>
<div <div
class="flex flex-col sm:flex-row justify-between items-center mt-3 space-y-2 sm:space-y-0"> class="flex flex-col sm:flex-row justify-between items-center mt-3 space-y-2 sm:space-y-0">
@if( @if(
($currentPleb && $currentPleb->id === $project->einundzwanzig_pleb_id) ($currentPleb && $currentPleb->id === $project->einundzwanzig_pleb_id)
|| ($currentPleb && in_array($currentPleb->npub, config('einundzwanzig.config.current_board'), true)) || ($currentPleb && in_array($currentPleb->npub, config('einundzwanzig.config.current_board'), true))
) )
<flux:modal.trigger name="delete-project"> <flux:button
<flux:button icon="trash"
icon="trash" size="xs"
xs variant="danger"
negative wire:click="$dispatch('confirmDeleteProject', { id: {{ $project->id }} })">
wire:loading.attr="disabled"> Löschen
Löschen </flux:button>
</flux:button>
</flux:modal.trigger> <flux:button
<flux:button icon="pencil"
icon="pencil" size="xs"
xs :href="route('association.projectSupport.edit', ['projectProposal' => $project])">
secondary Editieren
:href="route('association.projectSupport.edit', ['projectProposal' => $project])"> </flux:button>
Editieren @endif
</flux:button>
@endif
@if(($currentPleb && $currentPleb->association_status->value > 2) || $project->accepted) @if(($currentPleb && $currentPleb->association_status->value > 2) || $project->accepted)
<flux:button <flux:button
icon="folder-open" icon="folder-open"
xs size="xs"
:href="route('association.projectSupport.item', ['projectProposal' => $project])"> :href="route('association.projectSupport.item', ['projectProposal' => $project])">
Öffnen Öffnen
</flux:button> </flux:button>

View File

@@ -19,9 +19,9 @@ class extends Component {
public bool $isAllowed = false; public bool $isAllowed = false;
public function mount(ProjectProposal $project): void public function mount($projectProposal): void
{ {
$this->project = $project; $this->project = ProjectProposal::query()->where('slug', $projectProposal)->firstOrFail();
if (NostrAuth::check()) { if (NostrAuth::check()) {
$currentPubkey = NostrAuth::pubkey(); $currentPubkey = NostrAuth::pubkey();
@@ -30,14 +30,14 @@ class extends Component {
if ( if (
( (
$currentPleb $currentPleb
&& $currentPleb->id === $project->einundzwanzig_pleb_id && $currentPleb->id === $this->project->einundzwanzig_pleb_id
) )
|| in_array($currentPleb->npub, config('einundzwanzig.config.current_board')) || in_array($currentPleb->npub, config('einundzwanzig.config.current_board'))
) { ) {
$this->isAllowed = true; $this->isAllowed = true;
$this->form = [ $this->form = [
'name' => $project->name, 'name' => $this->project->name,
'description' => $project->description, 'description' => $this->project->description,
]; ];
} }
} }

View File

@@ -22,9 +22,12 @@ new class extends Component {
public ?EinundzwanzigPleb $currentPleb = null; public ?EinundzwanzigPleb $currentPleb = null;
public ?ProjectProposal $projectToDelete = null;
protected $listeners = [ protected $listeners = [
'nostrLoggedIn' => 'handleNostrLoggedIn', 'nostrLoggedIn' => 'handleNostrLoggedIn',
'nostrLoggedOut' => 'handleNostrLoggedOut', 'nostrLoggedOut' => 'handleNostrLoggedOut',
'confirmDeleteProject' => 'confirmDeleteProject',
]; ];
public function mount(): void public function mount(): void
@@ -76,9 +79,9 @@ new class extends Component {
$this->currentPleb = null; $this->currentPleb = null;
} }
public function confirmDelete($id): void public function confirmDeleteProject($id): void
{ {
$this->confirmDeleteId = $id; $this->projectToDelete = ProjectProposal::query()->findOrFail($id);
Flux::modal('delete-project')->show(); Flux::modal('delete-project')->show();
} }
@@ -89,10 +92,13 @@ new class extends Component {
public function delete(): void public function delete(): void
{ {
ProjectProposal::query()->findOrFail($this->confirmDeleteId)->delete(); if ($this->projectToDelete) {
Flux::toast('Projektunterstützung gelöscht.'); $this->projectToDelete->delete();
$this->loadProjects(); Flux::toast('Projektunterstützung gelöscht.');
Flux::modals()->close(); $this->loadProjects();
Flux::modals()->close();
$this->projectToDelete = null;
}
} }
}; };
@@ -158,33 +164,33 @@ new class extends Component {
</li> </li>
</ul> </ul>
</div> </div>
<div class="text-sm text-gray-500 dark:text-gray-400 italic mb-4">{{ $projects->count() }} Projekte</div> <div class="text-sm text-gray-500 dark:text-gray-400 italic mb-4">{{ $projects->count() }} Projekte</div>
<!-- Content --> <!-- Content -->
<div class="grid xl:grid-cols-2 gap-6 mb-8"> <div class="grid xl:grid-cols-2 gap-6 mb-8">
@foreach($this->projects as $project) @foreach($this->projects as $project)
<x-project-card :project="$project" :currentPleb="$currentPleb" :section="$activeFilter"/> <x-project-card :project="$project" :currentPleb="$currentPleb" :section="$activeFilter"/>
@endforeach @endforeach
</div> </div>
<!-- Confirmation modal --> <!-- Delete confirmation modal -->
<flux:modal name="delete-project" class="min-w-88"> <flux:modal name="delete-project" class="min-w-88">
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<flux:heading size="lg">Projektunterstützung löschen</flux:heading> <flux:heading size="lg">Projektunterstützung löschen?</flux:heading>
<flux:text class="mt-2"> <flux:text class="mt-2">
<p>Bist du sicher, dass du diese Projektunterstützung löschen möchtest?</p> <p>Du bist dabei, diese Projektunterstützung zu löschen.</p>
<p>Diese Aktion kann nicht rückgängig gemacht werden.</p> <p>Diese Aktion kann nicht rückgängig gemacht werden.</p>
</flux:text> </flux:text>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<flux:spacer/> <flux:spacer/>
<flux:modal.close> <flux:modal.close>
<flux:button variant="ghost">Abbrechen</flux:button> <flux:button variant="ghost">Abbrechen</flux:button>
</flux:modal.close> </flux:modal.close>
<flux:button type="submit" wire:click="delete" variant="danger">Ja, löschen</flux:button> <flux:button wire:click="delete" variant="danger">Löschen</flux:button>
</div> </div>
</div> </div>
</flux:modal> </flux:modal>
</div> </div>
</div> </div>

View File

@@ -1,90 +1,186 @@
<?php <?php
use App\Models\ProjectProposal; use App\Livewire\Forms\VoteForm;
use App\Models\Vote;
use App\Support\NostrAuth; use App\Support\NostrAuth;
use Livewire\Component; use Livewire\Component;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
new new class extends Component {
#[Layout('layouts.app')] public $projectProposal;
#[Title('Projektförderung ')]
class extends Component {
public ProjectProposal $project;
public bool $isAllowed = false; public bool $isAllowed = false;
public function mount(ProjectProposal $project): void public ?string $currentPubkey = null;
public ?object $currentPleb = null;
public bool $ownVoteExists = false;
public function mount($projectProposal): void
{ {
$this->project = $project; $this->projectProposal = \App\Models\ProjectProposal::query()->where('slug', $projectProposal)->firstOrFail();
if (NostrAuth::check()) { if (NostrAuth::check()) {
$this->currentPubkey = NostrAuth::pubkey();
$this->handleNostrLoggedIn($this->currentPubkey);
$this->isAllowed = true; $this->isAllowed = true;
} }
} }
};
public function getBoardVotesProperty()
{
return Vote::query()
->where('project_proposal_id', $this->projectProposal->id)
->whereHas('einundzwanzigPleb', fn($q) => $q->whereIn('npub', config('einundzwanzig.config.current_board')))
->get();
}
public function getOtherVotesProperty()
{
return Vote::query()
->where('project_proposal_id', $this->projectProposal->id)
->whereDoesntHave(
'einundzwanzigPleb',
fn($q) => $q->whereIn('npub', config('einundzwanzig.config.current_board'))
)
->get();
}
public function handleNostrLoggedIn($pubkey): void
{
$this->currentPubkey = $pubkey;
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
$this->isAllowed = true;
$this->ownVoteExists = Vote::query()
->where('project_proposal_id', $this->projectProposal->id)
->where('einundzwanzig_pleb_id', $this->currentPleb->id)
->exists();
}
public function handleApprove(): void
{
Vote::query()->updateOrCreate([
'project_proposal_id' => $this->projectProposal->id,
'einundzwanzig_pleb_id' => $this->currentPleb->id,
], [
'value' => true,
]);
}
public function handleNotApprove(): void
{
Vote::query()->updateOrCreate([
'project_proposal_id' => $this->projectProposal->id,
'einundzwanzig_pleb_id' => $this->currentPleb->id,
], [
'value' => false,
]);
}
}
?> ?>
<div> <div>
@if($isAllowed) @if($projectProposal->accepted || $isAllowed)
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto"> <div class="px-4 sm:px-6 lg:px-8 py-8 w-full">
<div <div class="max-w-5xl mx-auto flex flex-col lg:flex-row lg:space-x-8 xl:space-x-16">
class="flex flex-col md:flex-row items-center mb-6 space-y-4 md:space-y-0 md:space-x-4"> <div>
<div class="flex items-center justify-between w-full"> <div class="mb-6">
<h1 class="text-2xl md:text-3xl text-gray-800 dark:text-gray-100 font-bold"> <a class="text-sm px-3 py-1 border border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-300"
{{ $project->name }} href="{{ route('association.projectSupport') }}">
</h1> <svg class="fill-current text-gray-400 dark:text-gray-500 mr-2" width="7" height="12"
<div> viewBox="0 0 7 12">
@if($project->status === 'pending') <path d="M5.4.6 6.8 2l-4 4 4 4-1.4 1.4L0 6z"></path>
<x-badge info label="Pending"/> </svg>
@elseif($project->status === 'active') <span>Zurück zur Übersicht</span>
<x-badge success label="Active"/> </a>
@else
<x-badge neutral label="Archiviert"/>
@endif
</div> </div>
</div> <div class="text-sm font-semibold text-violet-500 uppercase mb-2">
</div> {{ $projectProposal->created_at->translatedFormat('d.m.Y') }}
</div>
<header class="mb-4">
<h1 class="text-2xl md:text-3xl text-gray-800 dark:text-gray-100 font-bold mb-2">
{{ $projectProposal->name }}
</h1>
{!! $projectProposal->description !!}
</header>
<div class="md:flex"> <div class="space-y-3 sm:flex sm:items-center sm:justify-between sm:space-y-0 mb-6">
<!-- Left column --> <div class="flex items-center sm:mr-4">
<div class="w-full md:w-60 mb-4 md:mb-0"> <a class="block mr-2 shrink-0" href="#0">
<flux:card> <img class="rounded-full"
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4"> src="{{ $projectProposal->einundzwanzigPleb->profile?->picture ?? asset('einundzwanzig-alpha.jpg') }}"
Details width="32" height="32" alt="User">
</h2> </a>
<dl class="space-y-3"> <div class="text-sm whitespace-nowrap">Eingereicht von
<div> <div class="font-semibold text-gray-800 dark:text-gray-100">
<dt class="text-xs font-semibold text-gray-500 uppercase mb-1">Status</dt> {{ $projectProposal->einundzwanzigPleb?->profile->name ?? str($projectProposal->einundzwanzigPleb->npub)->limit(32) }}
<dd class="text-sm text-gray-800 dark:text-gray-100"> </div>
@if($project->status === 'pending')
Ausstehend
@elseif($project->status === 'active')
Aktiv
@else
Archiviert
@endif
</dd>
</div> </div>
<div> </div>
<dt class="text-xs font-semibold text-gray-500 uppercase mb-1">Erstellt am</dt> <div class="flex flex-wrap items-center sm:justify-end space-x-2">
<dd class="text-sm text-gray-800 dark:text-gray-100"> <div
{{ $project->created_at->format('d.m.Y') }} class="text-xs inline-flex items-center font-medium border border-gray-200 dark:border-gray-700/60 text-gray-600 dark:text-gray-400 rounded-full text-center px-2.5 py-1">
</dd> <a target="_blank" href="{{ $projectProposal->website }}">Webseite</a>
</div> </div>
</dl> <div
</flux:card> class="text-xs inline-flex font-medium uppercase bg-green-500/20 text-green-700 rounded-full text-center px-2.5 py-1">
</div> {{ number_format($projectProposal->support_in_sats, 0, ',', '.') }} Sats
</div>
</div>
</div>
<!-- Right column --> <figure class="mb-6">
<div class="flex-1 md:ml-8"> <img class="rounded-sm h-48" src="{{ $projectProposal->getFirstMediaUrl('main') }}"
<flux:card> alt="Picture">
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4"> </figure>
Beschreibung
</h2> <hr class="my-6 border-t border-gray-100 dark:border-gray-700/60">
<p class="text-sm text-gray-800 dark:text-gray-100">
{{ $project->description ?? 'Keine Beschreibung' }} @if($isAllowed && !$projectProposal->accepted)
</p> <div class="space-y-4">
</flux:card> <div class="bg-white dark:bg-gray-800 p-5 shadow-sm rounded-xl lg:w-72 xl:w-80">
@if(!$ownVoteExists)
<div class="space-y-2">
<flux:button wire:click="handleApprove" class="w-full">
<i class="fill-current shrink-0 fa-sharp-duotone fa-solid fa-thumbs-up mr-2"></i>
Zustimmen
</flux:button>
<flux:button wire:click="handleNotApprove" variant="danger" class="w-full">
<i class="fill-current shrink-0 fa-sharp-duotone fa-solid fa-thumbs-down mr-2"></i>
Ablehnen
</flux:button>
</div>
@else
<p class="text-sm text-gray-700 dark:text-gray-300">Du hast bereits abgestimmt.</p>
@endif
</div>
<div class="bg-white dark:bg-gray-800 p-5 shadow-sm rounded-xl lg:w-72 xl:w-80">
<div class="text-sm font-semibold text-gray-800 dark:text-gray-100 mb-2">
Zustimmungen des Vorstands ({{ count($this->boardVotes->where('value', 1)) }})
</div>
</div>
<div class="bg-white dark:bg-gray-800 p-5 shadow-sm rounded-xl lg:w-72 xl:w-80">
<div class="text-sm font-semibold text-gray-800 dark:text-gray-100 mb-2">
Ablehnungen des Vorstands ({{ count($this->boardVotes->where('value', 0)) }})
</div>
</div>
<div class="bg-white dark:bg-gray-800 p-5 shadow-sm rounded-xl lg:w-72 xl:w-80">
<div class="text-sm font-semibold text-gray-800 dark:text-gray-100 mb-2">
Zustimmungen der übrigen Mitglieder
({{ count($this->otherVotes->where('value', 1)) }})
</div>
</div>
<div class="bg-white dark:bg-gray-800 p-5 shadow-sm rounded-xl lg:w-72 xl:w-80">
<div class="text-sm font-semibold text-gray-800 dark:text-gray-100 mb-2">
Ablehnungen der übrigen Mitglieder
({{ count($this->otherVotes->where('value', 0)) }})
</div>
</div>
</div>
@endif
</div> </div>
</div> </div>
</div> </div>