feat: add QR code generator and Nostr event handling
- Added the `simplesoftwareio/simple-qrcode` package to the project - Created a new JavaScript file `nostrZap.js` to handle Nostr events - Added the `nostrZap` function to the Alpine.js data property in `app.js` - Updated the `services.php` configuration file to include the `nostr` environment variable - Created a new database migration to add a `payment_event` field to the `einundzwanzig_plebs` table - Made adjustments in the `Election:year.blade.php` view file to handle potential null values - Updated `composer.lock` and `package.json` with the new dependencies
@@ -23,6 +23,7 @@
|
||||
"livewire/volt": "^1.6",
|
||||
"openspout/openspout": "^4.24",
|
||||
"power-components/livewire-powergrid": "^5.10",
|
||||
"simplesoftwareio/simple-qrcode": "^4.2",
|
||||
"spatie/image": "^3.7",
|
||||
"spatie/laravel-google-fonts": "^1.4",
|
||||
"spatie/laravel-medialibrary": "^11.9",
|
||||
|
||||
174
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0eb579a82f9d6b1b37ea98f73debb4a0",
|
||||
"content-hash": "c0d4ad4f35c733ba8e694a6d0c7ff836",
|
||||
"packages": [
|
||||
{
|
||||
"name": "akuechler/laravel-geoly",
|
||||
@@ -110,6 +110,60 @@
|
||||
},
|
||||
"time": "2024-07-15T14:28:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "2.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Bacon/BaconQrCode.git",
|
||||
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
|
||||
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dasprid/enum": "^1.0.3",
|
||||
"ext-iconv": "*",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phly/keep-a-changelog": "^2.1",
|
||||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.2.9",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "to generate QR code images"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"BaconQrCode\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "BaconQrCode is a QR code generator for PHP.",
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
||||
"support": {
|
||||
"issues": "https://github.com/Bacon/BaconQrCode/issues",
|
||||
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
|
||||
},
|
||||
"time": "2022-12-07T17:46:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bitwasp/bech32",
|
||||
"version": "v0.0.1",
|
||||
@@ -550,6 +604,56 @@
|
||||
],
|
||||
"time": "2024-09-19T14:15:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dasprid/enum",
|
||||
"version": "1.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DASPRiD/Enum.git",
|
||||
"reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
|
||||
"reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1 <9.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
|
||||
"squizlabs/php_codesniffer": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DASPRiD\\Enum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP 7.1 enum implementation",
|
||||
"keywords": [
|
||||
"enum",
|
||||
"map"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/DASPRiD/Enum/issues",
|
||||
"source": "https://github.com/DASPRiD/Enum/tree/1.0.6"
|
||||
},
|
||||
"time": "2024-08-09T14:30:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
"version": "v3.0.3",
|
||||
@@ -5493,6 +5597,74 @@
|
||||
],
|
||||
"time": "2024-06-11T12:45:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "simplesoftwareio/simple-qrcode",
|
||||
"version": "4.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git",
|
||||
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/916db7948ca6772d54bb617259c768c9cdc8d537",
|
||||
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"ext-gd": "*",
|
||||
"php": ">=7.2|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~1",
|
||||
"phpunit/phpunit": "~9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "Allows the generation of PNG QrCodes.",
|
||||
"illuminate/support": "Allows for use within Laravel."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"SimpleSoftwareIO\\QrCode\\QrCodeServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"QrCode": "SimpleSoftwareIO\\QrCode\\Facades\\QrCode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SimpleSoftwareIO\\QrCode\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Simple Software LLC",
|
||||
"email": "support@simplesoftware.io"
|
||||
}
|
||||
],
|
||||
"description": "Simple QrCode is a QR code generator made for Laravel.",
|
||||
"homepage": "https://www.simplesoftware.io/#/docs/simple-qrcode",
|
||||
"keywords": [
|
||||
"Simple",
|
||||
"generator",
|
||||
"laravel",
|
||||
"qrcode",
|
||||
"wrapper"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues",
|
||||
"source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/4.2.0"
|
||||
},
|
||||
"time": "2021-02-08T20:43:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "simplito/bigint-wrapper-php",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -15,6 +15,7 @@ return [
|
||||
*/
|
||||
|
||||
'relay' => env('NOSTR_RELAY'),
|
||||
'nostr' => env('NOSTR_P'),
|
||||
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_TOKEN'),
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('einundzwanzig_plebs', function (Blueprint $table) {
|
||||
$table->string('payment_event', 255 * 2)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('einundzwanzig_plebs', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -67,7 +67,7 @@ services:
|
||||
timeout: 5s
|
||||
relay:
|
||||
ports:
|
||||
- "7000"
|
||||
- "7000:7000"
|
||||
volumes:
|
||||
- ./relay:/usr/src/app/db
|
||||
- ./relay/config.toml:/usr/src/app/config.toml
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
"flatpickr": "^4.6.13",
|
||||
"laravel-echo": "^1.16.1",
|
||||
"laravel-vite-plugin": "^1.0",
|
||||
"nostr-tools": "^2.7.2",
|
||||
"postcss": "^8.4.41",
|
||||
"pusher-js": "^8.4.0-rc2",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"vite": "^5.0"
|
||||
"vite": "^5.0",
|
||||
"webln": "^0.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/einundzwanzig-alpha.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 780 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 15 KiB |
@@ -2,6 +2,7 @@ import {Alpine, Livewire} from '../../vendor/livewire/livewire/dist/livewire.esm
|
||||
|
||||
import nostrApp from "./nostrApp.js";
|
||||
import nostrLogin from "./nostrLogin.js";
|
||||
import nostrZap from "./nostrZap.js";
|
||||
import electionAdminCharts from "./electionAdminCharts.js";
|
||||
|
||||
import './bootstrap';
|
||||
@@ -19,6 +20,7 @@ Alpine.store('nostr', {
|
||||
|
||||
Alpine.data('nostrApp', nostrApp);
|
||||
Alpine.data('nostrLogin', nostrLogin);
|
||||
Alpine.data('nostrZap', nostrZap);
|
||||
Alpine.data('electionAdminCharts', electionAdminCharts);
|
||||
|
||||
Livewire.start();
|
||||
|
||||
58
resources/js/nostrZap.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import NDK, {NDKEvent} from "@nostr-dev-kit/ndk";
|
||||
import {
|
||||
nip57,
|
||||
} from "nostr-tools";
|
||||
|
||||
export default (livewireComponent) => ({
|
||||
|
||||
invoice: livewireComponent.entangle('invoice', true),
|
||||
|
||||
async zap(message, sender, amountToPay) {
|
||||
const ndk = new NDK({
|
||||
explicitRelayUrls: ['wss://simple-relay.steuernsindraub21.xyz'],
|
||||
enableOutboxModel: false,
|
||||
});
|
||||
// Now connect to specified relays
|
||||
await ndk.connect();
|
||||
const event = await ndk.fetchEvent({
|
||||
kinds: [32121],
|
||||
authors: ['daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6']
|
||||
});
|
||||
const amount = amountToPay * 1000;
|
||||
console.log('event', event);
|
||||
|
||||
const zapEndpoint = 'https://getalby.com/lnurlp/portaleinundzwanzig/callback';
|
||||
|
||||
const zapEvent = nip57.makeZapRequest({
|
||||
profile: sender,
|
||||
event: event.id,
|
||||
amount: amount,
|
||||
relays: ['wss://simple-relay.steuernsindraub21.xyz'],
|
||||
comment: message,
|
||||
});
|
||||
console.log('zapEvent', zapEvent);
|
||||
|
||||
const signedEvent = await window.nostr.signEvent(zapEvent);
|
||||
console.log('signedEvent', signedEvent);
|
||||
|
||||
let url = `${zapEndpoint}?amount=${amount}&nostr=${encodeURIComponent(
|
||||
JSON.stringify(signedEvent)
|
||||
)}`;
|
||||
url = `${url}&comment=${encodeURIComponent(message)}`;
|
||||
console.log('url', url);
|
||||
|
||||
const res = await fetch(url);
|
||||
const { pr: invoice, reason, status } = await res.json();
|
||||
|
||||
if (invoice) {
|
||||
console.log('invoice', invoice);
|
||||
this.invoice = invoice;
|
||||
} else if (status === "ERROR") {
|
||||
throw new Error(reason ?? "Unable to fetch invoice");
|
||||
} else {
|
||||
throw new Error("Other error");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
</script>
|
||||
<div x-data="nostrLogin"
|
||||
class="flex h-[100dvh] overflow-hidden">
|
||||
class="flex h-[100dvh] overflow-hidden">
|
||||
<livewire:layout.sidebar/>
|
||||
<div
|
||||
class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
@@ -102,7 +102,9 @@
|
||||
<ul>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-amber-500 hover:text-amber-600 dark:hover:text-amber-400 flex items-center py-1 px-3"
|
||||
target="_blank" href="https://gitworkshop.dev/r/naddr1qvzqqqrhnypzqzklvar4enzu53t06vpzu3h465nwkzhk9p9ls4y5crwhs3lnu5pnqy88wumn8ghj7mn0wvhxcmmv9uqpxetfde6kuer6wasku7nfvukkummnw3eqdgsn8w/issues" @click="open = false" @focus="open = true"
|
||||
target="_blank"
|
||||
href="https://gitworkshop.dev/r/naddr1qvzqqqrhnypzqzklvar4enzu53t06vpzu3h465nwkzhk9p9ls4y5crwhs3lnu5pnqy88wumn8ghj7mn0wvhxcmmv9uqpxetfde6kuer6wasku7nfvukkummnw3eqdgsn8w/issues"
|
||||
@click="open = false" @focus="open = true"
|
||||
@focusout="open = false">
|
||||
<i class="fa-sharp-duotone fa-solid fa-code w-3 h-3 fill-current text-amber-500 shrink-0 mr-2"></i>
|
||||
<span>Issues/Feedback</span>
|
||||
@@ -118,7 +120,8 @@
|
||||
</li>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-amber-500 hover:text-amber-600 dark:hover:text-amber-400 flex items-center py-1 px-3"
|
||||
href="https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr" target="_blank" @click="open = false" @focus="open = true"
|
||||
href="https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr"
|
||||
target="_blank" @click="open = false" @focus="open = true"
|
||||
@focusout="open = false">
|
||||
<i class="fa-brands fa-github w-3 h-3 fill-current text-amber-500 shrink-0 mr-2"></i>
|
||||
<span>Github</span>
|
||||
@@ -126,7 +129,8 @@
|
||||
</li>
|
||||
<li>
|
||||
<a class="font-medium text-sm text-amber-500 hover:text-amber-600 dark:hover:text-amber-400 flex items-center py-1 px-3"
|
||||
href="https://einundzwanzig.space/kontakt/" target="_blank" @click="open = false" @focus="open = true"
|
||||
href="https://einundzwanzig.space/kontakt/" target="_blank"
|
||||
@click="open = false" @focus="open = true"
|
||||
@focusout="open = false">
|
||||
<i class="fa-sharp-duotone fa-solid fa-info w-3 h-3 fill-current text-amber-500 shrink-0 mr-2"></i>
|
||||
<span>Impressum</span>
|
||||
|
||||
@@ -507,13 +507,13 @@ $signEvent = function ($event) {
|
||||
<div>{{ $event['kind'] }}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div>{{ $event['profile']['name'] }}</div>
|
||||
<div>{{ $event['profile']['name'] ?? '' }}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div>{{ $event['created_at'] }}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div>{{ $event['votedFor']['name'] ?? 'error' }}</div>
|
||||
<div>{{ $event['votedFor']['name'] ?? '' }}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div>{{ $event['type'] }}</div>
|
||||
|
||||
@@ -2,28 +2,125 @@
|
||||
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Key\Key;
|
||||
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;
|
||||
use swentel\nostr\Event\Event as NostrEvent;
|
||||
use swentel\nostr\Sign\Sign;
|
||||
|
||||
use function Livewire\Volt\computed;
|
||||
use function Livewire\Volt\mount;
|
||||
use function Livewire\Volt\state;
|
||||
use function Livewire\Volt\with;
|
||||
use function Laravel\Folio\{middleware};
|
||||
use function Laravel\Folio\name;
|
||||
use function Livewire\Volt\{on, form};
|
||||
use function Livewire\Volt\{on, form, updated};
|
||||
|
||||
name('association.profile');
|
||||
|
||||
state(['yearsPaid' => []]);
|
||||
state(['events' => []]);
|
||||
state(['payments' => []]);
|
||||
state(['invoice' => null]);
|
||||
state(['qrCode' => null]);
|
||||
state(['paid' => false]);
|
||||
state(['amountToPay' => 1]);
|
||||
state(['currentYearIsPaid' => false]);
|
||||
state(['currentPubkey' => null]);
|
||||
state(['currentPleb' => null]);
|
||||
|
||||
form(\App\Livewire\Forms\ApplicationForm::class);
|
||||
|
||||
updated([
|
||||
'invoice' => function () {
|
||||
$this->qrCode = base64_encode(
|
||||
QrCode::format('png')
|
||||
->size(300)
|
||||
->merge('/public/android-chrome-192x192.png', .3)
|
||||
->errorCorrection('H')
|
||||
->generate($this->invoice),
|
||||
);
|
||||
},
|
||||
]);
|
||||
|
||||
on([
|
||||
'nostrLoggedIn' => function ($pubkey) {
|
||||
$this->currentPubkey = $pubkey;
|
||||
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $pubkey)->first();
|
||||
if ($this->currentPleb->association_status === \App\Enums\AssociationStatus::ACTIVE) {
|
||||
$this->amountToPay = 21;
|
||||
}
|
||||
if (!$this->currentPleb->payment_event) {
|
||||
$this->createPaymentEvent();
|
||||
}
|
||||
$this->loadEvents();
|
||||
$this->searchPaymentEvent();
|
||||
},
|
||||
]);
|
||||
|
||||
$listenForPayment = function () {
|
||||
if (!$this->paid) {
|
||||
$this->searchPaymentEvent();
|
||||
}
|
||||
};
|
||||
|
||||
$searchPaymentEvent = function () {
|
||||
$subscription = new Subscription();
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter();
|
||||
$filter1->setKinds([9735]);
|
||||
$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();
|
||||
|
||||
if (count($response[config('services.relay')]) > 0) {
|
||||
$this->payments = collect($response[config('services.relay')])->map(fn($event)
|
||||
=> [
|
||||
'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,
|
||||
])->toArray();
|
||||
|
||||
$this->yearsPaid = collect($this->payments)->map(fn($payment)
|
||||
=> [
|
||||
'year' => $payment['content'],
|
||||
'amount' => collect(
|
||||
json_decode(
|
||||
collect($payment['tags'])->firstWhere('0', 'description')[1],
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
)['tags']
|
||||
)->firstWhere('0', 'amount')[1] / 1000,
|
||||
]);
|
||||
|
||||
$this->currentYearIsPaid = collect($this->yearsPaid)->contains(
|
||||
fn($yearPaid) => $yearPaid['year'] == date('Y') && $yearPaid['amount'] == $this->amountToPay
|
||||
);
|
||||
|
||||
$this->paid = true;
|
||||
}
|
||||
};
|
||||
|
||||
$save = function ($type) {
|
||||
$this->form->validate();
|
||||
$this->currentPleb
|
||||
@@ -33,11 +130,90 @@ $save = function ($type) {
|
||||
]);
|
||||
};
|
||||
|
||||
$createKind0 = function () {
|
||||
$note = new NostrEvent();
|
||||
$note->setKind(0);
|
||||
$note->setContent('');
|
||||
$note->setTags([
|
||||
['display_name', 'Einundzwanzig Portal'],
|
||||
['lud16', 'portaleinundzwanzig@getalby.com'],
|
||||
['pubkey', 'daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6'],
|
||||
]);
|
||||
$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();
|
||||
};
|
||||
|
||||
$createPaymentEvent = function () {
|
||||
$note = new NostrEvent();
|
||||
$note->setKind(32121);
|
||||
$note->setContent(
|
||||
'Dieses Event dient der Zahlung des Mitgliedsbeitrags für das Jahr ' . date(
|
||||
'Y',
|
||||
) . '. Bitte zappe den Betrag von 1 Satoshi.',
|
||||
);
|
||||
$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->update([
|
||||
'payment_event' => $result->eventId,
|
||||
]);
|
||||
};
|
||||
|
||||
$loadEvents = function () {
|
||||
$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(fn($event)
|
||||
=> [
|
||||
'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,
|
||||
])
|
||||
->unique('id')
|
||||
->toArray();
|
||||
};
|
||||
|
||||
?>
|
||||
|
||||
<x-layouts.app title="{{ __('Wahl') }}">
|
||||
@volt
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" x-data="nostrZap(@this)">
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="mb-8">
|
||||
@@ -157,7 +333,8 @@ $save = function ($type) {
|
||||
<div class="text-sm">
|
||||
<x-textarea
|
||||
corner="Beschreibe deine Motivation, passives Mitglied zu werden."
|
||||
label="Warum möchtest du passives Mitglied werden?" wire:model="form.reason"/>
|
||||
label="Warum möchtest du passives Mitglied werden?"
|
||||
wire:model="form.reason"/>
|
||||
</div>
|
||||
<div class="sm:flex sm:items-center space-y-4 sm:space-y-0 sm:space-x-4 mt-5">
|
||||
<div class="sm:w-1/3 flex flex-col space-y-2">
|
||||
@@ -180,7 +357,8 @@ $save = function ($type) {
|
||||
<x-textarea
|
||||
corner="Woher kennen wir dich? Was möchtest du einbringen?"
|
||||
description="Wir bitten dich mindestens von 3 aktiven Mitgliedern auf Nostr gefolgt zu werden."
|
||||
label="Warum möchtest du aktives Mitglied werden?" wire:model="form.reason"/>
|
||||
label="Warum möchtest du aktives Mitglied werden?"
|
||||
wire:model="form.reason"/>
|
||||
</div>
|
||||
<div class="sm:flex sm:items-center space-y-4 sm:space-y-0 sm:space-x-4 mt-5">
|
||||
<div class="sm:w-1/3 flex flex-col space-y-2">
|
||||
@@ -205,8 +383,8 @@ $save = function ($type) {
|
||||
d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="font-medium text-gray-800 dark:text-gray-100 mb-1">Du hast
|
||||
dich erfolgreich mit folgendem Grund beworben:
|
||||
<div class="font-medium text-gray-800 dark:text-gray-100 mb-1">
|
||||
Du hast dich erfolgreich mit folgendem Grund beworben:
|
||||
</div>
|
||||
<div>{{ $currentPleb->application_text }}</div>
|
||||
</div>
|
||||
@@ -228,9 +406,117 @@ $save = function ($type) {
|
||||
d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="font-medium text-gray-800 dark:text-gray-100 mb-1">Dein
|
||||
aktueller
|
||||
Status: {{ $currentPleb->association_status->label() }}</div>
|
||||
<div class="font-medium text-gray-800 dark:text-gray-100 mb-1">
|
||||
Dein aktueller
|
||||
Status: {{ $currentPleb->association_status->label() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@if($currentPleb && $currentPleb->association_status->value > 1)
|
||||
<div
|
||||
class="inline-flex flex-col w-full px-4 py-2 rounded-lg text-sm bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700/60 text-gray-600 dark:text-gray-400">
|
||||
<div class="flex w-full justify-between items-start">
|
||||
<div class="flex">
|
||||
<svg class="shrink-0 fill-current text-yellow-500 mt-[3px] mr-3" width="16"
|
||||
height="16" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<div
|
||||
class="font-medium text-gray-800 dark:text-gray-100 mb-1 space-y-2">
|
||||
<p>Nostr Event für die Zahlung des
|
||||
Mitgliedsbeitrags: {{ $currentPleb->payment_event }}</p>
|
||||
<div>
|
||||
@if(isset($events[0]))
|
||||
<p>{{ $events[0]['content'] }}</p>
|
||||
<div class="mt-8">
|
||||
@if(!$invoice && !$currentYearIsPaid)
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
@click="zap('{{ date('Y') }}', '{{ $currentPubkey }}', {{ $amountToPay }})"
|
||||
class="btn text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-green-500"
|
||||
>
|
||||
<i class="fa-sharp-duotone fa-solid fa-bolt-lightning mr-2"></i>
|
||||
Zap
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
@if(!$currentYearIsPaid && $qrCode)
|
||||
<div class="flex justify-center"
|
||||
wire:key="qrcode"
|
||||
wire:poll="listenForPayment">
|
||||
<a href="lightning:{{ $invoice }}">
|
||||
<img
|
||||
class="p-12 bg-white"
|
||||
src="{{ 'data:image/png;base64, '. $qrCode }}"
|
||||
alt="qrcode">
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
@if($currentYearIsPaid)
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
class="btn text-2xl dark:bg-gray-800 border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-green-500"
|
||||
>
|
||||
<i class="fa-sharp-duotone fa-solid fa-check-circle mr-2"></i>
|
||||
aktuelles Jahr bezahlt
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<section>
|
||||
<h3 class="text-xl leading-snug text-gray-800 dark:text-gray-100 font-bold mb-1">
|
||||
bisherige Zahlungen</h3>
|
||||
<!-- Table -->
|
||||
<table class="table-auto w-full dark:text-gray-400">
|
||||
<!-- Table header -->
|
||||
<thead
|
||||
class="text-xs uppercase text-gray-400 dark:text-gray-500">
|
||||
<tr class="flex flex-wrap md:table-row md:flex-no-wrap">
|
||||
<th class="w-full block md:w-auto md:table-cell py-2">
|
||||
<div class="font-semibold text-left">Satoshis</div>
|
||||
</th>
|
||||
<th class="w-full hidden md:w-auto md:table-cell py-2">
|
||||
<div class="font-semibold text-left">Jahr</div>
|
||||
</th>
|
||||
<th class="w-full hidden md:w-auto md:table-cell py-2">
|
||||
<div class="font-semibold text-left">Event-ID</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- Table body -->
|
||||
<tbody class="text-sm">
|
||||
@foreach($payments as $payment)
|
||||
<tr class="flex flex-wrap md:table-row md:flex-no-wrap border-b border-gray-200 dark:border-gray-700/60 py-2 md:py-0">
|
||||
<td class="w-full block md:w-auto md:table-cell py-0.5 md:py-2">
|
||||
<div
|
||||
class="text-left font-medium text-gray-800 dark:text-gray-100">{{ collect(json_decode(collect($payment['tags'])->firstWhere('0', 'description')[1], true, 512, JSON_THROW_ON_ERROR)['tags'])->firstWhere('0', 'amount')[1] / 1000 }}</div>
|
||||
</td>
|
||||
<td class="w-full block md:w-auto md:table-cell py-0.5 md:py-2">
|
||||
<div
|
||||
class="text-left">{{ $payment['content'] }}</div>
|
||||
</td>
|
||||
<td class="w-full block md:w-auto md:table-cell py-0.5 md:py-2">
|
||||
<div
|
||||
class="text-left font-medium">{{ $payment['id'] }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,22 +526,6 @@ $save = function ($type) {
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Panel footer -->
|
||||
{{--<footer>
|
||||
<div class="flex flex-col px-6 py-5 border-t border-gray-200 dark:border-gray-700/60">
|
||||
<div class="flex self-end">
|
||||
<button
|
||||
class="btn dark:bg-[#1B1B1B] border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 text-[#1B1B1B] dark:text-gray-300">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="btn bg-gray-900 text-gray-100 hover:bg-[#1B1B1B] dark:bg-gray-100 dark:text-[#1B1B1B] dark:hover:bg-white ml-3">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>--}}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
28
yarn.lock
@@ -380,11 +380,30 @@
|
||||
dependencies:
|
||||
mini-svg-data-uri "^1.2.3"
|
||||
|
||||
"@types/chrome@^0.0.74":
|
||||
version "0.0.74"
|
||||
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.74.tgz#f69827c48fcf7fecc90c96089807661749a5a5e3"
|
||||
integrity sha512-hzosS5CkQcIKCgxcsV2AzbJ36KNxG/Db2YEN/erEu7Boprg+KpMDLBQqKFmSo+JkQMGqRcicUyqCowJpuT+C6A==
|
||||
dependencies:
|
||||
"@types/filesystem" "*"
|
||||
|
||||
"@types/estree@1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
||||
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
||||
|
||||
"@types/filesystem@*":
|
||||
version "0.0.36"
|
||||
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857"
|
||||
integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==
|
||||
dependencies:
|
||||
"@types/filewriter" "*"
|
||||
|
||||
"@types/filewriter@*":
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8"
|
||||
integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==
|
||||
|
||||
ansi-regex@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
@@ -988,7 +1007,7 @@ normalize-range@^0.1.2:
|
||||
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
||||
|
||||
nostr-tools@^2.7.1:
|
||||
nostr-tools@^2.7.1, nostr-tools@^2.7.2:
|
||||
version "2.7.2"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.7.2.tgz#74a6ff543a81da1dcce9563b9317faa17221acce"
|
||||
integrity sha512-Bq3Ug0SZFtgtL1+0wCnAe8AJtI7yx/00/a2nUug9SkhfOwlKS92Tef12iCK9FdwXw+oFZWMtRnSwcLayQso+xA==
|
||||
@@ -1408,6 +1427,13 @@ web-streams-polyfill@^3.0.3:
|
||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
|
||||
integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
|
||||
|
||||
webln@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/webln/-/webln-0.3.2.tgz#bbadf52916666b6059e3661ef5ab73a76b7cd0f4"
|
||||
integrity sha512-YYT83aOCLup2AmqvJdKtdeBTaZpjC6/JDMe8o6x1kbTYWwiwrtWHyO//PAsPixF3jwFsAkj5DmiceB6w/QSe7Q==
|
||||
dependencies:
|
||||
"@types/chrome" "^0.0.74"
|
||||
|
||||
websocket-polyfill@^0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz#7321ada0f5f17516290ba1cb587ac111b74ce6a5"
|
||||
|
||||