From 0694a2d8379d0717758cdb677a89b221f14a00e9 Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode Date: Sun, 18 Jan 2026 15:19:00 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20Livewire=20Flux=20components?= =?UTF-8?q?=20and=20new=20tests=20for=20project=20proposal=20and=20editing?= =?UTF-8?q?=20forms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .junie/guidelines.md | 31 ++ AGENTS.md | 31 ++ CLAUDE.md | 31 ++ app/Livewire/EinundzwanzigPlebTable.php | 66 ++--- composer.json | 12 +- composer.lock | 270 +++++++++--------- .../views/components/layouts/app.blade.php | 20 +- .../views/components/project-card.blade.php | 22 +- resources/views/layouts/app.blade.php | 240 +++++----------- .../association/election/index.blade.php | 11 +- .../association/election/show.blade.php | 26 +- .../views/livewire/association/news.blade.php | 58 ++-- .../livewire/association/profile.blade.php | 55 ++-- .../project-support/form/create.blade.php | 25 +- .../project-support/form/edit.blade.php | 33 ++- .../project-support/index.blade.php | 11 +- .../Livewire/ProjectProposalFormTest.php | 111 +++++++ .../Livewire/ProjectSupportCreateTest.php | 98 +++++++ .../Livewire/ProjectSupportEditTest.php | 109 +++++++ 19 files changed, 816 insertions(+), 444 deletions(-) create mode 100644 tests/Feature/Livewire/ProjectProposalFormTest.php create mode 100644 tests/Feature/Livewire/ProjectSupportCreateTest.php create mode 100644 tests/Feature/Livewire/ProjectSupportEditTest.php diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 1d02318..1f03f94 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -14,6 +14,8 @@ This application is a Laravel application and its main Laravel ecosystems packag - laravel/prompts (PROMPTS) - v0 - laravel/reverb (REVERB) - v1 - laravel/sail (SAIL) - v1 +- livewire/flux (FLUXUI_FREE) - v2 +- livewire/flux-pro (FLUXUI_PRO) - v2 - livewire/livewire (LIVEWIRE) - v4 - laravel/mcp (MCP) - v0 - laravel/pint (PINT) - v1 @@ -124,6 +126,13 @@ protected function isAccessible(User $user, ?string $path = null): bool - Execute PHP scripts: `vendor/bin/sail php [script]` - View all available Sail commands by running `vendor/bin/sail` without arguments. +=== tests rules === + +## Test Enforcement + +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter. + === laravel/core rules === ## Do Things the Laravel Way @@ -191,6 +200,28 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Models - Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. +=== fluxui-pro/core rules === + +## Flux UI Pro + +- This project is using the Pro version of Flux UI. It has full access to the free components and variants, as well as full access to the Pro components and variants. +- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. +- You should use Flux UI components when available. +- Fallback to standard Blade components if Flux is unavailable. +- If available, use the `search-docs` tool to get the exact documentation and code snippets available for this project. +- Flux UI components look like this: + + + + + +### Available Components +This is correct as of Boost installation, but there may be additional components within the codebase. + + +accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip + + === livewire/core rules === ## Livewire diff --git a/AGENTS.md b/AGENTS.md index 1d02318..1f03f94 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,8 @@ This application is a Laravel application and its main Laravel ecosystems packag - laravel/prompts (PROMPTS) - v0 - laravel/reverb (REVERB) - v1 - laravel/sail (SAIL) - v1 +- livewire/flux (FLUXUI_FREE) - v2 +- livewire/flux-pro (FLUXUI_PRO) - v2 - livewire/livewire (LIVEWIRE) - v4 - laravel/mcp (MCP) - v0 - laravel/pint (PINT) - v1 @@ -124,6 +126,13 @@ protected function isAccessible(User $user, ?string $path = null): bool - Execute PHP scripts: `vendor/bin/sail php [script]` - View all available Sail commands by running `vendor/bin/sail` without arguments. +=== tests rules === + +## Test Enforcement + +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter. + === laravel/core rules === ## Do Things the Laravel Way @@ -191,6 +200,28 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Models - Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. +=== fluxui-pro/core rules === + +## Flux UI Pro + +- This project is using the Pro version of Flux UI. It has full access to the free components and variants, as well as full access to the Pro components and variants. +- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. +- You should use Flux UI components when available. +- Fallback to standard Blade components if Flux is unavailable. +- If available, use the `search-docs` tool to get the exact documentation and code snippets available for this project. +- Flux UI components look like this: + + + + + +### Available Components +This is correct as of Boost installation, but there may be additional components within the codebase. + + +accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip + + === livewire/core rules === ## Livewire diff --git a/CLAUDE.md b/CLAUDE.md index 1d02318..1f03f94 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,8 @@ This application is a Laravel application and its main Laravel ecosystems packag - laravel/prompts (PROMPTS) - v0 - laravel/reverb (REVERB) - v1 - laravel/sail (SAIL) - v1 +- livewire/flux (FLUXUI_FREE) - v2 +- livewire/flux-pro (FLUXUI_PRO) - v2 - livewire/livewire (LIVEWIRE) - v4 - laravel/mcp (MCP) - v0 - laravel/pint (PINT) - v1 @@ -124,6 +126,13 @@ protected function isAccessible(User $user, ?string $path = null): bool - Execute PHP scripts: `vendor/bin/sail php [script]` - View all available Sail commands by running `vendor/bin/sail` without arguments. +=== tests rules === + +## Test Enforcement + +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter. + === laravel/core rules === ## Do Things the Laravel Way @@ -191,6 +200,28 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Models - Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. +=== fluxui-pro/core rules === + +## Flux UI Pro + +- This project is using the Pro version of Flux UI. It has full access to the free components and variants, as well as full access to the Pro components and variants. +- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. +- You should use Flux UI components when available. +- Fallback to standard Blade components if Flux is unavailable. +- If available, use the `search-docs` tool to get the exact documentation and code snippets available for this project. +- Flux UI components look like this: + + + + + +### Available Components +This is correct as of Boost installation, but there may be additional components within the codebase. + + +accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, callout, card, chart, checkbox, command, composer, context, date-picker, dropdown, editor, field, file-upload, heading, icon, input, kanban, modal, navbar, otp-input, pagination, pillbox, popover, profile, radio, select, separator, skeleton, slider, switch, table, tabs, text, textarea, time-picker, toast, tooltip + + === livewire/core rules === ## Livewire diff --git a/app/Livewire/EinundzwanzigPlebTable.php b/app/Livewire/EinundzwanzigPlebTable.php index 4c6f265..880e099 100644 --- a/app/Livewire/EinundzwanzigPlebTable.php +++ b/app/Livewire/EinundzwanzigPlebTable.php @@ -9,19 +9,15 @@ use PowerComponents\LivewirePowerGrid\Button; use PowerComponents\LivewirePowerGrid\Column; use PowerComponents\LivewirePowerGrid\Detail; use PowerComponents\LivewirePowerGrid\Exportable; -use PowerComponents\LivewirePowerGrid\Facades\Rule; use PowerComponents\LivewirePowerGrid\Footer; use PowerComponents\LivewirePowerGrid\Header; -use PowerComponents\LivewirePowerGrid\PowerGrid; -use PowerComponents\LivewirePowerGrid\PowerGridFields; use PowerComponents\LivewirePowerGrid\PowerGridComponent; -use PowerComponents\LivewirePowerGrid\Traits\WithExport; -use WireUi\Traits\WireUiActions; -final class EinundzwanzigPlebTable extends PowerGridComponent +class EinundzwanzigPlebTable extends PowerGridComponent { - use WireUiActions; - use WithExport; + public string $sortField = 'association_status'; + + public string $sortDirection = 'desc'; public string $sortField = 'association_status'; @@ -49,8 +45,7 @@ final class EinundzwanzigPlebTable extends PowerGridComponent return EinundzwanzigPleb::query() ->with([ 'profile', - 'paymentEvents' => fn($query) - => $query + 'paymentEvents' => fn ($query) => $query ->where('year', date('Y')) ->where('paid', true), ]) @@ -64,41 +59,37 @@ final class EinundzwanzigPlebTable extends PowerGridComponent ->add('pubkey') ->add( 'avatar', - fn($model, - ) - => '', + fn ($model, + ) => '', ) ->add( 'for', - fn($model, - ) - => $model->application_for ? '
'.AssociationStatus::from( - $model->application_for, - )->label().'
' : '', + fn ($model, + ) => $model->application_for ? '
'.AssociationStatus::from( + $model->application_for, + )->label().'
' : '', ) ->add( 'payment', - fn(EinundzwanzigPleb $model) - => $model->paymentEvents->count() > 0 && $model->paymentEvents->first()->paid ? ''.number_format( - $model->paymentEvents->first()->amount, - 0, - ',', - '.', - ).'' : 'keine Zahlung vorhanden', + fn (EinundzwanzigPleb $model) => $model->paymentEvents->count() > 0 && $model->paymentEvents->first()->paid ? ''.number_format( + $model->paymentEvents->first()->amount, + 0, + ',', + '.', + ).'' : 'keine Zahlung vorhanden', ) - ->add('npub_export', fn(EinundzwanzigPleb $model) => $model->npub) + ->add('npub_export', fn (EinundzwanzigPleb $model) => $model->npub) ->add( 'npub', - fn(EinundzwanzigPleb $model) - => 'Nostr Profile', + fn (EinundzwanzigPleb $model) => 'Nostr Profile', ) ->add('association_status') - ->add('association_status_name', fn(EinundzwanzigPleb $model) => $model->association_status->name) - ->add('paid_export', fn(EinundzwanzigPleb $model) => $model->paymentEvents->first()?->amount) + ->add('association_status_name', fn (EinundzwanzigPleb $model) => $model->association_status->name) + ->add('paid_export', fn (EinundzwanzigPleb $model) => $model->paymentEvents->first()?->amount) ->add( 'association_status_formatted', function (EinundzwanzigPleb $model) { @@ -109,13 +100,13 @@ final class EinundzwanzigPlebTable extends PowerGridComponent AssociationStatus::HONORARY => 'text-xs inline-flex font-medium rounded-full text-center px-2.5 py-1 bg-blue-500/20 text-blue-700', default => 'text-xs inline-flex font-medium rounded-full text-center px-2.5 py-1 text-red-700', }; + return ''.$model->association_status->label().''; }, ) ->add( 'name_lower', - fn(EinundzwanzigPleb $model) - => strtolower( + fn (EinundzwanzigPleb $model) => strtolower( e($model->profile?->name ?: $model->profile?->display_name ?? ''), ), ); @@ -251,9 +242,8 @@ final class EinundzwanzigPlebTable extends PowerGridComponent return [ // Hide button edit for ID 1 Rule::button('accept') - ->when(fn($row) => $row->application_for === null) + ->when(fn ($row) => $row->application_for === null) ->hide(), ]; } - } diff --git a/composer.json b/composer.json index fcc89f2..3fbc015 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,8 @@ "laravel/reverb": "^1.0", "laravel/sail": "^1.31", "laravel/tinker": "^2.9", + "livewire/flux": "^2.10", + "livewire/flux-pro": "^2.10", "livewire/livewire": "^4.0", "openspout/openspout": "^4.24", "power-components/livewire-powergrid": "^6.7", @@ -33,8 +35,7 @@ "spatie/laravel-sluggable": "^3.6", "spatie/laravel-tags": "^4.9.2", "staudenmeir/eloquent-has-many-deep": "^1.7", - "swentel/nostr-php": "^1.4", - "wireui/wireui": "^2.5.1" + "swentel/nostr-php": "^1.4" }, "require-dev": { "fakerphp/faker": "^1.23", @@ -95,5 +96,10 @@ } }, "minimum-stability": "stable", - "prefer-stable": true + "prefer-stable": true, + "repositories": [{ + "name": "flux-pro", + "type": "composer", + "url": "https://composer.fluxui.dev" + }] } diff --git a/composer.lock b/composer.lock index c0a5d32..cbadb66 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7ce22785474454dbae00168baafb6ccf", + "content-hash": "39a9ce519dbfeb237966b7441b59a562", "packages": [ { "name": "akuechler/laravel-geoly", @@ -3025,6 +3025,145 @@ ], "time": "2026-01-15T06:54:53+00:00" }, + { + "name": "livewire/flux", + "version": "v2.10.2", + "source": { + "type": "git", + "url": "https://github.com/livewire/flux.git", + "reference": "e7a93989788429bb6c0a908a056d22ea3a6c7975" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/livewire/flux/zipball/e7a93989788429bb6c0a908a056d22ea3a6c7975", + "reference": "e7a93989788429bb6c0a908a056d22ea3a6c7975", + "shasum": "" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/view": "^10.0|^11.0|^12.0", + "laravel/prompts": "^0.1|^0.2|^0.3", + "livewire/livewire": "^3.7.3|^4.0", + "php": "^8.1", + "symfony/console": "^6.0|^7.0" + }, + "conflict": { + "livewire/blaze": "<1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flux": "Flux\\Flux" + }, + "providers": [ + "Flux\\FluxServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Flux\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "The official UI component library for Livewire.", + "keywords": [ + "components", + "flux", + "laravel", + "livewire", + "ui" + ], + "support": { + "issues": "https://github.com/livewire/flux/issues", + "source": "https://github.com/livewire/flux/tree/v2.10.2" + }, + "time": "2025-12-19T02:11:45+00:00" + }, + { + "name": "livewire/flux-pro", + "version": "2.10.2", + "dist": { + "type": "zip", + "url": "https://composer.fluxui.dev/download/a0a0798f-1cf8-4999-8f57-8688c21f2d59/flux-pro-2.10.2.zip", + "reference": "9440435e467c4bb775efbc2c510ec7dea61e17d7", + "shasum": "177bf08ae75c628c96a1a3a4fd1e788b1b798e1d" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/view": "^10.0|^11.0|^12.0", + "laravel/prompts": "^0.1.24|^0.2|^0.3", + "livewire/flux": "2.10.2|dev-main", + "livewire/livewire": "^3.7.3|^4.0", + "php": "^8.1", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "livewire/volt": "*", + "orchestra/testbench": "^10.8" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Flux": "FluxPro\\FluxPro" + }, + "providers": [ + "FluxPro\\FluxProServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "FluxPro\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\": "workbench/app/" + } + }, + "scripts": { + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@php vendor/bin/testbench workbench:build --ansi", + "@php vendor/bin/testbench serve --port 3000 --ansi" + ] + }, + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "The pro version of Flux, the official UI component library for Livewire.", + "keywords": [ + "components", + "flux", + "laravel", + "livewire", + "ui" + ], + "time": "2025-12-19T02:22:27+00:00" + }, { "name": "livewire/livewire", "version": "v4.0.1", @@ -10924,135 +11063,6 @@ } ], "time": "2024-11-21T01:49:47+00:00" - }, - { - "name": "wireui/heroicons", - "version": "v2.9.0", - "source": { - "type": "git", - "url": "https://github.com/wireui/heroicons.git", - "reference": "ccd2ab94293d6f231271c0847c1db34305313c6f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wireui/heroicons/zipball/ccd2ab94293d6f231271c0847c1db34305313c6f", - "reference": "ccd2ab94293d6f231271c0847c1db34305313c6f", - "shasum": "" - }, - "require": { - "laravel/framework": "^9.16|^10.0|^11.0|^12.0", - "php": "^8.1|^8.2|^8.3|^8.4" - }, - "require-dev": { - "larastan/larastan": "^3.0", - "laravel/pint": "^1.6", - "orchestra/testbench": "^10.0", - "pestphp/pest": "^3.0" - }, - "type": "library", - "extra": { - "aliases": [], - "laravel": { - "providers": [ - "WireUi\\Heroicons\\HeroiconsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "WireUi\\Heroicons\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Pedro Oliveira", - "email": "pedrolivertwd@gmail.com" - } - ], - "description": "The Tailwind Heroicons for laravel blade by WireUI", - "keywords": [ - "blade components", - "blade heroicons", - "laravel components", - "livewire icons", - "livewire icons components", - "wireui" - ], - "support": { - "issues": "https://github.com/wireui/heroicons/issues", - "source": "https://github.com/wireui/heroicons/tree/v2.9.0" - }, - "time": "2025-03-02T22:06:22+00:00" - }, - { - "name": "wireui/wireui", - "version": "v2.5.1", - "source": { - "type": "git", - "url": "https://github.com/wireui/wireui.git", - "reference": "e4d966689eb840986281f5237149b2480eacf87c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wireui/wireui/zipball/e4d966689eb840986281f5237149b2480eacf87c", - "reference": "e4d966689eb840986281f5237149b2480eacf87c", - "shasum": "" - }, - "require": { - "laravel/framework": "^10.0|^11.0|^12.0", - "php": "^8.2|^8.3|^8.4|^8.5", - "wireui/heroicons": "^2.8" - }, - "require-dev": { - "laravel/pint": "^1.19", - "livewire/livewire": "^3.6", - "orchestra/testbench": "^10.0", - "orchestra/testbench-dusk": "^10.0", - "pestphp/pest": "^4.0", - "pestphp/pest-plugin-laravel": "^4.0", - "pestphp/pest-plugin-livewire": "^4.0" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": [], - "providers": [ - "WireUi\\ServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "WireUi\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Pedro Oliveira", - "email": "pedrolivertwd@gmail.com" - } - ], - "description": "TallStack components", - "keywords": [ - "blade components", - "laravel components", - "livewire components", - "livewire-ui", - "wireui" - ], - "support": { - "issues": "https://github.com/wireui/wireui/issues", - "source": "https://github.com/wireui/wireui/tree/v2.5.1" - }, - "time": "2025-11-10T04:46:35+00:00" } ], "packages-dev": [ diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index f884b3d..5ea9ca7 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -70,14 +70,18 @@ @if(\App\Support\NostrAuth::check()) -
- @csrf - - - @else - +
+ @csrf + + Logout + +
+ @else + + Mit Nostr verbinden + @endif diff --git a/resources/views/components/project-card.blade.php b/resources/views/components/project-card.blade.php index a8d6c5c..8bc98d0 100644 --- a/resources/views/components/project-card.blade.php +++ b/resources/views/components/project-card.blade.php @@ -88,26 +88,30 @@ @if( ($currentPleb && $currentPleb->id === $project->einundzwanzig_pleb_id) || ($currentPleb && in_array($currentPleb->npub, config('einundzwanzig.config.current_board'), true)) - ) - - + Löschen + + + :href="route('association.projectSupport.edit', ['projectProposal' => $project])"> + Editieren + @endif @if(($currentPleb && $currentPleb->association_status->value > 2) || $project->accepted) - + :href="route('association.projectSupport.item', ['projectProposal' => $project])"> + Öffnen + @endif
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index f884b3d..befcf41 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -3,11 +3,10 @@ - {!! seo($seo ?? null) !!} + {!! seo($seo ?? null) !} {{ $title ?? 'Page Title' }} @livewireStyles - @wireUiScripts @stack('scripts') @vite(['resources/js/app.js','resources/css/app.css']) @googlefonts @@ -15,183 +14,92 @@ @include('components.layouts.partials.styles') + @fluxAppearance - - - - -
- -
- -
-
-
+ + - -
- - + + Logo + + -
+ + @if(\App\Support\NostrAuth::check()) + routeIs('association.news') ? 'current' : '' }}>News + routeIs('association.profile') ? 'current' : '' }}>Profil + routeIs('association.projectSupport') ? 'current' : '' }}>Projekt-Unterstützungen + @endif + - -
+ - {{--@include('components.layouts.partials.search-button')--}} + + + Info - {{--@include('components.layouts.partials.notification-buttons')--}} + + Issues/Feedback + Changelog + Github + Impressum + + + @if(\App\Support\NostrAuth::check()) +
+ @csrf + Logout +
+ @else + Mit Nostr verbinden + @endif +
+ - @if(\App\Support\NostrAuth::check()) -
- @csrf - - - @else - - @endif + + + + Logo + - -
- - -
+ +
- {{--@include('components.layouts.partials.dark-mode-toggle')--}} + + @if(\App\Support\NostrAuth::check()) + routeIs('association.news') ? 'current' : '' }}>News + routeIs('association.profile') ? 'current' : '' }}>Meine Mitgliedschaft + routeIs('association.projectSupport') ? 'current' : '' }}>Projekt-Unterstützungen + @endif - - {{--
--}} + @include('components.layouts.navigation.admin') +
+
- {{--@include('components.layouts.partials.user-button')--}} + + {{ $slot }} + -
- -
-
-
-
- {{ $slot }} -
-
-
-@livewireScriptConfig - - + @fluxScripts + @livewireScriptConfig + + diff --git a/resources/views/livewire/association/election/index.blade.php b/resources/views/livewire/association/election/index.blade.php index 28fdbc3..86872fd 100644 --- a/resources/views/livewire/association/election/index.blade.php +++ b/resources/views/livewire/association/election/index.blade.php @@ -78,11 +78,16 @@ new class extends Component { {{ $election['year'] }}
- + + Kandidaten + + +
- + + Speichern +
@endforeach diff --git a/resources/views/livewire/association/election/show.blade.php b/resources/views/livewire/association/election/show.blade.php index 0aa72be..8bc4f0a 100644 --- a/resources/views/livewire/association/election/show.blade.php +++ b/resources/views/livewire/association/election/show.blade.php @@ -475,19 +475,19 @@ new class extends Component {
-
- @if($isNotClosed) - - @else - - @endif -
-
- + class="flex flex-col space-y-2 sm:space-y-0 sm:flex-row justify-between items-center w-full"> +
+ @if($isNotClosed) + + @else + + @endif +
+
+ +
diff --git a/resources/views/livewire/association/news.blade.php b/resources/views/livewire/association/news.blade.php index c36ef16..93f05af 100644 --- a/resources/views/livewire/association/news.blade.php +++ b/resources/views/livewire/association/news.blade.php @@ -202,19 +202,22 @@ class extends Component {
- + icon="cloud-arrow-down"> + Öffnen + @if($canEdit) - + icon="trash"> + Löschen + @endif
@@ -253,27 +256,40 @@ class extends Component { @enderror
- + + Kategorie + + @foreach(\App\Enums\NewsCategory::selectOptions() as $category) + + @endforeach + + +
- + + Titel + + +
- + + Beschreibung + optional + + +
- + @endif diff --git a/resources/views/livewire/association/profile.blade.php b/resources/views/livewire/association/profile.blade.php index c70481d..0bd290d 100644 --- a/resources/views/livewire/association/profile.blade.php +++ b/resources/views/livewire/association/profile.blade.php @@ -503,19 +503,22 @@ new class extends Component { sich bitte direkt an den Vorstand.
-
-
-
- -
+
+
+
+ + + + +
- + + Mit deinem aktuellen Nostr-Profil Mitglied werden +
@endif @@ -543,25 +546,35 @@ new class extends Component { diese Adresse AES-256 verschlüsselt in der Datenbank ab.
- - - Ich informiere mich selbst in der News Sektion und gebe keine E-Mail Adresse raus. - - + class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 text-amber-500"> + + NEIN + Ich informiere mich selbst in der News Sektion und gebe keine E-Mail Adresse raus. + + +
@if($showEmail)
- - + + Fax-Nummer + + + + + E-Mail Adresse + + +
- + + Speichern +
@endif
diff --git a/resources/views/livewire/association/project-support/form/create.blade.php b/resources/views/livewire/association/project-support/form/create.blade.php index 9060af8..3633876 100644 --- a/resources/views/livewire/association/project-support/form/create.blade.php +++ b/resources/views/livewire/association/project-support/form/create.blade.php @@ -70,23 +70,22 @@ class extends Component {
- - @error('form.name') - {{ $message }} - @enderror + + Name + + +
- - @error('form.description') - {{ $message }} - @enderror + + Beschreibung + + +
- +
diff --git a/resources/views/livewire/association/project-support/form/edit.blade.php b/resources/views/livewire/association/project-support/form/edit.blade.php index a946dd2..b594264 100644 --- a/resources/views/livewire/association/project-support/form/edit.blade.php +++ b/resources/views/livewire/association/project-support/form/edit.blade.php @@ -27,7 +27,13 @@ class extends Component { $currentPubkey = NostrAuth::pubkey(); $currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $currentPubkey)->first(); - if ($currentPleb && $currentPleb->id === $project->einundzwanzig_pleb_id) { + if ( + ( + $currentPleb + && $currentPleb->id === $project->einundzwanzig_pleb_id + ) + || in_array($currentPleb->npub, config('einundzwanzig.config.current_board')) + ) { $this->isAllowed = true; $this->form = [ 'name' => $project->name, @@ -76,23 +82,22 @@ class extends Component {
- - @error('form.name') - {{ $message }} - @enderror + + Name + + +
- - @error('form.description') - {{ $message }} - @enderror + + Beschreibung + + +
- +
diff --git a/resources/views/livewire/association/project-support/index.blade.php b/resources/views/livewire/association/project-support/index.blade.php index d5f1bb3..611fbde 100644 --- a/resources/views/livewire/association/project-support/index.blade.php +++ b/resources/views/livewire/association/project-support/index.blade.php @@ -118,14 +118,15 @@ new class extends Component {
- + - + @if($currentPleb && $currentPleb->association_status->value > 1 && $currentPleb->paymentEvents()->where('year', date('Y'))->where('paid', true)->exists()) - + + Projekt einreichen + @endif diff --git a/tests/Feature/Livewire/ProjectProposalFormTest.php b/tests/Feature/Livewire/ProjectProposalFormTest.php new file mode 100644 index 0000000..95439ae --- /dev/null +++ b/tests/Feature/Livewire/ProjectProposalFormTest.php @@ -0,0 +1,111 @@ +name = ''; + expect(fn () => $form->validate())->toThrow(); + + $form->name = 'short'; // Less than 5 characters + expect(fn () => $form->validate())->toThrow(); + + // Test support_in_sats field - required|numeric|min:21 + $form->name = 'Valid Project'; + $form->support_in_sats = ''; + expect(fn () => $form->validate())->toThrow(); + + $form->support_in_sats = 'not-numeric'; + expect(fn () => $form->validate())->toThrow(); + + $form->support_in_sats = '20'; // Less than 21 + expect(fn () => $form->validate())->toThrow(); + + // Test description field - required|string|min:5 + $form->name = 'Valid Project'; + $form->support_in_sats = '21000'; + $form->description = ''; + expect(fn () => $form->validate())->toThrow(); + + $form->description = 'short'; + expect(fn () => $form->validate())->toThrow(); + + // Test website field - required|url + $form->name = 'Valid Project'; + $form->support_in_sats = '21000'; + $form->description = 'Valid description'; + $form->website = 'not-a-url'; + expect(fn () => $form->validate())->toThrow(); +}); + +it('accepts valid project proposal data', function () { + $form = new ProjectProposalForm; + + $form->name = 'Test Project'; + $form->support_in_sats = '21000'; + $form->description = 'This is a test project description that meets the minimum length requirement.'; + $form->website = 'https://example.com'; + $form->accepted = true; + $form->sats_paid = 5000; + + $result = $form->validate(); + expect($result)->toBeArray(); + expect($result)->toBeEmpty(); +}); + +it('validates accepted field as boolean', function () { + $form = new ProjectProposalForm; + $form->name = 'Valid Project'; + $form->support_in_sats = '21000'; + $form->description = 'Valid description'; + $form->website = 'https://example.com'; + + $form->accepted = 'not-boolean'; + expect(fn () => $form->validate())->toThrow(); + + // Test with boolean values + $form->accepted = false; + expect($form->accepted)->toBeBool(); + + $form->accepted = true; + expect($form->accepted)->toBeBool(); +}); + +it('validates sats_paid as nullable numeric', function () { + $form = new ProjectProposalForm; + $form->name = 'Valid Project'; + $form->support_in_sats = '21000'; + $form->description = 'Valid description'; + $form->website = 'https://example.com'; + + // Test with null (should be acceptable) + $form->sats_paid = null; + $form->accepted = false; + + $result = $form->validate(); + expect($result)->toBeArray(); + expect($result)->toBeEmpty(); + + // Test with numeric + $form->sats_paid = 'not-numeric'; + expect(fn () => $form->validate())->toThrow(); + + $form->sats_paid = 10000; + $form->accepted = false; + $result = $form->validate(); + expect($result)->toBeArray(); + expect($result)->toBeEmpty(); +}); + +it('has correct default values', function () { + $form = new ProjectProposalForm; + + expect($form->name)->toBe(''); + expect($form->support_in_sats)->toBe(''); + expect($form->description)->toBe(''); + expect($form->website)->toBe(''); + expect($form->accepted)->toBeFalse(); + expect($form->sats_paid)->toBe(0); +}); diff --git a/tests/Feature/Livewire/ProjectSupportCreateTest.php b/tests/Feature/Livewire/ProjectSupportCreateTest.php new file mode 100644 index 0000000..7c1a814 --- /dev/null +++ b/tests/Feature/Livewire/ProjectSupportCreateTest.php @@ -0,0 +1,98 @@ +pleb = EinundzwanzigPleb::query()->create([ + 'pubkey' => 'test_pubkey_'.Str::random(20), + 'npub' => 'test_npub_'.Str::random(20), + 'association_status' => AssociationStatus::ACTIVE->value, + ]); + + // Create payment event for the current year + $this->pleb->paymentEvents()->create([ + 'year' => date('Y'), + 'amount' => 21000, + 'paid' => true, + 'event_id' => 'test_event_'.Str::random(40), + ]); +}); + +it('renders create form for authorized users', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.create') + ->assertStatus(200) + ->assertSee('Projektförderung anlegen') + ->assertSeeLivewire('association.project-support.form.create'); +}); + +it('does not render create form for unauthorized users', function () { + $unauthorizedPleb = EinundzwanzigPleb::query()->create([ + 'pubkey' => 'test_pubkey_'.Str::random(20), + 'npub' => 'test_npub_'.Str::random(20), + 'association_status' => AssociationStatus::DEFAULT->value, + ]); + + Livewire::actingAs($unauthorizedPleb) + ->test('association.project-support.form.create') + ->assertSet('isAllowed', false) + ->assertDontSee('Projektförderung anlegen'); +}); + +it('validates required name field', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.create') + ->set('form.name', '') + ->set('form.description', 'Test description') + ->call('save') + ->assertHasErrors(['form.name']); +}); + +it('validates name max length', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.create') + ->set('form.name', Str::random(300)) + ->set('form.description', 'Test description') + ->call('save') + ->assertHasErrors(['form.name']); +}); + +it('validates required description field', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.create') + ->set('form.name', 'Test Project') + ->set('form.description', '') + ->call('save') + ->assertHasErrors(['form.description']); +}); + +it('creates project proposal successfully', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.create') + ->set('form.name', 'Test Project') + ->set('form.description', 'This is a test project for unit testing purposes.') + ->call('save') + ->assertHasNoErrors() + ->assertRedirect(route('association.projectSupport')); + + expect(ProjectProposal::count())->toBe(1); + $project = ProjectProposal::first(); + expect($project->name)->toBe('Test Project'); + expect($project->description)->toBe('This is a test project for unit testing purposes.'); +}); + +it('associates project proposal with current pleb', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.create') + ->set('form.name', 'Test Project') + ->set('form.description', 'Test description') + ->call('save') + ->assertHasNoErrors(); + + $project = ProjectProposal::first(); + expect($project->einundzwanzig_pleb_id)->toBe($this->pleb->id); +}); diff --git a/tests/Feature/Livewire/ProjectSupportEditTest.php b/tests/Feature/Livewire/ProjectSupportEditTest.php new file mode 100644 index 0000000..c4c9273 --- /dev/null +++ b/tests/Feature/Livewire/ProjectSupportEditTest.php @@ -0,0 +1,109 @@ +pleb = EinundzwanzigPleb::query()->create([ + 'pubkey' => 'test_pubkey_'.Str::random(20), + 'npub' => 'test_npub_'.Str::random(20), + 'association_status' => AssociationStatus::ACTIVE->value, + ]); + + // Create payment event for the current year + $this->pleb->paymentEvents()->create([ + 'year' => date('Y'), + 'amount' => 21000, + 'paid' => true, + 'event_id' => 'test_event_'.Str::random(40), + ]); + + $this->project = ProjectProposal::query()->create([ + 'einundzwanzig_pleb_id' => $this->pleb->id, + 'name' => 'Original Project', + 'description' => 'Original Description', + ]); + + // Get board member pubkeys from config + $boardPubkeys = config('einundzwanzig.config.current_board', []); + $this->boardMember = EinundzwanzigPleb::query()->create([ + 'pubkey' => 'board_pubkey_'.Str::random(20), + 'npub' => 'board_npub_'.Str::random(20), + 'association_status' => AssociationStatus::HONORARY->value, + ]); + + // Simulate board member by temporarily updating config for testing + config(['einundzwanzig.config.current_board' => [$this->boardMember->npub]]); +}); + +it('renders edit form for authorized project owners', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.edit', ['project' => $this->project]) + ->assertStatus(200) + ->assertSee('Projektförderung bearbeiten') + ->assertSet('form.name', $this->project->name) + ->assertSet('form.description', $this->project->description); +}); + +it('renders edit form for board members', function () { + Livewire::actingAs($this->boardMember) + ->test('association.project-support.form.edit', ['project' => $this->project]) + ->assertStatus(200) + ->assertSee('Projektförderung bearbeiten'); +}); + +it('does not render edit form for unauthorized users', function () { + $unauthorizedPleb = EinundzwanzigPleb::query()->create([ + 'pubkey' => 'test_pubkey_'.Str::random(20), + 'npub' => 'test_npub_'.Str::random(20), + 'association_status' => AssociationStatus::ACTIVE->value, + ]); + + Livewire::actingAs($unauthorizedPleb) + ->test('association.project-support.form.edit', ['project' => $this->project]) + ->assertSet('isAllowed', false); +}); + +it('validates required name field', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.edit', ['project' => $this->project]) + ->set('form.name', '') + ->set('form.description', 'Test description') + ->call('update') + ->assertHasErrors(['form.name']); +}); + +it('validates required description field', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.edit', ['project' => $this->project]) + ->set('form.name', 'Test Project') + ->set('form.description', '') + ->call('update') + ->assertHasErrors(['form.description']); +}); + +it('updates project proposal successfully', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.edit', ['project' => $this->project]) + ->set('form.name', 'Updated Name') + ->set('form.description', 'Updated Description') + ->call('update') + ->assertHasNoErrors() + ->assertRedirect(route('association.projectSupport.item', $this->project)); + + $this->project->refresh(); + expect($this->project->name)->toBe('Updated Name'); + expect($this->project->description)->toBe('Updated Description'); +}); + +it('disables update button during save', function () { + Livewire::actingAs($this->pleb) + ->test('association.project-support.form.edit', ['project' => $this->project]) + ->set('form.name', 'Test') + ->set('form.description', 'Test') + ->call('update') + ->assertSeeHtml('wire:loading'); +});