From dfb1c3fa0f6e1266c74c56321fc0aa552495cc7e Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode Date: Fri, 23 Jan 2026 16:51:31 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20membership=20benefits=20page:?= =?UTF-8?q?=20implement=20Nostr=20Relay,=20NIP-05=20verification,=20and=20?= =?UTF-8?q?Lightning=20Watchtower=20features=20with=20interactive=20UI=20a?= =?UTF-8?q?nd=20backend=20logic.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/layouts/app.blade.php | 7 + .../livewire/association/benefits.blade.php | 358 ++++++++++++++++++ routes/web.php | 1 + 3 files changed, 366 insertions(+) create mode 100644 resources/views/livewire/association/benefits.blade.php diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index e576da0..671326b 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -43,6 +43,10 @@ :current="request()->routeIs('association.profile')"> Mitgliederstatus + + Vorteile + Projekt-Unterstützungen @@ -101,6 +105,9 @@ Meine Mitgliedschaft + Vorteile + Projekt-Unterstützungen diff --git a/resources/views/livewire/association/benefits.blade.php b/resources/views/livewire/association/benefits.blade.php new file mode 100644 index 0000000..934a4a5 --- /dev/null +++ b/resources/views/livewire/association/benefits.blade.php @@ -0,0 +1,358 @@ + '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')), + 'profile', + ]) + ->where('pubkey', $this->currentPubkey)->first(); + if ($this->currentPleb) { + if ($this->currentPleb->nip05_handle) { + $this->nip05Handle = $this->currentPleb->nip05_handle; + + // Verify NIP-05 handle against nostr.json + try { + $response = \Illuminate\Support\Facades\Http::get( + 'https://einundzwanzig.space/.well-known/nostr.json', + ); + $data = $response->json(); + if (isset($data['names'])) { + foreach ($data['names'] as $handle => $pubkey) { + if ($pubkey === $this->currentPubkey) { + $this->nip05Verified = true; + $this->nip05VerifiedHandle = $handle; + // Check if verified handle differs from database handle + if ($handle !== $this->nip05Handle) { + $this->nip05HandleMismatch = true; + } + break; + } + } + } + } catch (\Exception) { + // Silently fail if nostr.json is not accessible + } + } + + // Check if current year is paid + $paymentEvent = $this->currentPleb->paymentEvents->first(); + if ($paymentEvent && $paymentEvent->paid) { + $this->currentYearIsPaid = true; + } + } + } + } + + public function updatedNip05Handle(): void + { + $this->nip05Handle = strtolower($this->nip05Handle); + } + + public function saveNip05Handle(): void + { + $this->validate([ + 'nip05Handle' => 'required|string|max:255|regex:/^[a-z0-9_-]+$/|unique:einundzwanzig_plebs,nip05_handle', + ]); + + $nip05Handle = strtolower($this->nip05Handle); + + $this->currentPleb->update([ + 'nip05_handle' => $nip05Handle, + ]); + Flux::toast('NIP-05 Handle gespeichert.'); + } + + public function copyRelayUrl(): void + { + $relayUrl = 'wss://nostr.einundzwanzig.space'; + $this->js("navigator.clipboard.writeText('{$relayUrl}')"); + Flux::toast('Relay-Adresse in die Zwischenablage kopiert!'); + } + + public function copyWatchtowerUrl(): void + { + $watchtowerUrl = '03a09f56bba3d2c200cc55eda2f1f069564a97c1fb74345e1560e2868a8ab3d7d0@62.171.139.240:9911'; + $this->js("navigator.clipboard.writeText('{$watchtowerUrl}')"); + Flux::toast('Watchtower-Adresse in die Zwischenablage kopiert!'); + } + + public function handleNostrLoggedIn(string $pubkey): void + { + NostrAuth::login($pubkey); + $this->mount(); + } + + public function handleNostrLoggedOut(): void + { + $this->currentPleb = null; + $this->currentPubkey = null; + $this->currentYearIsPaid = false; + $this->nip05Handle = ''; + $this->nip05Verified = false; + $this->nip05VerifiedHandle = null; + $this->nip05HandleMismatch = false; + } +} +?> + +
+ +
+

+ Vorteile deiner Mitgliedschaft +

+
+ + +
+ +
+
+
+
+ +
+
+
+

+ Nostr Relay +

+

+ Exklusive Schreib-Rechte auf Premium Nostr Relay von Einundzwanzig. +

+ @if($currentPleb && $currentPleb->association_status->value > 1 && $currentYearIsPaid) +
+

+ Ein Outbox-Relay ist wie ein Postbote für deine Nostr-Nachrichten. Es speichert und + verteilt deine Posts. Um unser Relay nutzen zu können, musst du es in deinem + Nostr-Client hinzufügen. +

+

+ Gehe in deinem Nostr-Client zu den Einstellungen (meistens "Settings" oder + "Relays") und füge folgende Outbox-Relay-Adresse hinzu: +

+
+ + wss://nostr.einundzwanzig.space + +
+

+ Wichtige Hinweise: Du kannst deine Posts auf mehreren Relays gleichzeitig + veröffentlichen. So stellst du sicher, dass deine Inhalte auch über unser Relay erreichbar sind. +

+
+ @endif +
+
+
+ + +
+
+
+
+ +
+
+
+

+ Get NIP-05 verified +

+

+ Verifiziere deine Identität mit einem menschenlesbaren Nostr-Namen. +

+
+
+ + + @if($currentPleb && $currentPleb->association_status->value > 1 && $currentYearIsPaid) +
+ + Dein NIP-05 Handle + + + @einundzwanzig.space + + + + +
+ + Speichern + +
+ + +
+

+ Regeln für dein Handle: Nur Kleinbuchstaben (a-z), Zahlen + (0-9) und die Zeichen "-" und "_" sind erlaubt. Dein Handle wird automatisch + kleingeschrieben. +

+
+ + +
+

+ NIP-05 + + verifiziert deine Identität auf Nostr. Das Handle ist wie eine + E-Mail-Adresse (z.B. name@einundzwanzig.space). Clients zeigen ein Häkchen + für verifizierte Benutzer. Dies macht dein Profil einfacher zu teilen und + vertrauenswürdiger. +

+
+ + + @if($nip05Verified) + @if($nip05HandleMismatch) + +

+ Dein aktiviertes Handle ist
{{ $nip05VerifiedHandle }}@einundzwanzig.space +

+

+ Die Synchronisation zu {{ $nip05Handle }}@einundzwanzig.space wird automatisch im Hintergrund durchgeführt. +

+
+ @else + +

+ Dein Handle {{ $nip05VerifiedHandle }}@einundzwanzig.space ist aktiv verifiziert! +

+

+ Dein Handle ist in der NIP-05 Konfiguration eingetragen und bereit für die Verwendung. +

+
+ @endif + @elseif($nip05Handle) + +

+ Dein Handle {{ $nip05Handle }}@einundzwanzig.space ist noch nicht aktiv. +

+

+ Das Handle ist gespeichert, aber noch nicht in der NIP-05 Konfiguration veröffentlicht. + Der Vorstand wird dies bald aktivieren. +

+
+ @endif +
+ @else +
+ Aktiviere deine Mitgliedschaft, um NIP-05 zu verifizieren. +
+ @endif +
+ + +
+
+
+
+ +
+
+
+

+ Lightning Watchtower +

+

+ Nutze unseren Watchtower zum Schutz deiner Lightning Channel. +

+
+
+ + @if($currentPleb && $currentPleb->association_status->value > 1 && $currentYearIsPaid) +
+

+ Ein Watchtower überwacht deine Lightning Channel und schützt sie, falls deine Node + offline ist. Wenn du die Zahlung von Channel-Closing-Transaktionen verpasst, kümmert sich + der Watchtower darum und verhindert den Verlust deiner Sats. +

+ +

+ Um unseren Watchtower zu nutzen, füge folgende URI in deiner Lightning Node + Konfiguration hinzu: +

+ +
+ + 03a09f56bba3d2c200cc55eda2f1f069564a97c1fb74345e1560e2868a8ab3d7d0@62.171.139.240:9911 + +
+ +
+

+ Einrichtung für gängige Lightning Clients: +

+
    +
  • LND: https://docs.lightning.engineering/lightning-network-tools/lnd/watchtower
  • +
  • Core Lightning: Nutze den watchtower-client Plugin mit der URI
  • +
  • Eclair: Füge die URI zu den Watchtower-Einstellungen in deiner eclair.conf hinzu
  • +
+
+ +

+ Wichtig: Der Watchtower überwacht deine Channel passiv. Er hat keinen Zugriff auf + deine privaten Schlüssel oder dein Guthaben. +

+
+ @else +
+ Aktiviere deine Mitgliedschaft, um den Lightning Watchtower zu nutzen. +
+ @endif +
+ +
+
diff --git a/routes/web.php b/routes/web.php index 3fc5328..8555414 100644 --- a/routes/web.php +++ b/routes/web.php @@ -22,6 +22,7 @@ Route::post('logout', function () { // Association Routes Route::livewire('/association/profile', 'association.profile')->name('association.profile'); +Route::livewire('/association/benefits', 'association.benefits')->name('association.benefits'); Route::livewire('/association/election', 'association.election.index')->name('association.elections'); Route::livewire('/association/election/{election:year}', 'association.election.show')->name('association.election');