diff --git a/src/App.jsx b/src/App.jsx index a0fa65c..60f6842 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -17,7 +17,6 @@ import { normalizedHref } from './js/common/utils/hrefUtils'; import initializejQuery from './js/common/utils/initializejQuery'; import { isAndroid, isCordova, isWebApp } from './js/common/utils/isCordovaOrWebApp'; import { renderLog } from './js/common/utils/logging'; -import Header from './js/components/Navigation/Header'; import HeaderBarSuspense from './js/components/Navigation/HeaderBarSuspense'; import webAppConfig from './js/config'; import VoterStore from './js/stores/VoterStore'; @@ -25,8 +24,10 @@ import VoterStore from './js/stores/VoterStore'; // Root URL pages +const Drawers = React.lazy(() => import(/* webpackChunkName: 'Drawers' */ './js/components/Drawers/Drawers')); const FAQ = React.lazy(() => import(/* webpackChunkName: 'FAQ' */ './js/pages/FAQ')); const Footer = React.lazy(() => import(/* webpackChunkName: 'Footer' */ './js/components/Navigation/Footer')); +const Header = React.lazy(() => import(/* webpackChunkName: 'Header' */ './js/components/Navigation/Header')); const PageNotFound = React.lazy(() => import(/* webpackChunkName: 'PageNotFound' */ './js/pages/PageNotFound')); const TeamMembers = React.lazy(() => import(/* webpackChunkName: 'FAQ' */ './js/pages/TeamMembers')); @@ -267,6 +268,9 @@ class App extends Component { }>
+ }> + + }> diff --git a/src/img/global/icons/cross.svg b/src/img/global/icons/cross.svg new file mode 100644 index 0000000..5809116 --- /dev/null +++ b/src/img/global/icons/cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/img/global/icons/search.svg b/src/img/global/icons/search.svg new file mode 100644 index 0000000..9390811 --- /dev/null +++ b/src/img/global/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/index.html b/src/index.html index bd68e4d..b9958af 100644 --- a/src/index.html +++ b/src/index.html @@ -30,8 +30,8 @@
-
-

Loading WeConnect...

+
+

Loading WeConnect...

Thank you for being part of WeVote!
diff --git a/src/js/common/components/Search/SearchBar2024.jsx b/src/js/common/components/Search/SearchBar2024.jsx new file mode 100644 index 0000000..e6313ef --- /dev/null +++ b/src/js/common/components/Search/SearchBar2024.jsx @@ -0,0 +1,114 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import styled from 'styled-components'; +import { blurTextFieldAndroid, focusTextFieldAndroid } from '../../utils/cordovaUtils'; +import { renderLog } from '../../utils/logging'; +import SearchBase from './SearchBase'; + +/* eslint-disable jsx-a11y/control-has-associated-label */ +class SearchBar2024 extends Component { + constructor (props) { + super(props); + + this.state = { + searchString: '', + }; + + this.handleKeyPress = this.handleKeyPress.bind(this); + this.updateResults = this.updateResults.bind(this); + this.clearQuery = this.clearQuery.bind(this); + } + + componentDidMount () { + if (this.props.clearSearchTextNow) { + if (this.props.clearFunction) { + this.props.clearFunction(); + } + const { searchString } = this.state; + if (searchString) { + this.setState({ + searchString: '', + }); + } + } + } + + componentDidUpdate (prevProps) { + if (this.props.clearSearchTextNow !== prevProps.clearSearchTextNow) { + if (this.props.clearSearchTextNow) { + if (this.props.clearFunction) { + this.props.clearFunction(); + } + const { searchString } = this.state; + if (searchString) { + this.setState({ + searchString: '', + }); + } + } + } + } + + componentWillUnmount () { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + } + + handleKeyPress () { + if (this.timer) clearTimeout(this.timer); + this.timer = setTimeout(() => { + this.props.searchFunction(this.state.searchString); + }, this.props.searchUpdateDelayTime); + } + + clearQuery () { + this.props.clearFunction(); + this.setState({ searchString: '' }); + } + + updateResults (event) { + const searchString = event.target.value; + this.setState({ + searchString, + }); + } + + // check limit of 50 characters + render () { + renderLog('SearchBar2024'); // Set LOG_RENDER_EVENTS to log all renders + const { placeholder } = this.props; + const { searchString } = this.state; + return ( + + focusTextFieldAndroid('SearchBar2024')} + onBlur={blurTextFieldAndroid} + onClear={this.clearQuery} + /> + + ); + } +} +SearchBar2024.propTypes = { + clearFunction: PropTypes.func.isRequired, + clearSearchTextNow: PropTypes.bool, + placeholder: PropTypes.string, + searchFunction: PropTypes.func.isRequired, + searchUpdateDelayTime: PropTypes.number.isRequired, +}; + +const SearchBar2024Wrapper = styled('div')` + width: 100%; + overflow: hidden; + position: relative; + padding: 4px; +`; + +export default SearchBar2024; diff --git a/src/js/common/components/Search/SearchBase.jsx b/src/js/common/components/Search/SearchBase.jsx new file mode 100644 index 0000000..422fc46 --- /dev/null +++ b/src/js/common/components/Search/SearchBase.jsx @@ -0,0 +1,120 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import styled from 'styled-components'; +import colors from '../Style/Colors'; +import normalizedImagePath from '../../utils/normalizedImagePath'; + +const closeIcon = normalizedImagePath('../../../img/global/icons/cross.svg'); +const searchIcon = normalizedImagePath('../../../img/global/icons/search.svg'); + +class SearchBase extends React.Component { + constructor (props) { + super(props); + this.state = { searchText: '' }; + } + + handleInputChange = (event) => { + this.setState({ searchText: event.target.value }, () => { + if (this.props.onChange) { + this.props.onChange(event); + } + if (this.props.onKeyDown) { + this.props.onKeyDown(event); + } + if (this.props.onFocus) { + this.props.onFocus(event); + } + }); + } + + handleClear = () => { + this.setState({ searchText: '' }, () => { + if (this.props.onClear) { + this.props.onClear(); + } + }); + } + + render () { + return ( + + {!this.state.searchText && } + + {this.state.searchText && } + + ); + } +} +SearchBase.propTypes = { + placeholder: PropTypes.string, + onChange: PropTypes.func, + onKeyDown: PropTypes.func, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + onClear: PropTypes.func, +}; + +const SearchBaseWrapper = styled('div')` + position: relative; + display: inline-block; + width: 100%; +`; + +const SearchIcon = styled('div')` + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + color: gray; + background-image: url(${searchIcon}); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + width: 24px; + height: 24px; +`; + +const ClearButton = styled('div')` + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + background-image: url(${closeIcon}); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + width: 18px; + height: 18px; + cursor: pointer; +`; + +const SearchInput = styled('input')` + &::-webkit-search-decoration, + &::-webkit-search-cancel-button, + &::-webkit-search-results-button, + &::-webkit-search-results-decoration { + display: none; + } + + border: 1px solid rgb(206, 212, 218); + height: 38px; + width: 100%; + border-radius: 0.25rem; + padding-right: 40px; + padding-left: 12px; + + + &:focus-visible { + border: none; + outline: ${colors.primary} solid 2px !important; + } +`; + +export default SearchBase; diff --git a/src/js/common/components/Style/Colors.js b/src/js/common/components/Style/Colors.js new file mode 100644 index 0000000..60255b7 --- /dev/null +++ b/src/js/common/components/Style/Colors.js @@ -0,0 +1,17 @@ +// Please also see DesignTokenColors.js +const colors = { + primary: '#0834CD', + primary2024: '#053C6D', + // primary50: '#E6F3FF', // Moved to DesignTokenColors + primaryHover: '#09288A', + secondaryHover: '#F5F7FD', + darkGrey: '#454F69', + middleGrey: '#5E5E5B', + grey: '#AEB2BE', + lightGrey: '#E5E6EA', + ultraLightGrey: '#FAFAFA', + white: '#ffffff', + green: '#007800', +}; + +export default colors; diff --git a/src/js/components/Drawers/AddPersonDrawer.jsx b/src/js/components/Drawers/AddPersonDrawer.jsx new file mode 100644 index 0000000..f2045af --- /dev/null +++ b/src/js/components/Drawers/AddPersonDrawer.jsx @@ -0,0 +1,97 @@ +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { withStyles } from '@mui/styles'; +import DrawerTemplateA from './DrawerTemplateA'; +import AppObservableStore, { messageService } from '../../stores/AppObservableStore'; +import PersonStore from '../../stores/PersonStore'; +// import TeamActions from '../actions/TeamActions'; +import TeamStore from '../../stores/TeamStore'; +import SearchBar2024 from '../../common/components/Search/SearchBar2024'; +import { renderLog } from '../../common/utils/logging'; + + +const AddPersonDrawer = ({ classes }) => { // classes, teamId + renderLog('AddPersonDrawer'); // Set LOG_RENDER_EVENTS to log all renders + const [mainContentJsx, setMainContentJsx] = React.useState(<>); + const [headerTitleJsx, setHeaderTitleJsx] = React.useState(<>); + const [headerFixedJsx, setHeaderFixedJsx] = React.useState(<>); + const [searchText, setSearchText] = React.useState(''); + const [teamId, setTeamId] = React.useState(-1); + + const onAppObservableStoreChange = () => { + setTeamId(AppObservableStore.getGlobalVariableState('addPersonDrawerTeamId')); + }; + + const onRetrieveTeamChange = () => { + }; + + const onPersonStoreChange = () => { + onRetrieveTeamChange(); + }; + + const onTeamStoreChange = () => { + onRetrieveTeamChange(); + }; + + const searchFunction = (incomingSearchText) => { + setSearchText(incomingSearchText); + }; + + const clearFunction = () => { + setSearchText(''); + } + + React.useEffect(() => { + const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); + onAppObservableStoreChange(); + const personStoreListener = PersonStore.addListener(onPersonStoreChange); + onPersonStoreChange(); + const teamStoreListener = TeamStore.addListener(onTeamStoreChange); + onTeamStoreChange(); + + setHeaderTitleJsx(<>Add Team Member); + setMainContentJsx( + + + + + + ); + + return () => { + appStateSubscription.unsubscribe(); + personStoreListener.remove(); + teamStoreListener.remove(); + }; + }, []); + + return ( + + ); +}; +AddPersonDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const styles = () => ({ +}); + +const AddPersonDrawerWrapper = styled('div')` +`; + +const SearchBarWrapper = styled('div')` + margin-bottom: 16px; +`; + +export default withStyles(styles)(AddPersonDrawer); diff --git a/src/js/components/Drawers/DrawerTemplateA.jsx b/src/js/components/Drawers/DrawerTemplateA.jsx new file mode 100644 index 0000000..6dc711b --- /dev/null +++ b/src/js/components/Drawers/DrawerTemplateA.jsx @@ -0,0 +1,190 @@ +import { Close } from '@mui/icons-material'; // Info +import { Drawer, IconButton } from '@mui/material'; +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { withStyles } from '@mui/styles'; +import AppObservableStore, { messageService } from '../../stores/AppObservableStore'; +import { DrawerHeaderAnimateDownInnerContainer, DrawerHeaderAnimateDownOuterContainer, DrawerTitle, DrawerHeaderWrapper } from '../Style/drawerLayoutStyles'; +import { cordovaDrawerTopMargin } from '../../utils/cordovaOffsets'; +import { hasIPhoneNotch } from '../../common/utils/cordovaUtils'; +import { renderLog } from '../../common/utils/logging'; + + +const DrawerTemplateA = ({ classes, drawerOpenGlobalVariableName, headerFixedJsx, headerTitleJsx, mainContentJsx }) => { // classes, teamId + renderLog('DrawerTemplateA'); // Set LOG_RENDER_EVENTS to log all renders + const [drawerOpen, setDrawerOpen] = React.useState(false); + const [scrolledDown, setScrolledDown] = React.useState(false); + + const handleScrolledDownDrawer = (evt) => { + const { scrollTop } = evt.target; + if (scrollTop > 200 && !AppObservableStore.getScrolledDownDrawer()) { + AppObservableStore.setScrolledDownDrawer(true); + } + if (scrollTop < 200 && AppObservableStore.getScrolledDownDrawer()) { + AppObservableStore.setScrolledDownDrawer(false); + } + }; + + const onAppObservableStoreChange = () => { + setScrolledDown(AppObservableStore.getScrolledDownDrawer()); + setDrawerOpen(AppObservableStore.getGlobalVariableState(drawerOpenGlobalVariableName)); + }; + + React.useEffect(() => { + const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); + onAppObservableStoreChange(); + + setTimeout(() => { + const drawer = document.querySelector('.MuiDrawer-paper'); + if (drawer) { + drawer.addEventListener('scroll', handleScrolledDownDrawer); + } else { + console.log('Drawer element NOT found make timeout longer.'); + } + }, 100); + + return () => { + appStateSubscription.unsubscribe(); + }; + }, []); + + return ( + AppObservableStore.setGlobalVariableState(drawerOpenGlobalVariableName, false)} + open={drawerOpen} + > + + + {headerTitleJsx} + + + AppObservableStore.setGlobalVariableState(drawerOpenGlobalVariableName, false)} + size="large" + > + + + + + + + + + {headerFixedJsx} + + + + {mainContentJsx} + + + ); +}; +DrawerTemplateA.propTypes = { + classes: PropTypes.object.isRequired, + drawerOpenGlobalVariableName: PropTypes.string, + mainContentJsx: PropTypes.object, + headerTitleJsx: PropTypes.object, + headerFixedJsx: PropTypes.object, +}; + +const styles = () => ({ + drawer: { + marginTop: cordovaDrawerTopMargin(), + maxWidth: '550px !important', + '& *': { + maxWidth: '550px !important', + }, + '@media(max-width: 576px)': { + maxWidth: '360px !important', + '& *': { + maxWidth: '360px !important', + }, + }, + }, + dialogPaper: { + display: 'block', + marginTop: hasIPhoneNotch() ? 68 : 48, + minWidth: '100%', + maxWidth: '100%', + width: '100%', + minHeight: '100%', + maxHeight: '100%', + height: '100%', + margin: '0 auto', + '@media (min-width: 577px)': { + maxWidth: '550px', + width: '90%', + height: 'fit-content', + margin: '0 auto', + minWidth: 0, + minHeight: 0, + transitionDuration: '.25s', + }, + '@media (max-width: 576px)': { + maxWidth: '360px', + }, + }, + dialogContent: { + padding: '24px 24px 36px 24px', + background: 'white', + height: 'fit-content', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + '@media(max-width: 576px)': { + justifyContent: 'flex-start !important', + }, + }, + backButton: { + // marginBottom: 6, + // marginLeft: -8, + paddingTop: 0, + paddingBottom: 0, + }, + backButtonIcon: { + fontSize: 14, + fontWeight: 'bold', + }, + closeButton: { + marginRight: 'auto', + padding: 6, + }, + closeButtonAbsolute: { + position: 'absolute', + right: 14, + top: 14, + }, + closeIcon: { + color: '#999', + width: 24, + height: 24, + }, + informationIcon: { + color: '#999', + width: 16, + height: 16, + marginTop: '-3px', + marginRight: 4, + }, +}); + +const DrawerTemplateAWrapper = styled('div')` + margin: 0 15px; + min-width: 300px; +`; + +const CloseDrawerIconWrapper = styled('div')` + display: flex; + justify-content: flex-end; +`; + +export default withStyles(styles)(DrawerTemplateA); diff --git a/src/js/components/Drawers/Drawers.jsx b/src/js/components/Drawers/Drawers.jsx new file mode 100644 index 0000000..130aab1 --- /dev/null +++ b/src/js/components/Drawers/Drawers.jsx @@ -0,0 +1,44 @@ +import React, { Suspense } from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { withStyles } from '@mui/styles'; +import AddPersonDrawer from './AddPersonDrawer'; +import { messageService } from '../../stores/AppObservableStore'; +import { renderLog } from '../../common/utils/logging'; + + +const Drawers = () => { // classes, teamId + renderLog('Drawers'); // Set LOG_RENDER_EVENTS to log all renders + + const onAppObservableStoreChange = () => { + }; + + React.useEffect(() => { + const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); + onAppObservableStoreChange(); + + return () => { + appStateSubscription.unsubscribe(); + }; + }, []); + + return ( + }> + + + ); +}; +Drawers.propTypes = { + match: PropTypes.object, +}; + +const styles = () => ({ +}); + +const SearchBarWrapper = styled('div')` + // margin-top: 14px; + // margin-bottom: 8px; + width: 100%; +`; + +export default withStyles(styles)(Drawers); diff --git a/src/js/components/Navigation/Footer.jsx b/src/js/components/Navigation/Footer.jsx index 32d2db9..e7ad128 100644 --- a/src/js/components/Navigation/Footer.jsx +++ b/src/js/components/Navigation/Footer.jsx @@ -3,6 +3,7 @@ import React, { Component, Suspense } from 'react'; import styled from 'styled-components'; import { normalizedHref } from '../../common/utils/hrefUtils'; import { isWebApp } from '../../common/utils/isCordovaOrWebApp'; +import { renderLog } from '../../common/utils/logging'; import { handleResize } from '../../common/utils/isMobileScreenSize'; import AppObservableStore, { messageService } from '../../stores/AppObservableStore'; import { getApplicationViewBooleans } from '../../utils/applicationUtils'; @@ -122,6 +123,7 @@ class Footer extends Component { } render () { + renderLog('Footer'); // Set LOG_RENDER_EVENTS to log all renders const { /* doShowHeader, doShowFooter, */ showFooterBar, showFooterMain } = this.state; // console.log('Footer render showFooterMain:', showFooterMain); return ( diff --git a/src/js/components/Style/drawerLayoutStyles.js b/src/js/components/Style/drawerLayoutStyles.js new file mode 100644 index 0000000..1a1b3d6 --- /dev/null +++ b/src/js/components/Style/drawerLayoutStyles.js @@ -0,0 +1,69 @@ +import styled, { css, keyframes } from 'styled-components'; +import standardBoxShadow from '../../common/components/Style/standardBoxShadow'; +import { cordovaBallotFilterTopMargin } from '../../utils/cordovaOffsets'; + +const slideIn = keyframes` + from { + transform: translateY(-100%); + } + to { + transform: translateY(0); + } +`; + +export const DrawerHeaderAnimateDownOuterContainer = styled.div.attrs(({ scrolledDown }) => ({ + style: { + display: scrolledDown ? 'block' : 'hidden', + }, +}))` + width: 100%; + background-color: #fff; + overflow: hidden; + position: fixed; + z-index: 9000; + right: 0; + transform: translateY(${({ scrolledDown }) => (scrolledDown ? '0' : '-100%')}); + transition: transform 0.3s ease-in-out; + visibility: ${({ scrolledDown }) => (scrolledDown ? 'visible' : 'hidden')}; + opacity: ${({ scrolledDown }) => (scrolledDown ? 1 : 0)}; + + ${({ scrolledDown }) => scrolledDown && + css` + animation: ${slideIn} 0.3s ease-out; + border-bottom: 1px solid #aaa; + box-shadow: ${standardBoxShadow('wide')}; + `} +`; +export const DrawerHeaderAnimateDownInnerContainer = styled('div')` + display: flex; + flex-direction: column; + justify-content: center; + padding: 10px; +`; + +export const DrawerHeaderContentContainer = styled('div')(({ theme }) => (` + margin: ${() => cordovaBallotFilterTopMargin()} auto 0 auto; + position: relative; + max-width: 960px; + width: 100%; + z-index: 0; + ${theme.breakpoints.down('sm')} { + min-height: 10px; + //margin: 0 10px; + } +`)); + +export const DrawerTitle = styled('div')` + font-weight: bold; + margin: 0; + text-align: left; + padding-left: 16px; +`; + +export const DrawerHeaderWrapper = styled('div')` + display: flex; + justify-content: space-between; + align-items: center; + margin-right: 0; + min-height: 28px; +`; diff --git a/src/js/components/Style/muiStyleOverrides.js b/src/js/components/Style/muiStyleOverrides.js index 6c94e29..90ef424 100644 --- a/src/js/components/Style/muiStyleOverrides.js +++ b/src/js/components/Style/muiStyleOverrides.js @@ -40,6 +40,16 @@ const muiStyleOverrides = { }, }, }, + MuiDrawer: { + styleOverrides: { + root: { + fontFamily: '"Poppins", "Helvetica Neue Light", "Helvetica Neue", "Helvetica", "Arial", sans-serif', + fontSize: 16, + outline: 'none !important', + textTransform: 'none', + }, + }, + }, MuiFormControlLabel: { styleOverrides: { root: { diff --git a/src/js/pages/PageNotFound.jsx b/src/js/pages/PageNotFound.jsx index 6122dee..58146bc 100644 --- a/src/js/pages/PageNotFound.jsx +++ b/src/js/pages/PageNotFound.jsx @@ -51,6 +51,18 @@ PageNotFound.propTypes = { classes: PropTypes.object, }; +const styles = (theme) => ({ + ballotButtonIconRoot: { + marginRight: 8, + }, + ballotButtonRoot: { + width: 250, + [theme.breakpoints.down('md')]: { + width: '100%', + }, + }, +}); + const Wrapper = styled('div')(({ theme }) => (` ${theme.breakpoints.down('md')} { margin: 1em 0; @@ -73,16 +85,4 @@ const EmptyBallotText = styled('p')(({ theme }) => (` } `)); -const styles = (theme) => ({ - ballotButtonIconRoot: { - marginRight: 8, - }, - ballotButtonRoot: { - width: 250, - [theme.breakpoints.down('md')]: { - width: '100%', - }, - }, -}); - export default withStyles(styles)(PageNotFound); diff --git a/src/js/pages/TeamMembers.jsx b/src/js/pages/TeamMembers.jsx index 8f16829..cbd5a69 100644 --- a/src/js/pages/TeamMembers.jsx +++ b/src/js/pages/TeamMembers.jsx @@ -1,16 +1,20 @@ +import { Button } from '@mui/material'; import React from 'react'; import { Helmet } from 'react-helmet-async'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import { withStyles } from '@mui/styles'; import AppObservableStore, { messageService } from '../stores/AppObservableStore'; +import PersonStore from '../stores/PersonStore'; import TeamActions from '../actions/TeamActions'; import TeamStore from '../stores/TeamStore'; import { PageContentContainer } from '../components/Style/pageLayoutStyles'; import webAppConfig from '../config'; +import { renderLog } from '../common/utils/logging'; -const TeamMembers = ({ match }) => { // classes, teamId +const TeamMembers = ({ classes, match }) => { // classes, teamId + renderLog('TeamMembers'); // Set LOG_RENDER_EVENTS to log all renders const [teamId, setTeamId] = React.useState(-1); const [teamMemberList, setTeamMemberList] = React.useState([]); const [teamMemberCount, setTeamMemberCount] = React.useState(0); @@ -18,28 +22,43 @@ const TeamMembers = ({ match }) => { // classes, teamId const onAppObservableStoreChange = () => { }; - const onTeamStoreChange = () => { - const teamMemberListTemp = TeamStore.getTeamMemberList(teamId); + const onRetrieveTeamChange = () => { + const { params } = match; + setTeamId(params.teamId); + const teamMemberListTemp = TeamStore.getTeamMemberList(params.teamId); + // console.log('TeamMembers onRetrieveTeamChange, params.teamId:', params.teamId, ', TeamStore.getTeamMemberList:', teamMemberListTemp); setTeamMemberList(teamMemberListTemp); setTeamMemberCount(teamMemberListTemp.length); }; + const onPersonStoreChange = () => { + onRetrieveTeamChange(); + }; + + const onTeamStoreChange = () => { + onRetrieveTeamChange(); + }; + + const addTeamMemberClick = () => { + AppObservableStore.setGlobalVariableState('addPersonDrawerOpen', true); + AppObservableStore.setGlobalVariableState('addPersonDrawerTeamId', true); + }; + React.useEffect(() => { - const { params: { - teamId: teamIdIncoming, - } } = match; - setTeamId(teamIdIncoming); + const { params } = match; - console.log('Fetching team members for:', teamIdIncoming); const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); onAppObservableStoreChange(); + const personStoreListener = PersonStore.addListener(onPersonStoreChange); + onPersonStoreChange(); const teamStoreListener = TeamStore.addListener(onTeamStoreChange); onTeamStoreChange(); - TeamActions.teamRetrieve(teamIdIncoming); + TeamActions.teamRetrieve(params.teamId); return () => { appStateSubscription.unsubscribe(); + personStoreListener.remove(); teamStoreListener.remove(); }; }, []); @@ -56,9 +75,24 @@ const TeamMembers = ({ match }) => { // classes, teamId - Team Members ( - {teamMemberCount} - ) +
+ Team Members ( + {teamMemberCount} + ) +
+ + {teamMemberList.map((teamMember) => ( +
+ {teamMember.firstName} +
+ ))} {pigsCanFly && ( Team Members will fly here )} @@ -72,13 +106,15 @@ TeamMembers.propTypes = { match: PropTypes.object.isRequired, }; -const styles = () => ({ - buttonDesktop: { - padding: '2px 16px', - borderRadius: 5, +const styles = (theme) => ({ + ballotButtonIconRoot: { + marginRight: 8, }, - searchButton: { - borderRadius: 50, + addTeamMemberButtonRoot: { + width: 100, + [theme.breakpoints.down('md')]: { + width: '100%', + }, }, }); diff --git a/src/js/stores/AppObservableStore.js b/src/js/stores/AppObservableStore.js index 65e3d5f..3a83939 100644 --- a/src/js/stores/AppObservableStore.js +++ b/src/js/stores/AppObservableStore.js @@ -21,7 +21,8 @@ export const messageService = { const nonFluxState = { activityTidbitWeVoteIdForDrawer: '', - blockCampaignXRedirectOnSignIn: false, // When signing in from the header, don't mark a campaign as supported + addPersonDrawerOpen: false, + addPersonDrawerTeamId: -1, // Team ID used when adding a new person blockChallengeRedirectOnSignIn: false, // When signing in from the header, don't mark a challenge as supported challengeParticipantNameWithHighestRankByChallengeWeVoteId: {}, // Key is challengeWeVoteId, value is name for voter with the highest rank for that challenge challengeParticipantRankOfVoterByChallengeWeVoteId: {}, // Key is challengeWeVoteId, value is rank of voter for that challenge @@ -42,7 +43,6 @@ const nonFluxState = { hideWeVoteLogo: false, hostname: '', observableUpdateCounter: 0, - organizationModalBallotItemWeVoteId: '', openReplayEnabled: false, openReplayPending: false, openReplayTracker: undefined, @@ -90,10 +90,6 @@ const nonFluxState = { export default { - blockCampaignXRedirectOnSignIn () { - return nonFluxState.blockCampaignXRedirectOnSignIn; - }, - blockChallengeRedirectOnSignIn () { return nonFluxState.blockChallengeRedirectOnSignIn; }, @@ -158,12 +154,8 @@ export default { return nonFluxState.currentPathname; }, - getPositionDrawerBallotItemWeVoteId () { - return nonFluxState.positionDrawerBallotItemWeVoteId; - }, - - getPositionDrawerOrganizationWeVoteId () { - return nonFluxState.positionDrawerOrganizationWeVoteId; + getGlobalVariableState (globalVariableName) { + return nonFluxState[globalVariableName]; }, getGoogleAnalyticsEnabled () { @@ -210,10 +202,6 @@ export default { return nonFluxState.hostname || ''; }, - getOrganizationModalBallotItemWeVoteId () { - return nonFluxState.organizationModalBallotItemWeVoteId; - }, - getPendingSnackMessage () { return nonFluxState.pendingSnackMessage; }, @@ -373,11 +361,6 @@ export default { messageService.sendMessage('state updated activityTidbitWeVoteIdForDrawerAndOpen'); }, - setBlockCampaignXRedirectOnSignIn (value) { - nonFluxState.blockCampaignXRedirectOnSignIn = value; - messageService.sendMessage('state updated blockCampaignXRedirectOnSignIn'); - }, - setBlockChallengeRedirectOnSignIn (value) { nonFluxState.blockChallengeRedirectOnSignIn = value; messageService.sendMessage('state updated blockChallengeRedirectOnSignIn'); @@ -411,6 +394,11 @@ export default { messageService.sendMessage('state updated getStartedMode'); }, + setGlobalVariableState (globalVariableName, newState) { + nonFluxState[globalVariableName] = newState; + messageService.sendMessage(`state updated ${globalVariableName}`); + }, + setHideOrganizationModalBallotItemInfo (hide) { nonFluxState.hideOrganizationModalBallotItemInfo = hide; messageService.sendMessage('state updated hideOrganizationModalBallotItemInfo'); @@ -466,26 +454,11 @@ export default { messageService.sendMessage('state updated openReplayVoterWeVoteId'); }, - setOrganizationModalBallotItemWeVoteId (ballotItemWeVoteId) { - nonFluxState.organizationModalBallotItemWeVoteId = ballotItemWeVoteId; - messageService.sendMessage('state updated organizationModalBallotItemWeVoteId'); - }, - setPendingSnackMessage (message, severity) { nonFluxState.pendingSnackMessage = message; nonFluxState.pendingSnackSeverity = severity; }, - setPositionDrawerBallotItemWeVoteId (ballotItemWeVoteId) { - nonFluxState.positionDrawerBallotItemWeVoteId = ballotItemWeVoteId; - messageService.sendMessage('state updated positionDrawerBallotItemWeVoteId'); - }, - - setPositionDrawerOrganizationWeVoteId (organizationWeVoteId) { - nonFluxState.positionDrawerOrganizationWeVoteId = organizationWeVoteId; - messageService.sendMessage('state updated positionDrawerOrganizationWeVoteId'); - }, - setRecommendedCampaignListFirstRetrieveInitiated (value) { nonFluxState.recommendedCampaignListFirstRetrieveInitiated = value; messageService.sendMessage('state updated recommendedCampaignListFirstRetrieveInitiated'); diff --git a/src/js/stores/PersonStore.js b/src/js/stores/PersonStore.js index 35bf3f3..b10ef47 100644 --- a/src/js/stores/PersonStore.js +++ b/src/js/stores/PersonStore.js @@ -1,6 +1,7 @@ import { ReduceStore } from 'flux/utils'; import Dispatcher from '../common/dispatcher/Dispatcher'; import Cookies from '../common/utils/js-cookie/Cookies'; +import arrayContains from '../common/utils/arrayContains'; class PersonStore extends ReduceStore { getInitialState () { @@ -44,6 +45,7 @@ class PersonStore extends ReduceStore { getPersonById (personId) { const { allCachedPeopleDict } = this.getState(); + // console.log('PersonStore getPersonById:', personId, ', allCachedPeopleDict:', allCachedPeopleDict); return allCachedPeopleDict[personId] || {}; } @@ -57,10 +59,39 @@ class PersonStore extends ReduceStore { } reduce (state, action) { + let { allCachedPeopleDict } = state; + let revisedState = state; + let teamId = -1; + let teamMemberList = []; + switch (action.type) { - case 'clearEmailAddressStatus': - // console.log('VoterStore clearEmailAddressStatus'); - return { ...state, emailAddressStatus: {} }; + case 'team-retrieve': + if (!action.res.success) { + console.log('PersonStore ', action.type, ' FAILED action.res:', action.res); + return state; + } + teamId = action.res.teamId || -1; + teamMemberList = action.res.teamMemberList || []; + revisedState = state; + + // console.log('PersonStore team-retrieve start allCachedPeopleDict:', allCachedPeopleDict); + if (!allCachedPeopleDict) { + allCachedPeopleDict = {}; + } + if (teamId > 0) { + teamMemberList.forEach((person) => { + // console.log('PersonStore team-retrieve adding person:', person); + if (person && (person.id >= 0) && !arrayContains(person.id, allCachedPeopleDict)) { + allCachedPeopleDict[person.id] = person; + } + }); + // console.log('allCachedPeopleDict:', allCachedPeopleDict); + revisedState = { + ...revisedState, + allCachedPeopleDict, + }; + } + return revisedState; default: return state; diff --git a/src/js/stores/TeamStore.js b/src/js/stores/TeamStore.js index 80a457e..921729d 100644 --- a/src/js/stores/TeamStore.js +++ b/src/js/stores/TeamStore.js @@ -33,6 +33,7 @@ class TeamStore extends ReduceStore { const teamMemberList = []; for (let i = 0; i < personIdList.length; i++) { const person = PersonStore.getPersonById(personIdList[i]); + // console.log('TeamStore getTeamMemberList person:', person); if (person) { teamMemberList.push(person); } @@ -70,7 +71,7 @@ class TeamStore extends ReduceStore { } }); allCachedTeamMembersDict[teamId] = teamMemberIdList; - console.log('allCachedTeamMembersDict:', allCachedTeamMembersDict); + // console.log('allCachedTeamMembersDict:', allCachedTeamMembersDict); // console.log('allCachedOrganizationsDict:', allCachedOrganizationsDict); revisedState = { ...revisedState,