From a252c2a35d977946876d95252a574812827039bb Mon Sep 17 00:00:00 2001 From: William Allen Date: Wed, 4 Mar 2026 09:57:12 -0500 Subject: [PATCH] Add "Member" tab to projects page --- .../Controllers/ViewProjectsController.php | 1 + resources/js/vue/components/ProjectsPage.vue | 125 +++++++++++++----- tests/Browser/Pages/ProjectsPageTest.php | 60 ++++++++- 3 files changed, 151 insertions(+), 35 deletions(-) diff --git a/app/Http/Controllers/ViewProjectsController.php b/app/Http/Controllers/ViewProjectsController.php index 3423c8a187..ebeac202c6 100644 --- a/app/Http/Controllers/ViewProjectsController.php +++ b/app/Http/Controllers/ViewProjectsController.php @@ -23,6 +23,7 @@ public function viewProjects(): View|RedirectResponse } return $this->vue('projects-page', 'Projects', [ + 'is-logged-in' => Auth::check(), 'can-create-projects' => Gate::allows('create', Project::class), ]); } diff --git a/resources/js/vue/components/ProjectsPage.vue b/resources/js/vue/components/ProjectsPage.vue index 8eac9fbd94..7b5d775f5e 100644 --- a/resources/js/vue/components/ProjectsPage.vue +++ b/resources/js/vue/components/ProjectsPage.vue @@ -9,6 +9,16 @@ role="tablist" class="tw-tabs tw-tabs-bordered" > + + Member +
- No projects with builds in the last 24 hours... -
-
- No projects to display... + {{ noProjectsMessage }}
data?.me?.projects, variables() { return { - countBuildsSince: DateTime.now().minus({days: 1}).startOf('second').toISO({suppressMilliseconds: true}), + countBuildsSince: this.oneDayAgo, }; }, }, @@ -187,7 +217,17 @@ export default { }, projects() { - const edges = this.allVisibleProjects?.edges.filter(({node: project}) => this.currentTab === 'ALL' || project.buildCount > 0); + let edges; + if (this.currentTab === 'MEMBER') { + edges = this.myProjects?.edges.map(x => x); + } + else if (this.currentTab === 'ACTIVE') { + edges = this.allVisibleProjects?.edges.filter(({node: project}) => project.buildCount > 0); + } + else { + edges = this.allVisibleProjects?.edges.map(x => x); + } + if (edges === null || edges === undefined) { return null; } @@ -195,6 +235,27 @@ export default { edges.sort((a, b) => b.node.buildCount - a.node.buildCount); return edges; }, + + noProjectsMessage() { + if (this.projects.length > 0) { + return null; + } + + switch (this.currentTab) { + case 'MEMBER': + return 'You are not a member of any projects yet...'; + case 'ACTIVE': + return 'No projects with builds in the last 24 hours...'; + case 'ALL': + return 'No projects to display...'; + default: + return null; + } + }, + + oneDayAgo() { + return DateTime.now().minus({days: 1}).startOf('second').toISO({suppressMilliseconds: true}); + }, }, methods: { diff --git a/tests/Browser/Pages/ProjectsPageTest.php b/tests/Browser/Pages/ProjectsPageTest.php index ea5a4ee705..07fd19773d 100644 --- a/tests/Browser/Pages/ProjectsPageTest.php +++ b/tests/Browser/Pages/ProjectsPageTest.php @@ -97,6 +97,7 @@ public function testShowsMessageWhenNoProjects(): void $browser->click('@all-tab') ->waitFor('@no-projects-message') ->assertVisible('@no-projects-message') + ->assertSee('No projects to display') ->assertMissing('@projects-table') ; }); @@ -109,6 +110,7 @@ public function testShowsMessageWhenNoProjects(): void $browser->click('@all-tab') ->waitFor('@projects-table') ->assertMissing('@no-projects-message') + ->assertDontSee('No projects to display') ->assertSeeIn('@projects-table', $this->projects['project1']->name) ; }); @@ -129,8 +131,8 @@ public function testShowsMessageWhenNoActiveProjects(): void $browser->visit('/projects') ->whenAvailable('@projects-page', function (Browser $browser): void { $browser->click('@active-tab') - ->waitFor('@no-active-projects-message') - ->assertVisible('@no-active-projects-message') + ->waitFor('@no-projects-message') + ->assertSee('No projects with builds in the last 24 hours') ->assertMissing('@projects-table') ; }); @@ -142,13 +144,65 @@ public function testShowsMessageWhenNoActiveProjects(): void ->whenAvailable('@projects-page', function (Browser $browser): void { $browser->click('@active-tab') ->waitFor('@projects-table') - ->assertMissing('@no-active-projects-message') + ->assertMissing('@no-projects-message') + ->assertDontSee('No projects with builds in the last 24 hours') + ->assertSeeIn('@projects-table', $this->projects['project1']->name) + ; + }); + }); + } + + public function testShowsMessageWhenNoMemberProjects(): void + { + $this->users['admin'] = $this->makeAdminUser(); + $this->projects['project1'] = $this->makePublicProject(); + + $this->browse(function (Browser $browser): void { + $browser->loginAs($this->users['admin']) + ->visit('/projects') + ->whenAvailable('@projects-page', function (Browser $browser): void { + $browser->click('@member-tab') + ->waitFor('@no-projects-message') + ->assertVisible('@no-projects-message') + ->assertSee('You are not a member of any projects yet') + ->assertMissing('@projects-table') + ; + }); + + $this->projects['project1']->users()->attach($this->users['admin'], ['role' => Project::PROJECT_USER]); + + $browser->loginAs($this->users['admin']) + ->visit('/projects') + ->whenAvailable('@projects-page', function (Browser $browser): void { + $browser->click('@member-tab') + ->waitFor('@projects-table') + ->assertMissing('@no-projects-message') + ->assertDontSee('You are not a member of any projects yet') ->assertSeeIn('@projects-table', $this->projects['project1']->name) ; }); }); } + public function testOnlyShowsMemberTabWhenLoggedIn(): void + { + $this->users['admin'] = $this->makeAdminUser(); + $this->projects['project1'] = $this->makePublicProject(); + + $this->browse(function (Browser $browser): void { + $browser->visit('/projects') + ->whenAvailable('@projects-page', function (Browser $browser): void { + $browser->assertMissing('@member-tab'); + }); + + $browser->loginAs($this->users['admin']) + ->visit('/projects') + ->whenAvailable('@projects-page', function (Browser $browser): void { + $browser->assertVisible('@member-tab'); + }); + }); + } + public function testSortsProjectsByNumberOfBuildsInLastDay(): void { $this->projects['project1'] = $this->makePublicProject();