diff --git a/libs/icon-factory/src/lib/icon-factory.tsx b/libs/icon-factory/src/lib/icon-factory.tsx index 12fd4da0f..1a39e5e1f 100644 --- a/libs/icon-factory/src/lib/icon-factory.tsx +++ b/libs/icon-factory/src/lib/icon-factory.tsx @@ -2,8 +2,10 @@ * Every icon used in the application must be individually imported * This ensures that we do not include icons that are not used in the application in the final bundle */ +import { SerializedStyles } from '@emotion/react'; import { logger } from '@jetstream/shared/client-logger'; import classNames from 'classnames'; +import ActionIcon_Announcement from './icons/action/Announcement'; import BrandIcon_Jetstream from './icons/brand/Jetstream'; import BrandIcon_JetstreamInverse from './icons/brand/JetstreamInverse'; import CustomIcon_Heart from './icons/custom/Heart'; @@ -141,14 +143,16 @@ import UtilityIcon_Warning from './icons/utility/Warning'; export type IconType = 'action' | 'custom' | 'doctype' | 'standard' | 'utility' | 'brand'; -export type IconName = StandardIcon | CustomIcon | UtilityIcon | DoctypeIcon | BrandIcon; +export type IconName = ActionIcon | StandardIcon | CustomIcon | UtilityIcon | DoctypeIcon | BrandIcon; +export type ActionIconObj = typeof actionIcons; export type StandardIconObj = typeof standardIcons; export type CustomIconObj = typeof customIcons; export type DoctypeIconObj = typeof doctypeIcons; export type UtilityIconObj = typeof utilityIcons; export type BrandIconObj = typeof brandIcons; +export type ActionIcon = keyof ActionIconObj; export type StandardIcon = keyof StandardIconObj; export type CustomIcon = keyof CustomIconObj; export type DoctypeIcon = keyof DoctypeIconObj; @@ -162,6 +166,10 @@ export interface IconObj { description?: string; } +const actionIcons = { + announcement: ActionIcon_Announcement, +} as const; + const standardIcons = { actions_and_buttons: StandardIcon_ActionsAndButtons, activations: StandardIcon_Activations, @@ -311,10 +319,16 @@ const brandIcons = { jetstream_inverse: BrandIcon_JetstreamInverse, } as const; -export function getIcon(type: IconType, icon: string, className?: string) { +export function getIcon(type: IconType, icon: string, className?: string, svgCss?: SerializedStyles) { let found = false; let IconOrFallback = UtilityIcon_Fallback; switch (type) { + case 'action': + if (actionIcons[icon]) { + IconOrFallback = actionIcons[icon]; + found = true; + } + break; case 'standard': if (standardIcons[icon]) { IconOrFallback = standardIcons[icon]; @@ -351,11 +365,13 @@ export function getIcon(type: IconType, icon: string, className?: string) { if (!found) { logger.warn('[ICON NOT FOUND]', `icon ${type}-${icon} not found, providing fallback`); } - return ; + return ; } export function getIconTypes(type: Omit): IconName[] { switch (type) { + case 'action': + return Object.keys(actionIcons) as ActionIcon[]; case 'doctype': return Object.keys(doctypeIcons) as StandardIcon[]; case 'standard': diff --git a/libs/shared/ui-core/src/app/HeaderAnnouncementPopover.tsx b/libs/shared/ui-core/src/app/HeaderAnnouncementPopover.tsx new file mode 100644 index 000000000..da2144577 --- /dev/null +++ b/libs/shared/ui-core/src/app/HeaderAnnouncementPopover.tsx @@ -0,0 +1,49 @@ +import { css } from '@emotion/react'; +import { Icon, Popover, PopoverRef } from '@jetstream/ui'; +import { FunctionComponent, ReactNode, useEffect, useRef } from 'react'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface HeaderAnnouncementPopoverProps { + children: ReactNode; +} + +export const HeaderAnnouncementPopover: FunctionComponent = ({ children }) => { + const isMounted = useRef(true); + const popoverRef = useRef(null); + + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); + + return ( + +

+ Announcement +

+ + } + content={children} + buttonProps={{ + className: + 'slds-button slds-button_icon slds-button_icon slds-button_icon-warning slds-button_icon-container slds-button_icon-small cursor-pointer', + }} + > + +
+ ); +}; diff --git a/libs/shared/ui-core/src/app/HeaderNavbar.tsx b/libs/shared/ui-core/src/app/HeaderNavbar.tsx index 6de4ab890..fb991815e 100644 --- a/libs/shared/ui-core/src/app/HeaderNavbar.tsx +++ b/libs/shared/ui-core/src/app/HeaderNavbar.tsx @@ -1,13 +1,14 @@ import { DropDownItem, Maybe, UserProfileUi } from '@jetstream/types'; -import { Header, Navbar, NavbarItem, NavbarMenuItems } from '@jetstream/ui'; +import { FeedbackLink, Header, Navbar, NavbarItem, NavbarMenuItems } from '@jetstream/ui'; import { Fragment, FunctionComponent, useEffect, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { useRecoilState, useRecoilValue } from 'recoil'; import Jobs from '../jobs/Jobs'; import OrgsDropdown from '../orgs/OrgsDropdown'; import { SelectedOrgReadOnly } from '../orgs/SelectedOrgReadOnly'; import { RecordSearchPopover } from '../record/RecordSearchPopover'; import { applicationCookieState, selectUserPreferenceState } from '../state-management/app-state'; +import { HeaderAnnouncementPopover } from './HeaderAnnouncementPopover'; import HeaderDonatePopover from './HeaderDonatePopover'; import HeaderHelpPopover from './HeaderHelpPopover'; import NotificationsRequestModal from './NotificationsRequestModal'; @@ -78,8 +79,37 @@ export const HeaderNavbar: FunctionComponent = ({ userProfile const rightHandMenuItems = useMemo(() => { return isChromeExtension ? [, , ] - : [, , , ]; - }, [isChromeExtension]); + : [ + +

We are working on upgrades to our authentication and user management systems in the coming weeks.

+

Upcoming Features:

+
    +
  • Multi-factor authentication
  • +
  • Visibility to all active sessions
  • +
+

Important information:

+
    +
  • All users will be signed out and need to sign back in
  • +
  • Some users may require a password reset to log back in
  • +
+
+ Stay tuned for a timeline. If you have any questions . + {!!userProfile && !userProfile.email_verified && ( + <> +
+

+ Your email address is not verified, make sure verify your email address or{' '} + link a social identity to make sure you can continue to login. +

+ + )} +
, + , + , + , + , + ]; + }, [isChromeExtension, userProfile]); return ( diff --git a/libs/shared/ui-core/src/index.ts b/libs/shared/ui-core/src/index.ts index ce41802bc..28fbff6a6 100644 --- a/libs/shared/ui-core/src/index.ts +++ b/libs/shared/ui-core/src/index.ts @@ -6,6 +6,7 @@ export * from './app/ConfirmPageChange'; export * from './app/DownloadFileStream'; export * from './app/EmailSupport'; export * from './app/ErrorBoundaryFallback'; +export * from './app/HeaderAnnouncementPopover'; export * from './app/HeaderDonatePopover'; export * from './app/HeaderHelpPopover'; export * from './app/HeaderNavbar'; diff --git a/libs/ui/src/lib/popover/Popover.tsx b/libs/ui/src/lib/popover/Popover.tsx index 602f38c9e..183b48075 100644 --- a/libs/ui/src/lib/popover/Popover.tsx +++ b/libs/ui/src/lib/popover/Popover.tsx @@ -33,7 +33,7 @@ export interface PopoverProps { bodyClassName?: string; bodyStyle?: SerializedStyles; placement?: Placement; - content: JSX.Element; + content: ReactNode; header?: JSX.Element; footer?: JSX.Element; panelStyle?: CSSProperties; diff --git a/libs/ui/src/lib/widgets/Icon.tsx b/libs/ui/src/lib/widgets/Icon.tsx index 38c6d6bc4..275c427ad 100644 --- a/libs/ui/src/lib/widgets/Icon.tsx +++ b/libs/ui/src/lib/widgets/Icon.tsx @@ -1,10 +1,13 @@ -import { IconName, IconType, getIcon } from '@jetstream/icon-factory'; +import { SerializedStyles } from '@emotion/react'; +import { getIcon, IconName, IconType } from '@jetstream/icon-factory'; import classNames from 'classnames'; export interface IconProps { containerClassname?: string; className?: string; omitContainer?: boolean; + containerCss?: SerializedStyles; + svgCss?: SerializedStyles; title?: string; type: IconType; icon: IconName; @@ -14,15 +17,25 @@ export interface IconProps { /** * https://www.lightningdesignsystem.com/components/icons/ */ -export const Icon = ({ containerClassname, className, title, omitContainer = false, type, icon, description }: IconProps) => { +export const Icon = ({ + containerClassname, + className, + title, + omitContainer = false, + type, + icon, + description, + containerCss, + svgCss, +}: IconProps) => { containerClassname = containerClassname || ''; className = className || ''; - const svg = getIcon(type, icon, className); + const svg = getIcon(type, icon, className, svgCss); if (omitContainer) { return svg; } else { return ( - + {svg} {(description || title) && {description || title}}