mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-02-04 15:53:17 +00:00
voting system with nostr added
This commit is contained in:
@@ -0,0 +1,502 @@
|
||||
<?php
|
||||
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
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 Livewire\Volt\updated;
|
||||
use function Laravel\Folio\{middleware};
|
||||
use function Laravel\Folio\name;
|
||||
use function Livewire\Volt\{on};
|
||||
|
||||
name('association.election');
|
||||
|
||||
state(['currentPubkey' => null]);
|
||||
state(['events' => []]);
|
||||
state(['election' => fn() => $election]);
|
||||
state(['plebs' => []]);
|
||||
state(['search' => '']);
|
||||
state(['signThisEvent' => '']);
|
||||
|
||||
mount(function () {
|
||||
$this->plebs = \App\Models\EinundzwanzigPleb::query()
|
||||
->with([
|
||||
'profile',
|
||||
])
|
||||
->whereIn('association_status', [3, 4])
|
||||
->orderBy('association_status', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
$this->loadEvents();
|
||||
});
|
||||
|
||||
on([
|
||||
'nostrLoggedIn' => function ($pubkey) {
|
||||
$this->currentPubkey = $pubkey;
|
||||
},
|
||||
]);
|
||||
|
||||
on(['echo:votes,.newVote' => function () {
|
||||
$this->loadEvents();
|
||||
}]);
|
||||
|
||||
updated([
|
||||
'search' => function ($value) {
|
||||
$this->plebs = \App\Models\EinundzwanzigPleb::query()
|
||||
->with([
|
||||
'profile',
|
||||
])
|
||||
->where('pubkey', 'like', "%$value%")
|
||||
->orWhereHas('profile', function ($query) use ($value) {
|
||||
$query->where('name', 'ilike', "%$value%");
|
||||
})
|
||||
->orderBy('association_status', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
},
|
||||
]);
|
||||
|
||||
$loadEvents = function () {
|
||||
$subscription = new Subscription();
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter();
|
||||
$filter1->setKinds([2121]); // You can add multiple kind numbers
|
||||
$filters = [$filter1]; // You can add multiple filters.
|
||||
|
||||
$requestMessage = new RequestMessage($subscriptionId, $filters);
|
||||
|
||||
$relays = [
|
||||
new Relay('ws://relay:7000'),
|
||||
];
|
||||
$relaySet = new RelaySet();
|
||||
$relaySet->setRelays($relays);
|
||||
|
||||
$request = new Request($relaySet, $requestMessage);
|
||||
$response = $request->send();
|
||||
|
||||
$this->events = collect($response['ws://relay:7000'])
|
||||
->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();
|
||||
};
|
||||
|
||||
$vote = function ($pubkey, $type) {
|
||||
$note = new NostrEvent();
|
||||
$note->setContent($pubkey . ',' . $type);
|
||||
$note->setKind(2121);
|
||||
$this->signThisEvent = $note->toJson();
|
||||
};
|
||||
|
||||
$signEvent = function ($event) {
|
||||
$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://relay:7000';
|
||||
$relay = new Relay($relayUrl);
|
||||
$relay->setMessage($eventMessage);
|
||||
$result = $relay->send();
|
||||
|
||||
Broadcast::on('votes')
|
||||
->as('newVote')
|
||||
->sendNow();
|
||||
};
|
||||
|
||||
?>
|
||||
|
||||
<x-layouts.app title="{{ __('Wahl') }}">
|
||||
@volt
|
||||
<div class="relative flex h-full" x-data="nostrApp(@this)">
|
||||
|
||||
@php
|
||||
$positions = [
|
||||
'presidency' => ['icon' => 'fa-crown', 'title' => 'Präsidium'],
|
||||
'vice_president' => ['icon' => 'fa-user-group-crown', 'title' => 'Vizepräsidium'],
|
||||
'finances' => ['icon' => 'fa-bitcoin-sign', 'title' => 'Finanzen'],
|
||||
'secretary' => ['icon' => 'fa-stapler', 'title' => 'Sekretär (Akurat)'],
|
||||
'press_officer' => ['icon' => 'fa-newspaper', 'title' => 'Pressewart'],
|
||||
'it_manager' => ['icon' => 'fa-server', 'title' => 'Technikwart'],
|
||||
];
|
||||
$loadedEvents = collect($events)
|
||||
->map(function($event) {
|
||||
$profile = \App\Models\Profile::query()
|
||||
->where('pubkey', $event['pubkey'])
|
||||
->first()
|
||||
->toArray();
|
||||
$votedFor = \App\Models\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(function ($event) {
|
||||
return $event['pubkey'] . $event['type'];
|
||||
})
|
||||
->values();
|
||||
@endphp
|
||||
|
||||
<!-- 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>
|
||||
</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/' . $pleb['pubkey'] }}"
|
||||
onerror="this.onerror=null; this.src='https://robohash.org/{{ $pleb['pubkey'] }}';"
|
||||
width="32"
|
||||
height="32"
|
||||
alt="{{ $pleb['pubkey'] }}"/>
|
||||
<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>
|
||||
<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 = $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>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inbox body -->
|
||||
@if($currentPubkey)
|
||||
|
||||
@php
|
||||
$electionConfig = collect(json_decode($election->candidates, true, 512, JSON_THROW_ON_ERROR))
|
||||
->map(function ($c) use ($loadedEvents, $currentPubkey) {
|
||||
$candidates = \App\Models\Profile::query()
|
||||
->whereIn('pubkey', $c['c'])
|
||||
->get()
|
||||
->map(function ($p) use ($loadedEvents, $c, $currentPubkey) {
|
||||
$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'] === $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,
|
||||
];
|
||||
});
|
||||
@endphp
|
||||
|
||||
<div class="grow flex flex-col md:translate-x-0 transition-transform duration-300 ease-in-out"
|
||||
:class="inboxSidebarOpen ? 'translate-x-1/3' : 'translate-x-0'">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="sticky top-16">
|
||||
<div
|
||||
class="flex items-center justify-between before:absolute before:inset-0 before:backdrop-blur-md before:bg-gray-50/90 dark:before:bg-[#1B1B1B]/90 before:-z-10 border-b border-gray-200 dark:border-gray-700/60 px-4 sm:px-6 md:px-5 h-16">
|
||||
<div class="flex justify-between items-center">
|
||||
<x-badge success label="Die Wahl ist geöffnet bis zum 31.12.2024 um 22:00 Uhr"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="grow px-4 sm:px-6 md:px-5 py-4">
|
||||
|
||||
<!-- Mail subject -->
|
||||
<header class="sm:flex sm:items-start sm:justify-between mb-4">
|
||||
<h1 class="text-xl leading-snug text-gray-800 dark:text-gray-100 font-bold mb-1 sm:mb-0 ml-2">
|
||||
Wahl des Vereinsvorstands
|
||||
</h1>
|
||||
<button
|
||||
class="text-xs inline-flex font-medium bg-sky-500/20 text-sky-700 rounded-full text-center px-2.5 py-1 whitespace-nowrap">
|
||||
2024
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Messages box -->
|
||||
<div
|
||||
class="shadow-sm rounded-xl px-6 divide-y divide-gray-200 dark:divide-gray-700/60">
|
||||
|
||||
<!-- Mail -->
|
||||
<div class="py-6">
|
||||
<div class="grid grid-cols-12 gap-6">
|
||||
|
||||
@foreach($positions as $type => $position)
|
||||
@if($electionConfig->firstWhere('type', $type))
|
||||
<div
|
||||
class="col-span-full sm:col-span-6 xl:col-span-4 bg-white dark:bg-gray-800 shadow-sm rounded-xl">
|
||||
<div class="flex flex-col h-full p-5">
|
||||
<header>
|
||||
<div class="flex items-center justify-between">
|
||||
<i class="fa-sharp-duotone fa-solid {{ $position['icon'] }} w-9 h-9 fill-current text-white"></i>
|
||||
</div>
|
||||
</header>
|
||||
<div class="grow mt-2">
|
||||
<div class="inline-flex text-gray-800 dark:text-gray-100 hover:text-gray-900 dark:hover:text-white mb-1">
|
||||
<h2 class="text-xl leading-snug font-semibold">{{ $position['title'] }}</h2>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
@php
|
||||
$votedResult = $loadedEvents->filter(fn ($event) => $event['pubkey'] === $currentPubkey)->firstWhere('type', $type);
|
||||
@endphp
|
||||
@if($votedResult)
|
||||
<span>Du hast "{{ $votedResult['votedFor']['name'] }}" gewählt</span>
|
||||
@else
|
||||
<span>Klicke auf den Kandidaten, den du wählen möchtest.</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<footer class="mt-5">
|
||||
<div class="flex justify-between items-center">
|
||||
@foreach($electionConfig->firstWhere('type', $type)['candidates'] as $c)
|
||||
<div wire:click="vote('{{ $c['pubkey'] }}', '{{ $type }}')"
|
||||
class="{{ $c['votedClass'] }} cursor-pointer text-xs inline-flex font-medium rounded-full text-center px-2.5 py-1">
|
||||
<div class="flex items-center">
|
||||
<img class="w-6 h-6 rounded-full mr-2 bg-black"
|
||||
src="{{ $c['picture'] ?? 'https://robohash.org/' . $c['pubkey'] }}"
|
||||
onerror="this.onerror=null; this.src='https://robohash.org/{{ $c['pubkey'] }}';"
|
||||
width="24" height="24" alt="{{ $c['name'] }}"/>
|
||||
{{ $c['name'] }}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Log events -->
|
||||
<div class="mt-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-xl mb-8">
|
||||
<header class="px-5 py-4">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100">Logged Votes on Nostr <span
|
||||
class="text-gray-400 dark:text-gray-500 font-medium">{{ $loadedEvents->count() }}</span>
|
||||
</h2>
|
||||
</header>
|
||||
<div>
|
||||
<!-- Table -->
|
||||
<div class="overflow-x-auto">
|
||||
<table
|
||||
class="table-auto w-full dark:text-gray-300 divide-y divide-gray-100 dark:divide-gray-700/60">
|
||||
<!-- Table header -->
|
||||
<thead
|
||||
class="text-xs uppercase text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-900/20 border-t border-gray-100 dark:border-gray-700/60">
|
||||
<tr>
|
||||
<th class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="font-semibold text-left">ID</div>
|
||||
</th>
|
||||
<th class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="font-semibold text-left">Kind</div>
|
||||
</th>
|
||||
<th class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="font-semibold text-left">Pubkey</div>
|
||||
</th>
|
||||
<th class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="font-semibold text-left">Created At</div>
|
||||
</th>
|
||||
<th class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="font-semibold text-left">Voted For</div>
|
||||
</th>
|
||||
<th class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="font-semibold text-left">Type</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- Table body -->
|
||||
<tbody class="text-sm">
|
||||
@foreach($loadedEvents as $event)
|
||||
<tr>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div class="font-medium">{{ \Illuminate\Support\Str::limit($event['id'], 10) }}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div>{{ $event['kind'] }}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<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'] }}</div>
|
||||
</td>
|
||||
<td class="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div>{{ $event['type'] }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@endvolt
|
||||
</x-layouts.app>
|
||||
54
resources/views/pages/association/election/index.blade.php
Normal file
54
resources/views/pages/association/election/index.blade.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
use function Livewire\Volt\computed;
|
||||
use function Livewire\Volt\mount;
|
||||
use function Livewire\Volt\state;
|
||||
use function Livewire\Volt\with;
|
||||
use function Livewire\Volt\updated;
|
||||
use function Laravel\Folio\{middleware};
|
||||
use function Laravel\Folio\name;
|
||||
use function Livewire\Volt\{on};
|
||||
|
||||
name('association.elections');
|
||||
|
||||
state(['elections' => []]);
|
||||
|
||||
mount(function () {
|
||||
$this->elections = \App\Models\Election::query()
|
||||
->get()
|
||||
->toArray();
|
||||
});
|
||||
|
||||
updated([
|
||||
]);
|
||||
|
||||
$saveElection = function ($index) {
|
||||
$election = $this->elections[$index];
|
||||
$electionModel = \App\Models\Election::find($election['id']);
|
||||
$electionModel->candidates = $election['candidates'];
|
||||
$electionModel->save();
|
||||
};
|
||||
|
||||
?>
|
||||
|
||||
<x-layouts.app title="{{ __('Wahlen') }}">
|
||||
@volt
|
||||
<div class="relative flex h-full">
|
||||
@foreach($elections as $election)
|
||||
<div class="w-1/3 p-4">
|
||||
<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 }})"/>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endvolt
|
||||
</x-layouts.app>
|
||||
164
resources/views/pages/association/profile.blade.php
Normal file
164
resources/views/pages/association/profile.blade.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
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};
|
||||
|
||||
name('association.profile');
|
||||
|
||||
?>
|
||||
|
||||
<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">
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="mb-8">
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="text-2xl md:text-3xl text-[#1B1B1B] dark:text-gray-100 font-bold">
|
||||
Einundzwanzig ist, was du draus machst
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-[#1B1B1B] shadow-sm rounded-xl mb-8">
|
||||
<div class="flex flex-col md:flex-row md:-mr-px">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div
|
||||
class="flex flex-nowrap overflow-x-scroll no-scrollbar md:block md:overflow-auto px-3 py-6 border-b md:border-b-0 md:border-r border-gray-200 dark:border-gray-700/60 min-w-60 md:space-y-3">
|
||||
<!-- Group 1 -->
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase mb-3">
|
||||
Meine Mitgliedschaft
|
||||
</div>
|
||||
<ul class="flex flex-nowrap md:block mr-3 md:mr-0">
|
||||
<li class="mr-0.5 md:mr-0 md:mb-0.5">
|
||||
<a class="flex items-center px-2.5 py-2 rounded-lg whitespace-nowrap bg-[linear-gradient(135deg,var(--tw-gradient-stops))] from-orange-500/[0.12] dark:from-orange-500/[0.24] to-orange-500/[0.04]"
|
||||
href="settings.html">
|
||||
<i class="fa-sharp-duotone fa-solid fa-id-card-clip shrink-0 fill-current text-orange-400 mr-2"></i>
|
||||
<span
|
||||
class="text-sm font-medium text-orange-500 dark:text-orange-400">Status</span>
|
||||
</a>
|
||||
</li>
|
||||
{{--<li class="mr-0.5 md:mr-0 md:mb-0.5">
|
||||
<a class="flex items-center px-2.5 py-2 rounded-lg whitespace-nowrap"
|
||||
href="notifications.html">
|
||||
<svg class="shrink-0 fill-current text-gray-400 dark:text-gray-500 mr-2" width="16"
|
||||
height="16" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="m9 12.614 4.806 1.374a.15.15 0 0 0 .174-.21L8.133 2.082a.15.15 0 0 0-.268 0L2.02 13.777a.149.149 0 0 0 .174.21L7 12.614V9a1 1 0 1 1 2 0v3.614Zm-1 1.794-5.257 1.503c-1.798.514-3.35-1.355-2.513-3.028L6.076 1.188c.791-1.584 3.052-1.584 3.845 0l5.848 11.695c.836 1.672-.714 3.54-2.512 3.028L8 14.408Z"/>
|
||||
</svg>
|
||||
<span
|
||||
class="text-sm font-medium text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200">My Notifications</span>
|
||||
</a>
|
||||
</li>--}}
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Group 2 -->
|
||||
{{--<div>
|
||||
<div class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase mb-3">Experience
|
||||
</div>
|
||||
<ul class="flex flex-nowrap md:block mr-3 md:mr-0">
|
||||
<li class="mr-0.5 md:mr-0 md:mb-0.5">
|
||||
<a class="flex items-center px-2.5 py-2 rounded-lg whitespace-nowrap"
|
||||
href="feedback.html">
|
||||
<svg class="shrink-0 fill-current text-gray-400 dark:text-gray-500 mr-2" width="16"
|
||||
height="16" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M14.3.3c.4-.4 1-.4 1.4 0 .4.4.4 1 0 1.4l-8 8c-.2.2-.4.3-.7.3-.3 0-.5-.1-.7-.3-.4-.4-.4-1 0-1.4l8-8zM15 7c.6 0 1 .4 1 1 0 4.4-3.6 8-8 8s-8-3.6-8-8 3.6-8 8-8c.6 0 1 .4 1 1s-.4 1-1 1C4.7 2 2 4.7 2 8s2.7 6 6 6 6-2.7 6-6c0-.6.4-1 1-1z"/>
|
||||
</svg>
|
||||
<span
|
||||
class="text-sm font-medium text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200">Give Feedback</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>--}}
|
||||
</div>
|
||||
|
||||
<!-- Panel -->
|
||||
<div class="grow">
|
||||
|
||||
<!-- Panel body -->
|
||||
<div class="p-6 space-y-6">
|
||||
<h2 class="text-2xl text-[#1B1B1B] dark:text-gray-100 font-bold mb-5">Aktueller Status</h2>
|
||||
|
||||
<!-- Picture -->
|
||||
<section>
|
||||
<div class="flex items-center">
|
||||
<x-button label="Mit Nostr verbinden" @click="openNostrLogin" x-show="!$store.nostr.user"/>
|
||||
<template x-if="$store.nostr.user">
|
||||
<div class="flex items">
|
||||
<img class="w-12 h-12 rounded-full"
|
||||
x-bind:src="$store.nostr.user.picture"
|
||||
alt="">
|
||||
<div class="ml-4">
|
||||
<h3 class="text-lg leading-snug text-[#1B1B1B] dark:text-gray-100 font-bold" x-text="$store.nostr.user.nip05"></h3>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400" x-text="$store.nostr.user.nip05"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Business Profile -->
|
||||
<section>
|
||||
<h3 class="text-xl leading-snug text-[#1B1B1B] dark:text-gray-100 font-bold mb-1">
|
||||
passives Mitglied werden
|
||||
</h3>
|
||||
<div class="text-sm">
|
||||
TEXT
|
||||
</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">
|
||||
<x-button label="Beantragen"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Email -->
|
||||
<section>
|
||||
<h3 class="text-xl leading-snug text-[#1B1B1B] dark:text-gray-100 font-bold mb-1">aktives
|
||||
Mitglied werden</h3>
|
||||
<div class="text-sm">
|
||||
TEXT
|
||||
</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">
|
||||
<x-button label="Beantragen"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endvolt
|
||||
</x-layouts.app>
|
||||
Reference in New Issue
Block a user