feat: overhaul project workspace navigation
This commit is contained in:
@@ -35,6 +35,7 @@ class BuildProjectBacklogPayload
|
||||
)->resolve(),
|
||||
'canCreateTickets' => $canManageTickets,
|
||||
'canEditTickets' => $canManageTickets,
|
||||
'canManageProject' => $this->permissionService->allows($user, Permission::ProjectAdmin, $project),
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
'status' => $status,
|
||||
'ticketStatusOptions' => TicketStatusResource::collection(
|
||||
|
||||
@@ -40,6 +40,7 @@ class BuildProjectBoardPayload
|
||||
)->resolve(),
|
||||
'canCreateTickets' => $canManageTickets,
|
||||
'canEditTickets' => $canManageTickets,
|
||||
'canManageProject' => $this->permissionService->allows($user, Permission::ProjectAdmin, $project),
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
'status' => $status,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Projects;
|
||||
|
||||
use App\Actions\Notifications\BuildProjectNotificationSettingsPayload;
|
||||
use App\Actions\Permissions\PermissionService;
|
||||
use App\Actions\Repositories\BuildProjectRepositorySettingsPayload;
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use App\Http\Resources\PermissionOptionResource;
|
||||
use App\Http\Resources\ProjectResource;
|
||||
use App\Http\Resources\ProjectTemplateResource;
|
||||
use App\Http\Resources\TeamResource;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectTemplate;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class BuildProjectSettingsPayload
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BuildProjectBoardSettingsPayload $buildProjectBoardSettingsPayload,
|
||||
private readonly BuildProjectNotificationSettingsPayload $buildProjectNotificationSettingsPayload,
|
||||
private readonly BuildProjectRepositorySettingsPayload $buildProjectRepositorySettingsPayload,
|
||||
private readonly BuildProjectTicketMetadataSettingsPayload $buildProjectTicketMetadataSettingsPayload,
|
||||
private readonly PermissionService $permissionService,
|
||||
private readonly ResolveProjectTemplateState $resolveProjectTemplateState,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function handle(
|
||||
Project $project,
|
||||
User $user,
|
||||
ProjectSettingsSection $section,
|
||||
mixed $status = null,
|
||||
mixed $error = null,
|
||||
): array {
|
||||
return [
|
||||
'activeSection' => $section->value,
|
||||
'error' => $error,
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
'sectionData' => $this->sectionData($project, $user, $section),
|
||||
'status' => $status,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sectionData(
|
||||
Project $project,
|
||||
User $user,
|
||||
ProjectSettingsSection $section,
|
||||
): array {
|
||||
$payload = match ($section) {
|
||||
ProjectSettingsSection::General => $this->generalData($project),
|
||||
ProjectSettingsSection::Board => $this->buildProjectBoardSettingsPayload->handle($project),
|
||||
ProjectSettingsSection::Metadata => $this->buildProjectTicketMetadataSettingsPayload->handle($project),
|
||||
ProjectSettingsSection::Repositories => $this->buildProjectRepositorySettingsPayload->handle($project, $user),
|
||||
ProjectSettingsSection::Notifications => $this->buildProjectNotificationSettingsPayload->handle($project),
|
||||
ProjectSettingsSection::Access => $this->accessData($project),
|
||||
};
|
||||
|
||||
return Arr::except($payload, ['error', 'project', 'status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function generalData(Project $project): array
|
||||
{
|
||||
return [
|
||||
'sourceTemplates' => ProjectTemplateResource::collection(
|
||||
ProjectTemplate::query()
|
||||
->published()
|
||||
->with('sourceProject')
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
)->resolve(),
|
||||
'templateState' => $this->resolveProjectTemplateState->handle($project),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function accessData(Project $project): array
|
||||
{
|
||||
return [
|
||||
'directPermissions' => $this->permissionService->assignmentMap(User::class, $project),
|
||||
'permissionOptions' => PermissionOptionResource::collection(
|
||||
$this->permissionService->permissionOptions(User::class, Project::class),
|
||||
)->resolve(),
|
||||
'teamPermissions' => $this->permissionService->assignmentMap(Team::class, $project),
|
||||
'teams' => TeamResource::collection(
|
||||
Team::query()
|
||||
->with('users')
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
)->resolve(),
|
||||
'users' => UserResource::collection(
|
||||
User::query()
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
)->resolve(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ use App\Http\Resources\TicketPriorityResource;
|
||||
use App\Http\Resources\TicketReferenceResource;
|
||||
use App\Http\Resources\TicketStatusResource;
|
||||
use App\Http\Resources\TicketTypeResource;
|
||||
use App\Models\Ticket;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\Project;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
|
||||
class BuildProjectTicketOptionsPayload
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ProjectSettingsSection: string
|
||||
{
|
||||
case General = 'general';
|
||||
case Board = 'board';
|
||||
case Metadata = 'metadata';
|
||||
case Repositories = 'repositories';
|
||||
case Notifications = 'notifications';
|
||||
case Access = 'access';
|
||||
}
|
||||
@@ -10,8 +10,8 @@ use App\Actions\ProjectBoard\StoreTicketStatus;
|
||||
use App\Actions\ProjectBoard\UpdateBoardColumn;
|
||||
use App\Actions\ProjectBoard\UpdateTicketStatus;
|
||||
use App\Actions\ProjectBoard\UpdateTicketStatusTransitions;
|
||||
use App\Actions\Projects\BuildProjectBoardSettingsPayload;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use App\Http\Requests\StoreBoardColumnRequest;
|
||||
use App\Http\Requests\StoreTicketStatusRequest;
|
||||
use App\Http\Requests\UpdateBoardColumnRequest;
|
||||
@@ -22,13 +22,10 @@ use App\Models\Project;
|
||||
use App\Models\TicketStatus;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProjectBoardSettingsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BuildProjectBoardSettingsPayload $buildProjectBoardSettingsPayload,
|
||||
private readonly DeleteBoardColumn $deleteBoardColumn,
|
||||
private readonly DeleteTicketStatus $deleteTicketStatus,
|
||||
private readonly PermissionService $permissionService,
|
||||
@@ -39,19 +36,6 @@ class ProjectBoardSettingsController extends Controller
|
||||
private readonly UpdateTicketStatusTransitions $updateTicketStatusTransitions,
|
||||
) {}
|
||||
|
||||
public function show(Request $request, Project $project): Response
|
||||
{
|
||||
abort_unless(
|
||||
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
|
||||
403,
|
||||
);
|
||||
|
||||
return Inertia::render(
|
||||
'projects/settings/Board',
|
||||
$this->buildProjectBoardSettingsPayload->handle($project, session('status')),
|
||||
);
|
||||
}
|
||||
|
||||
public function store(
|
||||
StoreBoardColumnRequest $request,
|
||||
Project $project,
|
||||
@@ -62,7 +46,7 @@ class ProjectBoardSettingsController extends Controller
|
||||
);
|
||||
$boardColumn = $this->storeBoardColumn->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.board', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$boardColumn->name} was added to the board workflow.");
|
||||
}
|
||||
|
||||
@@ -77,7 +61,7 @@ class ProjectBoardSettingsController extends Controller
|
||||
);
|
||||
$boardColumn = $this->updateBoardColumn->handle($project, $boardColumn, $request->validated());
|
||||
|
||||
return to_route('projects.settings.board', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$boardColumn->fresh()->name} was updated.");
|
||||
}
|
||||
|
||||
@@ -93,7 +77,7 @@ class ProjectBoardSettingsController extends Controller
|
||||
|
||||
$deletedName = $this->deleteBoardColumn->handle($project, $boardColumn);
|
||||
|
||||
return to_route('projects.settings.board', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$deletedName} was removed from the board workflow.");
|
||||
}
|
||||
|
||||
@@ -107,7 +91,7 @@ class ProjectBoardSettingsController extends Controller
|
||||
);
|
||||
$ticketStatus = $this->storeTicketStatus->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.board', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketStatus->name} was added to {$this->statusContextLabel($ticketStatus)}.");
|
||||
}
|
||||
|
||||
@@ -122,7 +106,7 @@ class ProjectBoardSettingsController extends Controller
|
||||
);
|
||||
$ticketStatus = $this->updateTicketStatus->handle($ticketStatus, $request->validated());
|
||||
|
||||
return to_route('projects.settings.board', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketStatus->fresh()->name} was updated.");
|
||||
}
|
||||
|
||||
@@ -138,7 +122,7 @@ class ProjectBoardSettingsController extends Controller
|
||||
|
||||
$result = $this->deleteTicketStatus->handle($project, $ticketStatus);
|
||||
|
||||
return to_route('projects.settings.board', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$result['deleted_name']} was removed from {$result['context_label']}.");
|
||||
}
|
||||
|
||||
@@ -153,7 +137,7 @@ class ProjectBoardSettingsController extends Controller
|
||||
);
|
||||
$this->updateTicketStatusTransitions->handle($ticketStatus, $request->validated());
|
||||
|
||||
return to_route('projects.settings.board', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketStatus->name} move targets were updated.");
|
||||
}
|
||||
|
||||
@@ -161,4 +145,12 @@ class ProjectBoardSettingsController extends Controller
|
||||
{
|
||||
return $ticketStatus->boardColumn?->name ?? 'Backlog';
|
||||
}
|
||||
|
||||
private function settingsRedirect(Project $project): RedirectResponse
|
||||
{
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::Board->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,16 @@ use App\Actions\Projects\CreateProject;
|
||||
use App\Actions\Projects\DeleteProject;
|
||||
use App\Actions\Projects\DuplicateProjectConfiguration;
|
||||
use App\Actions\Projects\ListVisibleProjects;
|
||||
use App\Actions\Projects\ResolveProjectTemplateState;
|
||||
use App\Actions\Projects\UpdateProjectTemplateLink;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use App\Http\Requests\DeleteProjectRequest;
|
||||
use App\Http\Requests\ResetProjectTemplateRequest;
|
||||
use App\Http\Requests\StoreProjectRequest;
|
||||
use App\Http\Requests\SwitchProjectTemplateRequest;
|
||||
use App\Http\Requests\SyncProjectPermissionsRequest;
|
||||
use App\Http\Resources\PermissionOptionResource;
|
||||
use App\Http\Resources\ProjectResource;
|
||||
use App\Http\Resources\ProjectTemplateResource;
|
||||
use App\Http\Resources\TeamResource;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectTemplate;
|
||||
use App\Models\Team;
|
||||
@@ -39,7 +36,6 @@ class ProjectController extends Controller
|
||||
private readonly DeleteProject $deleteProject,
|
||||
private readonly ListVisibleProjects $listVisibleProjects,
|
||||
private readonly PermissionService $permissionService,
|
||||
private readonly ResolveProjectTemplateState $resolveProjectTemplateState,
|
||||
private readonly UpdateProjectTemplateLink $updateProjectTemplateLink,
|
||||
) {}
|
||||
|
||||
@@ -83,10 +79,10 @@ class ProjectController extends Controller
|
||||
{
|
||||
$project = $this->createProject->handle($request->validated(), $request->user());
|
||||
|
||||
return to_route('projects.show', $project);
|
||||
return to_route('projects.board.index', $project);
|
||||
}
|
||||
|
||||
public function show(Request $request, Project $project): Response
|
||||
public function show(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
@@ -95,11 +91,7 @@ class ProjectController extends Controller
|
||||
403,
|
||||
);
|
||||
|
||||
return Inertia::render('projects/Show', [
|
||||
'canManageAccess' => $this->permissionService->allows($user, Permission::ProjectAdmin, $project),
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
'templateState' => $this->resolveProjectTemplateState->handle($project),
|
||||
]);
|
||||
return to_route('projects.board.index', $project);
|
||||
}
|
||||
|
||||
public function backlog(Request $request, Project $project): Response
|
||||
@@ -117,31 +109,19 @@ class ProjectController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function general(Request $request, Project $project): Response
|
||||
{
|
||||
return Inertia::render('projects/settings/General', [
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
'sourceTemplates' => ProjectTemplateResource::collection(
|
||||
ProjectTemplate::query()
|
||||
->published()
|
||||
->with('sourceProject')
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
)->resolve(),
|
||||
'status' => session('status'),
|
||||
'templateState' => $this->resolveProjectTemplateState->handle($project),
|
||||
]);
|
||||
}
|
||||
|
||||
public function switchTemplate(SwitchProjectTemplateRequest $request, Project $project): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$template = ProjectTemplate::query()
|
||||
->published()
|
||||
->findOrFail($request->integer('source_template_id'));
|
||||
->findOrFail((int) Arr::get($validated, 'source_template_id'));
|
||||
|
||||
$this->updateProjectTemplateLink->switch($project, $template);
|
||||
|
||||
return to_route('projects.settings.general', $project)
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::General->value,
|
||||
])
|
||||
->with('status', "{$project->name} now uses the {$template->name} template.");
|
||||
}
|
||||
|
||||
@@ -155,40 +135,13 @@ class ProjectController extends Controller
|
||||
|
||||
$this->updateProjectTemplateLink->reset($project->load('projectTemplate'));
|
||||
|
||||
return to_route('projects.settings.general', $project)
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::General->value,
|
||||
])
|
||||
->with('status', "{$project->name} was reset to the {$template->name} template baseline.");
|
||||
}
|
||||
|
||||
public function access(Request $request, Project $project): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
abort_unless(
|
||||
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
|
||||
403,
|
||||
);
|
||||
|
||||
return Inertia::render('projects/Access', [
|
||||
'directPermissions' => $this->permissionService->assignmentMap(User::class, $project),
|
||||
'permissionOptions' => PermissionOptionResource::collection(
|
||||
$this->permissionService->permissionOptions(User::class, Project::class),
|
||||
)->resolve(),
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
'status' => session('status'),
|
||||
'teamPermissions' => $this->permissionService->assignmentMap(Team::class, $project),
|
||||
'teams' => TeamResource::collection(
|
||||
Team::query()
|
||||
->with('users')
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
)->resolve(),
|
||||
'users' => UserResource::collection(
|
||||
User::query()
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
)->resolve(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateUserPermissions(
|
||||
SyncProjectPermissionsRequest $request,
|
||||
Project $project,
|
||||
@@ -205,7 +158,10 @@ class ProjectController extends Controller
|
||||
|
||||
$this->permissionService->sync($user, $project, $permissions);
|
||||
|
||||
return to_route('projects.access', $project)
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::Access->value,
|
||||
])
|
||||
->with('status', "{$user->name} permissions were updated.");
|
||||
}
|
||||
|
||||
@@ -224,7 +180,10 @@ class ProjectController extends Controller
|
||||
|
||||
$this->permissionService->sync($team, $project, $permissions);
|
||||
|
||||
return to_route('projects.access', $project)
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::Access->value,
|
||||
])
|
||||
->with('status', "{$team->name} permissions were updated.");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,41 +2,28 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Notifications\BuildProjectNotificationSettingsPayload;
|
||||
use App\Actions\Notifications\DeleteProjectNotificationAction;
|
||||
use App\Actions\Notifications\StoreProjectNotificationAction;
|
||||
use App\Actions\Notifications\UpdateProjectNotificationAction;
|
||||
use App\Actions\Permissions\PermissionService;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use App\Http\Requests\StoreProjectNotificationActionRequest;
|
||||
use App\Http\Requests\UpdateProjectNotificationActionRequest;
|
||||
use App\Models\NotificationAction;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProjectNotificationSettingsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BuildProjectNotificationSettingsPayload $buildProjectNotificationSettingsPayload,
|
||||
private readonly DeleteProjectNotificationAction $deleteProjectNotificationAction,
|
||||
private readonly PermissionService $permissionService,
|
||||
private readonly StoreProjectNotificationAction $storeProjectNotificationAction,
|
||||
private readonly UpdateProjectNotificationAction $updateProjectNotificationAction,
|
||||
) {}
|
||||
|
||||
public function show(Request $request, Project $project): Response
|
||||
{
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
return Inertia::render(
|
||||
'projects/settings/Notifications',
|
||||
$this->buildProjectNotificationSettingsPayload->handle($project, session('status')),
|
||||
);
|
||||
}
|
||||
|
||||
public function store(
|
||||
StoreProjectNotificationActionRequest $request,
|
||||
Project $project,
|
||||
@@ -45,7 +32,7 @@ class ProjectNotificationSettingsController extends Controller
|
||||
|
||||
$notificationAction = $this->storeProjectNotificationAction->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.notifications', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$notificationAction->name} notification was added.");
|
||||
}
|
||||
|
||||
@@ -62,7 +49,7 @@ class ProjectNotificationSettingsController extends Controller
|
||||
$request->validated(),
|
||||
);
|
||||
|
||||
return to_route('projects.settings.notifications', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$notificationAction->name} notification was updated.");
|
||||
}
|
||||
|
||||
@@ -80,7 +67,7 @@ class ProjectNotificationSettingsController extends Controller
|
||||
|
||||
$this->deleteProjectNotificationAction->handle($project, $notificationAction);
|
||||
|
||||
return to_route('projects.settings.notifications', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$deletedName} notification was deleted.");
|
||||
}
|
||||
|
||||
@@ -91,4 +78,12 @@ class ProjectNotificationSettingsController extends Controller
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
private function settingsRedirect(Project $project): RedirectResponse
|
||||
{
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::Notifications->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,44 +3,27 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Permissions\PermissionService;
|
||||
use App\Actions\Repositories\BuildProjectRepositorySettingsPayload;
|
||||
use App\Actions\Repositories\DeleteProjectRepositoryConnection;
|
||||
use App\Actions\Repositories\StoreProjectRepositoryConnection;
|
||||
use App\Actions\Repositories\UpdateProjectRepositoryConnection;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use App\Http\Requests\StoreRepositoryConnectionRequest;
|
||||
use App\Http\Requests\UpdateRepositoryConnectionRequest;
|
||||
use App\Models\Project;
|
||||
use App\Models\RepositoryConnection;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProjectRepositorySettingsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BuildProjectRepositorySettingsPayload $buildProjectRepositorySettingsPayload,
|
||||
private readonly DeleteProjectRepositoryConnection $deleteProjectRepositoryConnection,
|
||||
private readonly PermissionService $permissionService,
|
||||
private readonly StoreProjectRepositoryConnection $storeProjectRepositoryConnection,
|
||||
private readonly UpdateProjectRepositoryConnection $updateProjectRepositoryConnection,
|
||||
) {}
|
||||
|
||||
public function show(Request $request, Project $project): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
abort_unless(
|
||||
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
|
||||
403,
|
||||
);
|
||||
|
||||
return Inertia::render(
|
||||
'projects/settings/Repositories',
|
||||
$this->buildProjectRepositorySettingsPayload->handle($project, $user, session('status')),
|
||||
);
|
||||
}
|
||||
|
||||
public function store(
|
||||
StoreRepositoryConnectionRequest $request,
|
||||
Project $project,
|
||||
@@ -53,7 +36,7 @@ class ProjectRepositorySettingsController extends Controller
|
||||
|
||||
$repositoryConnection = $this->storeProjectRepositoryConnection->handle($project, $user, $request->validated());
|
||||
|
||||
return to_route('projects.settings.repositories', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$repositoryConnection->repository_full_name} was linked to {$project->name}.");
|
||||
}
|
||||
|
||||
@@ -76,7 +59,7 @@ class ProjectRepositorySettingsController extends Controller
|
||||
$request->validated(),
|
||||
);
|
||||
|
||||
return to_route('projects.settings.repositories', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$repositoryConnection->fresh()->repository_full_name} connection was updated.");
|
||||
}
|
||||
|
||||
@@ -92,7 +75,15 @@ class ProjectRepositorySettingsController extends Controller
|
||||
|
||||
$repositoryLabel = $this->deleteProjectRepositoryConnection->handle($repositoryConnection);
|
||||
|
||||
return to_route('projects.settings.repositories', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$repositoryLabel} was removed from the project.");
|
||||
}
|
||||
|
||||
private function settingsRedirect(Project $project): RedirectResponse
|
||||
{
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::Repositories->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Permissions\PermissionService;
|
||||
use App\Actions\Projects\BuildProjectSettingsPayload;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use App\Http\Requests\ProjectSettingsRequest;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProjectSettingsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BuildProjectSettingsPayload $buildProjectSettingsPayload,
|
||||
private readonly PermissionService $permissionService,
|
||||
) {}
|
||||
|
||||
public function index(ProjectSettingsRequest $request, Project $project): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
abort_unless(
|
||||
$this->permissionService->allows($user, Permission::ProjectAdmin, $project),
|
||||
403,
|
||||
);
|
||||
|
||||
$validated = $request->validated();
|
||||
$section = ProjectSettingsSection::from(
|
||||
(string) Arr::get($validated, 'section', ProjectSettingsSection::General->value),
|
||||
);
|
||||
|
||||
return Inertia::render(
|
||||
'projects/Settings',
|
||||
$this->buildProjectSettingsPayload->handle(
|
||||
$project,
|
||||
$user,
|
||||
$section,
|
||||
session('status'),
|
||||
session('error'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function general(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
return $this->redirectToSection($request, $project, ProjectSettingsSection::General);
|
||||
}
|
||||
|
||||
public function board(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
return $this->redirectToSection($request, $project, ProjectSettingsSection::Board);
|
||||
}
|
||||
|
||||
public function metadata(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
return $this->redirectToSection($request, $project, ProjectSettingsSection::Metadata);
|
||||
}
|
||||
|
||||
public function repositories(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
return $this->redirectToSection($request, $project, ProjectSettingsSection::Repositories);
|
||||
}
|
||||
|
||||
public function notifications(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
return $this->redirectToSection($request, $project, ProjectSettingsSection::Notifications);
|
||||
}
|
||||
|
||||
public function access(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
return $this->redirectToSection($request, $project, ProjectSettingsSection::Access);
|
||||
}
|
||||
|
||||
private function redirectToSection(
|
||||
Request $request,
|
||||
Project $project,
|
||||
ProjectSettingsSection $section,
|
||||
): RedirectResponse {
|
||||
abort_unless(
|
||||
$this->permissionService->allows($request->user(), Permission::ProjectAdmin, $project),
|
||||
403,
|
||||
);
|
||||
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => $section->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -40,11 +40,18 @@ class ProjectTicketController extends Controller
|
||||
|
||||
return Inertia::render(
|
||||
'projects/TicketCreate',
|
||||
$this->buildProjectTicketOptionsPayload->handle(
|
||||
$project,
|
||||
Arr::get($request->query(), 'source') === 'board' ? 'board' : 'backlog',
|
||||
session('status'),
|
||||
),
|
||||
[
|
||||
...$this->buildProjectTicketOptionsPayload->handle(
|
||||
$project,
|
||||
Arr::get($request->query(), 'source') === 'board' ? 'board' : 'backlog',
|
||||
session('status'),
|
||||
),
|
||||
'canManageProject' => $this->permissionService->allows(
|
||||
$user,
|
||||
Permission::ProjectAdmin,
|
||||
$project,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Permissions\PermissionService;
|
||||
use App\Actions\Projects\BuildProjectTicketMetadataSettingsPayload;
|
||||
use App\Actions\ProjectTicketMetadata\DeleteProjectTicketPriority;
|
||||
use App\Actions\ProjectTicketMetadata\DeleteProjectTicketType;
|
||||
use App\Actions\ProjectTicketMetadata\DeleteTicketFieldDefinition;
|
||||
@@ -18,6 +17,7 @@ use App\Actions\ProjectTicketMetadata\UpdateProjectTicketType;
|
||||
use App\Actions\ProjectTicketMetadata\UpdateTicketFieldDefinition;
|
||||
use App\Actions\ProjectTicketMetadata\UpdateTicketLinkType;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use App\Http\Requests\DestroyProjectTicketTypeRequest;
|
||||
use App\Http\Requests\StoreProjectNamedSettingRequest;
|
||||
use App\Http\Requests\StoreProjectTicketTypeRequest;
|
||||
@@ -35,13 +35,10 @@ use App\Models\TicketPriority;
|
||||
use App\Models\TicketType;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProjectTicketMetadataSettingsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BuildProjectTicketMetadataSettingsPayload $buildProjectTicketMetadataSettingsPayload,
|
||||
private readonly DeleteProjectTicketPriority $deleteProjectTicketPriority,
|
||||
private readonly DeleteProjectTicketType $deleteProjectTicketType,
|
||||
private readonly DeleteTicketFieldDefinition $deleteTicketFieldDefinition,
|
||||
@@ -58,23 +55,13 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
private readonly UpdateTicketLinkType $updateTicketLinkType,
|
||||
) {}
|
||||
|
||||
public function show(Request $request, Project $project): Response
|
||||
{
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
return Inertia::render(
|
||||
'projects/settings/Metadata',
|
||||
$this->buildProjectTicketMetadataSettingsPayload->handle($project, session('status'), session('error')),
|
||||
);
|
||||
}
|
||||
|
||||
public function update(UpdateProjectTicketMetadataSettings $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->abortUnlessCanManage($request, $project);
|
||||
|
||||
$this->updateProjectTicketMetadataSettings->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', 'Settings have been updated.');
|
||||
}
|
||||
|
||||
@@ -84,7 +71,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketType = $this->storeProjectTicketType->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketType->name} type was added.");
|
||||
}
|
||||
|
||||
@@ -97,7 +84,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketType = $this->updateProjectTicketType->handle($project, $ticketType, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketType->fresh()->name} type was updated.");
|
||||
}
|
||||
|
||||
@@ -116,12 +103,12 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
);
|
||||
|
||||
if (! $result['deleted']) {
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('error', true)
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
@@ -131,7 +118,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketPriority = $this->storeProjectTicketPriority->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketPriority->name} priority was added.");
|
||||
}
|
||||
|
||||
@@ -144,7 +131,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketPriority = $this->updateProjectTicketPriority->handle($project, $ticketPriority, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketPriority->fresh()->name} priority was updated.");
|
||||
}
|
||||
|
||||
@@ -158,12 +145,12 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
$result = $this->deleteProjectTicketPriority->handle($project, $ticketPriority);
|
||||
|
||||
if (! $result['deleted']) {
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('error', true)
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', $result['message']);
|
||||
}
|
||||
|
||||
@@ -175,7 +162,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketFieldDefinition = $this->storeTicketFieldDefinition->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketFieldDefinition->label} field was added.");
|
||||
}
|
||||
|
||||
@@ -188,7 +175,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketFieldDefinition = $this->updateTicketFieldDefinition->handle($project, $ticketFieldDefinition, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketFieldDefinition->fresh()->label} field was updated.");
|
||||
}
|
||||
|
||||
@@ -201,7 +188,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$deletedLabel = $this->deleteTicketFieldDefinition->handle($project, $ticketFieldDefinition);
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$deletedLabel} field was removed.");
|
||||
}
|
||||
|
||||
@@ -211,7 +198,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketLinkType = $this->storeTicketLinkType->handle($project, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketLinkType->label} link type was added.");
|
||||
}
|
||||
|
||||
@@ -225,7 +212,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$ticketLinkType = $this->updateTicketLinkType->handle($project, $ticketLinkType, $request->validated());
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$ticketLinkType->fresh()->label} link type was updated.");
|
||||
}
|
||||
|
||||
@@ -238,7 +225,7 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
|
||||
$deletedLabel = $this->deleteTicketLinkType->handle($project, $ticketLinkType);
|
||||
|
||||
return to_route('projects.settings.metadata', $project)
|
||||
return $this->settingsRedirect($project)
|
||||
->with('status', "{$deletedLabel} link type was removed.");
|
||||
}
|
||||
|
||||
@@ -249,4 +236,12 @@ class ProjectTicketMetadataSettingsController extends Controller
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
private function settingsRedirect(Project $project): RedirectResponse
|
||||
{
|
||||
return to_route('projects.settings.index', [
|
||||
'project' => $project,
|
||||
'section' => ProjectSettingsSection::Metadata->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\ProjectSettingsSection;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProjectSettingsRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'section' => ['required', Rule::enum(ProjectSettingsSection::class)],
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$section = Arr::get($this->query(), 'section');
|
||||
|
||||
$this->merge([
|
||||
'section' => is_string($section) && ProjectSettingsSection::tryFrom($section) !== null
|
||||
? $section
|
||||
: ProjectSettingsSection::General->value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user