mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-01-28 07:43:18 +00:00
voting system with nostr added
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
@import 'utility-patterns.css';
|
||||
@import 'flatpickr.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
237
resources/css/flatpickr.css
Normal file
237
resources/css/flatpickr.css
Normal file
@@ -0,0 +1,237 @@
|
||||
/* Customise flatpickr */
|
||||
* {
|
||||
--calendarPadding: 24px;
|
||||
--daySize: 36px;
|
||||
--daysWidth: calc(var(--daySize)*7);
|
||||
}
|
||||
|
||||
@keyframes fpFadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -8px, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-calendar {
|
||||
border: inherit;
|
||||
@apply bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700/60 left-1/2;
|
||||
margin-left: calc(calc(var(--daysWidth) + calc(var(--calendarPadding)*2))*0.5*-1);
|
||||
padding: var(--calendarPadding);
|
||||
width: calc(var(--daysWidth) + calc(var(--calendarPadding)*2));
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.flatpickr-calendar {
|
||||
@apply left-0 right-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-right.flatpickr-calendar {
|
||||
@apply right-0 left-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.animate.open {
|
||||
animation: fpFadeInDown 200ms ease-out;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static.open {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.flatpickr-days {
|
||||
width: var(--daysWidth);
|
||||
}
|
||||
|
||||
.dayContainer {
|
||||
width: var(--daysWidth);
|
||||
min-width: var(--daysWidth);
|
||||
max-width: var(--daysWidth);
|
||||
}
|
||||
|
||||
.flatpickr-day {
|
||||
@apply bg-gray-50 dark:bg-gray-700/20 text-sm font-medium text-gray-600 dark:text-gray-100;
|
||||
max-width: var(--daySize);
|
||||
height: var(--daySize);
|
||||
line-height: var(--daySize);
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
.flatpickr-day.flatpickr-disabled:hover,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.notAllowed,
|
||||
.flatpickr-day.notAllowed.prevMonthDay,
|
||||
.flatpickr-day.notAllowed.nextMonthDay {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.selected.startRange,
|
||||
.flatpickr-day.startRange.startRange,
|
||||
.flatpickr-day.endRange.startRange,
|
||||
.flatpickr-day.selected.endRange,
|
||||
.flatpickr-day.startRange.endRange,
|
||||
.flatpickr-day.endRange.endRange,
|
||||
.flatpickr-day.selected.startRange.endRange,
|
||||
.flatpickr-day.startRange.startRange.endRange,
|
||||
.flatpickr-day.endRange.startRange.endRange {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
.flatpickr-day.flatpickr-disabled:hover,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.notAllowed,
|
||||
.flatpickr-day.notAllowed.prevMonthDay,
|
||||
.flatpickr-day.notAllowed.nextMonthDay {
|
||||
@apply text-gray-400 dark:text-gray-500;
|
||||
}
|
||||
|
||||
.rangeMode .flatpickr-day {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected,
|
||||
.flatpickr-day.startRange,
|
||||
.flatpickr-day.endRange,
|
||||
.flatpickr-day.selected.inRange,
|
||||
.flatpickr-day.startRange.inRange,
|
||||
.flatpickr-day.endRange.inRange,
|
||||
.flatpickr-day.selected:focus,
|
||||
.flatpickr-day.startRange:focus,
|
||||
.flatpickr-day.endRange:focus,
|
||||
.flatpickr-day.selected:hover,
|
||||
.flatpickr-day.startRange:hover,
|
||||
.flatpickr-day.endRange:hover,
|
||||
.flatpickr-day.selected.prevMonthDay,
|
||||
.flatpickr-day.startRange.prevMonthDay,
|
||||
.flatpickr-day.endRange.prevMonthDay,
|
||||
.flatpickr-day.selected.nextMonthDay,
|
||||
.flatpickr-day.startRange.nextMonthDay,
|
||||
.flatpickr-day.endRange.nextMonthDay {
|
||||
@apply bg-violet-600 text-violet-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.prevMonthDay.inRange,
|
||||
.flatpickr-day.nextMonthDay.inRange,
|
||||
.flatpickr-day.today.inRange,
|
||||
.flatpickr-day.prevMonthDay.today.inRange,
|
||||
.flatpickr-day.nextMonthDay.today.inRange,
|
||||
.flatpickr-day:hover,
|
||||
.flatpickr-day.prevMonthDay:hover,
|
||||
.flatpickr-day.nextMonthDay:hover,
|
||||
.flatpickr-day:focus,
|
||||
.flatpickr-day.prevMonthDay:focus,
|
||||
.flatpickr-day.nextMonthDay:focus,
|
||||
.flatpickr-day.today:hover,
|
||||
.flatpickr-day.today:focus {
|
||||
@apply bg-violet-500 text-violet-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.flatpickr-months {
|
||||
align-items: center;
|
||||
margin-top: -8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month,
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
position: static;
|
||||
height: auto;
|
||||
@apply text-gray-400 hover:text-gray-900 dark:text-gray-500 dark:hover:text-gray-300;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month svg,
|
||||
.flatpickr-months .flatpickr-next-month svg {
|
||||
width: 7px;
|
||||
height: 11px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month:hover svg,
|
||||
.flatpickr-months .flatpickr-next-month:hover svg {
|
||||
@apply fill-current;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
margin-right: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-month {
|
||||
@apply text-gray-800 dark:text-gray-100;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.flatpickr-current-month {
|
||||
@apply text-sm font-medium;
|
||||
position: static;
|
||||
height: auto;
|
||||
width: auto;
|
||||
left: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month {
|
||||
@apply font-medium m-0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.flatpickr-current-month input.cur-year {
|
||||
font-weight: inherit;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.numInputWrapper:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.numInputWrapper span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.flatpickr-weekday {
|
||||
@apply text-gray-400 dark:text-gray-500 font-medium text-xs;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowTop::before,
|
||||
.flatpickr-calendar.arrowTop::after,
|
||||
.flatpickr-calendar.arrowBottom::before,
|
||||
.flatpickr-calendar.arrowBottom::after {
|
||||
display: none;
|
||||
}
|
||||
138
resources/css/utility-patterns.css
Normal file
138
resources/css/utility-patterns.css
Normal file
@@ -0,0 +1,138 @@
|
||||
/* Typography */
|
||||
.h1 {
|
||||
@apply text-4xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-3xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
@apply text-3xl font-extrabold;
|
||||
}
|
||||
|
||||
.h4 {
|
||||
@apply text-2xl font-extrabold tracking-tight;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-4xl;
|
||||
}
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn,
|
||||
.btn-lg,
|
||||
.btn-sm,
|
||||
.btn-xs {
|
||||
@apply font-medium text-sm inline-flex items-center justify-center border border-transparent rounded-lg leading-5 shadow-sm transition;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
@apply px-4 py-3;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply px-2 py-1;
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
@apply px-2 py-0.5;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-results-button,
|
||||
input[type="search"]::-webkit-search-results-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select,
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply bg-white dark:bg-gray-900/30 border focus:ring-0 focus:ring-offset-0 dark:disabled:bg-gray-700/30 dark:disabled:border-gray-700 dark:disabled:hover:border-gray-700;
|
||||
}
|
||||
|
||||
.form-checkbox {
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select {
|
||||
@apply text-sm text-gray-800 dark:text-gray-100 leading-5 py-2 px-3 border-gray-200 hover:border-gray-300 focus:border-gray-300 dark:border-gray-700/60 dark:hover:border-gray-600 dark:focus:border-gray-600 shadow-sm rounded-lg;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
@apply placeholder-gray-400 dark:placeholder-gray-500;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@apply pr-10;
|
||||
}
|
||||
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply text-violet-500 checked:bg-violet-500 dark:checked:border-transparent border border-gray-300 focus:border-violet-300 dark:border-gray-700/60 dark:focus:border-violet-500/50;
|
||||
}
|
||||
|
||||
/* Switch element */
|
||||
.form-switch {
|
||||
@apply relative select-none;
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.form-switch label {
|
||||
@apply block overflow-hidden cursor-pointer h-6 rounded-full;
|
||||
}
|
||||
|
||||
.form-switch label > span:first-child {
|
||||
@apply absolute block rounded-full;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
right: 50%;
|
||||
transition: all .15s ease-out;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:checked + label {
|
||||
@apply bg-violet-500;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:checked + label > span:first-child {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:disabled + label {
|
||||
@apply cursor-not-allowed bg-gray-100 dark:bg-gray-700/20 border border-gray-200 dark:border-gray-700/60;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"]:disabled + label > span:first-child {
|
||||
@apply bg-gray-400 dark:bg-gray-600;
|
||||
}
|
||||
|
||||
/* Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
import {Alpine, Livewire} from '../../vendor/livewire/livewire/dist/livewire.esm';
|
||||
|
||||
import nostrApp from "./nostrApp.js";
|
||||
import nostrLogin from "./nostrLogin.js";
|
||||
|
||||
import './bootstrap';
|
||||
|
||||
// Light switcher
|
||||
@@ -6,3 +11,12 @@ document.documentElement.classList.add('dark');
|
||||
document.querySelector('html').style.colorScheme = 'dark';
|
||||
localStorage.setItem('dark-mode', true);
|
||||
document.dispatchEvent(new CustomEvent('darkMode', { detail: { mode: 'on' } }));
|
||||
|
||||
Alpine.store('nostr', {
|
||||
user: null,
|
||||
});
|
||||
|
||||
Alpine.data('nostrApp', nostrApp);
|
||||
Alpine.data('nostrLogin', nostrLogin);
|
||||
|
||||
Livewire.start();
|
||||
|
||||
15
resources/js/nostrApp.js
Normal file
15
resources/js/nostrApp.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export default (livewireComponent) => ({
|
||||
|
||||
signThisEvent: livewireComponent.entangle('signThisEvent'),
|
||||
|
||||
init() {
|
||||
// on change of signThisEvent, call the method
|
||||
this.$watch('signThisEvent', async () => {
|
||||
const toBeSigned = JSON.parse(this.signThisEvent);
|
||||
console.log(toBeSigned);
|
||||
const signedEvent = await window.nostr.signEvent(toBeSigned);
|
||||
this.$wire.call('signEvent', signedEvent);
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
29
resources/js/nostrLogin.js
Normal file
29
resources/js/nostrLogin.js
Normal file
@@ -0,0 +1,29 @@
|
||||
export default () => ({
|
||||
|
||||
openNostrLogin() {
|
||||
window.nostr.getPublicKey();
|
||||
},
|
||||
|
||||
init() {
|
||||
// listen for nostr auth events
|
||||
document.addEventListener('nlAuth', (e) => {
|
||||
// type is login, signup or logout
|
||||
if (e.detail.type === 'login' || e.detail.type === 'signup') {
|
||||
console.log('User logged in');
|
||||
// fetch profile from /api/nostr/profile/{publicKey}
|
||||
fetch('/api/nostr/profile/' + e.detail.pubkey)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Profile fetched', data);
|
||||
// store the profile in AlpineJS store
|
||||
Alpine.store('nostr', { user: data });
|
||||
this.$dispatch('nostrLoggedIn', {pubkey: data.pubkey});
|
||||
});
|
||||
} else {
|
||||
console.log('User logged out')
|
||||
Alpine.store('nostr', { user: null });
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
});
|
||||
@@ -5,17 +5,19 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>{{ $title ?? 'Page Title' }}</title>
|
||||
@livewireStyles
|
||||
@vite(['resources/js/app.js','resources/css/app.css'])
|
||||
@googlefonts
|
||||
<script src="https://kit.fontawesome.com/866fd3d0ab.js" crossorigin="anonymous"></script>
|
||||
<script src='https://www.unpkg.com/nostr-login@latest/dist/unpkg.js' data-perms="sign_event:1,sign_event:0"
|
||||
data-theme="default" data-dark-mode="true"></script>
|
||||
@wireUiScripts
|
||||
@stack('scripts')
|
||||
</head>
|
||||
<body
|
||||
class="font-sans antialiased bg-gray-100 dark:bg-[#222222] text-gray-600 dark:text-gray-400"
|
||||
:class="{ 'sidebar-expanded': sidebarExpanded }"
|
||||
x-data="{ sidebarOpen: false, sidebarExpanded: localStorage.getItem('sidebar-expanded') == 'true' }"
|
||||
x-data="{ sidebarOpen: false, sidebarExpanded: localStorage.getItem('sidebar-expanded') == 'true', inboxSidebarOpen: false }"
|
||||
x-init="$watch('sidebarExpanded', value => localStorage.setItem('sidebar-expanded', value))"
|
||||
>
|
||||
<script>
|
||||
@@ -25,9 +27,11 @@
|
||||
document.querySelector('body').classList.remove('sidebar-expanded');
|
||||
}
|
||||
</script>
|
||||
<div class="flex h-[100dvh] overflow-hidden">
|
||||
@include('components.layouts.sidebar')
|
||||
<div class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
<div x-data="nostrLogin"
|
||||
class="flex h-[100dvh] overflow-hidden">
|
||||
<livewire:layout.sidebar/>
|
||||
<div
|
||||
class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
<!-- Site header -->
|
||||
<header
|
||||
class="sticky top-0 before:absolute before:inset-0 before:backdrop-blur-md before:bg-white/90 dark:before:bg-[#222222]/90 lg:before:bg-[#222222]/90 dark:lg:before:bg-[#222222]/90 before:-z-10 max-lg:shadow-sm z-30">
|
||||
@@ -125,11 +129,10 @@
|
||||
</div>
|
||||
</header>
|
||||
<main class="grow">
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
@livewireScriptConfig
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!-- Association group -->
|
||||
<div>
|
||||
<h3 class="text-xs uppercase text-gray-400 dark:text-gray-500 font-semibold pl-3">
|
||||
<span class="hidden lg:block lg:sidebar-expanded:hidden 2xl:hidden text-center w-6"
|
||||
aria-hidden="true">•••</span>
|
||||
<span class="lg:hidden lg:sidebar-expanded:block 2xl:block">Verein</span>
|
||||
</h3>
|
||||
<ul class="mt-3">
|
||||
<li class="{{ $currentRoute === 'association.profile' ? $isCurrentRouteClass : $isNotCurrentRouteClass }}">
|
||||
<a class="block text-gray-800 dark:text-gray-100 hover:text-gray-900 dark:hover:text-white truncate transition" href="{{ route('association.profile') }}">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-sharp-duotone fa-solid fa-id-card-clip h-6 w-6"></i>
|
||||
<span class="text-sm font-medium ml-4 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Meine Mitgliedschaft</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ $currentRoute === 'association.election' ? $isCurrentRouteClass : $isNotCurrentRouteClass }}">
|
||||
<a class="block text-gray-800 dark:text-gray-100 hover:text-gray-900 dark:hover:text-white truncate transition" href="{{ route('association.election', ['election' => date('Y')]) }}">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-sharp-duotone fa-solid fa-check-to-slot h-6 w-6"></i>
|
||||
<span class="text-sm font-medium ml-4 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Vorstand</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ $currentRoute === 'association.elections' ? $isCurrentRouteClass : $isNotCurrentRouteClass }}">
|
||||
<a class="block text-gray-800 dark:text-gray-100 hover:text-gray-900 dark:hover:text-white truncate transition" href="{{ route('association.elections') }}">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-sharp-duotone fa-solid fa-booth-curtain h-6 w-6"></i>
|
||||
<span class="text-sm font-medium ml-4 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Wahlen</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -15,7 +15,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="pl-4 pr-3 py-2 rounded-lg mb-0.5 last:mb-0">
|
||||
<a class="block text-gray-800 dark:text-gray-100 hover:text-gray-900 dark:hover:text-white truncate transition" href="{{ route('meetups.table') }}">
|
||||
<a class="block text-gray-800 dark:text-gray-100 hover:text-gray-900 dark:hover:text-white truncate transition" href="{{ route('meetups.grid') }}">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-sharp-duotone fa-solid fa-handshake-angle h-6 w-6"></i>
|
||||
<span class="text-sm font-medium ml-4 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">Alle Meetups</span>
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<div class="min-w-fit">
|
||||
@php
|
||||
$isCurrentRouteClass = 'pl-4 pr-3 py-2 rounded-lg mb-0.5 last:mb-0 bg-[linear-gradient(135deg,var(--tw-gradient-stops))] from-violet-500/[0.12] dark:from-violet-500/[0.24] to-violet-500/[0.04]';
|
||||
$isNotCurrentRouteClass = 'pl-4 pr-3 py-2 rounded-lg mb-0.5 last:mb-0';
|
||||
$isCurrentSubItem = 'block text-violet-500 transition truncate';
|
||||
$isNotCurrentSubItem = 'block text-gray-500/90 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition truncate';
|
||||
@endphp
|
||||
|
||||
<!-- Sidebar backdrop (mobile only) -->
|
||||
<div
|
||||
class="fixed inset-0 bg-gray-900 bg-opacity-30 z-40 lg:hidden lg:z-auto transition-opacity duration-200"
|
||||
@@ -10,7 +17,7 @@
|
||||
<!-- Sidebar -->
|
||||
<div
|
||||
id="sidebar"
|
||||
class="flex flex-col absolute z-40 left-0 top-0 lg:static lg:left-auto lg:top-auto lg:translate-x-0 h-[100dvh] overflow-y-scroll lg:overflow-y-auto no-scrollbar w-64 lg:w-20 lg:sidebar-expanded:!w-64 2xl:!w-64 shrink-0 bg-white dark:bg-[#1B1B1B] shadow-sm rounded-r-2xl p-4 transition-all duration-200 ease-in-out"
|
||||
class="flex flex-col absolute z-40 left-0 top-0 lg:static lg:left-auto lg:top-auto lg:translate-x-0 h-[100dvh] overflow-y-scroll lg:overflow-y-auto no-scrollbar w-64 lg:w-20 lg:sidebar-expanded:!w-64 2xl:!w-64 shrink-0 bg-white dark:bg-[#222222] shadow-sm rounded-r-2xl p-4 transition-all duration-200 ease-in-out"
|
||||
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-64'"
|
||||
@click.outside="sidebarOpen = false"
|
||||
@keydown.escape.window="sidebarOpen = false"
|
||||
@@ -38,7 +45,8 @@
|
||||
|
||||
<!-- Links -->
|
||||
<div class="space-y-8">
|
||||
@include('components.layouts.navigation.meetups')
|
||||
@include('components.layouts.navigation.meetups', ['isCurrentRouteClass' => $isCurrentRouteClass, 'isNotCurrentRouteClass' => $isNotCurrentRouteClass])
|
||||
@include('components.layouts.navigation.association', ['isCurrentRouteClass' => $isCurrentRouteClass, 'isNotCurrentRouteClass' => $isNotCurrentRouteClass])
|
||||
{{--@include('components.layouts.navigation.events')
|
||||
@include('components.layouts.navigation.courses')
|
||||
@include('components.layouts.navigation.nostr')
|
||||
|
||||
87
resources/views/livewire/layout/sidebar.blade.php
Normal file
87
resources/views/livewire/layout/sidebar.blade.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
use function Livewire\Volt\state;
|
||||
use function Livewire\Volt\mount;
|
||||
|
||||
state(['currentRoute' => '']);
|
||||
|
||||
mount(function() {
|
||||
$currentLivewireRouteName = request()->route()->getName();
|
||||
$this->currentRoute = $currentLivewireRouteName;
|
||||
});
|
||||
|
||||
?>
|
||||
|
||||
<div class="min-w-fit">
|
||||
@php
|
||||
$isCurrentRouteClass = 'pl-4 pr-3 py-2 rounded-lg mb-0.5 last:mb-0 bg-[linear-gradient(135deg,var(--tw-gradient-stops))] from-orange-500/[0.12] dark:from-orange-500/[0.24] to-orange-500/[0.04]';
|
||||
$isNotCurrentRouteClass = 'pl-4 pr-3 py-2 rounded-lg mb-0.5 last:mb-0';
|
||||
$isCurrentSubItem = 'block text-orange-500 transition truncate';
|
||||
$isNotCurrentSubItem = 'block text-gray-500/90 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition truncate';
|
||||
@endphp
|
||||
|
||||
<!-- Sidebar backdrop (mobile only) -->
|
||||
<div
|
||||
class="fixed inset-0 bg-gray-900 bg-opacity-30 z-40 lg:hidden lg:z-auto transition-opacity duration-200"
|
||||
:class="sidebarOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'"
|
||||
aria-hidden="true"
|
||||
x-cloak
|
||||
></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div
|
||||
id="sidebar"
|
||||
class="flex flex-col absolute z-40 left-0 top-0 lg:static lg:left-auto lg:top-auto lg:translate-x-0 h-[100dvh] overflow-y-scroll lg:overflow-y-auto no-scrollbar w-64 lg:w-20 lg:sidebar-expanded:!w-64 2xl:!w-64 shrink-0 bg-white dark:bg-[#222222] shadow-sm rounded-r-2xl p-4 transition-all duration-200 ease-in-out"
|
||||
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-64'"
|
||||
@click.outside="sidebarOpen = false"
|
||||
@keydown.escape.window="sidebarOpen = false"
|
||||
x-cloak="lg"
|
||||
>
|
||||
|
||||
<!-- Sidebar header -->
|
||||
<div class="flex justify-between mb-10 pr-3 sm:px-2">
|
||||
<!-- Close button -->
|
||||
<button class="lg:hidden text-gray-500 hover:text-gray-400" @click.stop="sidebarOpen = !sidebarOpen"
|
||||
aria-controls="sidebar" :aria-expanded="sidebarOpen">
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7 18.7l1.4-1.4L7.8 13H20v-2H7.8l4.3-4.3-1.4-1.4L4 12z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Logo -->
|
||||
<img src="{{ asset('img/einundzwanzig-horizontal-inverted.svg') }}" alt="Logo" width="auto" height="32">
|
||||
</div>
|
||||
|
||||
@php
|
||||
$activeLinkGroupClass = ' bg-[linear-gradient(135deg,var(--tw-gradient-stops))] from-amber-500/[0.12] dark:from-amber-500/[0.24] to-amber-500/[0.04]';
|
||||
$activeItemClass = 'block text-amber-500 transition truncate';
|
||||
@endphp
|
||||
|
||||
<!-- Links -->
|
||||
<div class="space-y-8">
|
||||
@include('components.layouts.navigation.meetups')
|
||||
@include('components.layouts.navigation.association')
|
||||
{{--@include('components.layouts.navigation.events')
|
||||
@include('components.layouts.navigation.courses')
|
||||
@include('components.layouts.navigation.nostr')
|
||||
@include('components.layouts.navigation.legacy')--}}
|
||||
</div>
|
||||
|
||||
<!-- Expand / collapse button -->
|
||||
<div class="pt-3 hidden lg:inline-flex 2xl:hidden justify-end mt-auto">
|
||||
<div class="w-12 pl-4 pr-3 py-2">
|
||||
<button
|
||||
class="text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400 transition-colors"
|
||||
@click="sidebarExpanded = !sidebarExpanded">
|
||||
<span class="sr-only">Expand / collapse sidebar</span>
|
||||
<svg class="shrink-0 fill-current text-gray-400 dark:text-gray-500 sidebar-expanded:rotate-180"
|
||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M15 16a1 1 0 0 1-1-1V1a1 1 0 1 1 2 0v14a1 1 0 0 1-1 1ZM8.586 7H1a1 1 0 1 0 0 2h7.586l-2.793 2.793a1 1 0 1 0 1.414 1.414l4.5-4.5A.997.997 0 0 0 12 8.01M11.924 7.617a.997.997 0 0 0-.217-.324l-4.5-4.5a1 1 0 0 0-1.414 1.414L8.586 7M12 7.99a.996.996 0 0 0-.076-.373Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -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>
|
||||
247
resources/views/pages/meetups/grid.blade.php
Normal file
247
resources/views/pages/meetups/grid.blade.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
use Livewire\Volt\Component;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
use swentel\nostr\Key\Key;
|
||||
use swentel\nostr\Message\RequestMessage;
|
||||
use swentel\nostr\Relay\Relay;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
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('meetups.grid');
|
||||
|
||||
?>
|
||||
|
||||
|
||||
<x-layouts.app title="{{ __('Meetups') }}">
|
||||
@volt
|
||||
<div class="relative flex">
|
||||
|
||||
<!-- Profile sidebar -->
|
||||
<div
|
||||
id="profile-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="profileSidebarOpen ? '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)]">
|
||||
|
||||
<!-- Profile 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">
|
||||
<!-- Profile image -->
|
||||
<div class="relative">
|
||||
<div class="grow flex items-center truncate">
|
||||
<div class="truncate">
|
||||
<span
|
||||
class="font-semibold text-gray-800 dark:text-gray-100">All meetups</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add button -->
|
||||
<button
|
||||
class="p-1.5 shrink-0 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700/60 hover:border-gray-300 dark:hover:border-gray-600 shadow-sm ml-2">
|
||||
<svg class="fill-current text-violet-500" width="16" height="16"
|
||||
viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Group body -->
|
||||
<div class="px-5 py-4">
|
||||
<!-- Search form -->
|
||||
<div class="relative">
|
||||
<label for="profile-search" class="sr-only">Search</label>
|
||||
<input id="profile-search" class="form-input w-full pl-9 bg-white dark:bg-gray-800 rounded"
|
||||
type="search" placeholder="Search…"/>
|
||||
<button class="absolute inset-0 right-auto group cursor-default" 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>
|
||||
</div>
|
||||
<!-- Team members -->
|
||||
<div class="mt-4">
|
||||
<div class="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase mb-3">
|
||||
Countries
|
||||
</div>
|
||||
<ul class="mb-6">
|
||||
<li class="-mx-2">
|
||||
<button
|
||||
class="w-full p-2 rounded-lg bg-[linear-gradient(135deg,var(--tw-gradient-stops))] from-violet-500/[0.12] dark:from-violet-500/[0.24] to-violet-500/[0.04]"
|
||||
@click="profileSidebarOpen = false">
|
||||
<div class="flex items-center">
|
||||
<div class="relative mr-2">
|
||||
<img class="w-8 h-8 rounded-full"
|
||||
src="{{ asset('vendor/blade-country-flags/1x1-de.svg') }}"
|
||||
width="32" height="32" alt="User 08"/>
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<span class="text-sm font-medium text-gray-800 dark:text-gray-100">Deutschland</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
<li class="-mx-2">
|
||||
<button
|
||||
class="w-full p-2"
|
||||
@click="profileSidebarOpen = false">
|
||||
<div class="flex items-center">
|
||||
<div class="relative mr-2">
|
||||
<img class="w-8 h-8 rounded-full"
|
||||
src="{{ asset('vendor/blade-country-flags/1x1-at.svg') }}"
|
||||
width="32" height="32" alt="User 08"/>
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<span class="text-sm font-medium text-gray-800 dark:text-gray-100">Österreich</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
<li class="-mx-2">
|
||||
<button
|
||||
class="w-full p-2"
|
||||
@click="profileSidebarOpen = false">
|
||||
<div class="flex items-center">
|
||||
<div class="relative mr-2">
|
||||
<img class="w-8 h-8 rounded-full"
|
||||
src="{{ asset('vendor/blade-country-flags/1x1-ch.svg') }}"
|
||||
width="32" height="32" alt="User 08"/>
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<span class="text-sm font-medium text-gray-800 dark:text-gray-100">Schweiz</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile body -->
|
||||
<div
|
||||
class="grow flex flex-col md:translate-x-0 transition-transform duration-300 ease-in-out"
|
||||
:class="profileSidebarOpen ? 'translate-x-1/3' : 'translate-x-0'"
|
||||
>
|
||||
|
||||
<!-- Profile background -->
|
||||
<div class="relative h-56 bg-gray-200 dark:bg-gray-900">
|
||||
<img class="object-cover object-top h-full w-full" src="{{ asset('img/meetup_saarland.jpg') }}"
|
||||
width="979" height="220"
|
||||
alt="Profile background"/>
|
||||
<!-- Close button -->
|
||||
<button
|
||||
class="md:hidden absolute top-4 left-4 sm:left-6 text-white opacity-80 hover:opacity-100"
|
||||
@click.stop="profileSidebarOpen = !profileSidebarOpen"
|
||||
aria-controls="profile-sidebar"
|
||||
:aria-expanded="profileSidebarOpen"
|
||||
>
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7 18.7l1.4-1.4L7.8 13H20v-2H7.8l4.3-4.3-1.4-1.4L4 12z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="relative px-4 sm:px-6 pb-8">
|
||||
|
||||
<!-- Pre-header -->
|
||||
<div class="-mt-16 mb-6 sm:mb-3">
|
||||
|
||||
<div class="flex flex-col items-center sm:flex-row sm:justify-between sm:items-end">
|
||||
|
||||
<!-- Avatar -->
|
||||
<div class="inline-flex -ml-1 -mt-1 mb-4 sm:mb-0" style="height: 128px">
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex space-x-2 sm:mb-2">
|
||||
{{-- ACTIONS --}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid xl:grid-cols-2 gap-6 mb-8">
|
||||
|
||||
<!-- Item 1 -->
|
||||
<article class="flex bg-white dark:bg-[#1B1B1B] shadow-sm rounded-xl overflow-hidden">
|
||||
<!-- Image -->
|
||||
<a class="relative block w-24 sm:w-56 xl:sidebar-expanded:w-40 2xl:sidebar-expanded:w-56 shrink-0" href="meetups-post.html">
|
||||
<img class="absolute object-cover object-center w-full h-full" src="./images/meetups-thumb-01.jpg" width="220" height="236" alt="Meetup 01" />
|
||||
<!-- Like button -->
|
||||
<button class="absolute top-0 right-0 mt-4 mr-4">
|
||||
<div class="text-gray-100 bg-gray-900 bg-opacity-60 rounded-full">
|
||||
<span class="sr-only">Like</span>
|
||||
<svg class="h-8 w-8 fill-current" viewBox="0 0 32 32">
|
||||
<path d="M22.682 11.318A4.485 4.485 0 0019.5 10a4.377 4.377 0 00-3.5 1.707A4.383 4.383 0 0012.5 10a4.5 4.5 0 00-3.182 7.682L16 24l6.682-6.318a4.5 4.5 0 000-6.364zm-1.4 4.933L16 21.247l-5.285-5A2.5 2.5 0 0112.5 12c1.437 0 2.312.681 3.5 2.625C17.187 12.681 18.062 12 19.5 12a2.5 2.5 0 011.785 4.251h-.003z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</a>
|
||||
<!-- Content -->
|
||||
<div class="grow p-5 flex flex-col">
|
||||
<div class="grow">
|
||||
<div class="text-sm font-semibold text-violet-500 uppercase mb-2">Mon 27 Dec, 2024</div>
|
||||
<a class="inline-flex mb-2" href="meetups-post.html">
|
||||
<h3 class="text-lg font-bold text-gray-800 dark:text-gray-100">Silicon Valley Bootstrapper Breakfast Online for 2024</h3>
|
||||
</a>
|
||||
<div class="text-sm">Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts.</div>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-between items-center mt-3">
|
||||
<!-- Tag -->
|
||||
<div class="text-xs inline-flex items-center font-medium border border-gray-200 dark:border-gray-700/60 text-gray-600 dark:text-gray-400 rounded-full text-center px-2.5 py-1">
|
||||
<svg class="w-4 h-3 fill-gray-400 dark:fill-gray-500 mr-2" viewBox="0 0 16 12">
|
||||
<path d="m16 2-4 2.4V2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7.6l4 2.4V2ZM2 10V2h8v8H2Z" />
|
||||
</svg>
|
||||
<span>Online Event</span>
|
||||
</div>
|
||||
<!-- Avatars -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="flex -space-x-3 -ml-0.5">
|
||||
<img class="rounded-full border-2 border-white dark:border-gray-800 box-content" src="./images/avatar-01.jpg" width="28" height="28" alt="User 01" />
|
||||
<img class="rounded-full border-2 border-white dark:border-gray-800 box-content" src="./images/avatar-04.jpg" width="28" height="28" alt="User 04" />
|
||||
<img class="rounded-full border-2 border-white dark:border-gray-800 box-content" src="./images/avatar-05.jpg" width="28" height="28" alt="User 05" />
|
||||
</div>
|
||||
<div class="text-xs font-medium text-gray-400 dark:text-gray-500 italic">+22</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endvolt
|
||||
</x-layouts.app>
|
||||
135
resources/views/pages/meetups/mockup.blade.php
Normal file
135
resources/views/pages/meetups/mockup.blade.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?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 Laravel\Folio\{middleware};
|
||||
use function Laravel\Folio\name;
|
||||
use function Livewire\Volt\{on};
|
||||
|
||||
name('meetups.mockup');
|
||||
|
||||
state(['events' => []]);
|
||||
state(['title' => '']);
|
||||
state(['description' => '']);
|
||||
state(['signThisEvent' => '']);
|
||||
|
||||
mount(function () {
|
||||
$this->loadEvents();
|
||||
});
|
||||
|
||||
$loadEvents = function() {
|
||||
$subscription = new Subscription();
|
||||
$subscriptionId = $subscription->setId();
|
||||
|
||||
$filter1 = new Filter();
|
||||
$filter1->setKinds([31924]); // You can add multiple kind numbers
|
||||
$filter1->setLimit(25); // Limit to fetch only a maximum of 25 events
|
||||
$filters = [$filter1]; // You can add multiple filters.
|
||||
|
||||
$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(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();
|
||||
};
|
||||
|
||||
$save = function () {
|
||||
$note = new NostrEvent();
|
||||
$note->setContent($this->description);
|
||||
$note->setKind(31924);
|
||||
$note->setTags([
|
||||
['d', str()->uuid()->toString()],
|
||||
['title', $this->title],
|
||||
]);
|
||||
$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://nostream:8008';
|
||||
$relay = new Relay($relayUrl);
|
||||
$relay->setMessage($eventMessage);
|
||||
$result = $relay->send();
|
||||
|
||||
$this->title = '';
|
||||
$this->description = '';
|
||||
$this->loadEvents();
|
||||
};
|
||||
|
||||
?>
|
||||
|
||||
<x-layouts.app title="{{ __('Mockup') }}">
|
||||
@volt
|
||||
<div class="relative" x-data="nostrApp(@this)">
|
||||
<div class="flex items-center space-x-2 mt-12">
|
||||
<div>
|
||||
<x-input wire:model.live.debounce="title" label="Title"/>
|
||||
</div>
|
||||
<div>
|
||||
<x-textarea wire:model.live.debounce="description" label="Description"/>
|
||||
</div>
|
||||
<div>
|
||||
<x-button wire:click="save" label="Save"/>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-2x font-bold py-6">Meetups</h1>
|
||||
<ul class="border-t border-white space-y-4 divide-y divide-white">
|
||||
@foreach($events as $event)
|
||||
<li>
|
||||
<div class="flex items">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div>
|
||||
Name: {{ collect($event['tags'])->firstWhere(0, 'title')[1] }}
|
||||
</div>
|
||||
<div>
|
||||
Beschreibung: {{ $event['content'] }}
|
||||
</div>
|
||||
<div>
|
||||
@dump($event)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endvolt
|
||||
</x-layouts.app>
|
||||
Reference in New Issue
Block a user