Header.js
320 lines
| 1 | /** |
| 2 | * External Dependencies |
| 3 | */ |
| 4 | import { |
| 5 | Box, |
| 6 | Button, |
| 7 | Center, |
| 8 | Container, |
| 9 | Divider, |
| 10 | Drawer, |
| 11 | DrawerBody, |
| 12 | DrawerCloseButton, |
| 13 | DrawerContent, |
| 14 | DrawerHeader, |
| 15 | DrawerOverlay, |
| 16 | Image, |
| 17 | Link, |
| 18 | Stack, |
| 19 | Tag, |
| 20 | Tooltip, |
| 21 | useDisclosure, |
| 22 | } from '@chakra-ui/react'; |
| 23 | import { __ } from '@wordpress/i18n'; |
| 24 | import { useEffect, useMemo, useRef } from 'react'; |
| 25 | import { NavLink, useLocation } from 'react-router-dom'; |
| 26 | |
| 27 | /** |
| 28 | * Internal Dependencies |
| 29 | */ |
| 30 | import ROUTES, { |
| 31 | convertRoute, |
| 32 | isExternalRoute, |
| 33 | isRouteActive, |
| 34 | } from '../../Constants'; |
| 35 | import announcement from '../../images/announcement.gif'; |
| 36 | import Changelog from '../Changelog/Changelog'; |
| 37 | import { EVF, ExternalLink } from '../Icon/Icon'; |
| 38 | import IntersectObserver from '../IntersectionObserver/IntersectionObserver'; |
| 39 | |
| 40 | const Header = ({ hideSiteAssistant = false }) => { |
| 41 | const { isOpen, onOpen, onClose } = useDisclosure(); |
| 42 | const ref = useRef(); |
| 43 | const location = useLocation(); |
| 44 | |
| 45 | /* global _EVF_DASHBOARD_ */ |
| 46 | const { version, isPro, upgradeURL, pageType, adminURL, currentPage } = |
| 47 | typeof _EVF_DASHBOARD_ !== 'undefined' && _EVF_DASHBOARD_; |
| 48 | |
| 49 | const isSettingsPage = pageType === 'settings'; |
| 50 | const isEntriesPage = pageType === 'entries'; |
| 51 | const isAnalyticsPage = pageType === 'analytics'; |
| 52 | const isNonDashboardPage = |
| 53 | isSettingsPage || |
| 54 | isEntriesPage || |
| 55 | isAnalyticsPage || |
| 56 | (currentPage && currentPage !== 'evf-dashboard'); |
| 57 | |
| 58 | // ------------------------------------------------------------------ |
| 59 | // Dismiss PHP skeleton once React has painted. |
| 60 | // requestAnimationFrame guarantees the browser has rendered this |
| 61 | // component before we remove the skeleton — prevents any white flash. |
| 62 | // ------------------------------------------------------------------ |
| 63 | useEffect(() => { |
| 64 | requestAnimationFrame(() => { |
| 65 | if (typeof window.evfHeaderReady === 'function') { |
| 66 | window.evfHeaderReady(); |
| 67 | } |
| 68 | }); |
| 69 | }, []); // runs once on mount only. |
| 70 | |
| 71 | // ------------------------------------------------------------------ |
| 72 | // Keep existing modal body-class logic unchanged. |
| 73 | // ------------------------------------------------------------------ |
| 74 | useEffect(() => { |
| 75 | if (isOpen) { |
| 76 | document.body.classList.add('ur-modal-open'); |
| 77 | } else { |
| 78 | document.body.classList.remove('ur-modal-open'); |
| 79 | } |
| 80 | return () => { |
| 81 | document.body.classList.remove('ur-modal-open'); |
| 82 | }; |
| 83 | }, [isOpen]); |
| 84 | |
| 85 | const { leftRoutes, rightRoutes } = useMemo(() => { |
| 86 | const allRoutes = hideSiteAssistant |
| 87 | ? ROUTES.filter((route) => route.key !== 'siteAssistant') |
| 88 | : ROUTES; |
| 89 | |
| 90 | const rightRoutePaths = ['/help', 'https://everestforms.net/free-vs-pro/']; |
| 91 | |
| 92 | return { |
| 93 | leftRoutes: allRoutes.filter( |
| 94 | (route) => !rightRoutePaths.includes(route.route), |
| 95 | ), |
| 96 | rightRoutes: allRoutes |
| 97 | .filter((route) => rightRoutePaths.includes(route.route)) |
| 98 | .sort( |
| 99 | (a, b) => |
| 100 | rightRoutePaths.indexOf(a.route) - rightRoutePaths.indexOf(b.route), |
| 101 | ), |
| 102 | }; |
| 103 | }, [hideSiteAssistant]); |
| 104 | |
| 105 | const renderNavLink = (route, label, external, showExternalIcon = false) => { |
| 106 | const convertedRoute = convertRoute(route, isNonDashboardPage, adminURL); |
| 107 | const isExternal = external || isExternalRoute(convertedRoute); |
| 108 | const isActive = isRouteActive(route, location.pathname, pageType); |
| 109 | const shouldUseExternalLink = isNonDashboardPage || isExternal; |
| 110 | const shouldShowIcon = showExternalIcon; |
| 111 | |
| 112 | return shouldUseExternalLink ? ( |
| 113 | <Link |
| 114 | data-target={route} |
| 115 | key={route} |
| 116 | href={convertedRoute} |
| 117 | isExternal={route === 'https://everestforms.net/free-vs-pro/'} |
| 118 | fontSize="14px" |
| 119 | fontWeight="medium" |
| 120 | lineHeight="150%" |
| 121 | color={isActive ? 'primary.500' : '#383838'} |
| 122 | borderBottom="2px solid" |
| 123 | // borderBottom={isActive ? '3px solid' : 'none'} |
| 124 | borderColor={isActive ? 'primary.500' : 'transparent'} |
| 125 | // marginBottom={isActive ? '-2px' : '0'} |
| 126 | _hover={{ color: 'primary.500' }} |
| 127 | _focus={{ boxShadow: 'none' }} |
| 128 | display="inline-flex" |
| 129 | alignItems="center" |
| 130 | gap="1" |
| 131 | px="2" |
| 132 | h="full" |
| 133 | > |
| 134 | {label} |
| 135 | {shouldShowIcon && ( |
| 136 | <ExternalLink w="16px" h="16px" fill="currentColor" /> |
| 137 | )} |
| 138 | </Link> |
| 139 | ) : ( |
| 140 | <Link |
| 141 | data-target={route} |
| 142 | key={route} |
| 143 | as={NavLink} |
| 144 | to={route} |
| 145 | fontSize="14px" |
| 146 | fontWeight="medium" |
| 147 | lineHeight="150%" |
| 148 | color="#383838" |
| 149 | borderBottom="2px solid" |
| 150 | borderColor={isActive ? 'primary.500' : 'transparent'} |
| 151 | _hover={{ color: 'primary.500' }} |
| 152 | _focus={{ boxShadow: 'none' }} |
| 153 | _activeLink={{ |
| 154 | color: 'primary.500', |
| 155 | // borderBottom: '3px solid', |
| 156 | borderColor: 'primary.500', |
| 157 | // marginBottom: '-2px', |
| 158 | }} |
| 159 | display="inline-flex" |
| 160 | alignItems="center" |
| 161 | gap="1" |
| 162 | px="2" |
| 163 | h="full" |
| 164 | > |
| 165 | {label} |
| 166 | {shouldShowIcon && ( |
| 167 | <ExternalLink w="16px" h="16px" fill="currentColor" /> |
| 168 | )} |
| 169 | </Link> |
| 170 | ); |
| 171 | }; |
| 172 | |
| 173 | return ( |
| 174 | <> |
| 175 | <Box |
| 176 | // bg={'white'} |
| 177 | bg="white" |
| 178 | borderBottom="1px solid #e1e1e1" |
| 179 | width="100%" |
| 180 | position={'relative'} |
| 181 | > |
| 182 | <Container maxW="full"> |
| 183 | <Stack direction="row" minH="60px" justify="space-between"> |
| 184 | {/* Left Side — Logo and Main Navigation */} |
| 185 | <Stack direction="row" align="center" gap="16px"> |
| 186 | <Box> |
| 187 | <EVF h="36px" w="36px" /> |
| 188 | </Box> |
| 189 | <IntersectObserver routes={leftRoutes}> |
| 190 | {leftRoutes.map(({ route, label, external }) => |
| 191 | renderNavLink(route, label, external), |
| 192 | )} |
| 193 | </IntersectObserver> |
| 194 | </Stack> |
| 195 | |
| 196 | {/* Right Side */} |
| 197 | <Stack direction="row" align="center" spacing="12px"> |
| 198 | <Stack direction="row" align="center" gap="1" h={'full'}> |
| 199 | {rightRoutes.map(({ route, label, external }) => |
| 200 | renderNavLink( |
| 201 | route, |
| 202 | label, |
| 203 | external, |
| 204 | route === 'https://everestforms.net/free-vs-pro/', |
| 205 | ), |
| 206 | )} |
| 207 | </Stack> |
| 208 | |
| 209 | {rightRoutes.length > 0 && ( |
| 210 | <Center height="18px"> |
| 211 | <Divider orientation="vertical" /> |
| 212 | </Center> |
| 213 | )} |
| 214 | |
| 215 | {!isPro && ( |
| 216 | <Link |
| 217 | color="orange" |
| 218 | fontSize="15px" |
| 219 | height="18px" |
| 220 | href={ |
| 221 | upgradeURL + |
| 222 | 'utm_medium=evf-dashboard&utm_source=evf-free&utm_campaign=header-upgrade-btn&utm_content=Upgrade%20to%20Pro' |
| 223 | } |
| 224 | isExternal |
| 225 | display="inline-flex" |
| 226 | alignItems="center" |
| 227 | gap="1" |
| 228 | > |
| 229 | {__('Upgrade To Pro', 'everest-forms')} |
| 230 | <ExternalLink w="16px" h="16px" fill="currentColor" /> |
| 231 | </Link> |
| 232 | )} |
| 233 | |
| 234 | <Tooltip |
| 235 | label={sprintf( |
| 236 | __( |
| 237 | 'You are currently using Everest Forms %s', |
| 238 | 'everest-forms', |
| 239 | ), |
| 240 | (isPro && 'Pro ') + 'v' + version, |
| 241 | )} |
| 242 | maxW={'180px'} |
| 243 | > |
| 244 | <Tag |
| 245 | display={'inline-flex !important'} |
| 246 | variant="outline" |
| 247 | // colorScheme="primary" |
| 248 | // borderRadius="xl" |
| 249 | bgColor="#F8FAFF" |
| 250 | // fontSize="xs" |
| 251 | p="2px 6px" |
| 252 | fontWeight="medium" |
| 253 | borderRadius="4px" |
| 254 | fontSize="12px" |
| 255 | color="#8f8f8f" |
| 256 | bg="#f3f3f3" |
| 257 | border="1px solid #e1e1e1" |
| 258 | outline="none" |
| 259 | boxShadow="none" |
| 260 | > |
| 261 | {'v' + version} |
| 262 | </Tag> |
| 263 | </Tooltip> |
| 264 | |
| 265 | <Button |
| 266 | onClick={onOpen} |
| 267 | variant="unstyled" |
| 268 | borderRadius="full" |
| 269 | border="2px" |
| 270 | borderColor="gray.200" |
| 271 | w="40px" |
| 272 | h="40px" |
| 273 | position="relative" |
| 274 | > |
| 275 | <Tooltip label={__('Latest Updates', 'everest-forms')}> |
| 276 | <Image |
| 277 | src={announcement} |
| 278 | alt="announcement" |
| 279 | h="35px" |
| 280 | w="35px" |
| 281 | position="absolute" |
| 282 | top="50%" |
| 283 | left="50%" |
| 284 | transform="translate(-40%, -50%)" |
| 285 | /> |
| 286 | </Tooltip> |
| 287 | </Button> |
| 288 | </Stack> |
| 289 | </Stack> |
| 290 | </Container> |
| 291 | </Box> |
| 292 | |
| 293 | <Drawer |
| 294 | isOpen={isOpen} |
| 295 | placement="right" |
| 296 | onClose={onClose} |
| 297 | finalFocusRef={ref} |
| 298 | size="md" |
| 299 | > |
| 300 | <DrawerOverlay |
| 301 | bgColor="rgb(0,0,0,0.05)" |
| 302 | sx={{ backdropFilter: 'blur(1px)' }} |
| 303 | /> |
| 304 | <DrawerContent |
| 305 | className="everest-forms-announcement" |
| 306 | top="var(--wp-admin--admin-bar--height, 0) !important" |
| 307 | > |
| 308 | <DrawerCloseButton /> |
| 309 | <DrawerHeader>{__('Latest Updates', 'everest-forms')}</DrawerHeader> |
| 310 | <DrawerBody> |
| 311 | <Changelog /> |
| 312 | </DrawerBody> |
| 313 | </DrawerContent> |
| 314 | </Drawer> |
| 315 | </> |
| 316 | ); |
| 317 | }; |
| 318 | |
| 319 | export default Header; |
| 320 |