diff --git a/package.json b/package.json index de2ccc79..8530f411 100644 --- a/package.json +++ b/package.json @@ -40,13 +40,15 @@ "@reduxjs/toolkit": "^1.7.2", "@sentry/browser": "7.13.0", "@types/react-transition-group": "^4.4.5", + "autoprefixer": "^10.4.12", "axios": "^0.27.2", "classnames": "^2.3.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-redux": "^7.2.6", "react-router-dom": "^6.3.0", - "react-transition-group": "^4.4.5" + "react-transition-group": "^4.4.5", + "terser": "^5.15.1" }, "devDependencies": { "@types/enzyme": "^3.10.11", @@ -73,7 +75,7 @@ "lint-staged": "13.0.3", "minimist": "^1.2.6", "node-sass": "7.0.1", - "postcss": "^8.4.6", + "postcss": "^8.4.17", "postcss-import": "^14.0.2", "postcss-nested": "^5.0.6", "prettier": "^2.5.1", diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 562c99a5..da8f04ed 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -1,4 +1,4 @@ -import FingerprintJS from '@fingerprintjs/fingerprintjs'; +// import FingerprintJS from '@fingerprintjs/fingerprintjs'; import React from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { setDeviceId } from './pages/auth/AuthActions'; @@ -6,6 +6,7 @@ import Login from './pages/auth/components/Login'; import DefaultLayout from './layout/DefaultLayout'; import { useSelector } from 'react-redux'; import { RootState } from './store'; +import { LOGIN_PATH } from './layout/Routes'; interface IProtectedRoute { children: JSX.Element; @@ -21,38 +22,38 @@ const ProtectedRoute: React.FC = props => { const { token } = useSelector((store: RootState) => store?.common?.userData); if (!token) { - return ; + return ; } return children; }; const AppRouter = () => { const { deviceId } = useSelector((store: RootState) => store?.common?.userData); - const fpPromise = FingerprintJS.load({ - monitoring: false + import(/* webpackChunkName: ["fp"] */ '@fingerprintjs/fingerprintjs').then(FingerprintJS => { + const fpPromise = FingerprintJS.load({ + monitoring: false + }); + (async () => { + if (!deviceId) { + const fp = await fpPromise; + const result = await fp.get(); + setDeviceId(result.visitorId); + } + })(); }); - (async () => { - if (!deviceId) { - const fp = await fpPromise; - const result = await fp.get(); - setDeviceId(result.visitorId); - } - })(); return ( - }> - - } /> - - - - } - /> - - + + } /> + + + + } + /> + ); }; diff --git a/src/assets/styles/_animations.scss b/src/assets/styles/_animations.scss index 8f265397..68ea3173 100644 --- a/src/assets/styles/_animations.scss +++ b/src/assets/styles/_animations.scss @@ -9,6 +9,17 @@ opacity: 1; } } +@keyframes growY { + 0% { + transform: translateY(10%); + opacity: 0; + } + + 100% { + transform: translateY(0); + opacity: 1; + } +} @keyframes fadeIn { 0% { @@ -36,3 +47,12 @@ transform: translateX(0); } } + +@keyframes slideInRight { + 0% { + transform: translateX(-5%); + } + 100% { + transform: translateX(0); + } +} diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx index cf3d6815..65355f20 100644 --- a/src/components/Loader/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -3,7 +3,7 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'; interface LoaderProps { show: boolean; - className: string; + className?: string; animate?: boolean; } diff --git a/src/components/Notifications/Notifications.module.scss b/src/components/Notifications/Notifications.module.scss index 268b666b..bd7ba255 100644 --- a/src/components/Notifications/Notifications.module.scss +++ b/src/components/Notifications/Notifications.module.scss @@ -1,4 +1,7 @@ +@import '../../assets/styles/animations'; + .notificationWrapper { + z-index: -2; cursor: default; .notificationOverlay { width: calc(100vw - var(--sidenav-width-open)); @@ -6,17 +9,18 @@ position: fixed; top: 0; right: 0; - background-color: #000; - opacity: 0.4; + background-color: rgba(0, 0, 0, 0.4); + animation: fadeIn 120ms; } .notificationPopup { + animation: fadeIn 300ms, grow 120ms; padding: 24px 16px; - left: 250px; - bottom: 16px; + left: calc(244px + 12px); + bottom: 14px; overflow-y: auto; position: absolute; - height: calc(100vh - 80px); + height: calc(100vh - 75px); width: 380px; background-color: var(--navi-color-gray-bg-primary); border-radius: 8px; diff --git a/src/components/Notifications/Notifications.tsx b/src/components/Notifications/Notifications.tsx index fb7f1786..8bc5ea3e 100644 --- a/src/components/Notifications/Notifications.tsx +++ b/src/components/Notifications/Notifications.tsx @@ -1,31 +1,35 @@ import styles from './Notifications.module.scss'; import { NotificationsProps } from './interfaces'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../store'; import Typography from '@navi/web-ui/lib/primitives/Typography/Typography'; import NotificationList from './NotificationList'; import { NotificationTypes } from './constants'; import { TemplatesMap } from './utils'; +import { useMemo } from 'react'; -const Notifications = ({ toggleNotifications }: NotificationsProps) => { +const Notifications = (props: NotificationsProps) => { const { - notifications, - notificationTemplates, - alerts = [] - } = useSelector((state: RootState) => state.notification); + notifications = [], + notificationTemplates = [], + alerts = [], + toggleNotifications + } = props; - const notificationAlerts = notifications?.filter( - notification => notification.flowType === NotificationTypes.LONGHORN_ALERT + const notificationAlerts = useMemo( + () => + notifications?.filter( + notification => notification.flowType === NotificationTypes.LONGHORN_ALERT + ), + [notifications] ); const templatesMap = TemplatesMap(notificationTemplates); return ( -
-
-
+
+
+
{notificationAlerts?.length ? ( -
+
Alerts { templatesMap={templatesMap} /> ) : ( - + Nothing to show here )} diff --git a/src/components/Notifications/interfaces.tsx b/src/components/Notifications/interfaces.tsx index 73c06aab..ec214375 100644 --- a/src/components/Notifications/interfaces.tsx +++ b/src/components/Notifications/interfaces.tsx @@ -43,7 +43,12 @@ export interface Widget { } export interface NotificationsProps { - toggleNotifications: () => void; + toggleNotifications: (show?: boolean | unknown) => void; + notifications: NotificationsData[]; + notificationTemplates: NotificationsTemplate[]; + alerts: []; + alertTemplates: string[]; + showNotifications: boolean; } export interface NotificationListProps { diff --git a/src/components/sidebar/NotificationsLink.tsx b/src/components/sidebar/NotificationsLink.tsx new file mode 100644 index 00000000..37835b7d --- /dev/null +++ b/src/components/sidebar/NotificationsLink.tsx @@ -0,0 +1,112 @@ +import { Typography } from '@navi/web-ui/lib/primitives'; +import React, { useMemo } from 'react'; +import { + NotificationActiveIcon, + NotificationInActiveIcon +} from '../../assets/images/icons/NotificationIcon'; +import cx from 'classnames'; +import styles from './SideNavBar.module.scss'; +import usePolling from '../../hooks/usePolling'; +import { PollStatus } from '../../utils/polling'; +import { NotificationsData } from '../Notifications/interfaces'; +import { fetchNotifications } from '../Notifications/NotificationsActions'; +import { hasNotCompletedAction } from '../Notifications/utils'; +import { GLOBAL, titleString } from '../../constants/Global'; + +interface NotificationLinkProps { + showNotifications: boolean; + toggleNotifications: (show: boolean | unknown) => void; +} + +const calculateUnreadNotificationCount = (notifications: NotificationsData[]) => { + if (notifications) { + const todaysDate = new Date(); + todaysDate.setDate(todaysDate.getDate() - 1); + const yesterdaysDate = todaysDate.getTime(); + const unreadNotifications = notifications?.reduce( + (count: number, notification: NotificationsData) => + notification.createdAt > yesterdaysDate && hasNotCompletedAction(notification) + ? count + 1 + : count, + 0 + ); + return unreadNotifications; + // setUnreadNotificationCount(unreadNotifications); + } +}; + +const Notifications = React.lazy(() => import('../Notifications/Notifications')); + +interface CountTagProps { + unreadNotificationCount?: number; + showNotifications: boolean; +} + +const CountTag = ({ unreadNotificationCount, showNotifications }: CountTagProps) => { + if (!unreadNotificationCount || unreadNotificationCount < 1) { + document.title = titleString(0); + return null; + } + + const displayCount = unreadNotificationCount > 999 ? '99+' : unreadNotificationCount; + + document.title = titleString(displayCount); + + return ( + + {displayCount} + + ); +}; + +const NotificationLink = ({ showNotifications, toggleNotifications }: NotificationLinkProps) => { + const { data, status } = usePolling({ + promiseFn: fetchNotifications(), + pollingInterval: GLOBAL.NOTIFICATIONS_POLLING_INTERVAL + }); + + const { notifications, notificationTemplates, alerts, alertTemplates } = + (status === PollStatus.SUCCESS && data.data) || {}; + + const unreadNotificationCount = useMemo( + () => calculateUnreadNotificationCount(notifications), + [notifications] + ); + + return ( + <> +
+ {showNotifications ? : } + + Notifications + + +
+ {showNotifications && ( + }> + + + )} + + ); +}; + +export { NotificationLink }; diff --git a/src/components/sidebar/SideNavBar.module.scss b/src/components/sidebar/SideNavBar.module.scss index 5278e156..af3d44b8 100644 --- a/src/components/sidebar/SideNavBar.module.scss +++ b/src/components/sidebar/SideNavBar.module.scss @@ -1,4 +1,11 @@ @import '../../assets/styles/animations'; + +.notificationExpanded { + top: 14px; + right: 8px; + left: auto; +} + .sideNavBarMain { flex: 1; height: 100%; @@ -13,37 +20,45 @@ top: 0; left: 0; width: 100%; + .logoContainer { - height: 180px; + // height: 180px; + height: 78px; background-color: var(--bg-transparent); width: 100%; + .naviLogo { margin-left: 18px; height: 30px; width: 30px; margin-top: 20px; } + .ProgressBar { margin-left: 18px; margin-top: 24px; } + .progressIndicator { display: flex; flex-direction: row; justify-content: space-between; flex-wrap: nowrap; - color: var(--text-primary) !important ; + color: var(--text-primary) !important; margin: 0px 18px 0px 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + .whiteColor { color: var(--text-primary); } } + .horizontalProgress { display: none; } + .circularProgress { display: block; } @@ -53,24 +68,30 @@ height: 100%; display: flex; flex-direction: column; + &:last-child { margin-top: auto; margin-bottom: 0; } + overflow: hidden; + .sideNavBarLinks { position: relative; + z-index: 0; margin-top: 24px; margin-left: 18px; height: 48px; - width: var(--sidenav-width-open); + width: calc(var(--sidenav-width-open) - 18px); display: flex; flex-direction: row; align-items: center; text-decoration: none; + .link { text-decoration: none; } + .text { visibility: hidden; color: var(--text-primary); @@ -78,28 +99,32 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + max-width: calc(var(--sidenav-width-open) - 100px); } + .notificationCount { + animation: grow 300ms; + position: absolute; + top: 0px; + left: 12px; + align-items: center; background-color: var(--navi-color-red-base); - margin-left: 50px; - width: 24px; - height: 18px; - border-radius: 96px; + border-radius: 12px; color: var(--navi-color-gray-bg-primary); display: flex; - justify-content: center; - align-items: center; - &__collapsed { - position: absolute; - top: 0px; - left: -38px; + padding: 0px 5px; + + &__expanded { + @extend .notificationExpanded; } } } + .alignBotton { margin-top: auto; cursor: pointer; overflow: hidden; + .logoutPopup { position: absolute; bottom: 50px; @@ -116,29 +141,40 @@ animation: fadeIn 1000ms; } } + .logoutLink { margin-top: 2px; } } + + &:hover, &__hovered { max-width: var(--sidenav-width-open); min-width: var(--sidenav-width-close); + .linksContainer { .sideNavBarLinks { .text { visibility: visible; } + + .notificationCount { + @extend .notificationExpanded; + } } + .alignBotton { .logoutPopup { display: block; } } } + .logoContainer { .horizontalProgress { display: block; } + .circularProgress { display: none; } diff --git a/src/components/sidebar/SideNavBar.tsx b/src/components/sidebar/SideNavBar.tsx index e860f663..9fe6a278 100644 --- a/src/components/sidebar/SideNavBar.tsx +++ b/src/components/sidebar/SideNavBar.tsx @@ -1,5 +1,5 @@ // Library -import { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import Typography from '@navi/web-ui/lib/primitives/Typography/index'; import cx from 'classnames'; import { useLocation } from 'react-router-dom'; @@ -7,8 +7,8 @@ import { useLocation } from 'react-router-dom'; import styles from './SideNavBar.module.scss'; // functions, components and utlis import { NaviLogo } from '../../assets/images/icons/NaviLogo'; -import CircularProgress from '../ProgressBars/circularProgress/CircularProgress'; -import HorizontalProgress from '../ProgressBars/horizontalProgress/HorizontalProgress'; +// import CircularProgress from '../ProgressBars/circularProgress/CircularProgress'; +// import HorizontalProgress from '../ProgressBars/horizontalProgress/HorizontalProgress'; import SideBarItems, { HideSideBar } from './SideBarItems'; import SidebarLinks from './SidebarLinks'; import AgentIcon from '../../assets/images/icons/AgentIcon'; @@ -16,121 +16,48 @@ import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../../store'; import { clearToken } from '../../pages/auth/AuthActions'; import { setAuthData } from '../../reducers/commonSlice'; -import { - NotificationActiveIcon, - NotificationInActiveIcon -} from '../../assets/images/icons/NotificationIcon'; -import useHover from '../../hooks/useHover'; -import Notifications from '../Notifications/Notifications'; -import usePolling from '../../hooks/usePolling'; -import { fetchNotifications } from '../Notifications/NotificationsActions'; -import { setNotificationsData } from '../../reducers/notificationsSlice'; -import { hasNotCompletedAction } from '../Notifications/utils'; -import { NotificationsData } from '../Notifications/interfaces'; import { setShowOTPScreen } from '../../pages/auth/reducers/authSlice'; +import { NotificationLink } from './NotificationsLink'; function SideNavBar() { - const location = useLocation().pathname; + const { pathname, search } = useLocation(); const user = useSelector((state: RootState) => state.common.userData); - const hide = HideSideBar.some(path => location.includes(path)); + const hide = HideSideBar.some(path => pathname.includes(path)); const [showLogout, setShowLogout] = useState(false); - const [sideNavRef, isHovered, setIsHovered] = useHover(); const [showNotifications, setShowNotifications] = useState(false); - const [unreadNotificationCount, setUnreadNotificationCount] = useState(0); - - const { notifications } = useSelector((state: RootState) => state.notification); const dispatch = useDispatch(); - const percentage = 30; + // const percentage = 30; const logOut = () => { clearToken(); dispatch(setAuthData({ token: null })); dispatch(setShowOTPScreen({ status: false })); }; - const toggleNotifications = () => { - if (showNotifications) { - setIsHovered(false); + const toggleNotifications = (show: boolean | unknown) => { + // enforce hide + if (!show) { + setShowNotifications(false); + return; } + setShowNotifications(!showNotifications); }; - const { data } = usePolling({ - promiseFn: fetchNotifications(), - pollingInterval: 30000 - }); - - useEffect(() => { - if (data) { - dispatch(setNotificationsData(data.data)); - } - }, [data]); - - useEffect(() => { - if (notifications) { - const todaysDate = new Date(); - todaysDate.setDate(todaysDate.getDate() - 1); - const yesterdaysDate = todaysDate.getTime(); - const unreadNotifications = notifications?.reduce( - (count: number, notification: NotificationsData) => - notification.createdAt > yesterdaysDate && hasNotCompletedAction(notification) - ? count + 1 - : count, - 0 - ); - setUnreadNotificationCount(unreadNotifications); - } - }, [notifications]); - - const renderNotification = () => { - return ( - <> -
- {showNotifications ? : } - - Notifications - - {renderNotificationCountTag()} -
- {showNotifications && } - - ); - }; - - const renderNotificationCountTag = () => { - if (unreadNotificationCount < 1) { - return null; - } - return ( - - {unreadNotificationCount > 9 ? '9+' : unreadNotificationCount} - - ); + const linkClickHandler = (e: unknown) => { + toggleNotifications(false); }; return ( <>
-
+ {/*
@@ -140,29 +67,35 @@ function SideNavBar() { 30%
-
-
+
*/} + {/*
-
+
*/}
{hide ? null : (
- {SideBarItems.map((i, key) => ( + {SideBarItems.map(i => ( ))}
- {renderNotification()} + {showLogout ? (