mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-01-25 04:13:17 +00:00
🗑️ Remove unused Livewire components and Blade views related to elections, member management, changelog, project support, and meetups.
This commit is contained in:
@@ -1,325 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\Election;
|
||||
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Models\Election;
|
||||
use App\Models\Profile;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
use swentel\nostr\Event\Event as NostrEvent;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Message\EventMessage;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
final class Show extends Component
|
||||
{
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public bool $showLog = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
public array $events = [];
|
||||
|
||||
public array $boardEvents = [];
|
||||
|
||||
public Election $election;
|
||||
|
||||
public array $plebs = [];
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $signThisEvent = '';
|
||||
|
||||
public bool $isNotClosed = true;
|
||||
|
||||
public array $positions = [
|
||||
'presidency' => ['icon' => 'fa-crown', 'title' => 'Präsidium'],
|
||||
'board' => ['icon' => 'fa-users', 'title' => 'Vizepräsidium'],
|
||||
];
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
'echo:votes,.newVote' => 'handleNewVote',
|
||||
];
|
||||
|
||||
#[Computed]
|
||||
public function loadedEvents(): array
|
||||
{
|
||||
return collect($this->events)
|
||||
->map(function ($event) {
|
||||
$profile = Profile::query()
|
||||
->where('pubkey', $event['pubkey'])
|
||||
->first()
|
||||
?->toArray();
|
||||
$votedFor = Profile::query()
|
||||
->where('pubkey', str($event['content'])->before(',')->toString())
|
||||
->first()
|
||||
?->toArray();
|
||||
|
||||
return [
|
||||
'id' => $event['id'],
|
||||
'kind' => $event['kind'],
|
||||
'content' => $event['content'],
|
||||
'pubkey' => $event['pubkey'],
|
||||
'tags' => $event['tags'],
|
||||
'created_at' => $event['created_at'],
|
||||
'profile' => $profile,
|
||||
'votedFor' => $votedFor,
|
||||
'type' => str($event['content'])->after(',')->toString(),
|
||||
];
|
||||
})
|
||||
->sortByDesc('created_at')
|
||||
->unique(fn ($event) => $event['pubkey'].$event['type'])
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function loadedBoardEvents(): array
|
||||
{
|
||||
return collect($this->boardEvents)
|
||||
->map(function ($event) {
|
||||
$profile = Profile::query()
|
||||
->where('pubkey', $event['pubkey'])
|
||||
->first()
|
||||
?->toArray();
|
||||
$votedFor = Profile::query()
|
||||
->where('pubkey', str($event['content'])->before(',')->toString())
|
||||
->first()
|
||||
?->toArray();
|
||||
|
||||
return [
|
||||
'id' => $event['id'],
|
||||
'kind' => $event['kind'],
|
||||
'content' => $event['content'],
|
||||
'pubkey' => $event['pubkey'],
|
||||
'tags' => $event['tags'],
|
||||
'created_at' => $event['created_at'],
|
||||
'profile' => $profile,
|
||||
'votedFor' => $votedFor,
|
||||
'type' => str($event['content'])->after(',')->toString(),
|
||||
];
|
||||
})
|
||||
->sortByDesc('created_at')
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function electionConfig(): array
|
||||
{
|
||||
$loadedEvents = $this->loadedEvents();
|
||||
|
||||
return collect(json_decode($this->election->candidates, true, 512, JSON_THROW_ON_ERROR))
|
||||
->map(function ($c) use ($loadedEvents) {
|
||||
$candidates = Profile::query()
|
||||
->whereIn('pubkey', $c['c'])
|
||||
->get()
|
||||
->map(function ($p) use ($loadedEvents, $c) {
|
||||
$votedClass = ' bg-green-500/20 text-green-700';
|
||||
$notVotedClass = ' bg-gray-500/20 text-gray-100';
|
||||
$hasVoted = $loadedEvents
|
||||
->filter(fn ($e) => $e['type'] === $c['type'] && $e['pubkey'] === $this->currentPubkey)
|
||||
->firstWhere('votedFor.pubkey', $p->pubkey);
|
||||
|
||||
return [
|
||||
'pubkey' => $p->pubkey,
|
||||
'name' => $p->name,
|
||||
'picture' => $p->picture,
|
||||
'votedClass' => $hasVoted ? $votedClass : $notVotedClass,
|
||||
];
|
||||
});
|
||||
|
||||
return [
|
||||
'type' => $c['type'],
|
||||
'c' => $c['c'],
|
||||
'candidates' => $candidates,
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function electionConfigBoard(): array
|
||||
{
|
||||
$loadedBoardEvents = $this->loadedBoardEvents();
|
||||
|
||||
return collect(json_decode($this->election->candidates, true, 512, JSON_THROW_ON_ERROR))
|
||||
->map(function ($c) use ($loadedBoardEvents) {
|
||||
$candidates = Profile::query()
|
||||
->whereIn('pubkey', $c['c'])
|
||||
->get()
|
||||
->map(function ($p) use ($loadedBoardEvents, $c) {
|
||||
$votedClass = ' bg-green-500/20 text-green-700';
|
||||
$notVotedClass = ' bg-gray-500/20 text-gray-100';
|
||||
$hasVoted = $loadedBoardEvents
|
||||
->filter(fn ($e) => $e['type'] === $c['type'] && $e['pubkey'] === $this->currentPubkey)
|
||||
->firstWhere('votedFor.pubkey', $p->pubkey);
|
||||
|
||||
return [
|
||||
'pubkey' => $p->pubkey,
|
||||
'name' => $p->name,
|
||||
'picture' => $p->picture,
|
||||
'votedClass' => $hasVoted ? $votedClass : $notVotedClass,
|
||||
'hasVoted' => $hasVoted,
|
||||
];
|
||||
});
|
||||
|
||||
return [
|
||||
'type' => $c['type'],
|
||||
'c' => $c['c'],
|
||||
'candidates' => $candidates,
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function mount(Election $election): void
|
||||
{
|
||||
$this->election = $election;
|
||||
$this->plebs = EinundzwanzigPleb::query()
|
||||
->with(['profile'])
|
||||
->whereIn('association_status', [3, 4])
|
||||
->orderBy('association_status', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
$this->loadEvents();
|
||||
$this->loadBoardEvents();
|
||||
if ($this->election->end_time?->isPast() || ! config('services.voting')) {
|
||||
$this->isNotClosed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedSearch($value): void
|
||||
{
|
||||
$this->plebs = EinundzwanzigPleb::query()
|
||||
->with(['profile'])
|
||||
->whereIn('association_status', [3, 4])
|
||||
->where(fn ($query) => $query
|
||||
->where('pubkey', 'like', "%$value%")
|
||||
->orWhereHas('profile', fn ($query) => $query->where('name', 'ilike', "%$value%")))
|
||||
->orderBy('association_status', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
$logPubkeys = [
|
||||
'0adf67475ccc5ca456fd3022e46f5d526eb0af6284bf85494c0dd7847f3e5033',
|
||||
'430169631f2f0682c60cebb4f902d68f0c71c498fd1711fd982f052cf1fd4279',
|
||||
];
|
||||
if (in_array($this->currentPubkey, $logPubkeys, true)) {
|
||||
$this->showLog = true;
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function handleNewVote(): void
|
||||
{
|
||||
$this->loadEvents();
|
||||
$this->loadBoardEvents();
|
||||
}
|
||||
|
||||
public function loadEvents(): void
|
||||
{
|
||||
$this->events = $this->loadNostrEvents([32122]);
|
||||
}
|
||||
|
||||
public function loadBoardEvents(): void
|
||||
{
|
||||
$this->boardEvents = $this->loadNostrEvents([2121]);
|
||||
}
|
||||
|
||||
public function loadNostrEvents($kinds): array
|
||||
{
|
||||
$subscription = new Subscription;
|
||||
$subscriptionId = $subscription->setId();
|
||||
$filter = new Filter;
|
||||
$filter->setKinds($kinds);
|
||||
$requestMessage = new RequestMessage($subscriptionId, [$filter]);
|
||||
$relaySet = new RelaySet;
|
||||
$relaySet->setRelays([new Relay(config('services.relay'))]);
|
||||
$request = new Request($relaySet, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
return collect($response[config('services.relay')])
|
||||
->map(function ($event) {
|
||||
if (! isset($event->event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $event->event->id,
|
||||
'kind' => $event->event->kind,
|
||||
'content' => $event->event->content,
|
||||
'pubkey' => $event->event->pubkey,
|
||||
'tags' => $event->event->tags,
|
||||
'created_at' => $event->event->created_at,
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function vote($pubkey, $type, $board = false): void
|
||||
{
|
||||
if ($this->election->end_time?->isPast()) {
|
||||
$this->isNotClosed = false;
|
||||
|
||||
return;
|
||||
}
|
||||
$note = new NostrEvent;
|
||||
$note->setKind($board ? 2121 : 32122);
|
||||
if (! $board) {
|
||||
$dTag = sprintf('%s,%s,%s', $this->currentPleb->pubkey, date('Y'), $type);
|
||||
$note->setTags([['d', $dTag]]);
|
||||
}
|
||||
$note->setContent("$pubkey,$type");
|
||||
$this->signThisEvent = $note->toJson();
|
||||
}
|
||||
|
||||
public function checkElection(): void
|
||||
{
|
||||
if ($this->election->end_time?->isPast()) {
|
||||
$this->isNotClosed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function signEvent($event): void
|
||||
{
|
||||
$note = new NostrEvent;
|
||||
$note->setId($event['id']);
|
||||
$note->setSignature($event['sig']);
|
||||
$note->setKind($event['kind']);
|
||||
$note->setContent($event['content']);
|
||||
$note->setPublicKey($event['pubkey']);
|
||||
$note->setTags($event['tags']);
|
||||
$note->setCreatedAt($event['created_at']);
|
||||
$eventMessage = new EventMessage($note);
|
||||
$relay = new Relay(config('services.relay'));
|
||||
$relay->setMessage($eventMessage);
|
||||
$relay->send();
|
||||
\App\Support\Broadcast::on('votes')->as('newVote')->sendNow();
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\News;
|
||||
|
||||
use App\Livewire\Forms\NotificationForm;
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Models\Notification;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use WireUi\Actions\Notification as WireNotification;
|
||||
|
||||
final class Index extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public NotificationForm $form;
|
||||
|
||||
public ?\Illuminate\Http\UploadedFile $file = null;
|
||||
|
||||
public \Illuminate\Database\Eloquent\Collection $news;
|
||||
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public bool $canEdit = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $this->currentPubkey)->first();
|
||||
if (in_array($this->currentPleb->npub, config('einundzwanzig.config.current_board'), true)) {
|
||||
$this->canEdit = true;
|
||||
}
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
$this->refreshNews();
|
||||
}
|
||||
|
||||
public function refreshNews(): void
|
||||
{
|
||||
$this->news = Notification::query()
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
if (in_array($this->currentPleb->npub, config('einundzwanzig.config.current_board'), true)) {
|
||||
$this->canEdit = true;
|
||||
}
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->form->validate();
|
||||
|
||||
$this->validate([
|
||||
'file' => 'required|file|mimes:pdf|max:1024',
|
||||
]);
|
||||
|
||||
$notification = Notification::query()
|
||||
->orderBy('created_at', 'desc')
|
||||
->create([
|
||||
'einundzwanzig_pleb_id' => $this->currentPleb->id,
|
||||
'category' => $this->form->category,
|
||||
'name' => $this->form->name,
|
||||
'description' => $this->form->description,
|
||||
]);
|
||||
|
||||
$notification
|
||||
->addMedia($this->file->getRealPath())
|
||||
->usingName($this->file->getClientOriginalName())
|
||||
->toMediaCollection('pdf');
|
||||
|
||||
$this->form->reset();
|
||||
$this->file = null;
|
||||
|
||||
$this->refreshNews();
|
||||
}
|
||||
|
||||
public function delete($id): void
|
||||
{
|
||||
$notification = new WireNotification($this);
|
||||
$notification->confirm([
|
||||
'title' => 'Post löschen',
|
||||
'message' => 'Bist du sicher, dass du diesen Post löschen möchtest?',
|
||||
'accept' => [
|
||||
'label' => 'Ja, löschen',
|
||||
'method' => 'deleteNow',
|
||||
'params' => $id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteNow($id): void
|
||||
{
|
||||
$notification = Notification::query()->find($id);
|
||||
$notification->delete();
|
||||
$this->refreshNews();
|
||||
}
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association;
|
||||
|
||||
use App\Enums\AssociationStatus;
|
||||
use App\Livewire\Forms\ApplicationForm;
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
use swentel\nostr\Event\Event as NostrEvent;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Message\EventMessage;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Sign\Sign;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
use WireUi\Actions\Notification;
|
||||
|
||||
final class Profile extends Component
|
||||
{
|
||||
public ApplicationForm $form;
|
||||
|
||||
public bool $no = false;
|
||||
|
||||
public bool $showEmail = true;
|
||||
|
||||
public string $fax = '';
|
||||
|
||||
public string $email = '';
|
||||
|
||||
public array $yearsPaid = [];
|
||||
|
||||
public array $events = [];
|
||||
|
||||
public \Illuminate\Database\Eloquent\Collection $payments;
|
||||
|
||||
public int $amountToPay;
|
||||
|
||||
public bool $currentYearIsPaid = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->currentPleb = EinundzwanzigPleb::query()
|
||||
->with([
|
||||
'paymentEvents' => fn ($query) => $query->where('year', date('Y')),
|
||||
])
|
||||
->where('pubkey', $this->currentPubkey)->first();
|
||||
$this->email = $this->currentPleb->email;
|
||||
$this->no = $this->currentPleb->no_email;
|
||||
$this->showEmail = ! $this->no;
|
||||
if ($this->currentPleb->association_status === AssociationStatus::ACTIVE) {
|
||||
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
||||
}
|
||||
if ($this->currentPleb->paymentEvents->count() < 1) {
|
||||
$this->createPaymentEvent();
|
||||
$this->currentPleb->load('paymentEvents');
|
||||
}
|
||||
$this->loadEvents();
|
||||
$this->listenForPayment();
|
||||
}
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = EinundzwanzigPleb::query()
|
||||
->with([
|
||||
'paymentEvents' => fn ($query) => $query->where('year', date('Y')),
|
||||
])
|
||||
->where('pubkey', $pubkey)->first();
|
||||
$this->email = $this->currentPleb->email;
|
||||
$this->no = $this->currentPleb->no_email;
|
||||
$this->showEmail = ! $this->no;
|
||||
if ($this->currentPleb->association_status === AssociationStatus::ACTIVE) {
|
||||
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
||||
}
|
||||
if ($this->currentPleb->paymentEvents->count() < 1) {
|
||||
$this->createPaymentEvent();
|
||||
$this->currentPleb->load('paymentEvents');
|
||||
}
|
||||
$this->loadEvents();
|
||||
$this->listenForPayment();
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
NostrAuth::logout();
|
||||
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
$this->yearsPaid = [];
|
||||
$this->events = [];
|
||||
$this->payments = [];
|
||||
$this->qrCode = null;
|
||||
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
||||
$this->currentYearIsPaid = false;
|
||||
}
|
||||
|
||||
public function updatedNo(): void
|
||||
{
|
||||
$this->showEmail = ! $this->no;
|
||||
$this->currentPleb->update([
|
||||
'no_email' => $this->no,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updatedFax(): void
|
||||
{
|
||||
$this->js('alert("Markus Turm wird sich per Fax melden!")');
|
||||
}
|
||||
|
||||
public function saveEmail(): void
|
||||
{
|
||||
$this->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
$this->currentPleb->update([
|
||||
'email' => $this->email,
|
||||
]);
|
||||
$notification = new Notification($this);
|
||||
$notification->success('E-Mail Adresse gespeichert.');
|
||||
}
|
||||
|
||||
public function pay($comment): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$paymentEvent = $this->currentPleb
|
||||
->paymentEvents()
|
||||
->where('year', date('Y'))
|
||||
->first();
|
||||
if ($paymentEvent->btc_pay_invoice) {
|
||||
return redirect('https://pay.einundzwanzig.space/i/'.$paymentEvent->btc_pay_invoice);
|
||||
}
|
||||
try {
|
||||
$response = \Illuminate\Support\Facades\Http::withHeaders([
|
||||
'Authorization' => 'token '.config('services.btc_pay.api_key'),
|
||||
])->post(
|
||||
'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices',
|
||||
[
|
||||
'amount' => $this->amountToPay,
|
||||
'metadata' => [
|
||||
'orderId' => $comment,
|
||||
'orderUrl' => url()->route('association.profile'),
|
||||
'itemDesc' => 'Mitgliedsbeitrag '.date('Y').' von nostr:'.$this->currentPleb->npub,
|
||||
'posData' => [
|
||||
'event' => $paymentEvent->event_id,
|
||||
'pubkey' => $this->currentPleb->pubkey,
|
||||
'npub' => $this->currentPleb->npub,
|
||||
],
|
||||
],
|
||||
'checkout' => [
|
||||
'expirationMinutes' => 60 * 24,
|
||||
'redirectURL' => url()->route('association.profile'),
|
||||
'redirectAutomatically' => true,
|
||||
'defaultLanguage' => 'de',
|
||||
],
|
||||
],
|
||||
)->throw();
|
||||
$paymentEvent->btc_pay_invoice = $response->json()['id'];
|
||||
$paymentEvent->save();
|
||||
|
||||
return redirect($response->json()['checkoutLink']);
|
||||
} catch (\Exception $e) {
|
||||
$notification = new Notification($this);
|
||||
$notification->error(
|
||||
'Fehler beim Erstellen der Rechnung. Bitte versuche es später erneut: '.$e->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function listenForPayment(): void
|
||||
{
|
||||
$paymentEvent = $this->currentPleb
|
||||
->paymentEvents()
|
||||
->where('year', date('Y'))
|
||||
->first();
|
||||
if ($paymentEvent->btc_pay_invoice) {
|
||||
$response = \Illuminate\Support\Facades\Http::withHeaders([
|
||||
'Authorization' => 'token '.config('services.btc_pay.api_key'),
|
||||
])
|
||||
->get(
|
||||
'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices/'.$paymentEvent->btc_pay_invoice,
|
||||
);
|
||||
if ($response->json()['status'] === 'Expired') {
|
||||
$paymentEvent->btc_pay_invoice = null;
|
||||
$paymentEvent->paid = false;
|
||||
$paymentEvent->save();
|
||||
}
|
||||
if ($response->json()['status'] === 'Settled') {
|
||||
$paymentEvent->paid = true;
|
||||
$paymentEvent->save();
|
||||
$this->currentYearIsPaid = true;
|
||||
}
|
||||
}
|
||||
if ($paymentEvent->paid) {
|
||||
$this->currentYearIsPaid = true;
|
||||
}
|
||||
$paymentEvent = $paymentEvent->refresh();
|
||||
$this->payments = $this->currentPleb
|
||||
->paymentEvents()
|
||||
->where('paid', true)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function save($type): void
|
||||
{
|
||||
$this->form->validate();
|
||||
if (! $this->form->check) {
|
||||
$this->js('alert("Du musst den Statuten zustimmen.")');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->currentPleb
|
||||
->update([
|
||||
'association_status' => $type,
|
||||
]);
|
||||
}
|
||||
|
||||
public function createPaymentEvent(): void
|
||||
{
|
||||
$note = new NostrEvent;
|
||||
$note->setKind(32121);
|
||||
$note->setContent(
|
||||
'Dieses Event dient der Zahlung des Mitgliedsbeitrags für das Jahr '.date(
|
||||
'Y',
|
||||
).'. Bitte bezahle den Betrag von '.number_format($this->amountToPay, 0, ',', '.').' Satoshis.',
|
||||
);
|
||||
$note->setTags([
|
||||
['d', $this->currentPleb->pubkey.','.date('Y')],
|
||||
['zap', 'daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6', config('services.relay'), '1'],
|
||||
]);
|
||||
$signer = new Sign;
|
||||
$signer->signEvent($note, config('services.nostr'));
|
||||
|
||||
$eventMessage = new EventMessage($note);
|
||||
|
||||
$relayUrl = config('services.relay');
|
||||
$relay = new Relay($relayUrl);
|
||||
$relay->setMessage($eventMessage);
|
||||
$result = $relay->send();
|
||||
|
||||
$this->currentPleb->paymentEvents()->create([
|
||||
'year' => date('Y'),
|
||||
'event_id' => $result->eventId,
|
||||
'amount' => $this->amountToPay,
|
||||
]);
|
||||
}
|
||||
|
||||
public function loadEvents(): void
|
||||
{
|
||||
$subscription = new Subscription;
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter;
|
||||
$filter1->setKinds([32121]);
|
||||
$filter1->setAuthors(['daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6']);
|
||||
$filters = [$filter1];
|
||||
|
||||
$requestMessage = new RequestMessage($subscriptionId, $filters);
|
||||
|
||||
$relays = [
|
||||
new Relay(config('services.relay')),
|
||||
];
|
||||
$relaySet = new RelaySet;
|
||||
$relaySet->setRelays($relays);
|
||||
|
||||
$request = new Request($relaySet, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
$this->events = collect($response[config('services.relay')])
|
||||
->map(function ($event) {
|
||||
if (! isset($event->event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $event->event->id,
|
||||
'kind' => $event->event->kind,
|
||||
'content' => $event->event->content,
|
||||
'pubkey' => $event->event->pubkey,
|
||||
'tags' => $event->event->tags,
|
||||
'created_at' => $event->event->created_at,
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->unique('id')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\ProjectSupport\Form;
|
||||
|
||||
use App\Livewire\Forms\ProjectProposalForm;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
final class Create extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public ProjectProposalForm $form;
|
||||
|
||||
public ?\Illuminate\Http\UploadedFile $image = null;
|
||||
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $this->currentPubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function save(): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$this->form->validate();
|
||||
|
||||
$projectProposal = \App\Models\ProjectProposal::query()->create([
|
||||
...$this->form->all(),
|
||||
'einundzwanzig_pleb_id' => $this->currentPleb->id,
|
||||
]);
|
||||
if ($this->image) {
|
||||
$this->validate([
|
||||
'image' => 'image|max:1024',
|
||||
]);
|
||||
$projectProposal
|
||||
->addMedia($this->image->getRealPath())
|
||||
->toMediaCollection('main');
|
||||
}
|
||||
|
||||
return redirect()->route('association.projectSupport');
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\ProjectSupport\Form;
|
||||
|
||||
use App\Livewire\Forms\ProjectProposalForm;
|
||||
use App\Models\ProjectProposal;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
final class Edit extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public ProjectProposalForm $form;
|
||||
|
||||
public ?ProjectProposal $projectProposal = null;
|
||||
|
||||
public ?\Illuminate\Http\UploadedFile $image = null;
|
||||
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(ProjectProposal $projectProposal): void
|
||||
{
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $this->currentPubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
$this->form->fill($projectProposal->toArray());
|
||||
$this->projectProposal = $projectProposal;
|
||||
$this->image = $projectProposal->getFirstMedia('main');
|
||||
}
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function save(): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$this->form->validate();
|
||||
if ($this->image && method_exists($this->image, 'temporaryUrl')) {
|
||||
$this->validate([
|
||||
'image' => 'nullable|image|max:1024',
|
||||
]);
|
||||
$this->projectProposal
|
||||
->addMedia($this->image->getRealPath())
|
||||
->toMediaCollection('main');
|
||||
}
|
||||
|
||||
$this->projectProposal->update([
|
||||
...$this->form->except('id', 'slug'),
|
||||
]);
|
||||
|
||||
return redirect()->route('association.projectSupport');
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\ProjectSupport;
|
||||
|
||||
use App\Models\ProjectProposal;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
use WireUi\Actions\Notification;
|
||||
|
||||
final class Index extends Component
|
||||
{
|
||||
public string $activeFilter = 'all';
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public \Illuminate\Database\Eloquent\Collection $projects;
|
||||
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?\App\Models\EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadProjects();
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $this->currentPubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->loadProjects();
|
||||
}
|
||||
|
||||
public function loadProjects(): void
|
||||
{
|
||||
$this->projects = ProjectProposal::query()
|
||||
->with([
|
||||
'einundzwanzigPleb.profile',
|
||||
'votes',
|
||||
])
|
||||
->where(function ($query) {
|
||||
$query
|
||||
->where('name', 'ilike', '%'.$this->search.'%')
|
||||
->orWhere('description', 'ilike', '%'.$this->search.'%')
|
||||
->orWhereHas('einundzwanzigPleb.profile', function ($q) {
|
||||
$q->where('name', 'ilike', '%'.$this->search.'%');
|
||||
});
|
||||
})
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function confirmDelete($id): void
|
||||
{
|
||||
$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,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function setFilter($filter): void
|
||||
{
|
||||
$this->activeFilter = $filter;
|
||||
}
|
||||
|
||||
public function delete($id): void
|
||||
{
|
||||
ProjectProposal::query()->findOrFail($id)->delete();
|
||||
$this->loadProjects();
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\ProjectSupport;
|
||||
|
||||
use App\Livewire\Forms\VoteForm;
|
||||
use App\Models\ProjectProposal;
|
||||
use App\Models\Vote;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
|
||||
final class Show extends Component
|
||||
{
|
||||
public VoteForm $form;
|
||||
|
||||
public ?ProjectProposal $projectProposal = null;
|
||||
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?\App\Models\EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
public bool $ownVoteExists = false;
|
||||
|
||||
public \Illuminate\Database\Eloquent\Collection $boardVotes;
|
||||
|
||||
public \Illuminate\Database\Eloquent\Collection $otherVotes;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(ProjectProposal $projectProposal): void
|
||||
{
|
||||
$this->projectProposal = $projectProposal;
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->handleNostrLoggedIn($this->currentPubkey);
|
||||
}
|
||||
$this->boardVotes = $this->getBoardVotes();
|
||||
$this->otherVotes = $this->getOtherVotes();
|
||||
}
|
||||
|
||||
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 handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function getBoardVotes(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
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 getOtherVotes(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
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 approve(): void
|
||||
{
|
||||
Vote::query()->updateOrCreate([
|
||||
'project_proposal_id' => $this->projectProposal->id,
|
||||
'einundzwanzig_pleb_id' => $this->currentPleb->id,
|
||||
], [
|
||||
'value' => true,
|
||||
]);
|
||||
$this->form->reset();
|
||||
$this->ownVoteExists = true;
|
||||
$this->boardVotes = $this->getBoardVotes();
|
||||
$this->otherVotes = $this->getOtherVotes();
|
||||
}
|
||||
|
||||
public function notApprove(): void
|
||||
{
|
||||
$this->form->validate();
|
||||
|
||||
Vote::query()->updateOrCreate([
|
||||
'project_proposal_id' => $this->projectProposal->id,
|
||||
'einundzwanzig_pleb_id' => $this->currentPleb->id,
|
||||
], [
|
||||
'value' => false,
|
||||
]);
|
||||
$this->form->reset();
|
||||
$this->ownVoteExists = true;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Livewire\Component;
|
||||
|
||||
final class Changelog extends Component
|
||||
{
|
||||
public array $entries = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$process = Process::fromShellCommandline('git log -n1000 --pretty=format:"%H|%s|%an|%ad" --date=format:"%Y-%m-%d %H:%M:%S"');
|
||||
$process->run();
|
||||
$output = $process->getOutput();
|
||||
$lines = explode("\n", trim($output));
|
||||
$entries = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
[$hash, $message, $author, $date] = explode('|', $line);
|
||||
$entries[] = [
|
||||
'hash' => $hash,
|
||||
'message' => $message,
|
||||
'author' => $author,
|
||||
'date' => $date,
|
||||
];
|
||||
}
|
||||
$this->entries = $entries;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\EinundzwanzigFeed;
|
||||
|
||||
use App\Models\Event;
|
||||
use Livewire\Component;
|
||||
|
||||
final class Index extends Component
|
||||
{
|
||||
public array $events = [];
|
||||
|
||||
public bool $newEvents = false;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->events = Event::query()
|
||||
->where('type', 'root')
|
||||
->orderBy('created_at', 'desc')
|
||||
->with([
|
||||
'renderedEvent',
|
||||
])
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function hydrate(): void
|
||||
{
|
||||
if ($this->newEvents) {
|
||||
$this->loadMore();
|
||||
}
|
||||
}
|
||||
|
||||
#[Rule('echo:events,.newEvents')]
|
||||
public function updated(): void
|
||||
{
|
||||
$this->newEvents = true;
|
||||
}
|
||||
|
||||
public function loadMore(): void
|
||||
{
|
||||
$this->newEvents = false;
|
||||
$this->events = Event::query()
|
||||
->where('type', 'root')
|
||||
->orderBy('created_at', 'desc')
|
||||
->with([
|
||||
'renderedEvent',
|
||||
])
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Meetups;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
final class Grid extends Component
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Meetups;
|
||||
|
||||
use Livewire\Component;
|
||||
use swentel\nostr\Event\Event as NostrEvent;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Message\EventMessage;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
final class Mockup extends Component
|
||||
{
|
||||
public array $events = [];
|
||||
|
||||
public string $title = '';
|
||||
|
||||
public string $description = '';
|
||||
|
||||
public string $signThisEvent = '';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadEvents();
|
||||
}
|
||||
|
||||
public function loadEvents(): void
|
||||
{
|
||||
$subscription = new Subscription;
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter;
|
||||
$filter1->setKinds([31924]);
|
||||
$filter1->setLimit(25);
|
||||
$filters = [$filter1];
|
||||
|
||||
$requestMessage = new RequestMessage($subscriptionId, $filters);
|
||||
|
||||
$relays = [
|
||||
new Relay('ws://nostream:8008'),
|
||||
];
|
||||
$relaySet = new RelaySet;
|
||||
$relaySet->setRelays($relays);
|
||||
|
||||
$request = new Request($relaySet, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
$this->events = collect($response['ws://nostream:8008'])
|
||||
->map(function ($event) {
|
||||
if (! isset($event->event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $event->event->id,
|
||||
'kind' => $event->event->kind,
|
||||
'content' => $event->event->content,
|
||||
'pubkey' => $event->event->pubkey,
|
||||
'tags' => $event->event->tags,
|
||||
'created_at' => $event->event->created_at,
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$note = new NostrEvent;
|
||||
$note->setContent($this->description);
|
||||
$note->setKind(31924);
|
||||
$note->setTags([
|
||||
['d', str()->uuid()->toString()],
|
||||
['title', $this->title],
|
||||
]);
|
||||
$this->signThisEvent = $note->toJson();
|
||||
}
|
||||
|
||||
public function signEvent($event): void
|
||||
{
|
||||
$note = new NostrEvent;
|
||||
$note->setId($event['id']);
|
||||
$note->setSignature($event['sig']);
|
||||
$note->setKind($event['kind']);
|
||||
$note->setContent($event['content']);
|
||||
$note->setPublicKey($event['pubkey']);
|
||||
$note->setTags($event['tags']);
|
||||
$note->setCreatedAt($event['created_at']);
|
||||
$eventMessage = new EventMessage($note);
|
||||
$relayUrl = 'ws://nostream:8008';
|
||||
$relay = new Relay($relayUrl);
|
||||
$relay->setMessage($eventMessage);
|
||||
$relay->send();
|
||||
|
||||
$this->title = '';
|
||||
$this->description = '';
|
||||
$this->loadEvents();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Meetups;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
final class Table extends Component
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Meetups;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
final class Worldmap extends Component
|
||||
{
|
||||
public array $markers = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->markers = [];
|
||||
}
|
||||
|
||||
public function filterByMarker($id): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
final class Welcome extends Component
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\Election;
|
||||
|
||||
use App\Models\Election;
|
||||
use Livewire\Component;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
@@ -11,12 +9,13 @@ use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
final class Admin extends Component
|
||||
{
|
||||
new class extends Component {
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?\App\Models\EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
public ?array $votes = null;
|
||||
|
||||
public ?array $boardVotes = null;
|
||||
@@ -168,4 +167,80 @@ final class Admin extends Component
|
||||
->filter()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
@php
|
||||
$positions = [
|
||||
'presidency' => ['icon' => 'fa-crown', 'title' => 'Präsidium'],
|
||||
'board' => ['icon' => 'fa-users', 'title' => 'Vorstandsmitglieder'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@if($isAllowed)
|
||||
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" x-data="electionAdminCharts()">
|
||||
|
||||
<!-- Dashboard actions -->
|
||||
<div class="sm:flex sm:justify-between sm:items-center mb-8">
|
||||
|
||||
<!-- Left: Title -->
|
||||
<div class="mb-4 sm:mb-0">
|
||||
<h1 class="text-2xl md:text-3xl text-gray-800 dark:text-gray-100 font-bold">
|
||||
Wahl des Vorstands {{ $election->year }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@php
|
||||
$president = $positions['presidency'];
|
||||
$board = $positions['board'];
|
||||
@endphp
|
||||
|
||||
<!-- Cards -->
|
||||
<div class="grid gap-y-4">
|
||||
<div wire:key="presidency" wire:ignore
|
||||
class="flex flex-col bg-white dark:bg-gray-800 shadow-sm rounded-xl">
|
||||
<header class="px-5 py-4 border-b border-gray-100 dark:border-gray-700/60">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100"><i
|
||||
class="fa-sharp-duotone fa-solid {{ $president['icon'] }} w-5 h-5 fill-current text-white mr-4"></i>{{ $president['title'] }}
|
||||
</h2>
|
||||
</header>
|
||||
<div class="grow">
|
||||
<!-- Change| height attribute to adjust chart height -->
|
||||
<canvas x-ref="chart_presidency" width="724" height="288"
|
||||
style="display: block; box-sizing: border-box; height: 288px; width: 724px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div wire:key="board" wire:ignore
|
||||
class="flex flex-col bg-white dark:bg-gray-800 shadow-sm rounded-xl">
|
||||
<header class="px-5 py-4 border-b border-gray-100 dark:border-gray-700/60">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100"><i
|
||||
class="fa-sharp-duotone fa-solid {{ $board['icon'] }} w-5 h-5 fill-current text-white mr-4"></i>{{ $board['title'] }}
|
||||
</h2>
|
||||
</header>
|
||||
<div class="grow">
|
||||
<!-- Change| height attribute to adjust chart height -->
|
||||
<canvas x-ref="chart_board" width="724" height="288"
|
||||
style="display: block; box-sizing: border-box; height: 288px; width: 724px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@else
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div class="bg-white dark:bg-[#1B1B1B] shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">Mitglieder</h3>
|
||||
<p class="mt-1 max-w">
|
||||
Du bist nicht berechtigt, Mitglieder zu bearbeiten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,14 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\Election;
|
||||
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Models\Election;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
|
||||
final class Index extends Component
|
||||
{
|
||||
new class extends Component {
|
||||
public $layout = 'layouts.app';
|
||||
public $title = __('Wahlen');
|
||||
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
@@ -68,4 +68,37 @@ final class Index extends Component
|
||||
$electionModel->candidates = $election['candidates'];
|
||||
$electionModel->save();
|
||||
}
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
@if($isAllowed)
|
||||
<div class="relative flex h-full">
|
||||
@foreach($elections as $election)
|
||||
<div class="w-full sm:w-1/3 p-4" wire:key="election-{{ $loop->index }}">
|
||||
<div class="shadow-lg rounded-lg overflow-hidden">
|
||||
{{ $election['year'] }}
|
||||
</div>
|
||||
<div class="shadow-lg rounded-lg overflow-hidden">
|
||||
<x-textarea wire:model="elections.{{ $loop->index }}.candidates" rows="25"
|
||||
label="candidates" placeholder=""/>
|
||||
</div>
|
||||
<div class="py-2">
|
||||
<x-button label="Speichern" wire:click="saveElection({{ $loop->index }})" wire:loading.attr="disabled"/>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div class="bg-white dark:bg-[#1B1B1B] shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">Einstellungen</h3>
|
||||
<p class="mt-1 max-w">
|
||||
Du bist nicht berechtigt, die Einstellungen zu bearbeiten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,148 +1,476 @@
|
||||
<x-layouts.app
|
||||
:seo="new \RalphJSmit\Laravel\SEO\Support\SEOData(title: 'Wahlen ' . $election->year, description: 'Wahlen des Vereins im Jahr ' . $election->year)"
|
||||
>
|
||||
<div>
|
||||
@if($isAllowed)
|
||||
<div x-cloak class="relative flex h-full" x-data="nostrApp(@this)"
|
||||
wire:poll.600000ms="checkElection">
|
||||
<?php
|
||||
|
||||
<!-- Inbox sidebar -->
|
||||
<div id="inbox-sidebar"
|
||||
class="absolute z-20 top-0 bottom-0 w-full md:w-auto md:static md:top-auto md:bottom-auto -mr-px md:translate-x-0 transition-transform duration-200 ease-in-out"
|
||||
:class="inboxSidebarOpen ? 'translate-x-0' : '-translate-x-full'">
|
||||
<div
|
||||
class="sticky top-16 bg-white dark:bg-[#1B1B1B] overflow-x-hidden overflow-y-auto no-scrollbar shrink-0 border-r border-gray-200 dark:border-gray-700/60 md:w-[18rem] xl:w-[20rem] h-[calc(100dvh-64px)]">
|
||||
use App\Models\Election;
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Models\Profile;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
use swentel\nostr\Event\Event as NostrEvent;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Message\EventMessage;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
<!-- #Marketing group -->
|
||||
<div>
|
||||
<!-- Group header -->
|
||||
<div class="sticky top-0 z-10">
|
||||
<div
|
||||
class="flex items-center bg-white dark:bg-[#1B1B1B] border-b border-gray-200 dark:border-gray-700/60 px-5 h-16">
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<!-- Channel menu -->
|
||||
<div class="relative" x-data="{ open: false }">
|
||||
<button class="grow flex items-center truncate" aria-haspopup="true"
|
||||
@click.prevent="open = !open" :aria-expanded="open">
|
||||
<div class="truncate">
|
||||
<span
|
||||
class="font-semibold text-gray-800 dark:text-gray-100">2024</span>
|
||||
</div>
|
||||
<svg
|
||||
class="w-3 h-3 shrink-0 ml-1 fill-current text-gray-400 dark:text-gray-500"
|
||||
viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="origin-top-right z-10 absolute top-full left-0 min-w-60 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700/60 py-1.5 rounded-lg shadow-lg overflow-hidden mt-1"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false"
|
||||
x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200 transform"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-out duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-cloak>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200 block py-1.5 px-3"
|
||||
href="#0" @click="open = false" @focus="open = true"
|
||||
@focusout="open = false">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center truncate">
|
||||
<div class="truncate">2024</div>
|
||||
</div>
|
||||
<svg
|
||||
class="w-3 h-3 shrink-0 fill-current text-orange-500 ml-1"
|
||||
viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M10.28 1.28L3.989 7.575 1.695 5.28A1 1 0 00.28 6.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 1.28z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
new class extends Component {
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public bool $showLog = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
public array $events = [];
|
||||
|
||||
public array $boardEvents = [];
|
||||
|
||||
public Election $election;
|
||||
|
||||
public array $plebs = [];
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $signThisEvent = '';
|
||||
|
||||
public bool $isNotClosed = true;
|
||||
|
||||
public array $positions = [
|
||||
'presidency' => ['icon' => 'fa-crown', 'title' => 'Präsidium'],
|
||||
'board' => ['icon' => 'fa-users', 'title' => 'Vizepräsidium'],
|
||||
];
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
'echo:votes,.newVote' => 'handleNewVote',
|
||||
];
|
||||
|
||||
#[Computed]
|
||||
public function loadedEvents(): array
|
||||
{
|
||||
return collect($this->events)
|
||||
->map(function ($event) {
|
||||
$profile = Profile::query()
|
||||
->where('pubkey', $event['pubkey'])
|
||||
->first()
|
||||
?->toArray();
|
||||
$votedFor = Profile::query()
|
||||
->where('pubkey', str($event['content'])->before(',')->toString())
|
||||
->first()
|
||||
?->toArray();
|
||||
|
||||
return [
|
||||
'id' => $event['id'],
|
||||
'kind' => $event['kind'],
|
||||
'content' => $event['content'],
|
||||
'pubkey' => $event['pubkey'],
|
||||
'tags' => $event['tags'],
|
||||
'created_at' => $event['created_at'],
|
||||
'profile' => $profile,
|
||||
'votedFor' => $votedFor,
|
||||
'type' => str($event['content'])->after(',')->toString(),
|
||||
];
|
||||
})
|
||||
->sortByDesc('created_at')
|
||||
->unique(fn ($event) => $event['pubkey'].$event['type'])
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function loadedBoardEvents(): array
|
||||
{
|
||||
return collect($this->boardEvents)
|
||||
->map(function ($event) {
|
||||
$profile = Profile::query()
|
||||
->where('pubkey', $event['pubkey'])
|
||||
->first()
|
||||
?->toArray();
|
||||
$votedFor = Profile::query()
|
||||
->where('pubkey', str($event['content'])->before(',')->toString())
|
||||
->first()
|
||||
?->toArray();
|
||||
|
||||
return [
|
||||
'id' => $event['id'],
|
||||
'kind' => $event['kind'],
|
||||
'content' => $event['content'],
|
||||
'pubkey' => $event['pubkey'],
|
||||
'tags' => $event['tags'],
|
||||
'created_at' => $event['created_at'],
|
||||
'profile' => $profile,
|
||||
'votedFor' => $votedFor,
|
||||
'type' => str($event['content'])->after(',')->toString(),
|
||||
];
|
||||
})
|
||||
->sortByDesc('created_at')
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function electionConfig(): array
|
||||
{
|
||||
$loadedEvents = $this->loadedEvents();
|
||||
|
||||
return collect(json_decode($this->election->candidates, true, 512, JSON_THROW_ON_ERROR))
|
||||
->map(function ($c) use ($loadedEvents) {
|
||||
$candidates = Profile::query()
|
||||
->whereIn('pubkey', $c['c'])
|
||||
->get()
|
||||
->map(function ($p) use ($loadedEvents, $c) {
|
||||
$votedClass = ' bg-green-500/20 text-green-700';
|
||||
$notVotedClass = ' bg-gray-500/20 text-gray-100';
|
||||
$hasVoted = $loadedEvents
|
||||
->filter(fn ($e) => $e['type'] === $c['type'] && $e['pubkey'] === $this->currentPubkey)
|
||||
->firstWhere('votedFor.pubkey', $p->pubkey);
|
||||
|
||||
return [
|
||||
'pubkey' => $p->pubkey,
|
||||
'name' => $p->name,
|
||||
'picture' => $p->picture,
|
||||
'votedClass' => $hasVoted ? $votedClass : $notVotedClass,
|
||||
];
|
||||
});
|
||||
|
||||
return [
|
||||
'type' => $c['type'],
|
||||
'c' => $c['c'],
|
||||
'candidates' => $candidates,
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function electionConfigBoard(): array
|
||||
{
|
||||
$loadedBoardEvents = $this->loadedBoardEvents();
|
||||
|
||||
return collect(json_decode($this->election->candidates, true, 512, JSON_THROW_ON_ERROR))
|
||||
->map(function ($c) use ($loadedBoardEvents) {
|
||||
$candidates = Profile::query()
|
||||
->whereIn('pubkey', $c['c'])
|
||||
->get()
|
||||
->map(function ($p) use ($loadedBoardEvents, $c) {
|
||||
$votedClass = ' bg-green-500/20 text-green-700';
|
||||
$notVotedClass = ' bg-gray-500/20 text-gray-100';
|
||||
$hasVoted = $loadedBoardEvents
|
||||
->filter(fn ($e) => $e['type'] === $c['type'] && $e['pubkey'] === $this->currentPubkey)
|
||||
->firstWhere('votedFor.pubkey', $p->pubkey);
|
||||
|
||||
return [
|
||||
'pubkey' => $p->pubkey,
|
||||
'name' => $p->name,
|
||||
'picture' => $p->picture,
|
||||
'votedClass' => $hasVoted ? $votedClass : $notVotedClass,
|
||||
'hasVoted' => $hasVoted,
|
||||
];
|
||||
});
|
||||
|
||||
return [
|
||||
'type' => $c['type'],
|
||||
'c' => $c['c'],
|
||||
'candidates' => $candidates,
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function mount(Election $election): void
|
||||
{
|
||||
$this->election = $election;
|
||||
$this->plebs = EinundzwanzigPleb::query()
|
||||
->with(['profile'])
|
||||
->whereIn('association_status', [3, 4])
|
||||
->orderBy('association_status', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
$this->loadEvents();
|
||||
$this->loadBoardEvents();
|
||||
if ($this->election->end_time?->isPast() || ! config('services.voting')) {
|
||||
$this->isNotClosed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedSearch($value): void
|
||||
{
|
||||
$this->plebs = EinundzwanzigPleb::query()
|
||||
->with(['profile'])
|
||||
->whereIn('association_status', [3, 4])
|
||||
->where(fn ($query) => $query
|
||||
->where('pubkey', 'like', "%$value%")
|
||||
->orWhereHas('profile', fn ($query) => $query->where('name', 'ilike', "%$value%")))
|
||||
->orderBy('association_status', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
$logPubkeys = [
|
||||
'0adf67475ccc5ca456fd3022e46f5d526eb0af6284bf85494c0dd7847f3e5033',
|
||||
'430169631f2f0682c60cebb4f902d68f0c71c498fd1711fd982f052cf1fd4279',
|
||||
];
|
||||
if (in_array($this->currentPubkey, $logPubkeys, true)) {
|
||||
$this->showLog = true;
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function handleNewVote(): void
|
||||
{
|
||||
$this->loadEvents();
|
||||
$this->loadBoardEvents();
|
||||
}
|
||||
|
||||
public function loadEvents(): void
|
||||
{
|
||||
$this->events = $this->loadNostrEvents([32122]);
|
||||
}
|
||||
|
||||
public function loadBoardEvents(): void
|
||||
{
|
||||
$this->boardEvents = $this->loadNostrEvents([2121]);
|
||||
}
|
||||
|
||||
public function loadNostrEvents($kinds): array
|
||||
{
|
||||
$subscription = new Subscription;
|
||||
$subscriptionId = $subscription->setId();
|
||||
$filter = new Filter;
|
||||
$filter->setKinds($kinds);
|
||||
$requestMessage = new RequestMessage($subscriptionId, [$filter]);
|
||||
$relaySet = new RelaySet;
|
||||
$relaySet->setRelays([new Relay(config('services.relay'))]);
|
||||
$request = new Request($relaySet, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
return collect($response[config('services.relay')])
|
||||
->map(function ($event) {
|
||||
if (! isset($event->event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $event->event->id,
|
||||
'kind' => $event->event->kind,
|
||||
'content' => $event->event->content,
|
||||
'pubkey' => $event->event->pubkey,
|
||||
'tags' => $event->event->tags,
|
||||
'created_at' => $event->event->created_at,
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function vote($pubkey, $type, $board = false): void
|
||||
{
|
||||
if ($this->election->end_time?->isPast()) {
|
||||
$this->isNotClosed = false;
|
||||
|
||||
return;
|
||||
}
|
||||
$note = new NostrEvent;
|
||||
$note->setKind($board ? 2121 : 32122);
|
||||
if (! $board) {
|
||||
$dTag = sprintf('%s,%s,%s', $this->currentPleb->pubkey, date('Y'), $type);
|
||||
$note->setTags([['d', $dTag]]);
|
||||
}
|
||||
$note->setContent("$pubkey,$type");
|
||||
$this->signThisEvent = $note->toJson();
|
||||
}
|
||||
|
||||
public function checkElection(): void
|
||||
{
|
||||
if ($this->election->end_time?->isPast()) {
|
||||
$this->isNotClosed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function signEvent($event): void
|
||||
{
|
||||
$note = new NostrEvent;
|
||||
$note->setId($event['id']);
|
||||
$note->setSignature($event['sig']);
|
||||
$note->setKind($event['kind']);
|
||||
$note->setContent($event['content']);
|
||||
$note->setPublicKey($event['pubkey']);
|
||||
$note->setTags($event['tags']);
|
||||
$note->setCreatedAt($event['created_at']);
|
||||
$eventMessage = new EventMessage($note);
|
||||
$relay = new Relay(config('services.relay'));
|
||||
$relay->setMessage($eventMessage);
|
||||
$relay->send();
|
||||
\App\Support\Broadcast::on('votes')->as('newVote')->sendNow();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.association.election.show')
|
||||
->layout('layouts.app')
|
||||
->with([
|
||||
'seo' => new \RalphJSmit\Laravel\SEO\Support\SEOData(title: 'Wahlen ' . $this->election->year, description: 'Wahlen des Vereins im Jahr ' . $this->election->year)
|
||||
]);
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
@if($isAllowed)
|
||||
<div x-cloak class="relative flex h-full" x-data="nostrApp(@this)"
|
||||
wire:poll.600000ms="checkElection">
|
||||
|
||||
<!-- Inbox sidebar -->
|
||||
<div id="inbox-sidebar"
|
||||
class="absolute z-20 top-0 bottom-0 w-full md:w-auto md:static md:top-auto md:bottom-auto -mr-px md:translate-x-0 transition-transform duration-200 ease-in-out"
|
||||
:class="inboxSidebarOpen ? 'translate-x-0' : '-translate-x-full'">
|
||||
<div
|
||||
class="sticky top-16 bg-white dark:bg-[#1B1B1B] overflow-x-hidden overflow-y-auto no-scrollbar shrink-0 border-r border-gray-200 dark:border-gray-700/60 md:w-[18rem] xl:w-[20rem] h-[calc(100dvh-64px)]">
|
||||
|
||||
<!-- #Marketing group -->
|
||||
<div>
|
||||
<!-- Group header -->
|
||||
<div class="sticky top-0 z-10">
|
||||
<div class="flex items-center bg-white dark:bg-[#1B1B1B] border-b border-gray-200 dark:border-gray-700/60 px-5 h-16">
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<!-- Channel menu -->
|
||||
<div class="relative" x-data="{ open: false }">
|
||||
<button class="grow flex items-center truncate" aria-haspopup="true"
|
||||
@click.prevent="open = !open" :aria-expanded="open">
|
||||
<div class="truncate">
|
||||
<span
|
||||
class="font-semibold text-gray-800 dark:text-gray-100">2024</span>
|
||||
</div>
|
||||
<svg
|
||||
class="w-3 h-3 shrink-0 ml-1 fill-current text-gray-400 dark:text-gray-500"
|
||||
viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="origin-top-right z-10 absolute top-full left-0 min-w-60 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700/60 py-1.5 rounded-lg shadow-lg overflow-hidden mt-1"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false"
|
||||
x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200 transform"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-out duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-cloak>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200 block py-1.5 px-3"
|
||||
href="#0" @click="open = false" @focus="open = true"
|
||||
@focusout="open = false">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="grow flex items-center truncate">
|
||||
<div class="truncate">2024</div>
|
||||
</div>
|
||||
<svg
|
||||
class="w-3 h-3 shrink-0 fill-current text-orange-500 ml-1"
|
||||
viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M10.28 1.28L3.989 7.575 1.695 5.28A1 1 0 00.28 6.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 1.28z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Group body -->
|
||||
<div class="px-5 py-4">
|
||||
<!-- Search form -->
|
||||
<form class="relative">
|
||||
<label for="inbox-search" class="sr-only">Search</label>
|
||||
<input
|
||||
wire:model.live.debounce="search"
|
||||
id="inbox-search" class="form-input w-full pl-9 bg-white dark:bg-gray-800"
|
||||
type="search" placeholder="Suche…"/>
|
||||
<button class="absolute inset-0 right-auto group" type="submit" aria-label="Search">
|
||||
<svg
|
||||
class="shrink-0 fill-current text-gray-400 dark:text-gray-500 group-hover:text-gray-500 dark:group-hover:text-gray-400 ml-3 mr-2"
|
||||
width="16" height="16" viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zM7 2C4.243 2 2 4.243 2 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z"/>
|
||||
<path
|
||||
d="M15.707 14.293L13.314 11.9a8.019 8.019 0 01-1.414 1.414l2.393 2.393a.997.997 0 001.414 0 .999.999 0 000-1.414z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
<!-- Inbox -->
|
||||
<div class="mt-4">
|
||||
<div class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase mb-3">
|
||||
Plebs
|
||||
</div>
|
||||
<ul class="mb-6">
|
||||
@foreach($plebs as $pleb)
|
||||
<li class="-mx-2">
|
||||
<div class="flex w-full p-2 rounded text-left">
|
||||
<img class="w-8 h-8 rounded-full mr-2 bg-black"
|
||||
src="{{ $pleb['profile']['picture'] ?? 'https://robohash.org/test' }}"
|
||||
onerror="this.onerror=null; this.src='https://robohash.org/test';"
|
||||
width="32"
|
||||
height="32"
|
||||
alt="A"/>
|
||||
<div class="grow truncate">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<div class="truncate">
|
||||
<span
|
||||
class="text-sm font-semibold text-gray-800 dark:text-gray-100 truncate">{{ $pleb['profile']['name'] ?? $pleb['pubkey'] }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 font-medium">
|
||||
<x-badge
|
||||
:color="\App\Enums\AssociationStatus::from($pleb['association_status'])->color()"
|
||||
:label="\App\Enums\AssociationStatus::from($pleb['association_status'])->label()"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Group body -->
|
||||
<div class="px-5 py-4">
|
||||
<!-- Search form -->
|
||||
<form class="relative">
|
||||
<label for="inbox-search" class="sr-only">Search</label>
|
||||
<input
|
||||
wire:model.live.debounce="search"
|
||||
id="inbox-search" class="form-input w-full pl-9 bg-white dark:bg-gray-800"
|
||||
type="search" placeholder="Suche…"/>
|
||||
<button class="absolute inset-0 right-auto group" type="submit" aria-label="Search">
|
||||
<svg
|
||||
class="shrink-0 fill-current text-gray-400 dark:text-gray-500 group-hover:text-gray-500 dark:group-hover:text-gray-400 ml-3 mr-2"
|
||||
width="16" height="16" viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zM7 2C4.243 2 2 4.243 2 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z"/>
|
||||
<path
|
||||
d="M15.707 14.293L13.314 11.9a8.019 8.019 0 01-1.414 1.414l2.393 2.393a.997.997 0 001.414 0 .999.999 0 000-1.414z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
<!-- Inbox -->
|
||||
<div class="mt-4">
|
||||
<div class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase mb-3">
|
||||
Plebs
|
||||
</div>
|
||||
<ul class="mb-6">
|
||||
@foreach($plebs as $pleb)
|
||||
<li class="-mx-2">
|
||||
<div class="flex w-full p-2 rounded text-left">
|
||||
<img class="w-8 h-8 rounded-full mr-2 bg-black"
|
||||
src="{{ $pleb['profile']['picture'] ?? 'https://robohash.org/test' }}"
|
||||
onerror="this.onerror=null; this.src='https://robohash.org/test';"
|
||||
width="32"
|
||||
height="32"
|
||||
alt="A"/>
|
||||
<div class="grow truncate">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<div class="truncate">
|
||||
<span
|
||||
class="text-sm font-semibold text-gray-800 dark:text-gray-100 truncate">{{ $pleb['profile']['name'] ?? $pleb['pubkey'] }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="text-xs font-medium text-gray-800 dark:text-gray-100 truncate mb-0.5">
|
||||
<div class="flex items-center space-x-2 h-5">
|
||||
@foreach($positions as $name => $p)
|
||||
@php
|
||||
$votedResult = $this->loadedEvents->filter(fn ($e) => $e['pubkey'] === $pleb['pubkey'])->firstWhere('type', $name);
|
||||
@endphp
|
||||
<div class="flex space-x-2"
|
||||
wire:key="p_{{ $name }}">
|
||||
@if($votedResult)
|
||||
<i class="fa-sharp-duotone fa-solid {{ $p['icon'] }} w-4 h-4 fill-current text-green-500"></i>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 font-medium">
|
||||
<x-badge
|
||||
:color="\App\Enums\AssociationStatus::from($pleb['association_status'])->color()"
|
||||
:label="\App\Enums\AssociationStatus::from($pleb['association_status'])->label()"/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-xs font-medium text-gray-800 dark:text-gray-100 truncate mb-0.5">
|
||||
<div class="flex items-center space-x-2 h-5">
|
||||
@foreach($positions as $name => $p)
|
||||
@php
|
||||
$votedResult = $this->loadedEvents->filter(fn ($e) => $e['pubkey'] === $pleb['pubkey'])->firstWhere('type', $name);
|
||||
@endphp
|
||||
<div class="flex space-x-2"
|
||||
wire:key="p_{{ $name }}">
|
||||
@if($votedResult)
|
||||
<i class="fa-sharp-duotone fa-solid {{ $p['icon'] }} w-4 h-4 fill-current text-green-500"></i>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Inbox body -->
|
||||
@@ -441,4 +769,3 @@
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -1,13 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Association\Members;
|
||||
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
|
||||
final class Admin extends Component
|
||||
{
|
||||
new class extends Component {
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
@@ -63,4 +60,31 @@ final class Admin extends Component
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.association.members.admin')
|
||||
->layout('layouts.app')
|
||||
->title(__('Mitglieder'));
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
@if($isAllowed)
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<livewire:einundzwanzig-pleb-table/>
|
||||
</div>
|
||||
@else
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div class="bg-white dark:bg-[#1B1B1B] shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">Mitglieder</h3>
|
||||
<p class="mt-1 max-w">
|
||||
Du bist nicht berechtigt, Mitglieder zu bearbeiten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,7 +1,114 @@
|
||||
<x-layouts.app
|
||||
:seo="new \RalphJSmit\Laravel\SEO\Support\SEOData(title: 'Projekt Unterstützungen', description: 'Einundzwanzig Projektunterstützungen')"
|
||||
>
|
||||
<div>
|
||||
<?php
|
||||
|
||||
use App\Models\ProjectProposal;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
use WireUi\Actions\Notification;
|
||||
|
||||
new class extends Component {
|
||||
public string $activeFilter = 'all';
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public \Illuminate\Database\Eloquent\Collection $projects;
|
||||
|
||||
public bool $isAllowed = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?\App\Models\EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadProjects();
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $this->currentPubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->loadProjects();
|
||||
}
|
||||
|
||||
public function loadProjects(): void
|
||||
{
|
||||
$this->projects = ProjectProposal::query()
|
||||
->with([
|
||||
'einundzwanzigPleb.profile',
|
||||
'votes',
|
||||
])
|
||||
->where(function ($query) {
|
||||
$query
|
||||
->where('name', 'ilike', '%'.$this->search.'%')
|
||||
->orWhere('description', 'ilike', '%'.$this->search.'%')
|
||||
->orWhereHas('einundzwanzigPleb.profile', function ($q) {
|
||||
$q->where('name', 'ilike', '%'.$this->search.'%');
|
||||
});
|
||||
})
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
$this->isAllowed = true;
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
$this->isAllowed = false;
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
}
|
||||
|
||||
public function confirmDelete($id): void
|
||||
{
|
||||
$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,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function setFilter($filter): void
|
||||
{
|
||||
$this->activeFilter = $filter;
|
||||
}
|
||||
|
||||
public function delete($id): void
|
||||
{
|
||||
ProjectProposal::query()->findOrFail($id)->delete();
|
||||
$this->loadProjects();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.association.project-support')
|
||||
->layout('layouts.app')
|
||||
->with([
|
||||
'seo' => new \RalphJSmit\Laravel\SEO\Support\SEOData(title: 'Projekt Unterstützungen', description: 'Einundzwanzig Projektunterstützungen')
|
||||
]);
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
|
||||
<!-- Page header -->
|
||||
@@ -72,4 +179,3 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -1,6 +1,312 @@
|
||||
<x-layouts.app
|
||||
:seo="new \RalphJSmit\Laravel\SEO\Support\SEOData(title: 'Mitgliedschaft', description: 'Einundzwanzig ist, was du draus machst.')"
|
||||
>
|
||||
<?php
|
||||
|
||||
use App\Enums\AssociationStatus;
|
||||
use App\Livewire\Forms\ApplicationForm;
|
||||
use App\Models\EinundzwanzigPleb;
|
||||
use App\Support\NostrAuth;
|
||||
use Livewire\Component;
|
||||
use swentel\nostr\Event\Event as NostrEvent;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Message\EventMessage;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Sign\Sign;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
use WireUi\Actions\Notification;
|
||||
|
||||
new class extends Component {
|
||||
public ApplicationForm $form;
|
||||
|
||||
public bool $no = false;
|
||||
|
||||
public bool $showEmail = true;
|
||||
|
||||
public string $fax = '';
|
||||
|
||||
public ?string $email = '';
|
||||
|
||||
public array $yearsPaid = [];
|
||||
|
||||
public array $events = [];
|
||||
|
||||
public $payments;
|
||||
|
||||
public int $amountToPay;
|
||||
|
||||
public bool $currentYearIsPaid = false;
|
||||
|
||||
public ?string $currentPubkey = null;
|
||||
|
||||
public ?EinundzwanzigPleb $currentPleb = null;
|
||||
|
||||
public ?string $qrCode = null;
|
||||
|
||||
protected $listeners = [
|
||||
'nostrLoggedIn' => 'handleNostrLoggedIn',
|
||||
'nostrLoggedOut' => 'handleNostrLoggedOut',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
if (NostrAuth::check()) {
|
||||
$this->currentPubkey = NostrAuth::pubkey();
|
||||
$this->currentPleb = EinundzwanzigPleb::query()
|
||||
->with([
|
||||
'paymentEvents' => fn ($query) => $query->where('year', date('Y')),
|
||||
])
|
||||
->where('pubkey', $this->currentPubkey)->first();
|
||||
if ($this->currentPleb) {
|
||||
$this->email = $this->currentPleb->email;
|
||||
$this->showEmail = ! $this->no;
|
||||
if ($this->currentPleb->association_status === AssociationStatus::ACTIVE) {
|
||||
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
||||
}
|
||||
if ($this->currentPleb->paymentEvents->count() < 1) {
|
||||
$this->createPaymentEvent();
|
||||
$this->currentPleb->load('paymentEvents');
|
||||
}
|
||||
$this->loadEvents();
|
||||
$this->listenForPayment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function handleNostrLoggedIn($pubkey): void
|
||||
{
|
||||
NostrAuth::login($pubkey);
|
||||
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = EinundzwanzigPleb::query()
|
||||
->with([
|
||||
'paymentEvents' => fn ($query) => $query->where('year', date('Y')),
|
||||
])
|
||||
->where('pubkey', $pubkey)->first();
|
||||
$this->email = $this->currentPleb->email;
|
||||
$this->no = $this->currentPleb->no_email;
|
||||
$this->showEmail = ! $this->no;
|
||||
if ($this->currentPleb->association_status === AssociationStatus::ACTIVE) {
|
||||
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
||||
}
|
||||
if ($this->currentPleb->paymentEvents->count() < 1) {
|
||||
$this->createPaymentEvent();
|
||||
$this->currentPleb->load('paymentEvents');
|
||||
}
|
||||
$this->loadEvents();
|
||||
$this->listenForPayment();
|
||||
}
|
||||
|
||||
public function handleNostrLoggedOut(): void
|
||||
{
|
||||
NostrAuth::logout();
|
||||
|
||||
$this->currentPubkey = null;
|
||||
$this->currentPleb = null;
|
||||
$this->yearsPaid = [];
|
||||
$this->events = [];
|
||||
$this->payments = [];
|
||||
$this->qrCode = null;
|
||||
$this->amountToPay = config('app.env') === 'production' ? 21000 : 1;
|
||||
$this->currentYearIsPaid = false;
|
||||
}
|
||||
|
||||
public function updatedNo(): void
|
||||
{
|
||||
$this->showEmail = ! $this->no;
|
||||
$this->currentPleb->update([
|
||||
'no_email' => $this->no,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updatedFax(): void
|
||||
{
|
||||
$this->js('alert("Markus Turm wird sich per Fax melden!")');
|
||||
}
|
||||
|
||||
public function saveEmail(): void
|
||||
{
|
||||
$this->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
$this->currentPleb->update([
|
||||
'email' => $this->email,
|
||||
]);
|
||||
$notification = new Notification($this);
|
||||
$notification->success('E-Mail Adresse gespeichert.');
|
||||
}
|
||||
|
||||
public function pay($comment): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$paymentEvent = $this->currentPleb
|
||||
->paymentEvents()
|
||||
->where('year', date('Y'))
|
||||
->first();
|
||||
if ($paymentEvent->btc_pay_invoice) {
|
||||
return redirect('https://pay.einundzwanzig.space/i/'.$paymentEvent->btc_pay_invoice);
|
||||
}
|
||||
try {
|
||||
$response = \Illuminate\Support\Facades\Http::withHeaders([
|
||||
'Authorization' => 'token '.config('services.btc_pay.api_key'),
|
||||
])->post(
|
||||
'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices',
|
||||
[
|
||||
'amount' => $this->amountToPay,
|
||||
'metadata' => [
|
||||
'orderId' => $comment,
|
||||
'orderUrl' => url()->route('association.profile'),
|
||||
'itemDesc' => 'Mitgliedsbeitrag '.date('Y').' von nostr:'.$this->currentPleb->npub,
|
||||
'posData' => [
|
||||
'event' => $paymentEvent->event_id,
|
||||
'pubkey' => $this->currentPleb->pubkey,
|
||||
'npub' => $this->currentPleb->npub,
|
||||
],
|
||||
],
|
||||
'checkout' => [
|
||||
'expirationMinutes' => 60 * 24,
|
||||
'redirectURL' => url()->route('association.profile'),
|
||||
'redirectAutomatically' => true,
|
||||
'defaultLanguage' => 'de',
|
||||
],
|
||||
],
|
||||
)->throw();
|
||||
$paymentEvent->btc_pay_invoice = $response->json()['id'];
|
||||
$paymentEvent->save();
|
||||
|
||||
return redirect($response->json()['checkoutLink']);
|
||||
} catch (\Exception $e) {
|
||||
$notification = new Notification($this);
|
||||
$notification->error(
|
||||
'Fehler beim Erstellen der Rechnung. Bitte versuche es später erneut: '.$e->getMessage(),
|
||||
);
|
||||
|
||||
return redirect()->route('association.profile');
|
||||
}
|
||||
}
|
||||
|
||||
public function listenForPayment(): void
|
||||
{
|
||||
$paymentEvent = $this->currentPleb
|
||||
->paymentEvents()
|
||||
->where('year', date('Y'))
|
||||
->first();
|
||||
if ($paymentEvent->btc_pay_invoice) {
|
||||
$response = \Illuminate\Support\Facades\Http::withHeaders([
|
||||
'Authorization' => 'token '.config('services.btc_pay.api_key'),
|
||||
])
|
||||
->get(
|
||||
'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices/'.$paymentEvent->btc_pay_invoice,
|
||||
);
|
||||
if ($response->json()['status'] === 'Expired') {
|
||||
$paymentEvent->btc_pay_invoice = null;
|
||||
$paymentEvent->paid = false;
|
||||
$paymentEvent->save();
|
||||
}
|
||||
if ($response->json()['status'] === 'Settled') {
|
||||
$paymentEvent->paid = true;
|
||||
$paymentEvent->save();
|
||||
$this->currentYearIsPaid = true;
|
||||
}
|
||||
}
|
||||
if ($paymentEvent->paid) {
|
||||
$this->currentYearIsPaid = true;
|
||||
}
|
||||
$paymentEvent = $paymentEvent->refresh();
|
||||
$this->payments = $this->currentPleb
|
||||
->paymentEvents()
|
||||
->where('paid', true)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function save($type): void
|
||||
{
|
||||
$this->form->validate();
|
||||
if (! $this->form->check) {
|
||||
$this->js('alert("Du musst den Statuten zustimmen.")');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->currentPleb
|
||||
->update([
|
||||
'association_status' => $type,
|
||||
]);
|
||||
}
|
||||
|
||||
public function createPaymentEvent(): void
|
||||
{
|
||||
$note = new NostrEvent;
|
||||
$note->setKind(32121);
|
||||
$note->setContent(
|
||||
'Dieses Event dient der Zahlung des Mitgliedsbeitrags für das Jahr '.date(
|
||||
'Y',
|
||||
).'. Bitte bezahle den Betrag von '.number_format($this->amountToPay, 0, ',', '.').' Satoshis.',
|
||||
);
|
||||
$note->setTags([
|
||||
['d', $this->currentPleb->pubkey.','.date('Y')],
|
||||
['zap', 'daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6', config('services.relay'), '1'],
|
||||
]);
|
||||
$signer = new Sign;
|
||||
$signer->signEvent($note, config('services.nostr'));
|
||||
|
||||
$eventMessage = new EventMessage($note);
|
||||
|
||||
$relayUrl = config('services.relay');
|
||||
$relay = new Relay($relayUrl);
|
||||
$relay->setMessage($eventMessage);
|
||||
$result = $relay->send();
|
||||
|
||||
$this->currentPleb->paymentEvents()->create([
|
||||
'year' => date('Y'),
|
||||
'event_id' => $result->eventId,
|
||||
'amount' => $this->amountToPay,
|
||||
]);
|
||||
}
|
||||
|
||||
public function loadEvents(): void
|
||||
{
|
||||
$subscription = new Subscription;
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter;
|
||||
$filter1->setKinds([32121]);
|
||||
$filter1->setAuthors(['daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6']);
|
||||
$filters = [$filter1];
|
||||
|
||||
$requestMessage = new RequestMessage($subscriptionId, $filters);
|
||||
|
||||
$relays = [
|
||||
new Relay(config('services.relay')),
|
||||
];
|
||||
$relaySet = new RelaySet;
|
||||
$relaySet->setRelays($relays);
|
||||
|
||||
$request = new Request($relaySet, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
$this->events = collect($response[config('services.relay')])
|
||||
->map(function ($event) {
|
||||
if (! isset($event->event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $event->event->id,
|
||||
'kind' => $event->event->kind,
|
||||
'content' => $event->event->content,
|
||||
'pubkey' => $event->event->pubkey,
|
||||
'tags' => $event->event->tags,
|
||||
'created_at' => $event->event->created_at,
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->unique('id')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div>
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
|
||||
<!-- Page header -->
|
||||
@@ -327,16 +633,16 @@
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
@if($currentYearIsPaid)
|
||||
<div class="flex sm:justify-center">
|
||||
<div
|
||||
class="btn sm:text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 text-green-500"
|
||||
>
|
||||
<i class="fa-sharp-duotone fa-solid fa-check-circle mr-2"></i>
|
||||
aktuelles Jahr bezahlt
|
||||
</div>
|
||||
@if($currentYearIsPaid)
|
||||
<div class="flex sm:justify-center">
|
||||
<div
|
||||
class="btn sm:text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 text-green-500"
|
||||
>
|
||||
<i class="fa-sharp-duotone fa-solid fa-check-circle mr-2"></i>
|
||||
aktuelles Jahr bezahlt
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
@@ -424,4 +730,4 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
</div>
|
||||
@@ -6,9 +6,9 @@
|
||||
<span class="lg:hidden lg:sidebar-expanded:block 2xl:block">Verein</span>
|
||||
</h3>
|
||||
<ul class="mt-3">
|
||||
<li class="{{ $currentRoute === 'news' ? $isCurrentRouteClass : $isNotCurrentRouteClass }}">
|
||||
<li class="{{ $currentRoute === 'association.news' ? $isCurrentRouteClass : $isNotCurrentRouteClass }}">
|
||||
<a class="block text-gray-800 dark:text-gray-100 hover:text-gray-900 dark:hover:text-white truncate transition"
|
||||
href="{{ route('news') }}">
|
||||
href="{{ route('association.news') }}">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-sharp-duotone fa-solid fa-rss h-4 w-4"></i>
|
||||
<span
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
<div>
|
||||
@php
|
||||
$positions = [
|
||||
'presidency' => ['icon' => 'fa-crown', 'title' => 'Präsidium'],
|
||||
'board' => ['icon' => 'fa-users', 'title' => 'Vorstandsmitglieder'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@if($isAllowed)
|
||||
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" x-data="electionAdminCharts()">
|
||||
|
||||
<!-- Dashboard actions -->
|
||||
<div class="sm:flex sm:justify-between sm:items-center mb-8">
|
||||
|
||||
<!-- Left: Title -->
|
||||
<div class="mb-4 sm:mb-0">
|
||||
<h1 class="text-2xl md:text-3xl text-gray-800 dark:text-gray-100 font-bold">
|
||||
Wahl des Vorstands {{ $election->year }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@php
|
||||
$president = $positions['presidency'];
|
||||
$board = $positions['board'];
|
||||
@endphp
|
||||
|
||||
<!-- Cards -->
|
||||
<div class="grid gap-y-4">
|
||||
<div wire:key="presidency" wire:ignore
|
||||
class="flex flex-col bg-white dark:bg-gray-800 shadow-sm rounded-xl">
|
||||
<header class="px-5 py-4 border-b border-gray-100 dark:border-gray-700/60">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100"><i
|
||||
class="fa-sharp-duotone fa-solid {{ $president['icon'] }} w-5 h-5 fill-current text-white mr-4"></i>{{ $president['title'] }}
|
||||
</h2>
|
||||
</header>
|
||||
<div class="grow">
|
||||
<!-- Change| height attribute to adjust chart height -->
|
||||
<canvas x-ref="chart_presidency" width="724" height="288"
|
||||
style="display: block; box-sizing: border-box; height: 288px; width: 724px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div wire:key="board" wire:ignore
|
||||
class="flex flex-col bg-white dark:bg-gray-800 shadow-sm rounded-xl">
|
||||
<header class="px-5 py-4 border-b border-gray-100 dark:border-gray-700/60">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100"><i
|
||||
class="fa-sharp-duotone fa-solid {{ $board['icon'] }} w-5 h-5 fill-current text-white mr-4"></i>{{ $board['title'] }}
|
||||
</h2>
|
||||
</header>
|
||||
<div class="grow">
|
||||
<!-- Change| height attribute to adjust chart height -->
|
||||
<canvas x-ref="chart_board" width="724" height="288"
|
||||
style="display: block; box-sizing: border-box; height: 288px; width: 724px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@else
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div class="bg-white dark:bg-[#1B1B1B] shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">Mitglieder</h3>
|
||||
<p class="mt-1 max-w">
|
||||
Du bist nicht berechtigt, Mitglieder zu bearbeiten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,33 +0,0 @@
|
||||
<x-layouts.app title="{{ __('Wahlen') }}">
|
||||
<div>
|
||||
@if($isAllowed)
|
||||
<div class="relative flex h-full">
|
||||
@foreach($elections as $election)
|
||||
<div class="w-full sm:w-1/3 p-4" wire:key="election-{{ $loop->index }}">
|
||||
<div class="shadow-lg rounded-lg overflow-hidden">
|
||||
{{ $election['year'] }}
|
||||
</div>
|
||||
<div class="shadow-lg rounded-lg overflow-hidden">
|
||||
<x-textarea wire:model="elections.{{ $loop->index }}.candidates" rows="25"
|
||||
label="candidates" placeholder=""/>
|
||||
</div>
|
||||
<div class="py-2">
|
||||
<x-button label="Speichern" wire:click="saveElection({{ $loop->index }})" wire:loading.attr="disabled"/>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div class="bg-white dark:bg-[#1B1B1B] shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">Einstellungen</h3>
|
||||
<p class="mt-1 max-w">
|
||||
Du bist nicht berechtigt, die Einstellungen zu bearbeiten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -1,20 +0,0 @@
|
||||
<x-layouts.app title="{{ __('Mitglieder') }}">
|
||||
<div>
|
||||
@if($isAllowed)
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<livewire:einundzwanzig-pleb-table/>
|
||||
</div>
|
||||
@else
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div class="bg-white dark:bg-[#1B1B1B] shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">Mitglieder</h3>
|
||||
<p class="mt-1 max-w">
|
||||
Du bist nicht berechtigt, Mitglieder zu bearbeiten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -1,22 +1,4 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Association\Election\Admin as ElectionAdmin;
|
||||
use App\Livewire\Association\Election\Index as ElectionIndex;
|
||||
use App\Livewire\Association\Election\Show as ElectionShow;
|
||||
use App\Livewire\Association\Members\Admin as MembersAdmin;
|
||||
use App\Livewire\Association\News\Index as NewsIndex;
|
||||
use App\Livewire\Association\Profile;
|
||||
use App\Livewire\Association\ProjectSupport\Form\Create as ProjectSupportCreate;
|
||||
use App\Livewire\Association\ProjectSupport\Form\Edit as ProjectSupportEdit;
|
||||
use App\Livewire\Association\ProjectSupport\Index as ProjectSupportIndex;
|
||||
use App\Livewire\Association\ProjectSupport\Show as ProjectSupportShow;
|
||||
use App\Livewire\Changelog;
|
||||
use App\Livewire\EinundzwanzigFeed\Index as EinundzwanzigFeedIndex;
|
||||
use App\Livewire\Meetups\Grid as MeetupsGrid;
|
||||
use App\Livewire\Meetups\Mockup as MeetupsMockup;
|
||||
use App\Livewire\Meetups\Table as MeetupsTable;
|
||||
use App\Livewire\Meetups\Worldmap as MeetupsWorldmap;
|
||||
use App\Livewire\Welcome;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
@@ -38,30 +20,30 @@ Route::post('logout', function () {
|
||||
})->name('logout');
|
||||
|
||||
// Association Routes
|
||||
Route::livewire('/association/profile', Profile::class)->name('association.profile');
|
||||
Route::livewire('/association/profile', 'association.⚡profile')->name('association.profile');
|
||||
|
||||
Route::livewire('/association/election', ElectionIndex::class)->name('association.elections');
|
||||
Route::livewire('/association/election/{election:year}', ElectionShow::class)->name('association.election');
|
||||
Route::livewire('/association/election/admin/{election:year}', ElectionAdmin::class)->name('association.election.admin');
|
||||
Route::livewire('/association/election', 'association.election.⚡index')->name('association.elections');
|
||||
Route::livewire('/association/election/{election:year}', 'association.election.⚡show')->name('association.election');
|
||||
Route::livewire('/association/election/admin/{election:year}', 'association.election.⚡admin')->name('association.election.admin');
|
||||
|
||||
Route::livewire('/association/members/admin', MembersAdmin::class)->name('association.members.admin');
|
||||
Route::livewire('/association/members/admin', 'association.members.⚡admin')->name('association.members.admin');
|
||||
|
||||
Route::livewire('/association/news', NewsIndex::class)->name('association.news');
|
||||
Route::livewire('/association/news', 'association.⚡news')->name('association.news');
|
||||
|
||||
Route::livewire('/association/project-support', ProjectSupportIndex::class)->name('association.projectSupport');
|
||||
Route::livewire('/association/project-support/create', ProjectSupportCreate::class)->name('association.projectSupport.create');
|
||||
Route::livewire('/association/project-support/{projectProposal:slug}', ProjectSupportShow::class)->name('association.projectSupport.item');
|
||||
Route::livewire('/association/project-support/edit/{projectProposal:slug}', ProjectSupportEdit::class)->name('association.projectSupport.edit');
|
||||
Route::livewire('/association/project-support', 'association.project-support.⚡index')->name('association.projectSupport');
|
||||
Route::livewire('/association/project-support/create', 'association.project-support.form.⚡create')->name('association.projectSupport.create');
|
||||
Route::livewire('/association/project-support/{projectProposal:slug}', 'association.project-support.⚡show')->name('association.projectSupport.item');
|
||||
Route::livewire('/association/project-support/edit/{projectProposal:slug}', 'association.project-support.form.⚡edit')->name('association.projectSupport.edit');
|
||||
|
||||
// Einundzwanzig Feed
|
||||
Route::livewire('/einundzwanzig-feed', EinundzwanzigFeedIndex::class)->name('einundzwanzig-feed');
|
||||
Route::livewire('/einundzwanzig-feed', 'einundzwanzig-feed.⚡index')->name('einundzwanzig-feed');
|
||||
|
||||
// Meetups
|
||||
Route::livewire('/meetups/grid', MeetupsGrid::class)->name('meetups.grid');
|
||||
Route::livewire('/meetups/mockup', MeetupsMockup::class)->name('meetups.mockup');
|
||||
Route::livewire('/meetups/table', MeetupsTable::class)->name('meetups.table');
|
||||
Route::livewire('/meetups/worldmap', MeetupsWorldmap::class)->name('meetups.worldmap');
|
||||
Route::livewire('/meetups/grid', 'meetups.⚡grid')->name('meetups.grid');
|
||||
Route::livewire('/meetups/mockup', 'meetups.⚡mockup')->name('meetups.mockup');
|
||||
Route::livewire('/meetups/table', 'meetups.⚡table')->name('meetups.table');
|
||||
Route::livewire('/meetups/worldmap', 'meetups.⚡worldmap')->name('meetups.worldmap');
|
||||
|
||||
// Other pages
|
||||
Route::livewire('/changelog', Changelog::class)->name('changelog');
|
||||
Route::livewire('/welcome', Welcome::class)->name('welcome');
|
||||
Route::livewire('/changelog', '⚡changelog')->name('changelog');
|
||||
Route::livewire('/welcome', '⚡welcome')->name('welcome');
|
||||
|
||||
Reference in New Issue
Block a user