Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Re-applies the UI change from #3365 by removing the top-nav “account” dropdown and introducing a notifications dropdown, while relocating logout + theme toggling into the v1 sidebar and adjusting onboarding redirect behavior to be less disruptive.
Changes:
- Replace the top-nav account dropdown with a notifications dropdown powered by new notification hooks (invites, onboarding hints, resource limit status).
- Move logout + theme toggling into the v1 sidebar (including support for sidebar items that perform actions instead of navigation).
- Refine onboarding redirects so users are only forced onto the invites screen in specific “must accept invite now” scenarios; update Cypress tests accordingly.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/app/src/providers/user-universe.tsx | Adds a shared logout mutation to the user-universe provider for use by navigation UI. |
| frontend/app/src/pages/main/v1/workflow-runs-v1/components/task-runs-table/table-actions.tsx | Adds a React key for a rendered bulk-actions container. |
| frontend/app/src/pages/main/v1/tenant-settings/resource-limits/components/resource-limit-columns.tsx | Uses shared resource-limit status logic for indicator coloring. |
| frontend/app/src/pages/main/v1/task-runs-v1/actions.tsx | Adjusts tooltip trigger to use asChild for correct composition. |
| frontend/app/src/pages/main/v1/side-nav-items.tsx | Reworks settings items and adds a theme toggle as an action item. |
| frontend/app/src/pages/main/v1/index.tsx | Wires theme toggle + logout into the v1 sidebar. |
| frontend/app/src/pages/authenticated.tsx | Updates onboarding redirect logic around pending invites. |
| frontend/app/src/lib/resource-limit-status.ts | New helper for classifying resource usage as ok/warn/exhausted. |
| frontend/app/src/hooks/use-pending-invites.ts | Improves typing and reduces polling frequency. |
| frontend/app/src/hooks/notifications/types.ts | Defines notification types/colors for the new notifications system. |
| frontend/app/src/hooks/notifications/resource-limits.ts | Generates notifications from per-tenant resource policy limits. |
| frontend/app/src/hooks/notifications/onboarding.ts | Generates a “get started” notification for a minimal onboarding state. |
| frontend/app/src/hooks/notifications/invites.ts | Generates notifications for tenant/org invites. |
| frontend/app/src/hooks/notifications/index.ts | Aggregates + sorts notifications across sources. |
| frontend/app/src/devtools.tsx | Raises devtools popover stacking context via z-index. |
| frontend/app/src/components/v1/nav/top-nav.tsx | Removes account dropdown and renders the new notifications dropdown. |
| frontend/app/src/components/v1/nav/sidebar-buttons.tsx | Adjusts primary sidebar action button props/styling (introduces muted). |
| frontend/app/src/components/v1/nav/side-nav.tsx | Adds logout button(s), supports action-only sidebar items, updates active-route logic. |
| frontend/app/src/components/v1/nav/notifications.tsx | New notifications dropdown UI component. |
| frontend/app/src/components/v1/nav/help-dropdown.tsx | Updates usage after sidebar button prop changes. |
| frontend/app/src/components/hooks/use-theme.tsx | Adds currentlyVisibleTheme to support UI that needs the displayed theme. |
| frontend/app/cypress/e2e/layout/viewports.cy.ts | Updates layout smoke assertions to reflect navbar changes. |
| frontend/app/cypress/e2e/layout/v1-sidebar-resize-collapse.cy.ts | Updates sidebar behavior expectations (settings always visible; no flyout). |
| frontend/app/cypress/e2e/auth/08-tenant-invite-decline.cy.ts | Updates flow to use notifications UI and new logout location. |
| frontend/app/cypress/e2e/auth/07-create-tenant-invites-redirect.cy.ts | Removes a redirect test that no longer matches intended behavior. |
| frontend/app/cypress/e2e/auth/06-tenant-switching.cy.ts | Updates logout interaction to the new sidebar button. |
| frontend/app/cypress/e2e/auth/05-tenant-invite-accept.cy.ts | Updates invite acceptance flow to use notifications UI and new logout location. |
| frontend/app/cypress/e2e/auth/02-logout.cy.ts | Updates logout interaction to the new sidebar button. |
| frontend/app/cypress/e2e/auth/01-login.cy.ts | Updates post-login assertion to a stable element given dropdown removal. |
|
|
||
| const logoutMutation = useMutation({ | ||
| mutationKey: ['user:update:logout'], | ||
| mutationFn: async () => { |
There was a problem hiding this comment.
logoutMutation always calls api.userUpdateLogout(), ignoring isControlPlaneEnabled. In control-plane mode this will hit the wrong endpoint (the existing useUserApi().userUpdateLogoutMutation() switches between controlPlaneApi.cloudUserUpdateLogout() and api.userUpdateLogout()). Please route the logout request through the control-plane API when enabled (or reuse useUserApi).
| mutationFn: async () => { | |
| mutationFn: async () => { | |
| if (isControlPlaneEnabled) { | |
| await controlPlaneApi.cloudUserUpdateLogout(); | |
| return; | |
| } |
| const logoutMutation = useMutation({ | ||
| mutationKey: ['user:update:logout'], | ||
| mutationFn: async () => { | ||
| await api.userUpdateLogout(); | ||
| }, | ||
| onSuccess: () => { | ||
| invalidate(); |
There was a problem hiding this comment.
On logout success this only resets the user-universe queries. Other parts of the app (e.g. AuthenticatedInner inactivity logout) call queryClient.clear() to avoid leaving authenticated data in the cache after logging out. Consider clearing the React Query cache (or at least removing user-scoped queries) here as well to prevent stale data being shown after logout / on re-login in the same session.
| const logoutMutation = useMutation({ | |
| mutationKey: ['user:update:logout'], | |
| mutationFn: async () => { | |
| await api.userUpdateLogout(); | |
| }, | |
| onSuccess: () => { | |
| invalidate(); | |
| const clearQueryCache = useCallback(() => { | |
| // Logout changes the authorization boundary, so retaining any cached | |
| // authenticated query data risks showing stale data after redirect or re-login. | |
| queryClient.clear(); | |
| }, [queryClient]); | |
| const logoutMutation = useMutation({ | |
| mutationKey: ['user:update:logout'], | |
| mutationFn: async () => { | |
| await api.userUpdateLogout(); | |
| }, | |
| onSuccess: () => { | |
| clearQueryCache(); |
| onClick={() => { | ||
| navigate({ | ||
| to: item.to, | ||
| params: commonParams, | ||
| }); | ||
| if ('onClick' in item) { | ||
| item.onClick(); | ||
| return; | ||
| } else { | ||
| navigate({ | ||
| to: item.to, | ||
| params: commonParams, | ||
| }); | ||
| } | ||
| onNavLinkClick(); |
There was a problem hiding this comment.
For SideNavItems that use onClick, the handler returns early and never calls onNavLinkClick(). On narrow viewports this means the sidebar overlay won’t close after clicking action items like the theme toggle, which is inconsistent with navigation items and can leave the UI obstructed. Consider calling onNavLinkClick() after item.onClick() as well (or explicitly document/encode which actions should keep the sidebar open).
| const navigate = useNavigate(); | ||
| const count = notifications.length; | ||
| const mostSevere = count > 0 ? getMostSevereColor(notifications) : null; | ||
| const ariaLabel = `${count} notification${count !== 1 ? 's' : ''} for ${currentUser?.email ?? ''}`; |
There was a problem hiding this comment.
ariaLabel can render with a trailing "for " when currentUser?.email is not available (e.g. during initial load), which is awkward for screen readers/tooltips. Consider omitting the "for …" suffix unless an email is present.
| const ariaLabel = `${count} notification${count !== 1 ? 's' : ''} for ${currentUser?.email ?? ''}`; | |
| const notificationLabel = `${count} notification${count !== 1 ? 's' : ''}`; | |
| const ariaLabel = currentUser?.email | |
| ? `${notificationLabel} for ${currentUser.email}` | |
| : notificationLabel; |
| const [theme, setTheme] = useState<SelectableTheme>( | ||
| () => (localStorage.getItem(storageKey) as SelectableTheme) || defaultTheme, | ||
| ); | ||
|
|
||
| const [currentlyVisibleTheme, setCurrentlyVisibleTheme] = | ||
| useState<DisplayableTheme>('light'); | ||
|
|
||
| useEffect(() => { | ||
| const root = window.document.documentElement; | ||
|
|
||
| root.classList.remove('light', 'dark'); | ||
|
|
||
| if (theme === 'system') { | ||
| const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') | ||
| .matches | ||
| ? 'dark' | ||
| : 'light'; | ||
|
|
||
| root.classList.add(systemTheme); | ||
| return; | ||
| } | ||
|
|
||
| root.classList.add(theme); | ||
| const themeToDisplay = getThemeToDisplay(theme); | ||
| setCurrentlyVisibleTheme(themeToDisplay); | ||
| root.classList.add(themeToDisplay); | ||
| }, [theme]); |
There was a problem hiding this comment.
currentlyVisibleTheme is initialized to 'light' and only corrected in an effect. If the saved theme is dark (or system preference is dark), components that read currentlyVisibleTheme (e.g. the sidebar label/icon) will briefly render the wrong theme on first paint. Initializing this state from the initial theme value (and matchMedia when needed) would avoid the flicker.
…dev#3665) * Revert "Revert "Remove "account" dropdown', add notifications dropdown (hatchet-dev#3365…" This reverts commit b26f536. * fix: couple copilot comments
Re-applies #3365