import React, { useRef, useState } from 'react';
import defaultsDeep from 'lodash/defaultsDeep';
import PropTypes from 'prop-types';
import Badge from '@leafygreen-ui/badge';
import Button from '@leafygreen-ui/button';
import { css, cx } from '@leafygreen-ui/emotion';
// @ts-expect-error
import ArrowRightIcon from '@leafygreen-ui/icon/dist/ArrowRight';
// @ts-expect-error
import BellIcon from '@leafygreen-ui/icon/dist/Bell';
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
import { Theme } from '@leafygreen-ui/lib';
import { MongoDBLogoMark } from '@leafygreen-ui/logo';
import {
FocusableMenuItem,
Menu,
MenuItem,
MenuSeparator,
SubMenu,
} from '@leafygreen-ui/menu';
import { Link } from '@leafygreen-ui/typography';
import { hostDefaults } from '../../data';
import {
CloudIcon,
DevHubIcon,
DocsIcon,
ForumsIcon,
MegaphoneIcon,
SupportIcon,
UniversityIcon,
} from '../../icons';
import { useOnElementClick } from '../../on-element-click-provider';
import {
AccountInterface,
ActiveNavElement,
Environment,
HostsInterface,
NavElement,
Platform,
URLS,
UserMenuURLS,
} from '../../types';
import {
descriptionStyle,
headerBaseStyle,
headerThemeStyle,
linkColorStyle,
logoMarkBaseStyle,
logoMarkThemeStyle,
menuBaseStyle,
menuGlyphThemeStyle,
menuThemeStyle,
mfaBannerBaseStyle,
mfaBannerThemeStyle,
nameStyle,
productLinkBaseStyle,
productLinkIconStyle,
productLinkThemeStyle,
subMenuActiveContainerStyle,
subMenuClassName,
subMenuContainerBaseStyle,
subMenuContainerThemeStyle,
subMenuItemStyle,
truncate,
} from './UserMenu.style';
import { UserMenuTrigger } from './UserMenuTrigger';
type PolyProps = { as: 'a'; href: string } | { as: 'button' };
type ProductString =
| 'cloud'
| 'university'
| 'support'
| 'developer'
| 'docs'
| 'forums';
type DescriptionProps = {
isActive: boolean;
isGovernment?: boolean;
theme: Theme;
} & (
| {
/** Required if product is not provided */
link?: string;
/** Required if link is not provided */
product: ProductString;
}
| {
/** Required if product is not provided */
link: string;
/** Required if link is not provided */
product?: ProductString;
}
);
function UserMenuItemDescription({
product,
link,
isGovernment = false,
theme,
}: DescriptionProps) {
if (!link) {
if (
isGovernment &&
['cloud', 'support'].includes(product as ProductString)
) {
link = `${product}.mongodbgov.com`;
} else {
link = `${product}.mongodb.com`;
}
} else {
link = link.replace(/https?:\/\//, ''); // replace any http in the provided link
}
return (
);
}
export interface UserMenuProps {
/**
* Object that contains information about the active user.
* {firstName: 'string', lastName: 'string', email: 'string'}
*/
account?: AccountInterface;
/**
* Determines what nav item is currently active.
*/
activeNav?: NavElement;
/**
* Callback invoked after the user clicks log out.
*/
onLogout?: React.MouseEventHandler;
/**
* Callback invoked after the user clicks a product.
*/
onProductChange?: React.MouseEventHandler;
/**
* Object that supplies URL overrides to UserMenu component.
* Shape: { userMenu:{ cloud: { userPreferences, organizations, invitations, mfa }, university: { universityPreferences }, support: { userPreferences }, account: { homepage } }}
*/
urls?: URLS | { userMenu: UserMenuURLS };
/**
* Object that supplies host overrides to UserMenu component.
* Shape: { cloud, realm, charts, account, university, support }
* Defaults to the production homepages of each product
*/
hosts?: HostsInterface;
/**
* MongoDB platform that is currently active.
*/
activePlatform?: Platform;
/**
* Describes the environment that the consumer is in: `commercial` or `government`
*/
environment?: Environment;
/**
* Whether the content has been loaded
*/
loading?: boolean;
}
/**
* # UserMenu
*
* UserMenu component
*
* ```
```
* @param props.account Object that contains information about the active user.
* {firstName: 'string', lastName: 'string', email: 'string'}
* @param props.activeNav Determines what nav item is currently active.
* @param props.onLogout Callback fired when a user logs out.
* @param props.onProductChange Callback invoked after the user clicks a product.
* @param props.hosts Object where keys are MDB products and values are the desired hostURL override for that product, to enable `` to work across all environments.
* @param props.urls Object to enable custom overrides for every `href` used in ``.
* @param props.activePlatform MongoDB platform that is currently active.
* @param props.environment Describes the environment that the consumer is in: `commercial` or `government`
*/
function UserMenu({
account,
activeNav,
activePlatform,
onLogout: onLogoutProp,
urls: urlsProp,
hosts: hostsProp,
onProductChange = () => {},
environment = Environment.Commercial,
loading,
}: UserMenuProps) {
const { theme, darkMode } = useDarkMode();
const hosts: Required = defaultsDeep(
hostsProp,
hostDefaults(environment === Environment.Government),
);
const onElementClick = useOnElementClick();
const triggerRef = useRef(null);
const onLogout = (e: React.MouseEvent) => {
if (onLogoutProp) {
return onLogoutProp(e);
} else {
return onElementClick(NavElement.Logout)(e);
}
};
const defaultURLs: Required = {
cloud: {
userPreferences: `${hosts.cloud}/v2#/preferences/personalization`,
organizations: `${hosts.cloud}/v2#/preferences/organizations`,
invitations: `${hosts.cloud}/v2#/preferences/invitations`,
mfa: `${hosts.cloud}/v2#/preferences/2fa`,
},
docs: 'https://docs.mongodb.com',
university: {
transcript: `${hosts.university}/learn/transcript`,
faqs: `${hosts.university}/support`,
},
devHub: 'https://developer.mongodb.com',
forums: {
forumsPreferences:
'https://www.mongodb.com/community/forums/my/preferences/account',
},
support: {
userPreferences: `${hosts.support}/user`,
},
account: {
homepage: `${hosts.account}/account/profile/overview`,
},
logout: `${hosts.account}/account/login?signedOut=true`,
};
const userMenuUrls: Required = defaultsDeep(
urlsProp?.userMenu,
defaultURLs,
);
const cloudUrls = userMenuUrls.cloud;
const [open, setOpen] = useState(false);
const name = account
? `${account.firstName ?? ''} ${account.lastName ?? ''}`
: '';
const isAccount = activePlatform === Platform.Account;
const isCloud = activePlatform === Platform.Cloud;
const isDocs = activePlatform === Platform.Docs;
const isSupport = activePlatform === Platform.Support;
const isUniversity = activePlatform === Platform.University;
const isGovernment = environment === Environment.Government;
const isDevHub = activePlatform === Platform.DevHub;
const isForums = activePlatform === Platform.Forum;
const onClick = (e: React.MouseEvent) => {
onProductChange(e);
setOpen(false);
};
const toggleOpen = (event?: React.MouseEvent) => {
// We stop the native event from bubbling, but allow the React.Synthetic event to bubble
// This way click handlers on parent components will still fire,
// but this click event won't propagate up to the document and immediately close the menu.
event?.nativeEvent?.stopPropagation?.();
setOpen(curr => !curr);
};
const sharedProps = {
target: '_blank',
rel: 'noopener noreferrer',
};
const feedbackAnchorProps = {
href: 'https://feedback.mongodb.com/',
target: '_blank',
rel: 'noopener noreferrer',
};
const ariaHiddenProps = {
'aria-hidden': true,
alt: '',
role: 'presentation',
};
const universitySharedProps = {
size: 'large',
active: isUniversity,
disabled: !account,
href: hosts.university,
['data-testid']: 'user-submenu-university',
glyph: (
),
onClick: onElementClick(NavElement.UserMenuUniversity, onClick),
className: cx(
subMenuClassName,
subMenuContainerBaseStyle,
subMenuContainerThemeStyle[theme],
{
[subMenuActiveContainerStyle]: isUniversity,
},
),
description: (
),
} as const;
const getItemProps = (href: string | undefined): PolyProps => {
if (href) {
return { href, as: 'a' } as PolyProps;
}
return { as: 'button' } as PolyProps;
};
return (
<>
>
);
}
UserMenu.displayName = 'UserMenu';
UserMenu.propTypes = {
user: PropTypes.objectOf(PropTypes.string),
activePlatform: PropTypes.oneOf([
'account',
'cloud',
'support',
'university',
'devHub',
'forums',
]),
onLogout: PropTypes.func,
onProductChange: PropTypes.func,
onAccountClick: PropTypes.func,
};
export default UserMenu;