From 92dfe3840cc57c698941e01a9eb1014867069327 Mon Sep 17 00:00:00 2001 From: Twyco Date: Thu, 26 Mar 2026 09:39:18 +0100 Subject: [PATCH 1/2] Add ticket-driven branch creation flow --- .../Git/BuildSuggestedTicketBranchName.php | 28 ++ .../Git/Contracts/GitProviderClient.php | 16 + app/Actions/Git/Data/GitRepository.php | 1 + app/Actions/Git/Gitea/GiteaProviderClient.php | 89 +++- app/Actions/Git/ValidateGitBranchName.php | 50 +++ .../ProjectTicketBranchController.php | 201 +++++++++ .../Controllers/ProjectTicketController.php | 3 + .../StoreProjectTicketBranchRequest.php | 40 ++ .../ValidateProjectTicketBranchRequest.php | 40 ++ docs/architecture-roadmap.md | 1 + docs/domain-model.md | 6 + docs/product-spec.md | 10 + ...cket-branch-creation-from-ticket-detail.md | 41 ++ resources/js/components/AppHeader.vue | 2 +- resources/js/components/AppSidebar.vue | 4 +- resources/js/components/UserMenuContent.vue | 4 +- resources/js/layouts/auth/AuthCardLayout.vue | 2 +- .../js/layouts/auth/AuthSimpleLayout.vue | 2 +- resources/js/layouts/auth/AuthSplitLayout.vue | 2 +- resources/js/layouts/settings/Layout.vue | 8 +- resources/js/pages/Dashboard.vue | 2 +- resources/js/pages/Welcome.vue | 2 +- resources/js/pages/auth/ForgotPassword.vue | 2 +- resources/js/pages/auth/Login.vue | 2 +- resources/js/pages/auth/Register.vue | 2 +- resources/js/pages/auth/VerifyEmail.vue | 2 +- resources/js/pages/projects/Access.vue | 2 +- resources/js/pages/projects/Backlog.vue | 2 +- resources/js/pages/projects/Board.vue | 2 +- resources/js/pages/projects/Create.vue | 2 +- resources/js/pages/projects/Index.vue | 2 +- resources/js/pages/projects/Show.vue | 2 +- resources/js/pages/projects/Ticket.vue | 415 +++++++++++++++++- .../js/pages/projects/settings/Board.vue | 2 +- .../js/pages/projects/settings/Metadata.vue | 2 +- .../pages/projects/settings/Repositories.vue | 4 +- resources/js/pages/settings/Appearance.vue | 2 +- .../js/pages/settings/GitConnections.vue | 2 +- resources/js/pages/settings/Profile.vue | 2 +- resources/js/pages/settings/Security.vue | 2 +- .../models/branch-name-validation-model.ts | 5 + resources/js/types/page-props.ts | 1 + routes/web.php | 5 + tests/Feature/Git/GiteaProviderTest.php | 69 +++ .../Git/TicketKeyReferenceMatcherTest.php | 37 ++ .../ProjectTicketBranchControllerTest.php | 258 +++++++++++ .../Projects/ProjectTicketControllerTest.php | 2 + 47 files changed, 1332 insertions(+), 50 deletions(-) create mode 100644 app/Actions/Git/BuildSuggestedTicketBranchName.php create mode 100644 app/Actions/Git/ValidateGitBranchName.php create mode 100644 app/Http/Controllers/ProjectTicketBranchController.php create mode 100644 app/Http/Requests/StoreProjectTicketBranchRequest.php create mode 100644 app/Http/Requests/ValidateProjectTicketBranchRequest.php create mode 100644 docs/releases/0.2-gitea-integration/05-ticket-branch-creation-from-ticket-detail.md create mode 100644 resources/js/types/models/branch-name-validation-model.ts create mode 100644 tests/Feature/Git/TicketKeyReferenceMatcherTest.php create mode 100644 tests/Feature/Projects/ProjectTicketBranchControllerTest.php diff --git a/app/Actions/Git/BuildSuggestedTicketBranchName.php b/app/Actions/Git/BuildSuggestedTicketBranchName.php new file mode 100644 index 0000000..9d5eeb8 --- /dev/null +++ b/app/Actions/Git/BuildSuggestedTicketBranchName.php @@ -0,0 +1,28 @@ +title) + ->lower() + ->replaceMatches('/[^a-z0-9\s-]+/', '') + ->trim() + ->replaceMatches('/\s+/', '-') + ->replaceMatches('/-+/', '-') + ->value(); + + $ticketKey = Str::lower($ticket->ticket_key); + + if ($titleSlug === '') { + return $ticketKey; + } + + return "{$ticketKey}-{$titleSlug}"; + } +} diff --git a/app/Actions/Git/Contracts/GitProviderClient.php b/app/Actions/Git/Contracts/GitProviderClient.php index df6caed..9153465 100644 --- a/app/Actions/Git/Contracts/GitProviderClient.php +++ b/app/Actions/Git/Contracts/GitProviderClient.php @@ -14,6 +14,13 @@ interface GitProviderClient public function getAuthenticatedUser(string $baseUrl, string $token): GitAuthenticatedUser; + public function getRepository( + string $baseUrl, + string $token, + string $owner, + string $repository, + ): GitRepository; + /** * @return array */ @@ -38,4 +45,13 @@ interface GitProviderClient string $owner, string $repository, ): array; + + public function createBranch( + string $baseUrl, + string $token, + string $owner, + string $repository, + string $newBranchName, + ?string $oldRefName = null, + ): GitBranch; } diff --git a/app/Actions/Git/Data/GitRepository.php b/app/Actions/Git/Data/GitRepository.php index de05510..98696fc 100644 --- a/app/Actions/Git/Data/GitRepository.php +++ b/app/Actions/Git/Data/GitRepository.php @@ -13,6 +13,7 @@ final readonly class GitRepository public string $name, public string $fullName, public string $htmlUrl, + public ?string $defaultBranch, public bool $isPrivate, ) {} } diff --git a/app/Actions/Git/Gitea/GiteaProviderClient.php b/app/Actions/Git/Gitea/GiteaProviderClient.php index 2d01204..18644ff 100644 --- a/app/Actions/Git/Gitea/GiteaProviderClient.php +++ b/app/Actions/Git/Gitea/GiteaProviderClient.php @@ -34,6 +34,30 @@ class GiteaProviderClient implements GitProviderClient ); } + public function getRepository( + string $baseUrl, + string $token, + string $owner, + string $repository, + ): GitRepository { + /** @var array{ + * default_branch?: string|null, + * id: int|string, + * name: string, + * full_name: string, + * html_url: string, + * owner: array{login: string}, + * private: bool + * } $payload + */ + $payload = $this->request($baseUrl, $token) + ->get('/repos/'.rawurlencode($owner).'/'.rawurlencode($repository)) + ->throw() + ->json(); + + return $this->mapRepository($payload); + } + /** * @return array */ @@ -61,14 +85,7 @@ class GiteaProviderClient implements GitProviderClient ->json(); foreach ($payload as $repository) { - $repositories[] = new GitRepository( - providerRepositoryId: (string) $repository['id'], - owner: $repository['owner']['login'], - name: $repository['name'], - fullName: $repository['full_name'], - htmlUrl: $repository['html_url'], - isPrivate: $repository['private'], - ); + $repositories[] = $this->mapRepository($repository); } $page++; @@ -166,6 +183,36 @@ class GiteaProviderClient implements GitProviderClient return $pullRequests; } + public function createBranch( + string $baseUrl, + string $token, + string $owner, + string $repository, + string $newBranchName, + ?string $oldRefName = null, + ): GitBranch { + $payload = [ + 'new_branch_name' => $newBranchName, + ]; + + if ($oldRefName !== null && $oldRefName !== '') { + $payload['old_ref_name'] = $oldRefName; + } + + /** @var array{name: string} $response */ + $response = $this->request($baseUrl, $token) + ->post( + '/repos/'.rawurlencode($owner).'/'.rawurlencode($repository).'/branches', + $payload, + ) + ->throw() + ->json(); + + return new GitBranch( + name: $response['name'], + ); + } + private function request(string $baseUrl, string $token): PendingRequest { return Http::acceptJson() @@ -189,4 +236,30 @@ class GiteaProviderClient implements GitProviderClient ->append('/api/v1') ->value(); } + + /** + * @param array{ + * default_branch?: string|null, + * id: int|string, + * name: string, + * full_name: string, + * html_url: string, + * owner: array{login: string}, + * private: bool + * } $repository + */ + private function mapRepository(array $repository): GitRepository + { + return new GitRepository( + providerRepositoryId: (string) $repository['id'], + owner: $repository['owner']['login'], + name: $repository['name'], + fullName: $repository['full_name'], + htmlUrl: $repository['html_url'], + defaultBranch: blank($repository['default_branch'] ?? null) + ? null + : $repository['default_branch'], + isPrivate: $repository['private'], + ); + } } diff --git a/app/Actions/Git/ValidateGitBranchName.php b/app/Actions/Git/ValidateGitBranchName.php new file mode 100644 index 0000000..5fc5391 --- /dev/null +++ b/app/Actions/Git/ValidateGitBranchName.php @@ -0,0 +1,50 @@ +projectPermissionService->userHasPermission($request->user(), $project, 'edit'), + 403, + ); + + $repositoryConnection = $this->projectRepositoryConnection( + $project, + $request->repositoryConnectionId(), + ); + $userGitConnection = $this->matchingUserGitConnection( + $request->user()->id, + $repositoryConnection, + ); + + if ($userGitConnection === null) { + return response()->json([ + 'is_valid' => false, + 'message' => 'Connect a matching Git connection in your user settings first.', + ]); + } + + $syntaxMessage = $this->validateGitBranchName->message($request->branchName()); + + if ($syntaxMessage !== null) { + return response()->json([ + 'is_valid' => false, + 'message' => $syntaxMessage, + ]); + } + + try { + $branchAlreadyExists = collect( + $this->gitProviderManager + ->client($userGitConnection->provider) + ->listBranches( + $userGitConnection->base_url, + $userGitConnection->api_token, + $repositoryConnection->owner_name, + $repositoryConnection->repository_name, + ), + ) + ->contains( + fn (GitBranch $branch): bool => $branch->name === $request->branchName(), + ); + } catch (Throwable) { + return response()->json([ + 'is_valid' => false, + 'message' => 'KanbanGit could not validate this branch name right now.', + ]); + } + + if ($branchAlreadyExists) { + return response()->json([ + 'is_valid' => false, + 'message' => 'This branch name already exists in the selected repository.', + ]); + } + + return response()->json([ + 'is_valid' => true, + 'message' => "Branch name is available for {$ticket->ticket_key}.", + ]); + } + + public function store( + StoreProjectTicketBranchRequest $request, + Project $project, + Ticket $ticket, + ): RedirectResponse { + abort_unless( + $this->projectPermissionService->userHasPermission($request->user(), $project, 'edit'), + 403, + ); + + $repositoryConnection = $this->projectRepositoryConnection( + $project, + $request->repositoryConnectionId(), + ); + $userGitConnection = $this->matchingUserGitConnection( + $request->user()->id, + $repositoryConnection, + ); + + if ($userGitConnection === null) { + throw ValidationException::withMessages([ + 'repository_connection_id' => 'Connect a matching Git connection in your user settings first.', + ]); + } + + $syntaxMessage = $this->validateGitBranchName->message($request->branchName()); + + if ($syntaxMessage !== null) { + throw ValidationException::withMessages([ + 'branch_name' => $syntaxMessage, + ]); + } + + $gitProvider = $this->gitProviderManager->client($userGitConnection->provider); + + try { + $branchAlreadyExists = collect( + $gitProvider->listBranches( + $userGitConnection->base_url, + $userGitConnection->api_token, + $repositoryConnection->owner_name, + $repositoryConnection->repository_name, + ), + ) + ->contains( + fn (GitBranch $branch): bool => $branch->name === $request->branchName(), + ); + } catch (Throwable) { + throw ValidationException::withMessages([ + 'branch_name' => 'KanbanGit could not validate this branch name right now.', + ]); + } + + if ($branchAlreadyExists) { + throw ValidationException::withMessages([ + 'branch_name' => 'This branch name already exists in the selected repository.', + ]); + } + + try { + $repository = $gitProvider->getRepository( + $userGitConnection->base_url, + $userGitConnection->api_token, + $repositoryConnection->owner_name, + $repositoryConnection->repository_name, + ); + + $gitProvider->createBranch( + $userGitConnection->base_url, + $userGitConnection->api_token, + $repositoryConnection->owner_name, + $repositoryConnection->repository_name, + $request->branchName(), + $repository->defaultBranch, + ); + } catch (Throwable) { + throw ValidationException::withMessages([ + 'branch_name' => 'KanbanGit could not create this branch right now.', + ]); + } + + return to_route('projects.tickets.show', [$project, $ticket]) + ->with( + 'status', + "{$request->branchName()} was created in {$repositoryConnection->repository_full_name}.", + ); + } + + private function projectRepositoryConnection(Project $project, int $repositoryConnectionId): RepositoryConnection + { + return $project->repositoryConnections() + ->findOrFail($repositoryConnectionId); + } + + private function matchingUserGitConnection( + int $userId, + RepositoryConnection $repositoryConnection, + ): ?UserGitConnection { + return UserGitConnection::query() + ->where('user_id', $userId) + ->where('provider', $repositoryConnection->provider) + ->where('base_url', $repositoryConnection->base_url) + ->first(); + } +} diff --git a/app/Http/Controllers/ProjectTicketController.php b/app/Http/Controllers/ProjectTicketController.php index dccfffd..63ba7b1 100644 --- a/app/Http/Controllers/ProjectTicketController.php +++ b/app/Http/Controllers/ProjectTicketController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Actions\Git\BuildSuggestedTicketBranchName; use App\Actions\Git\ResolveTicketBranchMatches; use App\Actions\Git\ResolveTicketPullRequestMatches; use App\Actions\ProjectPermissions\ProjectPermissionService; @@ -19,6 +20,7 @@ use Inertia\Response; class ProjectTicketController extends Controller { public function __construct( + private readonly BuildSuggestedTicketBranchName $buildSuggestedTicketBranchName, private readonly ProjectPermissionService $projectPermissionService, private readonly ResolveTicketBranchMatches $resolveTicketBranchMatches, private readonly ResolveTicketPullRequestMatches $resolveTicketPullRequestMatches, @@ -61,6 +63,7 @@ class ProjectTicketController extends Controller $ticket, ), 'status' => session('status'), + 'suggestedBranchName' => $this->buildSuggestedTicketBranchName->handle($ticket), 'ticket' => $ticket->load([ 'fieldValues.ticketFieldDefinition', 'fieldValues.userValue', diff --git a/app/Http/Requests/StoreProjectTicketBranchRequest.php b/app/Http/Requests/StoreProjectTicketBranchRequest.php new file mode 100644 index 0000000..208f9a9 --- /dev/null +++ b/app/Http/Requests/StoreProjectTicketBranchRequest.php @@ -0,0 +1,40 @@ +user() !== null; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'branch_name' => ['required', 'string', 'max:255'], + 'repository_connection_id' => ['required', 'integer', 'exists:repository_connections,id'], + ]; + } + + public function branchName(): string + { + return trim((string) $this->validated('branch_name')); + } + + public function repositoryConnectionId(): int + { + return (int) $this->validated('repository_connection_id'); + } +} diff --git a/app/Http/Requests/ValidateProjectTicketBranchRequest.php b/app/Http/Requests/ValidateProjectTicketBranchRequest.php new file mode 100644 index 0000000..94fc65e --- /dev/null +++ b/app/Http/Requests/ValidateProjectTicketBranchRequest.php @@ -0,0 +1,40 @@ +user() !== null; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'branch_name' => ['required', 'string', 'max:255'], + 'repository_connection_id' => ['required', 'integer', 'exists:repository_connections,id'], + ]; + } + + public function branchName(): string + { + return trim((string) $this->validated('branch_name')); + } + + public function repositoryConnectionId(): int + { + return (int) $this->validated('repository_connection_id'); + } +} diff --git a/docs/architecture-roadmap.md b/docs/architecture-roadmap.md index fa69406..feaae9f 100644 --- a/docs/architecture-roadmap.md +++ b/docs/architecture-roadmap.md @@ -202,6 +202,7 @@ Git integration foundation: - Gitea repository connections - Branch correlation - Pull request correlation +- Ticket-driven branch creation ### Phase 4 diff --git a/docs/domain-model.md b/docs/domain-model.md index a3e1bcd..6e02213 100644 --- a/docs/domain-model.md +++ b/docs/domain-model.md @@ -220,6 +220,12 @@ Required properties: - Repository connection - Branch name +Behavior: + +- Branch matching is based on the ticket key prefix. +- The branch list may also be the entry point for creating a new branch from the ticket detail view. +- Branch creation depends on the current user's personal Git credentials for the selected repository connection. + ### PullRequestReference Represents a pull request associated with a ticket through a repository connection. diff --git a/docs/product-spec.md b/docs/product-spec.md index 6fe862e..d21f1ba 100644 --- a/docs/product-spec.md +++ b/docs/product-spec.md @@ -252,11 +252,21 @@ Initial expectations: - The first supported provider is Gitea. - Later providers should include GitHub and potentially GitLab. - Tickets will later expose branch and pull request sections. +- The ticket branch section should eventually allow users to create a new branch directly from the ticket detail view. Ticket-to-Git matching rule: - A branch or pull request belongs to a ticket when its branch name starts with the ticket key. +Branch creation expectations: + +- The ticket detail view may expose a branch creation action inside the branch section. +- The branch creation flow should let the user choose from all project-linked repositories. +- Repository choices remain visible even when the current user has no matching personal Git connection. +- If the user lacks access to the selected repository, branch creation fields must stay disabled and guide the user to their Git connection settings. +- The suggested branch name should start from the ticket key plus a title-derived suffix and remain editable before creation. +- The UI should validate that the proposed branch name is syntactically acceptable and not already taken before creation is submitted. + Example: - Ticket key: `AUTH-42` diff --git a/docs/releases/0.2-gitea-integration/05-ticket-branch-creation-from-ticket-detail.md b/docs/releases/0.2-gitea-integration/05-ticket-branch-creation-from-ticket-detail.md new file mode 100644 index 0000000..e1d8617 --- /dev/null +++ b/docs/releases/0.2-gitea-integration/05-ticket-branch-creation-from-ticket-detail.md @@ -0,0 +1,41 @@ +# 05 Ticket Branch Creation from Ticket Detail + +## Goal + +Allow users to create a new repository branch directly from the ticket detail view while preserving the existing project-linked repository model and per-user Git credentials. + +## Requirements + +- Add a branch creation action to the ticket branch section. +- Opening the action must show a modal dialog. +- The dialog must list all repositories linked to the project. +- The repository dropdown must still show repositories the current user cannot access through their own Git connection. +- If the selected repository is not accessible for the current user: + - the branch name field must be disabled + - the final create button must be disabled + - the UI must guide the user to their Git connection settings +- If the selected repository is accessible: + - prefill the branch name with the ticket key plus the ticket title + - convert the suggestion to lowercase + - replace spaces with `-` + - allow the user to edit the branch name before creation +- The dialog must show a validation state with a positive or negative indicator and a human-readable message. +- Validation must cover both: + - branch name syntax + - branch name uniqueness for the selected repository +- The create action must create the branch in Gitea. + +## Acceptance Criteria + +- The ticket branch section exposes a branch creation button. +- Clicking the button opens a modal with a repository dropdown. +- Selecting an inaccessible repository disables the rest of the flow and points the user to Git connection settings. +- Selecting an accessible repository prepopulates the branch name using the ticket key and title pattern. +- The validation message updates before creation and prevents creating an already existing branch. +- Submitting the dialog creates the branch in the selected repository and returns the user to the ticket detail view. + +## Out of Scope + +- Choosing a custom source branch inside the UI +- Branch creation for providers other than Gitea +- Automatic pull request creation after branch creation diff --git a/resources/js/components/AppHeader.vue b/resources/js/components/AppHeader.vue index f1bb49e..82c79f5 100644 --- a/resources/js/components/AppHeader.vue +++ b/resources/js/components/AppHeader.vue @@ -35,7 +35,7 @@ import UserMenuContent from '@/components/UserMenuContent.vue'; import { useCurrentUrl } from '@/composables/useCurrentUrl'; import { getInitials } from '@/composables/useInitials'; import { toUrl } from '@/lib/utils'; -import { dashboard } from '@/routes'; +import { dashboard } from '@/routes/index'; import type { BreadcrumbItem, NavItem } from '@/types'; type Props = { diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 5924144..c2dcf2d 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -24,8 +24,8 @@ import { } from '@/components/ui/sidebar'; import { index as adminTeams } from '@/routes/admin/teams'; import { index as adminUsers } from '@/routes/admin/users'; -import { index as projectsIndex } from '@/routes/projects'; -import { dashboard } from '@/routes'; +import { index as projectsIndex } from '@/routes/projects/index'; +import { dashboard } from '@/routes/index'; import type { NavItem } from '@/types'; const page = usePage(); diff --git a/resources/js/components/UserMenuContent.vue b/resources/js/components/UserMenuContent.vue index cfbc4e6..a7e6e81 100644 --- a/resources/js/components/UserMenuContent.vue +++ b/resources/js/components/UserMenuContent.vue @@ -8,8 +8,8 @@ import { DropdownMenuSeparator, } from '@/components/ui/dropdown-menu'; import UserInfo from '@/components/UserInfo.vue'; -import { logout } from '@/routes'; -import { edit } from '@/routes/profile'; +import { logout } from '@/routes/index'; +import { edit } from '@/routes/profile/index'; import type { User } from '@/types'; type Props = { diff --git a/resources/js/layouts/auth/AuthCardLayout.vue b/resources/js/layouts/auth/AuthCardLayout.vue index 57afbb5..3dbc172 100644 --- a/resources/js/layouts/auth/AuthCardLayout.vue +++ b/resources/js/layouts/auth/AuthCardLayout.vue @@ -8,7 +8,7 @@ import { CardHeader, CardTitle, } from '@/components/ui/card'; -import { home } from '@/routes'; +import { home } from '@/routes/index'; defineProps<{ title?: string; diff --git a/resources/js/layouts/auth/AuthSimpleLayout.vue b/resources/js/layouts/auth/AuthSimpleLayout.vue index b54c094..854a030 100644 --- a/resources/js/layouts/auth/AuthSimpleLayout.vue +++ b/resources/js/layouts/auth/AuthSimpleLayout.vue @@ -1,7 +1,7 @@ diff --git a/resources/js/pages/auth/VerifyEmail.vue b/resources/js/pages/auth/VerifyEmail.vue index 4f8f63e..cf4991c 100644 --- a/resources/js/pages/auth/VerifyEmail.vue +++ b/resources/js/pages/auth/VerifyEmail.vue @@ -4,7 +4,7 @@ import TextLink from '@/components/TextLink.vue'; import { Button } from '@/components/ui/button'; import { Spinner } from '@/components/ui/spinner'; import AuthLayout from '@/layouts/AuthLayout.vue'; -import { logout } from '@/routes'; +import { logout } from '@/routes/index'; import { send } from '@/routes/verification'; defineProps<{ diff --git a/resources/js/pages/projects/Access.vue b/resources/js/pages/projects/Access.vue index c08d46e..09b4bd6 100644 --- a/resources/js/pages/projects/Access.vue +++ b/resources/js/pages/projects/Access.vue @@ -12,7 +12,7 @@ import { CardTitle, } from '@/components/ui/card'; import AppLayout from '@/layouts/AppLayout.vue'; -import { access, index, show } from '@/routes/projects'; +import { access, index, show } from '@/routes/projects/index'; import type { BreadcrumbItem, ProjectAccessPageProps } from '@/types'; const props = defineProps(); diff --git a/resources/js/pages/projects/Backlog.vue b/resources/js/pages/projects/Backlog.vue index 33b0fb6..1e54e31 100644 --- a/resources/js/pages/projects/Backlog.vue +++ b/resources/js/pages/projects/Backlog.vue @@ -18,7 +18,7 @@ import { Label } from '@/components/ui/label'; import AppLayout from '@/layouts/AppLayout.vue'; import { index as boardIndex } from '@/routes/projects/board'; import { index as backlogIndex } from '@/routes/projects/backlog'; -import { index, show } from '@/routes/projects'; +import { index, show } from '@/routes/projects/index'; import { show as showTicket } from '@/routes/projects/tickets'; import { metadata as metadataSettings } from '@/routes/projects/settings'; import type { BreadcrumbItem, ProjectBacklogPageProps } from '@/types'; diff --git a/resources/js/pages/projects/Board.vue b/resources/js/pages/projects/Board.vue index 8d07e92..8751309 100644 --- a/resources/js/pages/projects/Board.vue +++ b/resources/js/pages/projects/Board.vue @@ -13,7 +13,7 @@ import { } from '@/components/ui/card'; import AppLayout from '@/layouts/AppLayout.vue'; import { index as backlogIndex } from '@/routes/projects/backlog'; -import { index as projectIndex, show } from '@/routes/projects'; +import { index as projectIndex, show } from '@/routes/projects/index'; import { board as boardSettings } from '@/routes/projects/settings'; import { index as boardIndex } from '@/routes/projects/board'; import { show as showTicket } from '@/routes/projects/tickets'; diff --git a/resources/js/pages/projects/Create.vue b/resources/js/pages/projects/Create.vue index 431e6f1..ea37fd0 100644 --- a/resources/js/pages/projects/Create.vue +++ b/resources/js/pages/projects/Create.vue @@ -14,7 +14,7 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import AppLayout from '@/layouts/AppLayout.vue'; -import { create, index } from '@/routes/projects'; +import { create, index } from '@/routes/projects/index'; import type { BreadcrumbItem } from '@/types'; const breadcrumbs: BreadcrumbItem[] = [ diff --git a/resources/js/pages/projects/Index.vue b/resources/js/pages/projects/Index.vue index 251a46b..521ba9e 100644 --- a/resources/js/pages/projects/Index.vue +++ b/resources/js/pages/projects/Index.vue @@ -12,7 +12,7 @@ import { CardTitle, } from '@/components/ui/card'; import AppLayout from '@/layouts/AppLayout.vue'; -import { create, index, show } from '@/routes/projects'; +import { create, index, show } from '@/routes/projects/index'; import type { BreadcrumbItem, ProjectsIndexPageProps } from '@/types'; defineProps(); diff --git a/resources/js/pages/projects/Show.vue b/resources/js/pages/projects/Show.vue index 5c6a7c1..c6d9d4a 100644 --- a/resources/js/pages/projects/Show.vue +++ b/resources/js/pages/projects/Show.vue @@ -12,7 +12,7 @@ import { CardTitle, } from '@/components/ui/card'; import AppLayout from '@/layouts/AppLayout.vue'; -import { access, index, show } from '@/routes/projects'; +import { access, index, show } from '@/routes/projects/index'; import { index as boardIndex } from '@/routes/projects/board'; import { index as backlogIndex } from '@/routes/projects/backlog'; import { diff --git a/resources/js/pages/projects/Ticket.vue b/resources/js/pages/projects/Ticket.vue index 735f6e5..0bdba48 100644 --- a/resources/js/pages/projects/Ticket.vue +++ b/resources/js/pages/projects/Ticket.vue @@ -1,6 +1,8 @@