Skip to content

Commit

Permalink
Merge pull request #3 from DaleMcGrew/Dale_WC_Dec7-2024
Browse files Browse the repository at this point in the history
Added first Drawer template, and a better way to include all Drawers in App (instead of in Header). Displaying simple list of Team Members on new TeamMembers page. Created more-maintainable way of setting / getting global variables with getGlobalVariableState and setGlobalVariableState. Storing data from team-retrieve API in both PersonStore and TeamStore.
  • Loading branch information
DaleMcGrew authored Dec 8, 2024
2 parents f1fdbd4 + c160482 commit 179b246
Show file tree
Hide file tree
Showing 18 changed files with 787 additions and 73 deletions.
6 changes: 5 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ 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';
// importRemoveCordovaListenersToken1 -- Do not remove this line!

// 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'));

Expand Down Expand Up @@ -267,6 +268,9 @@ class App extends Component {
<Suspense fallback={<HeaderBarSuspense />}>
<Header hideHeader={hideHeader} params={{ }} pathname={normalizedHref()} />
</Suspense>
<Suspense fallback={<></>}>
<Drawers />
</Suspense>
<Suspense fallback={<LoadingWheelComp />}>
<Switch>
<Route path="/faq" exact><FAQ /></Route>
Expand Down
3 changes: 3 additions & 0 deletions src/img/global/icons/cross.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/img/global/icons/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
<div>
<div id="app">
<div id="loading-screen">
<div style="display: flex; position: fixed; height: 100vh; width: 100vw; top: 0; left: 0; background-color: #fff; justify-content: center; align-items: center; font-size: 20px; color: #2E3C5D; flex-direction: column; font-family: 'Source Sans Pro', sans-serif; text-align: center;">
<h1 style="font-family: 'Source Sans Pro', sans-serif; font-size: 32px; font-weight: normal; color: #2E3C5D;">Loading WeConnect...</h1>
<div style="display: flex; position: fixed; height: 100vh; width: 100vw; top: 0; left: 0; background-color: #fff; justify-content: center; align-items: center; font-size: 20px; color: #2E3C5D; flex-direction: column; font-family: 'Helvetica', sans-serif; text-align: center;">
<h1 style="font-family: 'Helvetica', sans-serif; font-size: 32px; font-weight: normal; color: #2E3C5D;">Loading WeConnect...</h1>
<div style="margin: 0 15px; text-align: center;">Thank you for being part of WeVote!</div>
</div>
</div>
Expand Down
114 changes: 114 additions & 0 deletions src/js/common/components/Search/SearchBar2024.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<SearchBar2024Wrapper>
<SearchBase
id="search_input"
placeholder={placeholder}
value={searchString}
onKeyDown={this.handleKeyPress}
onChange={this.updateResults}
onFocus={() => focusTextFieldAndroid('SearchBar2024')}
onBlur={blurTextFieldAndroid}
onClear={this.clearQuery}
/>
</SearchBar2024Wrapper>
);
}
}
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;
120 changes: 120 additions & 0 deletions src/js/common/components/Search/SearchBase.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<SearchBaseWrapper>
{!this.state.searchText && <SearchIcon />}
<SearchInput
type="search"
placeholder={this.props.placeholder}
value={this.state.searchText}
onBlur={this.props.onBlur}
onChange={this.handleInputChange}
maxLength={50}
/>
{this.state.searchText && <ClearButton onClick={this.handleClear} />}
</SearchBaseWrapper>
);
}
}
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;
17 changes: 17 additions & 0 deletions src/js/common/components/Style/Colors.js
Original file line number Diff line number Diff line change
@@ -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;
97 changes: 97 additions & 0 deletions src/js/components/Drawers/AddPersonDrawer.jsx
Original file line number Diff line number Diff line change
@@ -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(
<AddPersonDrawerWrapper>
<SearchBarWrapper>
<SearchBar2024
placeholder="Search by name"
searchFunction={searchFunction}
clearFunction={clearFunction}
searchUpdateDelayTime={250}
/>
</SearchBarWrapper>
</AddPersonDrawerWrapper>
);

return () => {
appStateSubscription.unsubscribe();
personStoreListener.remove();
teamStoreListener.remove();
};
}, []);

return (
<DrawerTemplateA
drawerOpenGlobalVariableName={'addPersonDrawerOpen'}
mainContentJsx={mainContentJsx}
headerTitleJsx={headerTitleJsx}
headerFixedJsx={headerFixedJsx}
/>
);
};
AddPersonDrawer.propTypes = {
classes: PropTypes.object.isRequired,
};

const styles = () => ({
});

const AddPersonDrawerWrapper = styled('div')`
`;

const SearchBarWrapper = styled('div')`
margin-bottom: 16px;
`;

export default withStyles(styles)(AddPersonDrawer);
Loading

0 comments on commit 179b246

Please sign in to comment.