From 942a22f618fdafe10523afff4cce0e04b92ee6d1 Mon Sep 17 00:00:00 2001 From: WieszczY Date: Fri, 25 Sep 2026 09:34:09 +0200 Subject: [PATCH 1/4] feat(ui): complete availability state baseline --- src/UI/Catalog/BaseUiCatalog.php | 6 +++ src/UI/Component/DegradedState.php | 38 ++++++++++++++ src/UI/Component/PermissionDeniedState.php | 41 +++++++++++++++ src/UI/Theme/Base/BaseTheme.php | 6 +++ .../Base/Renderer/DegradedStateRenderer.php | 32 ++++++++++++ .../PermissionDeniedStateRenderer.php | 35 +++++++++++++ tests/UI/BaseThemeTest.php | 6 +++ tests/UI/UiAvailabilityStateTest.php | 50 +++++++++++++++++++ 8 files changed, 214 insertions(+) create mode 100644 src/UI/Component/DegradedState.php create mode 100644 src/UI/Component/PermissionDeniedState.php create mode 100644 src/UI/Theme/Base/Renderer/DegradedStateRenderer.php create mode 100644 src/UI/Theme/Base/Renderer/PermissionDeniedStateRenderer.php create mode 100644 tests/UI/UiAvailabilityStateTest.php diff --git a/src/UI/Catalog/BaseUiCatalog.php b/src/UI/Catalog/BaseUiCatalog.php index 59dd5ce..13f0e4e 100644 --- a/src/UI/Catalog/BaseUiCatalog.php +++ b/src/UI/Catalog/BaseUiCatalog.php @@ -18,6 +18,8 @@ use SyntaxDevTeam\MiniPortal\UI\Component\ErrorState; use SyntaxDevTeam\MiniPortal\UI\Component\LoadingState; use SyntaxDevTeam\MiniPortal\UI\Component\Pagination; +use SyntaxDevTeam\MiniPortal\UI\Component\PermissionDeniedState; +use SyntaxDevTeam\MiniPortal\UI\Component\DegradedState; use SyntaxDevTeam\MiniPortal\UI\Model\AlertSeverity; use SyntaxDevTeam\MiniPortal\UI\Model\ComponentIdentity; use SyntaxDevTeam\MiniPortal\UI\Model\PageRegion; @@ -59,6 +61,10 @@ public function page(): PageDefinition ['name' => 'Worker', 'status' => 'idle', 'jobs' => 0], ], 'Usługi platformy'), new Pagination(2, 3, '/catalog?page=1', '/catalog?page=3'), + new PermissionDeniedState('Brak dostępu', 'Nie masz uprawnienia do tej sekcji.', 'admin.system.read'), + new DegradedState('Dane częściowe', 'Jedno ze źródeł jest chwilowo niedostępne.', [ + new Text('Pozostałe dane pozostają dostępne.', TextTone::Muted), + ]), ], new ComponentIdentity('catalog.content'))]], ); } diff --git a/src/UI/Component/DegradedState.php b/src/UI/Component/DegradedState.php new file mode 100644 index 0000000..356fd5f --- /dev/null +++ b/src/UI/Component/DegradedState.php @@ -0,0 +1,38 @@ + $availableContent */ + public function __construct( + public string $title, + public string $description, + private array $availableContent = [], + private ?ComponentIdentity $componentIdentity = null, + ) { + if (trim($title) === '' || trim($description) === '') { + throw new \InvalidArgumentException('Degraded state requires a title and description.'); + } + } + + public static function componentType(): string + { + return 'degraded_state'; + } + + public function identity(): ?ComponentIdentity + { + return $this->componentIdentity; + } + + public function children(): array + { + return $this->availableContent; + } +} diff --git a/src/UI/Component/PermissionDeniedState.php b/src/UI/Component/PermissionDeniedState.php new file mode 100644 index 0000000..9227a3d --- /dev/null +++ b/src/UI/Component/PermissionDeniedState.php @@ -0,0 +1,41 @@ +componentIdentity; + } + + public function children(): array + { + return []; + } +} diff --git a/src/UI/Theme/Base/BaseTheme.php b/src/UI/Theme/Base/BaseTheme.php index 34eb39a..2577fa5 100644 --- a/src/UI/Theme/Base/BaseTheme.php +++ b/src/UI/Theme/Base/BaseTheme.php @@ -19,6 +19,8 @@ use SyntaxDevTeam\MiniPortal\UI\Component\ErrorState; use SyntaxDevTeam\MiniPortal\UI\Component\LoadingState; use SyntaxDevTeam\MiniPortal\UI\Component\Pagination; +use SyntaxDevTeam\MiniPortal\UI\Component\PermissionDeniedState; +use SyntaxDevTeam\MiniPortal\UI\Component\DegradedState; use SyntaxDevTeam\MiniPortal\UI\Rendering\RendererRegistry; use SyntaxDevTeam\MiniPortal\UI\PageDefinition; use SyntaxDevTeam\MiniPortal\UI\Theme\Base\Renderer\AlertRenderer; @@ -35,6 +37,8 @@ use SyntaxDevTeam\MiniPortal\UI\Theme\Base\Renderer\ErrorStateRenderer; use SyntaxDevTeam\MiniPortal\UI\Theme\Base\Renderer\LoadingStateRenderer; use SyntaxDevTeam\MiniPortal\UI\Theme\Base\Renderer\PaginationRenderer; +use SyntaxDevTeam\MiniPortal\UI\Theme\Base\Renderer\PermissionDeniedStateRenderer; +use SyntaxDevTeam\MiniPortal\UI\Theme\Base\Renderer\DegradedStateRenderer; final class BaseTheme implements Theme { @@ -70,6 +74,8 @@ public function renderers(): RendererRegistry $registry->register(LoadingState::class, new LoadingStateRenderer()); $registry->register(DataTable::class, new DataTableRenderer()); $registry->register(Pagination::class, new PaginationRenderer()); + $registry->register(PermissionDeniedState::class, new PermissionDeniedStateRenderer()); + $registry->register(DegradedState::class, new DegradedStateRenderer()); return $registry; } diff --git a/src/UI/Theme/Base/Renderer/DegradedStateRenderer.php b/src/UI/Theme/Base/Renderer/DegradedStateRenderer.php new file mode 100644 index 0000000..db9eea2 --- /dev/null +++ b/src/UI/Theme/Base/Renderer/DegradedStateRenderer.php @@ -0,0 +1,32 @@ +children() === [] + ? '' + : '
' . $registry->renderMany($component->children()) . '
'; + + return '
' + . '

' . Html::escape($component->title) . '

' + . '

' . Html::escape($component->description) . '

' + . $content . '
'; + } +} diff --git a/src/UI/Theme/Base/Renderer/PermissionDeniedStateRenderer.php b/src/UI/Theme/Base/Renderer/PermissionDeniedStateRenderer.php new file mode 100644 index 0000000..8aea881 --- /dev/null +++ b/src/UI/Theme/Base/Renderer/PermissionDeniedStateRenderer.php @@ -0,0 +1,35 @@ +description === null + ? '' + : '

' . Html::escape($component->description) . '

'; + $permission = $component->requiredPermission === null + ? '' + : '

Wymagane uprawnienie: ' + . Html::escape($component->requiredPermission) . '

'; + + return ''; + } +} diff --git a/tests/UI/BaseThemeTest.php b/tests/UI/BaseThemeTest.php index 4a80959..791484d 100644 --- a/tests/UI/BaseThemeTest.php +++ b/tests/UI/BaseThemeTest.php @@ -21,6 +21,8 @@ use SyntaxDevTeam\MiniPortal\UI\Component\ErrorState; use SyntaxDevTeam\MiniPortal\UI\Component\LoadingState; use SyntaxDevTeam\MiniPortal\UI\Component\Pagination; +use SyntaxDevTeam\MiniPortal\UI\Component\PermissionDeniedState; +use SyntaxDevTeam\MiniPortal\UI\Component\DegradedState; use SyntaxDevTeam\MiniPortal\UI\Model\AlertSeverity; use SyntaxDevTeam\MiniPortal\UI\Model\ComponentIdentity; use SyntaxDevTeam\MiniPortal\UI\Model\PageRegion; @@ -53,6 +55,8 @@ public function testEveryCurrentPublicComponentHasBaseRenderer(): void LoadingState::class, DataTable::class, Pagination::class, + PermissionDeniedState::class, + DegradedState::class, ], $registered); } @@ -92,6 +96,8 @@ public function testCatalogExercisesEveryRegisteredComponent(): void self::assertStringContainsString('mp-error-state', $html); self::assertStringContainsString('mp-data-table', $html); self::assertStringContainsString('mp-pagination', $html); + self::assertStringContainsString('mp-permission-denied-state', $html); + self::assertStringContainsString('mp-degraded-state', $html); } public function testUnknownComponentFailsExplicitly(): void diff --git a/tests/UI/UiAvailabilityStateTest.php b/tests/UI/UiAvailabilityStateTest.php new file mode 100644 index 0000000..356b987 --- /dev/null +++ b/tests/UI/UiAvailabilityStateTest.php @@ -0,0 +1,50 @@ +renderers()->render(new PermissionDeniedState( + 'Brak ', + 'Skontaktuj się z administratorem & spróbuj ponownie.', + 'module.files.read', + )); + + self::assertStringContainsString('role="alert"', $html); + self::assertStringContainsString('Brak <dostępu>', $html); + self::assertStringContainsString('administrator & spróbuj', $html); + self::assertStringContainsString('module.files.read', $html); + } + + public function testPermissionDeniedStateRejectsUnsafePermissionIdentifier(): void + { + $this->expectException(\InvalidArgumentException::class); + new PermissionDeniedState(requiredPermission: '