TenantAtlas/app/Filament/Pages/Settings/WorkspaceSettings.php
ahmido e241e27853 Settings foundation: workspace controls (#119)
Implements the Settings foundation workspace controls.

Includes:
- Settings foundation UI/controls scoped to workspace context
- Related onboarding/consent flow adjustments as included in branch history

Testing:
- `vendor/bin/sail artisan test --compact --no-ansi --filter=SettingsFoundation`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #119
2026-02-16 01:11:24 +00:00

257 lines
8.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages\Settings;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\Settings\SettingsResolver;
use App\Services\Settings\SettingsWriter;
use App\Support\Auth\Capabilities;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Workspaces\WorkspaceContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Validation\ValidationException;
use UnitEnum;
class WorkspaceSettings extends Page
{
protected static bool $isDiscovered = false;
protected static bool $shouldRegisterNavigation = false;
protected static ?string $slug = 'settings/workspace';
protected static ?string $title = 'Workspace settings';
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-cog-6-tooth';
protected static string|UnitEnum|null $navigationGroup = 'Settings';
protected static ?int $navigationSort = 20;
public Workspace $workspace;
/**
* @var array<string, mixed>
*/
public array $data = [];
/**
* @return array<Action>
*/
protected function getHeaderActions(): array
{
return [
Action::make('save')
->label('Save')
->action(function (): void {
$this->save();
})
->disabled(fn (): bool => ! $this->currentUserCanManage())
->tooltip(fn (): ?string => $this->currentUserCanManage()
? null
: 'You do not have permission to manage workspace settings.'),
Action::make('reset')
->label('Reset to default')
->color('danger')
->requiresConfirmation()
->action(function (): void {
$this->resetSetting();
})
->disabled(fn (): bool => ! $this->currentUserCanManage())
->tooltip(fn (): ?string => $this->currentUserCanManage()
? null
: 'You do not have permission to manage workspace settings.'),
];
}
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions provide save and reset controls for the settings form.')
->exempt(ActionSurfaceSlot::InspectAffordance, 'Workspace settings are edited as a singleton form without a record inspect action.')
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The page does not render table rows with secondary actions.')
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The page has no bulk actions because it manages a single settings scope.')
->exempt(ActionSurfaceSlot::ListEmptyState, 'The settings form is always rendered and has no list empty state.');
}
public function mount(): void
{
$user = auth()->user();
if (! $user instanceof User) {
abort(403);
}
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
if ($workspaceId === null) {
$this->redirect('/admin/choose-workspace');
return;
}
$workspace = Workspace::query()->whereKey($workspaceId)->first();
if (! $workspace instanceof Workspace) {
abort(404);
}
$this->workspace = $workspace;
$this->authorizeWorkspaceView($user);
$this->loadFormState();
}
public function content(Schema $schema): Schema
{
return $schema
->statePath('data')
->schema([
Section::make('Backup settings')
->description('Workspace defaults used when a schedule has no explicit value.')
->schema([
TextInput::make('backup_retention_keep_last_default')
->label('Default retention keep-last')
->numeric()
->integer()
->minValue(1)
->required()
->disabled(fn (): bool => ! $this->currentUserCanManage())
->helperText('Fallback value for backup schedule retention when retention_keep_last is empty.'),
]),
]);
}
public function save(): void
{
$user = auth()->user();
if (! $user instanceof User) {
abort(403);
}
$this->authorizeWorkspaceManage($user);
try {
app(SettingsWriter::class)->updateWorkspaceSetting(
actor: $user,
workspace: $this->workspace,
domain: 'backup',
key: 'retention_keep_last_default',
value: $this->data['backup_retention_keep_last_default'] ?? null,
);
} catch (ValidationException $exception) {
$errors = $exception->errors();
if (isset($errors['value'])) {
throw ValidationException::withMessages([
'data.backup_retention_keep_last_default' => $errors['value'],
]);
}
throw $exception;
}
$this->loadFormState();
Notification::make()
->title('Workspace settings saved')
->success()
->send();
}
public function resetSetting(): void
{
$user = auth()->user();
if (! $user instanceof User) {
abort(403);
}
$this->authorizeWorkspaceManage($user);
app(SettingsWriter::class)->resetWorkspaceSetting(
actor: $user,
workspace: $this->workspace,
domain: 'backup',
key: 'retention_keep_last_default',
);
$this->loadFormState();
Notification::make()
->title('Workspace setting reset to default')
->success()
->send();
}
private function loadFormState(): void
{
$resolvedValue = app(SettingsResolver::class)->resolveValue(
workspace: $this->workspace,
domain: 'backup',
key: 'retention_keep_last_default',
);
$this->data = [
'backup_retention_keep_last_default' => is_numeric($resolvedValue) ? (int) $resolvedValue : 30,
];
}
private function currentUserCanManage(): bool
{
$user = auth()->user();
if (! $user instanceof User || ! $this->workspace instanceof Workspace) {
return false;
}
/** @var WorkspaceCapabilityResolver $resolver */
$resolver = app(WorkspaceCapabilityResolver::class);
return $resolver->isMember($user, $this->workspace)
&& $resolver->can($user, $this->workspace, Capabilities::WORKSPACE_SETTINGS_MANAGE);
}
private function authorizeWorkspaceView(User $user): void
{
/** @var WorkspaceCapabilityResolver $resolver */
$resolver = app(WorkspaceCapabilityResolver::class);
if (! $resolver->isMember($user, $this->workspace)) {
abort(404);
}
if (! $resolver->can($user, $this->workspace, Capabilities::WORKSPACE_SETTINGS_VIEW)) {
abort(403);
}
}
private function authorizeWorkspaceManage(User $user): void
{
/** @var WorkspaceCapabilityResolver $resolver */
$resolver = app(WorkspaceCapabilityResolver::class);
if (! $resolver->isMember($user, $this->workspace)) {
abort(404);
}
if (! $resolver->can($user, $this->workspace, Capabilities::WORKSPACE_SETTINGS_MANAGE)) {
abort(403);
}
}
}