user(); $draft = $user instanceof User ? $this->relatedOnboardingDraft($tenant, $user) : null; $lifecycle = $this->tenantOperabilityService->lifecycleFor($tenant); return new TenantActionContext( tenant: $tenant, lifecycle: $lifecycle, surface: $surface, relatedOnboardingDraft: $draft, relatedOnboardingIsResumable: $draft instanceof TenantOnboardingSession && $this->onboardingLifecycleService->canResumeDraft($draft), hasRelatedOnboardingDraft: $draft instanceof TenantOnboardingSession, isArchived: $tenant->trashed() || $this->tenantOperabilityService->canRestore($tenant), ); } /** * @return list */ public function catalogForTenant(Tenant $tenant, TenantActionSurface $surface, ?User $user = null): array { $context = $this->buildContext($tenant, $surface, $user); $actions = [ $this->viewAction(), ]; $primaryAction = $this->primaryActionForContext($context); if ($primaryAction instanceof TenantActionDescriptor) { $actions[] = $primaryAction; } $secondaryActions = [ $this->secondaryRelatedOnboardingActionForContext($context, $primaryAction), $this->secondaryLifecycleActionForContext($context, $primaryAction), ]; foreach ($secondaryActions as $secondaryAction) { if ($secondaryAction instanceof TenantActionDescriptor) { $actions[] = $secondaryAction; } } return array_values(array_filter($actions, static fn (mixed $action): bool => $action instanceof TenantActionDescriptor && $action->visible)); } public function lifecycleActionForTenant(Tenant $tenant): ?TenantActionDescriptor { return $this->lifecycleActionForContext($this->buildContext($tenant, TenantActionSurface::ContextMenu)); } public function relatedOnboardingActionForTenant(Tenant $tenant, TenantActionSurface $surface, ?User $user = null): ?TenantActionDescriptor { return $this->relatedOnboardingActionForContext($this->buildContext($tenant, $surface, $user)); } public function onboardingEntryDescriptor(int $resumableDraftCount): TenantActionDescriptor { return match (true) { $resumableDraftCount === 1 => new TenantActionDescriptor( key: 'resume_onboarding', family: TenantActionFamily::OnboardingWorkflow, label: 'Resume onboarding', icon: 'heroicon-m-arrow-path', group: 'primary', ), $resumableDraftCount > 1 => new TenantActionDescriptor( key: 'choose_onboarding_draft', family: TenantActionFamily::OnboardingWorkflow, label: 'Choose onboarding draft', icon: 'heroicon-m-queue-list', group: 'primary', ), default => new TenantActionDescriptor( key: 'add_tenant', family: TenantActionFamily::OnboardingWorkflow, label: 'Add tenant', icon: 'heroicon-m-plus', group: 'primary', ), }; } public function relatedOnboardingDraft(Tenant $tenant, ?User $user = null): ?TenantOnboardingSession { $user ??= auth()->user(); if (! $user instanceof User) { return null; } return TenantOnboardingSession::query() ->where('workspace_id', (int) $tenant->workspace_id) ->where('tenant_id', (int) $tenant->getKey()) ->orderByDesc('updated_at') ->get() ->first(fn (TenantOnboardingSession $draft): bool => Gate::forUser($user)->allows('view', $draft)); } private function viewAction(): TenantActionDescriptor { return new TenantActionDescriptor( key: 'view', family: TenantActionFamily::Neutral, label: 'View', icon: 'heroicon-o-eye', group: 'primary', ); } private function primaryActionForContext(TenantActionContext $context): ?TenantActionDescriptor { if ($context->relatedOnboardingIsResumable && $context->lifecycle->canResumeOnboarding()) { return $this->relatedOnboardingActionForContext($context, 'primary'); } return $this->lifecycleActionForContext($context, 'primary'); } private function secondaryLifecycleActionForContext( TenantActionContext $context, ?TenantActionDescriptor $primaryAction, ): ?TenantActionDescriptor { $action = $this->lifecycleActionForContext($context); if (! $action instanceof TenantActionDescriptor) { return null; } if ($primaryAction instanceof TenantActionDescriptor && $primaryAction->key === $action->key) { return null; } return $action; } private function secondaryRelatedOnboardingActionForContext( TenantActionContext $context, ?TenantActionDescriptor $primaryAction, ): ?TenantActionDescriptor { $action = $this->relatedOnboardingActionForContext($context); if (! $action instanceof TenantActionDescriptor) { return null; } if ($primaryAction instanceof TenantActionDescriptor && $primaryAction->key === $action->key) { return null; } return $action; } private function lifecycleActionForContext( TenantActionContext $context, string $group = 'overflow', ): ?TenantActionDescriptor { if (! $context->isGenericTenantManagementSurface()) { return null; } if ($this->tenantOperabilityService->canRestore($context->tenant)) { return new TenantActionDescriptor( key: 'restore', family: TenantActionFamily::LifecycleManagement, label: 'Restore', icon: 'heroicon-o-arrow-uturn-left', destructive: true, requiresConfirmation: true, auditActionId: AuditActionId::TenantRestored, successNotificationTitle: 'Tenant restored', successNotificationBody: 'The tenant is available again in normal tenant management flows and can be selected as active context.', modalHeading: 'Restore tenant', modalDescription: 'Restore this archived tenant so it can be selected again in normal tenant management flows.', group: $group, ); } if ($this->tenantOperabilityService->canArchive($context->tenant)) { return new TenantActionDescriptor( key: 'archive', family: TenantActionFamily::LifecycleManagement, label: 'Archive', icon: 'heroicon-o-archive-box-x-mark', destructive: true, requiresConfirmation: true, auditActionId: AuditActionId::TenantArchived, successNotificationTitle: 'Tenant archived', successNotificationBody: 'The tenant remains available for inspection and audit history, but it is no longer selectable as active context.', modalHeading: 'Archive tenant', modalDescription: 'Archive this tenant to retain it for inspection and audit history while removing it from active management flows.', group: $group, ); } return null; } private function relatedOnboardingActionForContext( TenantActionContext $context, string $group = 'overflow', ): ?TenantActionDescriptor { $draft = $context->relatedOnboardingDraft; if (! $draft instanceof TenantOnboardingSession) { return null; } if ($context->relatedOnboardingIsResumable && $context->lifecycle->canResumeOnboarding()) { return new TenantActionDescriptor( key: 'related_onboarding', family: TenantActionFamily::OnboardingWorkflow, label: 'Resume onboarding', icon: 'heroicon-o-arrow-path', group: $group, ); } if ($draft->isCancelled()) { return new TenantActionDescriptor( key: 'related_onboarding', family: TenantActionFamily::OnboardingWorkflow, label: 'View cancelled onboarding draft', icon: 'heroicon-o-eye', group: $group, ); } if ($draft->isCompleted()) { return new TenantActionDescriptor( key: 'related_onboarding', family: TenantActionFamily::OnboardingWorkflow, label: 'View completed onboarding', icon: 'heroicon-o-eye', group: $group, ); } return new TenantActionDescriptor( key: 'related_onboarding', family: TenantActionFamily::OnboardingWorkflow, label: 'View related onboarding', icon: 'heroicon-o-eye', group: $group, ); } }