TenantAtlas/resources/views/filament/pages/choose-tenant.blade.php
ahmido 38d9826f5e feat: workspace context enforcement + ownership safeguards (#86)
Implements workspace-first enforcement and UX:
- Workspace selected before tenant flows; /admin routes into choose-workspace/choose-tenant
- Tenant lists and default tenant selection are scoped to current workspace
- Workspaces UI is tenantless at /admin/workspaces

Security hardening:
- Workspaces can never have 0 owners (blocks last-owner removal/demotion)
- Blocked attempts are audited with action_id=workspace_membership.last_owner_blocked + required metadata
- Optional break-glass recovery page to re-assign workspace owner (audited)

Tests:
- Added/updated Pest feature tests covering redirects, scoping, tenantless workspaces, last-owner guards, and break-glass recovery.

Notes:
- Filament v5 strict Page property signatures respected in RepairWorkspaceOwners.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #86
2026-02-02 23:00:56 +00:00

76 lines
3.5 KiB
PHP

<x-filament-panels::page>
<x-filament::section>
<div class="flex flex-col gap-4">
<div class="text-sm text-gray-600 dark:text-gray-300">
Select a tenant to continue.
</div>
@php
$tenants = $this->getTenants();
@endphp
@if ($tenants->isEmpty())
<div class="rounded-md border border-gray-200 bg-gray-50 p-4 text-sm text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-200">
<div class="font-medium text-gray-900 dark:text-gray-100">No tenants are available for your account.</div>
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
@if ($this->canRegisterTenant())
Register a tenant for this workspace, or switch workspaces.
@else
Switch workspaces, or contact an administrator.
@endif
</div>
<div class="mt-4 flex flex-col gap-2 sm:flex-row">
@if ($this->canRegisterTenant())
<x-filament::button
type="button"
color="primary"
tag="a"
href="{{ route('filament.admin.tenant.registration') }}"
>
Register tenant
</x-filament::button>
@endif
<x-filament::button
type="button"
color="gray"
tag="a"
href="{{ route('filament.admin.pages.choose-workspace') }}"
>
Change workspace
</x-filament::button>
</div>
</div>
@else
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
@foreach ($tenants as $tenant)
<div
wire:key="tenant-{{ $tenant->id }}"
x-data
@click="if ($event.target.closest('button,a,input,select,textarea')) return; $refs.form.submit();"
class="cursor-pointer rounded-lg border border-gray-200 p-4 dark:border-gray-800"
>
<form x-ref="form" method="POST" action="{{ route('admin.select-tenant') }}" class="flex flex-col gap-3">
@csrf
<input type="hidden" name="tenant_id" value="{{ (int) $tenant->id }}" />
<div class="font-medium text-gray-900 dark:text-gray-100">
{{ $tenant->name }}
</div>
<x-filament::button
type="submit"
color="primary"
class="w-full"
>
Continue
</x-filament::button>
</form>
</div>
@endforeach
</div>
@endif
</div>
</x-filament::section>
</x-filament-panels::page>