Skip to content

Commit e71b6c0

Browse files
TehShrikemrkaye97
andauthored
Remove "account" dropdown', add notifications dropdown (#3365)
* Move theme + logout to the side nav, and a few refactors for sanity - items in the side nav can now expose an `onClick` instead of just a `to` - renamed the "activeTo" side nav item option to "displayAsActiveWhenThisRouteIsMatched" to increase my understanding - the theme hook now exposes a currentlyVisibleTheme value which is either "dark" or "light", as opposed to the existing theme value which can also be "system" – at some point we should probably make the toggle function toggle through all 3 possible values, but I'll settle for this incremental improvement for now * Don't redirect to the invite screen unless we have to – otherwise, just show a notification * Refetch invites every 1 minute, resource limits every 2 * make the notifications button a perfect circle * Make notification text more likely to be readable - notification dropdown wider - move more important text earlier in the notification message - kill some passive voice in the process - put notification message in title so you can see it on hover * Use shadcn button for notifications * Change the syntax of the resourceLabels map slightly For slightly nicer type-checking warnings * Move the logout mutation into the user universe * Move the logout button to the bottom of the navbar next to "help" * move all settings options up to the top level also make the theme button not stand out so much * merge detritus * Add an onboarding notification if the user has one tenant and no workflows and no api keys, show them a notification that redirects them to the onboarding screen * Put a short title into the top nav * Get cypress tests working again 07 isn't relevant now, its case is covered by 05 and 08 * A few more references to "User Menu" * settings items aren't nested any more * mm lint:fix * fix: import * fix: tenant invite type * fix: remove overview tab from left sidebar * fix: notification styling, don't show if no notifications exist * fix: react query devtools z index * fix: icon position * fix: drive by fixing some react errors in the console * fix: remove no notifs state, since we just hide the icon * fix: some cypress tests * fix: more cy tests --------- Co-authored-by: mrkaye97 <mrkaye97@gmail.com>
1 parent 3e1d53a commit e71b6c0

29 files changed

Lines changed: 791 additions & 683 deletions

frontend/app/cypress/e2e/auth/01-login.cy.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,6 @@ describe('auth: login', () => {
3333
}
3434
});
3535
cy.location('pathname', { timeout: 30000 }).should('include', '/tenants/');
36-
cy.get('button[aria-label="User Menu"]').filter(':visible').first().click();
37-
// `data-cy="user-name"` exists in both the trigger and the dropdown content; scope to the open menu.
38-
cy.get('[role="menu"]')
39-
.filter(':visible')
40-
.first()
41-
.within(() => {
42-
cy.get('[data-cy="user-name"]')
43-
.filter(':visible')
44-
.first()
45-
.should('have.text', seededUsers.owner.name);
46-
});
36+
cy.get('[data-cy="v1-sidebar"]', { timeout: 30000 }).should('be.visible');
4737
});
4838
});

frontend/app/cypress/e2e/auth/02-logout.cy.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@ describe('auth: logout', () => {
33
// Some environments don't have an "admin" seeded user; "owner" is sufficient to validate logout.
44
cy.login('owner');
55
cy.visit('/');
6-
cy.get('button[aria-label="User Menu"]')
7-
.filter(':visible')
8-
.should('be.visible')
9-
.first()
10-
.click();
11-
// Menu item includes a keyboard shortcut, so match by substring.
12-
cy.contains('[role="menuitem"]', 'Log out').filter(':visible').click();
6+
cy.contains('button', 'Logout').filter(':visible').first().click();
137
cy.location('pathname').should('include', '/auth/login');
148
});
159
});

frontend/app/cypress/e2e/auth/05-tenant-invite-accept.cy.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,7 @@ describe('Tenant Invite: accept', () => {
7878
});
7979
});
8080

81-
cy.get('button[aria-label="User Menu"]')
82-
.filter(':visible')
83-
.should('be.visible')
84-
.first()
85-
.click();
86-
cy.contains('[role="menuitem"]', 'Log out').filter(':visible').click();
81+
cy.contains('button', 'Logout').filter(':visible').first().click();
8782

8883
cy.location('pathname').should('include', '/auth/login');
8984
cy.get('input#email').type(seededUsers.member.email);
@@ -96,6 +91,19 @@ describe('Tenant Invite: accept', () => {
9691
.should('be.enabled')
9792
.click();
9893
});
94+
cy.location('pathname', { timeout: 30000 }).should(
95+
'match',
96+
/\/tenants\/.+/,
97+
);
98+
99+
// Open the notification dropdown and click the tenant invite notification
100+
cy.get('[data-cy="notifications-button"]', { timeout: 10000 })
101+
.filter(':visible')
102+
.first()
103+
.click();
104+
cy.contains(`Tenant invite: ${tenant2Name}`).first().click();
105+
106+
// Should be on the invites page now
99107
cy.location('pathname', { timeout: 5000 }).should(
100108
'eq',
101109
'/onboarding/invites',
@@ -104,15 +112,14 @@ describe('Tenant Invite: accept', () => {
104112
// Find the specific invite and accept it
105113
cy.contains(`invited to join the ${tenant2Name} tenant`).should('exist');
106114

107-
// Step 4: Accept the invite - register intercept before clicking
115+
// Accept the invite
108116
cy.intercept('POST', '/api/v1/users/invites/accept').as('acceptInvite');
109117
cy.contains(`invited to join the ${tenant2Name} tenant`)
110118
.parent()
111119
.contains('button', 'Accept')
112120
.should('exist')
113121
.click();
114122

115-
// Wait for the accept API call to complete
116123
cy.wait('@acceptInvite').its('response.statusCode').should('eq', 200);
117124

118125
// Wait for the accepted invite card to be removed from the DOM before
@@ -141,7 +148,7 @@ describe('Tenant Invite: accept', () => {
141148
};
142149
declineAll();
143150

144-
// Step 5: Verify redirect to the tenant page (no infinite loop)
151+
// Verify redirect to the tenant page
145152
cy.location('pathname', { timeout: 5000 }).should(
146153
'match',
147154
/\/tenants\/[^/]+/,

frontend/app/cypress/e2e/auth/06-tenant-switching.cy.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,7 @@ describe('Tenants: switching', () => {
127127
.filter(':visible')
128128
.first()
129129
.should('contain.text', tenant2Name);
130-
cy.get('button[aria-label="User Menu"]')
131-
.filter(':visible')
132-
.should('be.visible')
133-
.first()
134-
.click();
135-
// Menu item includes a keyboard shortcut, so match by substring.
136-
cy.contains('[role="menuitem"]', 'Log out').filter(':visible').click();
130+
cy.contains('button', 'Logout').filter(':visible').first().click();
137131

138132
cy.login('member');
139133
cy.visit('/');

frontend/app/cypress/e2e/auth/07-create-tenant-invites-redirect.cy.ts

Lines changed: 0 additions & 132 deletions
This file was deleted.

frontend/app/cypress/e2e/auth/08-tenant-invite-decline.cy.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,7 @@ describe('Tenant Invite: decline', () => {
7575
});
7676

7777
// Step 3: Logout
78-
cy.get('button[aria-label="User Menu"]')
79-
.filter(':visible')
80-
.should('be.visible')
81-
.first()
82-
.click();
83-
cy.contains('[role="menuitem"]', 'Log out').filter(':visible').click();
78+
cy.contains('button', 'Logout').filter(':visible').first().click();
8479
cy.location('pathname').should('include', '/auth/login');
8580

8681
// Step 4: Login as member (who has pending invite)
@@ -95,7 +90,23 @@ describe('Tenant Invite: decline', () => {
9590
.click();
9691
});
9792

98-
// Should be redirected to invites page
93+
// Wait for navigation after sign in
94+
cy.location('pathname', { timeout: 30000 }).should(
95+
'match',
96+
/\/tenants\/.+/,
97+
);
98+
99+
// Open the notification dropdown and click the tenant invite notification
100+
cy.get('[data-cy="notifications-button"]', { timeout: 10000 })
101+
.filter(':visible')
102+
.first()
103+
.click();
104+
cy.contains(`Tenant invite: ${tenantName}`)
105+
.filter(':visible')
106+
.first()
107+
.click();
108+
109+
// Should be on the invites page now
99110
cy.location('pathname', { timeout: 5000 }).should(
100111
'eq',
101112
'/onboarding/invites',
@@ -106,19 +117,25 @@ describe('Tenant Invite: decline', () => {
106117
'be.visible',
107118
);
108119

109-
// Step 5: Decline the invite - register intercept before clicking
110-
cy.intercept('POST', '/api/v1/users/invites/reject').as('rejectInvite');
111-
cy.contains(`invited to join the ${tenantName} tenant`)
112-
.parent()
113-
.contains('button', 'Decline')
114-
.should('be.visible')
115-
.click();
116-
117-
// Wait for the reject API call to complete
118-
cy.wait('@rejectInvite').its('response.statusCode').should('eq', 200);
120+
// Step 5: Decline all invites
121+
const declineAll = (remaining = 20) => {
122+
cy.get('body').then(($body) => {
123+
if (
124+
remaining > 0 &&
125+
$body.find('button:contains("Decline")').length > 0
126+
) {
127+
cy.intercept('POST', '/api/v1/users/invites/reject').as(
128+
'rejectInvite',
129+
);
130+
cy.contains('button', 'Decline').click({ force: true });
131+
cy.wait('@rejectInvite');
132+
declineAll(remaining - 1);
133+
}
134+
});
135+
};
136+
declineAll();
119137

120138
// Step 6: Verify redirect away from invites page
121-
// User should be redirected to authenticated route (which may further redirect)
122139
cy.location('pathname', { timeout: 10000 }).should(
123140
'not.eq',
124141
'/onboarding/invites',

frontend/app/cypress/e2e/layout/v1-sidebar-resize-collapse.cy.ts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
describe('v1 sidebar: resize + collapse', () => {
22
const DEFAULT_EXPANDED_WIDTH = 200;
33

4-
const visitAuthed = (viewport: { width: number; height: number }) => {
4+
const visitAuthed = (
5+
viewport: { width: number; height: number },
6+
opts?: { requireVisibleSidebar?: boolean },
7+
) => {
58
cy.viewport(viewport.width, viewport.height);
69
cy.login('owner');
710

@@ -14,9 +17,16 @@ describe('v1 sidebar: resize + collapse', () => {
1417
},
1518
});
1619

17-
cy.get('button[aria-label="User Menu"]', { timeout: 30000 }).should(
18-
'be.visible',
19-
);
20+
if (opts?.requireVisibleSidebar !== false) {
21+
if (viewport.width < 768) {
22+
cy.get('button[aria-label="Toggle sidebar"]', { timeout: 30000 })
23+
.should('be.visible')
24+
.click();
25+
}
26+
27+
cy.get('[data-cy="v1-sidebar"]', { timeout: 30000 }).should('be.visible');
28+
}
29+
2030
cy.location('pathname', { timeout: 30000 }).should(
2131
'match',
2232
/\/tenants\/.+/,
@@ -30,17 +40,14 @@ describe('v1 sidebar: resize + collapse', () => {
3040
};
3141

3242
const waitForShell = () => {
33-
cy.get('button[aria-label="User Menu"]', { timeout: 30000 }).should(
34-
'be.visible',
35-
);
36-
cy.get('[data-cy="v1-sidebar"]').should('be.visible');
43+
cy.get('[data-cy="v1-sidebar"]', { timeout: 30000 }).should('be.visible');
3744
};
3845

3946
it('navbar: sidebar toggle button is only visible on mobile', () => {
4047
visitAuthed({ width: 1280, height: 800 });
4148
cy.get('button[aria-label="Toggle sidebar"]').should('not.be.visible');
4249

43-
visitAuthed({ width: 375, height: 667 });
50+
visitAuthed({ width: 375, height: 667 }, { requireVisibleSidebar: false });
4451
cy.get('button[aria-label="Toggle sidebar"]').should('be.visible');
4552
});
4653

@@ -189,21 +196,19 @@ describe('v1 sidebar: resize + collapse', () => {
189196
expectSidebarWidthStyle(56);
190197
});
191198

192-
it('collapsed: settings flyout renders and has a visible panel background', () => {
199+
it('collapsed: settings items are always visible without a flyout', () => {
193200
visitAuthed({ width: 1280, height: 800 });
194201

195202
// Collapse.
196203
cy.get('[data-cy="v1-sidebar-resize-handle"]').click({ force: true });
204+
expectSidebarWidthStyle(56);
197205

198-
// Open settings flyout.
199-
cy.get('button[aria-label="General"]').click({ force: true });
200-
cy.get('[role="menu"]').filter(':visible').first().as('settingsMenu');
201-
cy.get('@settingsMenu').contains('Overview').should('be.visible');
202-
203-
// Content should have the bg-secondary class (explicit panel surface).
204-
cy.get('@settingsMenu')
205-
.invoke('attr', 'class')
206-
// UI uses popover surfaces; accept either explicit secondary surface or popover surface.
207-
.should('match', /\bbg-(secondary|popover)\b/);
206+
// Settings items exist directly — no flyout needed.
207+
cy.get('button[aria-label="API Tokens"]')
208+
.scrollIntoView()
209+
.should('be.visible');
210+
cy.get('button[aria-label="Members"]')
211+
.scrollIntoView()
212+
.should('be.visible');
208213
});
209214
});

0 commit comments

Comments
 (0)