diff --git a/src/app/guards/TenantGuard/index.tsx b/src/app/guards/TenantGuard/index.tsx
index 56df5c858c..ef7e1d8a40 100644
--- a/src/app/guards/TenantGuard/index.tsx
+++ b/src/app/guards/TenantGuard/index.tsx
@@ -9,6 +9,7 @@ import { useUserInfoSummaryStore } from 'src/context/UserInfoSummary/useUserInfo
import useGlobalSearchParams, {
GlobalSearchParams,
} from 'src/hooks/searchParams/useGlobalSearchParams';
+import { useInitializeSelectedTenant } from 'src/hooks/useInitializeSelectedTenant';
// This is a way to very simply "hide" the flow where anyone
// can create a tenant but allow us to test it out in prod.
@@ -27,6 +28,8 @@ function TenantGuard({ children }: BaseComponentProps) {
const mutate = useUserInfoSummaryStore((state) => state.mutate);
const usedSSO = useUserStore((state) => state.userDetails?.usedSSO);
+ useInitializeSelectedTenant();
+
const showOnboarding = !hasAnyAccess || showBeta;
if (showOnboarding) {
if (usedSSO && !showBeta) {
diff --git a/src/components/admin/Billing/TenantOptions.tsx b/src/components/admin/Billing/TenantOptions.tsx
deleted file mode 100644
index 17466aaa7a..0000000000
--- a/src/components/admin/Billing/TenantOptions.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useCallback } from 'react';
-
-import TenantSelector from 'src/components/shared/TenantSelector';
-import { useBillingStore } from 'src/stores/Billing';
-
-function TenantOptions() {
- const resetBillingState = useBillingStore((state) => state.resetState);
-
- const updateStoreState = useCallback(() => {
- resetBillingState();
- }, [resetBillingState]);
-
- return ;
-}
-
-export default TenantOptions;
diff --git a/src/components/admin/Billing/index.tsx b/src/components/admin/Billing/index.tsx
index 4e2892fc40..e769541e49 100644
--- a/src/components/admin/Billing/index.tsx
+++ b/src/components/admin/Billing/index.tsx
@@ -19,7 +19,7 @@ import BillingLoadError from 'src/components/admin/Billing/LoadError';
import PaymentMethods from 'src/components/admin/Billing/PaymentMethods';
import PricingTierDetails from 'src/components/admin/Billing/PricingTierDetails';
import { INVOICE_ROW_HEIGHT } from 'src/components/admin/Billing/shared';
-import TenantOptions from 'src/components/admin/Billing/TenantOptions';
+import { useTenantChangeReset } from 'src/components/admin/Billing/useTenantChangeReset';
import AdminTabs from 'src/components/admin/Tabs';
import GraphLoadingState from 'src/components/graphs/states/Loading';
import GraphStateWrapper from 'src/components/graphs/states/Wrapper';
@@ -50,6 +50,8 @@ function AdminBilling({ showAddPayment }: AdminBillingProps) {
headerLink: 'https://www.estuary.dev/pricing/',
});
+ useTenantChangeReset();
+
const intl = useIntl();
const selectedTenant = useTenantStore((state) => state.selectedTenant);
@@ -138,13 +140,6 @@ function AdminBilling({ showAddPayment }: AdminBillingProps) {
-
-
-
-
diff --git a/src/components/admin/Billing/useTenantChangeReset.ts b/src/components/admin/Billing/useTenantChangeReset.ts
new file mode 100644
index 0000000000..a68e69bf53
--- /dev/null
+++ b/src/components/admin/Billing/useTenantChangeReset.ts
@@ -0,0 +1,22 @@
+import { useCallback, useEffect, useRef } from 'react';
+
+import { useBillingStore } from 'src/stores/Billing';
+import { useTenantStore } from 'src/stores/Tenant';
+
+export function useTenantChangeReset() {
+ const selectedTenant = useTenantStore((state) => state.selectedTenant);
+
+ const resetBillingState = useBillingStore((state) => state.resetState);
+
+ const resetStores = useCallback(() => {
+ resetBillingState();
+ }, [resetBillingState]);
+
+ const previousTenant = useRef(selectedTenant);
+ useEffect(() => {
+ if (previousTenant.current !== selectedTenant) {
+ previousTenant.current = selectedTenant;
+ resetStores();
+ }
+ }, [selectedTenant, resetStores]);
+}
diff --git a/src/components/admin/Settings/index.tsx b/src/components/admin/Settings/index.tsx
index e731f94c79..10054fd37d 100644
--- a/src/components/admin/Settings/index.tsx
+++ b/src/components/admin/Settings/index.tsx
@@ -1,11 +1,10 @@
-import { Divider, Grid, Stack } from '@mui/material';
+import { Divider, Stack } from '@mui/material';
import { authenticatedRoutes } from 'src/app/routes';
import DataPlanes from 'src/components/admin/Settings/DataPlanes';
import PrefixAlerts from 'src/components/admin/Settings/PrefixAlerts';
import { StorageMappings } from 'src/components/admin/Settings/StorageMappings';
import AdminTabs from 'src/components/admin/Tabs';
-import TenantSelector from 'src/components/shared/TenantSelector';
import usePageTitle from 'src/hooks/usePageTitle';
function Settings() {
@@ -17,19 +16,6 @@ function Settings() {
<>
-
-
-
-
-
-
diff --git a/src/components/fullPage/Error.tsx b/src/components/fullPage/Error.tsx
index 7fa4957e34..aa34066e67 100644
--- a/src/components/fullPage/Error.tsx
+++ b/src/components/fullPage/Error.tsx
@@ -3,7 +3,7 @@ import type { ErrorDetails } from 'src/components/shared/Error/types';
import { useMemo } from 'react';
-import { Divider, Stack, Typography } from '@mui/material';
+import { Button, Divider, Stack, Typography } from '@mui/material';
import { FormattedMessage } from 'react-intl';
import { useMount } from 'react-use';
@@ -11,6 +11,7 @@ import { useMount } from 'react-use';
import FullPageWrapper from 'src/app/FullPageWrapper';
import MessageWithLink from 'src/components/content/MessageWithLink';
import Error from 'src/components/shared/Error';
+import { supabaseClient } from 'src/context/GlobalProviders';
import { logRocketEvent } from 'src/services/shared';
import { CustomEvents } from 'src/services/types';
@@ -56,6 +57,27 @@ function FullPageError({
+
+
+
+
+
+
+
diff --git a/src/components/home/dashboard/index.tsx b/src/components/home/dashboard/index.tsx
index ed59efb0a2..6112494686 100644
--- a/src/components/home/dashboard/index.tsx
+++ b/src/components/home/dashboard/index.tsx
@@ -1,20 +1,10 @@
-import { Box, Grid } from '@mui/material';
+import { Grid } from '@mui/material';
import EntityStatOverview from 'src/components/home/dashboard/EntityStatOverview';
-import TenantSelector from 'src/components/shared/TenantSelector';
export default function Dashboard() {
return (
-
-
-
-
-
-
);
diff --git a/src/components/menus/HelpMenu.tsx b/src/components/menus/HelpMenu.tsx
index c1855ce0a8..c9d458afed 100644
--- a/src/components/menus/HelpMenu.tsx
+++ b/src/components/menus/HelpMenu.tsx
@@ -1,18 +1,29 @@
-import { HelpCircle } from 'iconoir-react';
+import { Menu } from '@mui/material';
+
import { FormattedMessage, useIntl } from 'react-intl';
-import IconMenu from 'src/components/menus/IconMenu';
+import {
+ sideNavMenuAnchorOrigin,
+ sideNavMenuTransformOrigin,
+} from 'src/components/menus/shared';
import ExternalLinkMenuItem from 'src/components/shared/ExternalLinkMenuItem';
-function HelpMenu() {
+interface HelpMenuProps {
+ anchorEl: HTMLElement | null;
+ onClose: () => void;
+}
+
+export function HelpMenu({ anchorEl, onClose }: HelpMenuProps) {
const intl = useIntl();
return (
- }
- identifier="help-menu"
- tooltip={intl.formatMessage({ id: 'helpMenu.tooltip' })}
+
);
}
-
-export default HelpMenu;
diff --git a/src/components/menus/OrgMenu.tsx b/src/components/menus/OrgMenu.tsx
new file mode 100644
index 0000000000..3db2b85488
--- /dev/null
+++ b/src/components/menus/OrgMenu.tsx
@@ -0,0 +1,132 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ MenuItem,
+ Popover,
+ Typography,
+} from '@mui/material';
+
+import { Check } from 'iconoir-react';
+import { FormattedMessage, useIntl } from 'react-intl';
+
+import PrefixSelector from 'src/components/inputs/PrefixedName/PrefixSelector';
+import {
+ sideNavMenuAnchorOrigin,
+ sideNavMenuTransformOrigin,
+} from 'src/components/menus/shared';
+import { useUserInfoSummaryStore } from 'src/context/UserInfoSummary/useUserInfoSummaryStore';
+import { useEntitiesStore_tenantsWithAdmin } from 'src/stores/Entities/hooks';
+import { useTenantStore } from 'src/stores/Tenant';
+
+interface OrgMenuProps {
+ anchorEl: HTMLElement | null;
+ onClose: () => void;
+}
+
+export const OrgMenu = ({ anchorEl, onClose }: OrgMenuProps) => {
+ const intl = useIntl();
+ const selectedTenant = useTenantStore((state) => state.selectedTenant);
+ const setSelectedTenant = useTenantStore(
+ (state) => state.setSelectedTenant
+ );
+ // Same tenant list the old TenantSelector showed everyone (incl. estuary_support).
+ const tenantNames = useEntitiesStore_tenantsWithAdmin();
+ const hasSupportAccess = useUserInfoSummaryStore(
+ (state) => state.hasSupportAccess
+ );
+
+ // Support users get the searchable dialog (every prefix); everyone else
+ // gets the popover list anchored to the trigger.
+ if (hasSupportAccess) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {tenantNames.map((tenant) => {
+ const label = tenant.replace(/\/$/, '');
+ const isSelected = tenant === selectedTenant;
+
+ return (
+
+ );
+ })}
+
+ );
+};
diff --git a/src/components/menus/UserMenu.tsx b/src/components/menus/UserMenu.tsx
index 7bf6a3ccd4..d2b52d23a0 100644
--- a/src/components/menus/UserMenu.tsx
+++ b/src/components/menus/UserMenu.tsx
@@ -1,97 +1,106 @@
-/* eslint-disable @typescript-eslint/no-unnecessary-condition */
-import type { SxProps } from '@mui/material';
-
-import { Stack, Typography } from '@mui/material';
-import Divider from '@mui/material/Divider';
-import ListItemIcon from '@mui/material/ListItemIcon';
-import MenuItem from '@mui/material/MenuItem';
+import {
+ Divider,
+ ListItemIcon,
+ Menu,
+ MenuItem,
+ Stack,
+ Typography,
+ useTheme,
+} from '@mui/material';
import { useShallow } from 'zustand/react/shallow';
-import { LogOut, Mail, ProfileCircle } from 'iconoir-react';
-import { FormattedMessage, useIntl } from 'react-intl';
+import { HalfMoon, LogOut, SunLight } from 'iconoir-react';
+import { FormattedMessage } from 'react-intl';
-import IconMenu from 'src/components/menus/IconMenu';
-import UserAvatar from 'src/components/shared/UserAvatar';
+import {
+ sideNavMenuAnchorOrigin,
+ sideNavMenuTransformOrigin,
+} from 'src/components/menus/shared';
import { supabaseClient } from 'src/context/GlobalProviders';
+import { useColorMode } from 'src/context/Theme';
import { useUserStore } from 'src/context/User/useUserContextStore';
-interface Props {
- iconColor: string;
+interface UserMenuProps {
+ anchorEl: HTMLElement | null;
+ onClose: () => void;
}
-const nonInteractiveMenuStyling: SxProps = {
- '&:hover': {
- cursor: 'revert',
- },
-};
-
-const UserMenu = ({ iconColor }: Props) => {
- const intl = useIntl();
+export const UserMenu = ({ anchorEl, onClose }: UserMenuProps) => {
+ const theme = useTheme();
+ const colorMode = useColorMode();
const userDetails = useUserStore(useShallow((state) => state.userDetails));
- const handlers = {
- logout: async () => {
- await supabaseClient.auth.signOut();
- },
- };
-
- if (userDetails) {
- const { avatar, email, emailVerified, userName } = userDetails;
- return (
-
- }
- identifier="account-menu"
- tooltip={intl.formatMessage({ id: 'accountMenu.tooltip' })}
- >
-
-
-
-
-
-
-
-
- );
- } else {
+ if (!userDetails) {
return null;
}
-};
-export default UserMenu;
+ return (
+
+ );
+};
diff --git a/src/components/menus/shared.ts b/src/components/menus/shared.ts
new file mode 100644
index 0000000000..8dfa7709b8
--- /dev/null
+++ b/src/components/menus/shared.ts
@@ -0,0 +1,13 @@
+import type { PopoverOrigin } from '@mui/material';
+
+// Side-nav menus anchor to the top-left of their trigger and grow upward, so
+// the menu's bottom-left corner lines up with the trigger's top-left corner.
+export const sideNavMenuAnchorOrigin: PopoverOrigin = {
+ horizontal: 'left',
+ vertical: 'top',
+};
+
+export const sideNavMenuTransformOrigin: PopoverOrigin = {
+ horizontal: 'left',
+ vertical: 'bottom',
+};
diff --git a/src/components/navigation/ListItemLink.tsx b/src/components/navigation/ListItemLink.tsx
index 7dd1eef94c..1917588577 100644
--- a/src/components/navigation/ListItemLink.tsx
+++ b/src/components/navigation/ListItemLink.tsx
@@ -1,108 +1,62 @@
-import type { ReactNode } from 'react';
+import type { MouseEvent, ReactNode } from 'react';
import {
- Badge,
ListItemButton,
ListItemIcon,
ListItemText,
Tooltip,
} from '@mui/material';
-import { useIntl } from 'react-intl';
-
import RouterLink from 'src/components/navigation/RouterLink';
-import { NavWidths } from 'src/context/Theme';
interface Props {
icon: ReactNode;
title: string;
- link: string | any;
isOpen?: boolean;
- menuWidth?: number;
- badgeContent?: number;
- tooltipDelay?: number;
+ // Hover tooltip shown when collapsed; defaults to `title`. Set it when the
+ // tooltip should differ from the label (e.g. a toggle).
+ tooltip?: string;
+ // Pass `to` for a route link, or `onClick` for a button (e.g. a menu trigger).
+ to?: string;
+ onClick?: (event: MouseEvent) => void;
}
-const ListItemLink = ({
+export const ListItemLink = ({
icon,
title,
- link,
isOpen,
- menuWidth,
- badgeContent,
- tooltipDelay,
+ tooltip,
+ to,
+ onClick,
}: Props) => {
- const intl = useIntl();
-
- const translatedTitle = intl.formatMessage({
- id: title,
- });
-
return (
- {menuWidth === NavWidths.FULL ? (
-
+ theme.palette.text.primary,
}}
>
- {icon ? (
-
- theme.palette.text.primary,
- }}
- >
- {icon}
-
- ) : null}
+ {icon}
+
-
-
-
-
- ) : (
-
- {icon ? (
-
- theme.palette.text.primary,
- }}
- >
-
- {icon}
-
-
- ) : null}
-
-
-
- )}
+
+
);
};
-
-export default ListItemLink;
diff --git a/src/components/navigation/ModeSwitch.tsx b/src/components/navigation/ModeSwitch.tsx
deleted file mode 100644
index 8328dd40f0..0000000000
--- a/src/components/navigation/ModeSwitch.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import {
- ListItemButton,
- ListItemIcon,
- ListItemText,
- Tooltip,
- useTheme,
-} from '@mui/material';
-
-import { HalfMoon, SunLight } from 'iconoir-react';
-import { FormattedMessage, useIntl } from 'react-intl';
-
-import { useColorMode } from 'src/context/Theme';
-
-function ModeSwitch() {
- const intl = useIntl();
- const theme = useTheme();
- const colorMode = useColorMode();
-
- return (
-
-
-
- {theme.palette.mode === 'dark' ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
- );
-}
-
-export default ModeSwitch;
diff --git a/src/components/navigation/Navigation.tsx b/src/components/navigation/Navigation.tsx
index 82ef0714bd..f90b4e49f3 100644
--- a/src/components/navigation/Navigation.tsx
+++ b/src/components/navigation/Navigation.tsx
@@ -1,31 +1,31 @@
-//TODO (UI / UX) - These icons are not final
-import {
- Box,
- List,
- ListItemButton,
- ListItemIcon,
- ListItemText,
- Stack,
- Toolbar,
- Tooltip,
- useTheme,
-} from '@mui/material';
+import { useState } from 'react';
+
+import { Box, List, Stack, Toolbar, useTheme } from '@mui/material';
import MuiDrawer, { drawerClasses } from '@mui/material/Drawer';
+import { useShallow } from 'zustand/react/shallow';
+
import {
+ Building,
CloudDownload,
CloudUpload,
DatabaseScript,
FastArrowLeft,
+ HelpCircle,
HomeSimple,
Settings,
} from 'iconoir-react';
import { useIntl } from 'react-intl';
import { authenticatedRoutes } from 'src/app/routes';
-import ListItemLink from 'src/components/navigation/ListItemLink';
-import ModeSwitch from 'src/components/navigation/ModeSwitch';
+import { HelpMenu } from 'src/components/menus/HelpMenu';
+import { OrgMenu } from 'src/components/menus/OrgMenu';
+import { UserMenu } from 'src/components/menus/UserMenu';
+import { ListItemLink } from 'src/components/navigation/ListItemLink';
+import UserAvatar from 'src/components/shared/UserAvatar';
import { paperBackground } from 'src/context/Theme';
+import { useUserStore } from 'src/context/User/useUserContextStore';
+import { useTenantStore } from 'src/stores/Tenant';
interface NavigationProps {
open: boolean;
@@ -36,6 +36,12 @@ interface NavigationProps {
const Navigation = ({ open, width, onNavigationToggle }: NavigationProps) => {
const intl = useIntl();
const theme = useTheme();
+ const userDetails = useUserStore(useShallow((state) => state.userDetails));
+ const selectedTenant = useTenantStore((state) => state.selectedTenant);
+
+ const [helpAnchor, setHelpAnchor] = useState(null);
+ const [userAnchor, setUserAnchor] = useState(null);
+ const [orgAnchor, setOrgAnchor] = useState(null);
const openNavigation = () => {
onNavigationToggle(true);
@@ -78,33 +84,48 @@ const Navigation = ({ open, width, onNavigationToggle }: NavigationProps) => {
}
- title={authenticatedRoutes.home.title}
- link={authenticatedRoutes.home.path}
+ title={intl.formatMessage({
+ id: authenticatedRoutes.home.title,
+ })}
+ to={authenticatedRoutes.home.path}
/>
}
- title={authenticatedRoutes.captures.title}
- link={authenticatedRoutes.captures.path}
+ title={intl.formatMessage({
+ id: authenticatedRoutes.captures.title,
+ })}
+ to={authenticatedRoutes.captures.path}
/>
}
- title={authenticatedRoutes.collections.title}
- link={authenticatedRoutes.collections.path}
+ title={intl.formatMessage({
+ id: authenticatedRoutes.collections.title,
+ })}
+ to={authenticatedRoutes.collections.path}
/>
}
- title={authenticatedRoutes.materializations.title}
- link={authenticatedRoutes.materializations.path}
+ title={intl.formatMessage({
+ id: authenticatedRoutes.materializations.title,
+ })}
+ to={authenticatedRoutes.materializations.path}
/>
}
- title={authenticatedRoutes.admin.title}
- link={authenticatedRoutes.admin.path}
+ title={intl.formatMessage({
+ id: authenticatedRoutes.admin.title,
+ })}
+ to={authenticatedRoutes.admin.path}
/>
@@ -112,51 +133,85 @@ const Navigation = ({ open, width, onNavigationToggle }: NavigationProps) => {
-
-
-
+ }
+ title={intl.formatMessage({
+ id: 'navigation.collapse',
+ })}
+ tooltip={intl.formatMessage({
+ id: 'navigation.tooltip.expand',
+ })}
+ onClick={openNavigation}
+ isOpen={open}
+ />
+ }
title={intl.formatMessage({
- id: 'navigation.toggle.ariaLabel',
+ id: 'helpMenu.tooltip',
})}
- placement="right-end"
- enterDelay={open ? 1000 : undefined}
- >
-
-
-
-
+ onClick={(e) => setHelpAnchor(e.currentTarget)}
+ isOpen={open}
+ />
+ setHelpAnchor(null)}
+ />
-
+
+ }
+ title={
+ userDetails.userName ??
+ userDetails.email
+ }
+ onClick={(e) =>
+ setUserAnchor(e.currentTarget)
+ }
+ isOpen={open}
+ />
+ setUserAnchor(null)}
+ />
+
+ }
+ title={
+ selectedTenant
+ ? selectedTenant.replace(/\/$/, '')
+ : ''
+ }
+ onClick={(e) =>
+ setOrgAnchor(e.currentTarget)
+ }
+ isOpen={open}
+ />
+ setOrgAnchor(null)}
/>
-
-
+ >
+ ) : null}
diff --git a/src/components/navigation/TopBar.tsx b/src/components/navigation/TopBar.tsx
index 5d35e1a122..6b2de160a8 100644
--- a/src/components/navigation/TopBar.tsx
+++ b/src/components/navigation/TopBar.tsx
@@ -4,8 +4,6 @@ import { useTheme } from '@mui/material/styles';
import { HeaderPill } from 'src/components/AgentSkills/HeaderPill';
import CompanyLogo from 'src/components/graphics/CompanyLogo';
-import HelpMenu from 'src/components/menus/HelpMenu';
-import UserMenu from 'src/components/menus/UserMenu';
import PageTitle from 'src/components/navigation/PageTitle';
import SidePanelDocsOpenButton from 'src/components/sidePanelDocs/OpenButton';
import { UpdateAlert } from 'src/components/UpdateAlert';
@@ -45,10 +43,6 @@ const Topbar = () => {
-
-
-
-
diff --git a/src/hooks/useInitializeSelectedTenant.ts b/src/hooks/useInitializeSelectedTenant.ts
new file mode 100644
index 0000000000..057ab84b2c
--- /dev/null
+++ b/src/hooks/useInitializeSelectedTenant.ts
@@ -0,0 +1,53 @@
+import { useEffect, useRef } from 'react';
+
+import useGlobalSearchParams, {
+ GlobalSearchParams,
+} from 'src/hooks/searchParams/useGlobalSearchParams';
+import { useEntitiesStore_tenantsWithAdmin } from 'src/stores/Entities/hooks';
+import { useTenantStore } from 'src/stores/Tenant';
+import { hasLength } from 'src/utils/misc-utils';
+
+// Ensures the app always has a tenant selected. Runs from TenantGuard, which
+// mounts once for any user with tenant access, so the selection is set
+// app-wide regardless of which page or menu is open. When the tenant list
+// loads it honors a `?prefix=` deep link (e.g. the billing "add payment
+// method" CTA) the first time it appears, then keeps a still-valid selection,
+// otherwise falls back to the first available tenant.
+export function useInitializeSelectedTenant() {
+ const selectedTenant = useTenantStore((state) => state.selectedTenant);
+ const setSelectedTenant = useTenantStore(
+ (state) => state.setSelectedTenant
+ );
+ const tenantNames = useEntitiesStore_tenantsWithAdmin();
+
+ const prefixParam = useGlobalSearchParams(GlobalSearchParams.PREFIX);
+ // The prefix value we've already applied. `?prefix=` isn't stripped from the
+ // URL, so without this a lingering param would re-assert the selection on
+ // every change and clobber an intentional org switch. Keyed by value, so a
+ // new deep-link prefix still applies once.
+ const appliedPrefixParam = useRef(null);
+
+ useEffect(() => {
+ if (!hasLength(tenantNames)) {
+ return;
+ }
+
+ if (
+ hasLength(prefixParam) &&
+ tenantNames.includes(prefixParam) &&
+ prefixParam !== appliedPrefixParam.current
+ ) {
+ appliedPrefixParam.current = prefixParam;
+
+ if (prefixParam !== selectedTenant) {
+ setSelectedTenant(prefixParam);
+ }
+
+ return;
+ }
+
+ if (!(selectedTenant && tenantNames.includes(selectedTenant))) {
+ setSelectedTenant(tenantNames[0]);
+ }
+ }, [prefixParam, selectedTenant, setSelectedTenant, tenantNames]);
+}
diff --git a/src/lang/en-US/Navigation.ts b/src/lang/en-US/Navigation.ts
index 304f759a81..f7481ad52f 100644
--- a/src/lang/en-US/Navigation.ts
+++ b/src/lang/en-US/Navigation.ts
@@ -2,15 +2,12 @@ import { CommonMessages } from 'src/lang/en-US/CommonMessages';
import { CTAs } from 'src/lang/en-US/CTAs';
export const Navigation: Record = {
- 'navigation.toggle.ariaLabel': `Toggle Navigation`,
- 'navigation.expand': `Expand Navigation`,
- 'navigation.collapse': `Collapse Navigation`,
+ 'navigation.ariaLabel': `Main Navigation`,
+ 'navigation.ariaLabel.secondary': `Account and Help`,
+ 'navigation.tooltip.expand': `Expand Navigation`,
+ 'navigation.collapse': `Collapse`,
- // Header
- 'mainMenu.tooltip': `Open Main Menu`,
-
- 'helpMenu.ariaLabel': `Open Help Menu`,
- 'helpMenu.tooltip': `Helpful Links`,
+ 'helpMenu.tooltip': `Help`,
'helpMenu.docs': `Docs`,
'helpMenu.docs.link': `https://docs.estuary.dev/`,
'helpMenu.slack': `Estuary Slack`,
@@ -19,15 +16,13 @@ export const Navigation: Record = {
'helpMenu.support.link': `${CommonMessages['support.email']}`,
'helpMenu.contact': `${CTAs['cta.contactUs']}`,
'helpMenu.contact.link': `https://estuary.dev/contact-us`,
- 'helpMenu.about': `About ${CommonMessages.productName}`,
'helpMenu.status': `Status`,
'helpMenu.status.link': `https://status.estuary.dev/`,
- 'accountMenu.ariaLabel': `Open Account Menu`,
- 'accountMenu.tooltip': `My Account`,
- 'accountMenu.emailVerified': `verified`,
+ 'modeSwitch.label.light': `Light mode`,
+ 'modeSwitch.label.dark': `Dark mode`,
- 'modeSwitch.label': `Toggle Color Mode`,
+ 'tenant.organization': `Organization`,
'updateAlert.cta': `Update`,
'updateAlert.title': `Dashboard Updated`,
diff --git a/src/services/shared.ts b/src/services/shared.ts
index 91b60a5d7c..3cfed1afac 100644
--- a/src/services/shared.ts
+++ b/src/services/shared.ts
@@ -20,6 +20,12 @@ export const getUserDetails = (
if (!isEmpty(user.user_metadata)) {
email = user.user_metadata.email;
+ // Identity-provider-asserted flag, not Supabase's authoritative
+ // `email_confirmed_at`. Production only supports SSO and social login,
+ // so the provider always vouches for the email and this is true for
+ // practically every user (confirmed against prod) — not a meaningful UI
+ // or analytics signal. Use `email_confirmed_at` if you ever need real
+ // confirmation state.
emailVerified = user.user_metadata.email_verified;
avatar = user.user_metadata.avatar_url;
userName = user.user_metadata.full_name ?? email;