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 ( + + + + + + { + setSelectedTenant(newValue); + onClose(); + }} + options={tenantNames} + value={selectedTenant} + variantString="outlined" + /> + + + ); + } + + return ( + + + + + + {tenantNames.map((tenant) => { + const label = tenant.replace(/\/$/, ''); + const isSelected = tenant === selectedTenant; + + return ( + { + setSelectedTenant(tenant); + onClose(); + }} + sx={{ + borderRadius: 1, + fontSize: 13, + py: 0.75, + justifyContent: 'space-between', + }} + > + {label} + + {isSelected ? : null} + + ); + })} + + ); +}; 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' })} - > - - - - - {userName} - - - - - - - - - {email} - - {emailVerified ? ( - - - - ) : null} - - - - - - { - void handlers.logout(); - }} - > - - - - - - - - ); - } else { + if (!userDetails) { return null; } -}; -export default UserMenu; + return ( + + + + + {userDetails.userName ?? userDetails.email} + + + {userDetails.email} + + + + + + + { + e.stopPropagation(); + colorMode.toggleColorMode(); + }} + > + + {theme.palette.mode === 'dark' ? ( + + ) : ( + + )} + + + + + + + { + void supabaseClient.auth.signOut(); + }} + > + + + + + + + ); +}; 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;