From c3b5204f15fedaa399b5886d283a66a47aab59e3 Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Thu, 9 Jan 2025 09:55:17 -0800 Subject: [PATCH 1/8] [WV-659] & [WV-660] VoterPositionEntryAndDisplay: Open modal when you click "What's your opinion?" & VoterPositionEntryAndDisplay: Open modal to edit name & add photo [WV-659] & [WV-660] Implement VoterPositionEntryAndDisplay with Related Modals - Created a new functional component `VoterPositionEntryAndDisplay` to manage user opinions and enable modal interactions. - Implemented two related modals: 1. "What's your opinion?" modal for adding or editing opinions (`WebApp/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx`). 2. Edit Name & Add Photo modal for updating user details (`WebApp/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx`). - Managed state for visibility, opinion text, and user details dynamically using `VoterStore`. - Styled components to align with the design system, utilizing `DesignTokenColors` for consistent theming. - Both modals were included in the same commit since one opens the other, ensuring seamless integration. Notes: - Future updates to each modal can be made independently in different branches i jest decided to show it and push commit together for this two modals. --- src/img/global/icons/drop-down-icon.svg | 3 + .../components/Activity/ActivityPostAdd.jsx | 26 +- .../Activity/ActivityPostPublicDropdown.jsx | 89 +++++ .../VoterPositionEditNameAndPhotoModal.jsx | 170 ++++++++++ .../VoterPositionEntryAndDisplay.jsx | 226 +++++++++++++ .../Widgets/ModalDisplayTemplateB.jsx | 313 ++++++++++++++++++ 6 files changed, 814 insertions(+), 13 deletions(-) create mode 100644 src/img/global/icons/drop-down-icon.svg create mode 100644 src/js/components/Activity/ActivityPostPublicDropdown.jsx create mode 100644 src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx create mode 100644 src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx create mode 100644 src/js/components/Widgets/ModalDisplayTemplateB.jsx diff --git a/src/img/global/icons/drop-down-icon.svg b/src/img/global/icons/drop-down-icon.svg new file mode 100644 index 000000000..d756f3768 --- /dev/null +++ b/src/img/global/icons/drop-down-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/js/components/Activity/ActivityPostAdd.jsx b/src/js/components/Activity/ActivityPostAdd.jsx index 68b5ae954..50f44eed1 100644 --- a/src/js/components/Activity/ActivityPostAdd.jsx +++ b/src/js/components/Activity/ActivityPostAdd.jsx @@ -1,22 +1,22 @@ +import React, { Component, Suspense } from 'react'; import { Card, InputBase } from '@mui/material'; import styled from 'styled-components'; import withStyles from '@mui/styles/withStyles'; import withTheme from '@mui/styles/withTheme'; import PropTypes from 'prop-types'; -import React, { Component, Suspense } from 'react'; import { isCordova } from '../../common/utils/isCordovaOrWebApp'; import { renderLog } from '../../common/utils/logging'; import VoterStore from '../../stores/VoterStore'; import { avatarGeneric } from '../../utils/applicationUtils'; import { cordovaNewsPaddingTop } from '../../utils/cordovaOffsets'; -const ActivityPostModal = React.lazy(() => import(/* webpackChunkName: 'ActivityPostModal' */ './ActivityPostModal')); +const VoterPositionEntryAndDisplay = React.lazy(() => import(/* webpackChunkName: 'VoterPositionEntryAndDisplay' */ '../PositionItem/VoterPositionEntryAndDisplay')); class ActivityPostAdd extends Component { constructor (props) { super(props); this.state = { - showActivityPostModal: false, + showVoterPositionEntryAndDisplay: false, statementText: '', }; this.updateStatementTextToBeSaved = this.updateStatementTextToBeSaved.bind(this); @@ -62,11 +62,11 @@ class ActivityPostAdd extends Component { }); } - toggleActivityPostModal = () => { - const { showActivityPostModal } = this.state; - // console.log('toggleActivityPostModal showActivityPostModal:', showActivityPostModal); + toggleVoterPositionEntryAndDisplay = () => { + const { showVoterPositionEntryAndDisplay } = this.state; + // console.log('toggleVoterPositionEntryAndDisplay showVoterPositionEntryAndDisplay:', showVoterPositionEntryAndDisplay); this.setState({ - showActivityPostModal: !showActivityPostModal, + showVoterPositionEntryAndDisplay: !showVoterPositionEntryAndDisplay, }); } @@ -80,7 +80,7 @@ class ActivityPostAdd extends Component { renderLog('ActivityPostAdd'); // Set LOG_RENDER_EVENTS to log all renders const { classes, externalUniqueId, activityTidbitWeVoteId } = this.props; const { - showActivityPostModal, + showVoterPositionEntryAndDisplay, voterPhotoUrlMedium, statementText, } = this.state; @@ -137,19 +137,19 @@ class ActivityPostAdd extends Component { inputRef={(tag) => { this.textarea = tag; }} multiline name="statementText" - onClick={this.toggleActivityPostModal} + onClick={this.toggleVoterPositionEntryAndDisplay} onFocus={this.handleFocus} placeholder={statementPlaceholderText} rows="1" /> - {showActivityPostModal && ( + {showVoterPositionEntryAndDisplay && ( }> - )} diff --git a/src/js/components/Activity/ActivityPostPublicDropdown.jsx b/src/js/components/Activity/ActivityPostPublicDropdown.jsx new file mode 100644 index 000000000..3c8d5d3a2 --- /dev/null +++ b/src/js/components/Activity/ActivityPostPublicDropdown.jsx @@ -0,0 +1,89 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Select, MenuItem, FormControl } from '@mui/material'; +import { withStyles } from '@mui/styles'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; + +const ActivityPostPublicDropdown = (props) => { + const { visibilityIsPublic, onVisibilityChange, classes } = props; + + const handleVisibilityChange = (event) => { + const value = event.target.value; + onVisibilityChange(value === 'Public'); + }; + + return ( +
+ Opinion visible to: + + + +
+ ); +}; + +ActivityPostPublicDropdown.propTypes = { + visibilityIsPublic: PropTypes.bool.isRequired, + onVisibilityChange: PropTypes.func.isRequired, + classes: PropTypes.object.isRequired, +}; + +const styles = (theme) => ({ + container: { + display: 'flex', + alignItems: 'center', + borderRadius: '4px', + }, + label: { + fontFamily: 'Poppins', + fontSize: '13px', + fontWeight: '400', + lineHeight: '19.5px', + textAlign: 'left', + color: 'var(--Neutral-900, #2A2A2C)', + }, + formControl: { + minWidth: '120px', + }, + selectVisibility: { + fontFamily: 'Nunito', + fontSize: '16px', + fontWeight: '400', + lineHeight: '21.82px', + color: DesignTokenColors.neutralUI900, + padding: '0 8px', + border: 'none', + boxShadow: 'none', + outline: 'none', + '&:focus': { + outline: 'none', + boxShadow: 'none', + }, + '& .MuiOutlinedInput-notchedOutline': { + border: 'none', + }, + }, + menuItem: { + fontFamily: 'Nunito', + fontSize: '16px', + fontWeight: '400', + lineHeight: '21.82px', + color: DesignTokenColors.neutralUI900, + }, +}); + +export default withStyles(styles)(ActivityPostPublicDropdown); diff --git a/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx b/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx new file mode 100644 index 000000000..03c2879be --- /dev/null +++ b/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx @@ -0,0 +1,170 @@ +import React, { useState } from 'react'; +import { Dialog, DialogTitle, DialogContent, Button, TextField, IconButton } from '@mui/material'; +import { withStyles } from '@mui/styles'; +import PropTypes from 'prop-types'; +import { Close as CloseIcon } from '@mui/icons-material'; +import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; + +const styles = { + modalContent: { + padding: '20px', + }, + uploadSection: { + alignItems: 'center', + border: `3px dashed ${DesignTokenColors.neutral100}`, + borderRadius: '8px', + display: 'flex', + flexDirection: 'column', + marginBottom: '20px', + padding: '20px', + }, + profilePhoto: { + alignItems: 'center', + backgroundColor: DesignTokenColors.neutral50, + borderRadius: '50%', + display: 'flex', + height: '80px', + justifyContent: 'center', + marginBottom: '10px', + width: '80px', + }, + formField: { + color: DesignTokenColors.neutral100, + marginBottom: '15px', + }, + a: { + color: DesignTokenColors.primary500, + }, + closeButton: { + color: DesignTokenColors.neutral100, + position: 'absolute', + right: '8px', + top: '8px', + }, +}; + +const VoterPositionEditNameAndPhotoModal = ({ show, toggleModal, classes }) => { + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [website, setWebsite] = useState(''); + const [description, setDescription] = useState(''); + const [photo, setPhoto] = useState(null); + + const handlePhotoUpload = (event) => { + const file = event.target.files[0]; + if (file && file.type.startsWith('image/')) { + const reader = new FileReader(); + reader.onload = (e) => setPhoto(e.target.result); + reader.readAsDataURL(file); + } else { + alert('Please upload a valid image file.'); + } + }; + + const handleSave = () => { + if (!firstName.trim() || !lastName.trim()) { + alert('First and Last name are required.'); + return; + } + if (website && !website.startsWith('http')) { + alert('Please enter a valid URL starting with http or https.'); + return; + } + // Add logic to save details + toggleModal(); + }; + + return ( + + + Name & Photo Settings + + + + + +

+ We are serious about protecting your information. We are non-profit and never sell information. + {' '} + + Frequently Asked Questions. + +

+
+
+ {photo ? Profile : 'Upload'} +
+ + +
+ setFirstName(e.target.value)} + fullWidth + variant="outlined" + className={classes.formField} + /> + setLastName(e.target.value)} + fullWidth + variant="outlined" + className={classes.formField} + /> + + setWebsite(e.target.value)} + fullWidth + variant="outlined" + className={classes.formField} + /> + setDescription(e.target.value)} + fullWidth + variant="outlined" + className={classes.formField} + multiline + rows={3} + /> + +
+
+ ); +}; + +VoterPositionEditNameAndPhotoModal.propTypes = { + show: PropTypes.bool.isRequired, + toggleModal: PropTypes.func.isRequired, + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(VoterPositionEditNameAndPhotoModal); diff --git a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx new file mode 100644 index 000000000..fd2e69def --- /dev/null +++ b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx @@ -0,0 +1,226 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Button, InputBase, Radio, RadioGroup, FormControlLabel, FormControl } from '@mui/material'; +import { withStyles } from '@mui/styles'; +import PropTypes from 'prop-types'; +import { Edit as EditIcon } from '@mui/icons-material'; +import ActivityActions from '../../actions/ActivityActions'; +import { prepareForCordovaKeyboard, restoreStylesAfterCordovaKeyboard } from '../../common/utils/cordovaUtils'; +import { isAndroid } from '../../common/utils/isCordovaOrWebApp'; +import { renderLog } from '../../common/utils/logging'; +import ActivityStore from '../../stores/ActivityStore'; +import VoterStore from '../../stores/VoterStore'; +import { avatarGeneric } from '../../utils/applicationUtils'; +import ModalDisplayTemplateB, { + templateBStyles, TextFieldDiv, + TextFieldForm, TextFieldWrapper, VoterAvatarImg, + UserInfoWrapper, UserInfoText, UserName, VisibilityText, editIcon, +} from '../Widgets/ModalDisplayTemplateB'; +// import ActivityPostPublicToggle from '../Activity/ActivityPostPublicToggle'; +import ActivityPostPublicDropdown from '../Activity/ActivityPostPublicDropdown'; +import VoterPositionEditNameAndPhotoModal from './VoterPositionEditNameAndPhotoModal'; + +const VoterPositionEntryAndDisplay = (props) => { + const { activityTidbitWeVoteId, classes, externalUniqueId, show, toggleModal } = props; + + // useState used for state variables + const [visibilityIsPublic, setVisibilityIsPublic] = useState(false); + const [voterPhotoUrlMedium, setVoterPhotoUrlMedium] = useState(''); + const [statementText, setStatementText] = useState(''); + const [initialFocusSet, setInitialFocusSet] = useState(false); + const [voterName, setVoterName] = useState(''); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + + const handleEditModalOpen = () => { + setIsEditModalOpen(true); // Open the modal + }; + const handleEditModalClose = () => { + setIsEditModalOpen(false); // Close the modal + }; + + // useRef to reference the post input + const activityPostInputRef = useRef(null); + + const onActivityStoreChange = () => { + const activityPost = ActivityStore.getActivityTidbitByWeVoteId(activityTidbitWeVoteId); + if (activityPost) { + const { statement_text: newStatementText, visibility_is_public: newVisibilityIsPublic } = activityPost; + setVisibilityIsPublic(newVisibilityIsPublic); + setStatementText(newStatementText); + } + }; + + const onVoterStoreChange = () => { + const voter = VoterStore.getVoter(); + setVoterPhotoUrlMedium(voter.voter_photo_url_medium); + setVoterName(voter.full_name || 'Anonymous'); + }; + const [selectedOpinion, setSelectedOpinion] = useState('Neutral'); + + const handleOpinionChange = (event) => { + setSelectedOpinion(event.target.value); + }; + + + // useEffect replaces componentDidMount and componentWillUnmount + useEffect(() => { + const activityStoreListener = ActivityStore.addListener(onActivityStoreChange); + const voterStoreListener = VoterStore.addListener(onVoterStoreChange); + onActivityStoreChange(); + onVoterStoreChange(); + + return () => { + activityStoreListener.remove(); + voterStoreListener.remove(); + }; + }, []); + + // useEffect handles setting inital focus replacing componentDidUpdate + useEffect(() => { + if (activityPostInputRef.current && !initialFocusSet) { + const input = activityPostInputRef.current; + const { length } = input.value; + input.focus(); + input.setSelectionRange(length, length); + setInitialFocusSet(true); + } + }, [initialFocusSet]); + + const onBlurInput = () => { + restoreStylesAfterCordovaKeyboard('VoterPositionEntryAndDisplay'); + }; + + + const onFocusInput = () => { + prepareForCordovaKeyboard('VoterPositionEntryAndDisplay'); + }; + + const onPublicToggleChange = (newVisibilityIsPublic) => { + setVisibilityIsPublic(newVisibilityIsPublic); + }; + + const saveActivityPost = (e) => { + e.preventDefault(); + const visibilitySetting = visibilityIsPublic ? 'SHOW_PUBLIC' : 'FRIENDS_ONLY'; + ActivityActions.activityPostSave(activityTidbitWeVoteId, statementText, visibilitySetting); + toggleModal(); + }; + + const updateStatementTextToBeSaved = (e) => { + setStatementText(e.target.value); + }; + + const activityTidbitIdCheck = activityTidbitWeVoteId === '' || activityTidbitWeVoteId === undefined; + + renderLog('VoterPositionEntryAndDisplay'); // Set LOG_RENDER_EVENTS to log all renders + + const dialogTitleText = activityTidbitIdCheck ? 'Create post' : 'Edit post'; + const statementPlaceholderText = 'What\'s on your mind?'; + const rowsToShow = isAndroid() ? 4 : 6; + + const textFieldJSX = ( + + + + + + + + {' '} + {voterName} + {/* Display the fetched name */} + + setVisibilityIsPublic(newVisibility)} + /> + + + + } + label="Endorsing" + classes={{ root: classes.radioLabel }} + /> + } + label="Opposing" + classes={{ root: classes.radioLabel }} + /> + } + label="Neutral" + classes={{ root: classes.radioLabel }} + /> + + + + + + + + ); + + return ( + <> + {dialogTitleText}} + show={show} + textFieldJSX={textFieldJSX} + toggleModal={toggleModal} + /> + {isEditModalOpen && ( + + )} + + ); +}; + +VoterPositionEntryAndDisplay.propTypes = { + activityTidbitWeVoteId: PropTypes.string, + classes: PropTypes.object, + externalUniqueId: PropTypes.string, + show: PropTypes.bool, + toggleModal: PropTypes.func.isRequired, +}; + +export default withStyles(templateBStyles)(VoterPositionEntryAndDisplay); diff --git a/src/js/components/Widgets/ModalDisplayTemplateB.jsx b/src/js/components/Widgets/ModalDisplayTemplateB.jsx new file mode 100644 index 000000000..c195e44db --- /dev/null +++ b/src/js/components/Widgets/ModalDisplayTemplateB.jsx @@ -0,0 +1,313 @@ +import { Close } from '@mui/icons-material'; +import { Dialog, DialogContent, DialogTitle, Divider, IconButton, Button } from '@mui/material'; +import withStyles from '@mui/styles/withStyles'; +import withTheme from '@mui/styles/withTheme'; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import styled from 'styled-components'; +import { hasIPhoneNotch, isAndroidSizeWide } from '../../common/utils/cordovaUtils'; +import { isAndroid, isCordova } from '../../common/utils/isCordovaOrWebApp'; +import { renderLog } from '../../common/utils/logging'; +import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; + + +class ModalDisplayTemplateB extends Component { + constructor(props) { + super(props); + this.state = { + }; + } + + render () { + renderLog('ModalDisplayTemplateB'); // Set LOG_RENDER_EVENTS to log all renders + const { + classes, dialogTitleJSX, externalUniqueId, show, tallMode, textFieldJSX, + } = this.props; + let dialogPaperCombined; + if (tallMode) { + dialogPaperCombined = { ...classes.dialogPaper, ...classes.dialogPaperAdditionTall }; + } else { + dialogPaperCombined = classes.dialogPaper; + } + // This template is used by other components like ActivityPostModal, and PositionStatementModal + return ( + this.props.toggleModal()} + open={show} + style={{ paddingTop: `${isCordova() ? '75px' : 'undefined'}` }} + > + + + + {dialogTitleJSX || <> </>} + + this.props.toggleModal()} + id={`closeModalDisplayTemplateB${externalUniqueId}`} + size="large" + > + + + + + + + + {textFieldJSX} + + + + ); + } +} +ModalDisplayTemplateB.propTypes = { + classes: PropTypes.object, + dialogTitleJSX: PropTypes.object, + externalUniqueId: PropTypes.string, + show: PropTypes.bool, + tallMode: PropTypes.bool, + textFieldJSX: PropTypes.object, + toggleModal: PropTypes.func.isRequired, +}; + +export const templateBStyles = (theme) => ({ + dialogTitle: { + padding: isAndroid() ? 8 : 'inherit', + fontFamily: 'Nunito, Arial, sans-serif', + }, + opposingLabel: { + margin: '0 8px', + padding: '4px 8px', + }, + editIcon: { + alignItems: 'center', + backgroundColor: '#E6F3FF', + borderRadius: '50%', + color: '#848484', + cursor: 'pointer', + display: 'flex', + fontSize: '24px', + marginTop: '25px', + padding: '5px', + position: 'relative', + }, + + dialogPaper: { + border: `1px solid ${DesignTokenColors.neutral100}`, + borderRadius: '30px', + boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', + marginTop: hasIPhoneNotch() ? 68 : 48, + minHeight: isAndroid() ? '257px' : '200px', + width: '90%', + maxWidth: '600px', + top: '0', + transform: isAndroid() ? 'translate(0%, -18%)' : 'translate(0%, -20%)', + [theme.breakpoints.down('xs')]: { + minWidth: '95%', + maxWidth: '95%', + width: '95%', + minHeight: isAndroid() ? '237px' : '200px', + maxHeight: '330px', + height: '70%', + margin: '0 auto', + transform: 'translate(0%, -30%)', + fontFamily: 'Nunito, sans-serif', + }, + }, + dialogPaperAdditionTall: { + maxHeight: '550px', + [theme.breakpoints.down('xs')]: { + maxHeight: '530px', + }, + }, + display: 'flex', + flexDirection: 'column', + height: '100%', + justifyContent: 'space-between', + padding: '16px', + closeButton: { + height: '24px', + position: 'absolute', + right: '16px', + width: '24.55px', + textAlign: 'left', + fontFamily: 'Nunito, sans-serif', + }, + formStyles: { + width: '100%', + }, + formControl: { + width: '100%', + marginTop: 16, + }, + inputMultiline: { + fontSize: 20, + height: '100%', + width: '100%', + [theme.breakpoints.down('sm')]: { + fontSize: 18, + }, + }, + inputStyles: { + flex: '1 1 0', + fontSize: 18, + height: '100%', + width: '100%', + [theme.breakpoints.down('sm')]: { + fontSize: 16, + }, + }, + select: { + padding: '12px 12px', + margin: '0 1px', + }, + saveButtonRoot: { + borderRadius: '30px', + marginTop: '18px', + height: '40px', + color: 'white', + width: '100%', + fontWeight: 600, + fontSize: 16, + lineHeight: '24px', + + '&.Mui-disabled': { + backgroundColor: DesignTokenColors.neutral100, + color: DesignTokenColors.whiteUI, + opacity: 0.6, + }, + }, + radioLabel: { + margin: '0 16px', + fontSize: '16px', + fontWeight: '400', + lineHeight: '22px', + fontFamily: 'Nunito, Arial, sans-serif', + color: DesignTokenColors.neutral900, + }, + radioRoot: { + color: DesignTokenColors.neutral900, + }, + radioChecked: { + color: DesignTokenColors.primary600, + }, +}); + +const DialogContentInnerWrapper = styled('div')` + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +const DialogTitleInnerWrapper = styled('div')` + display: flex; + justify-content: space-between; + align-items: center; + min-height: 56px; +`; + +export const horizontalEllipsis = '\u2026'; + +export const TextFieldDiv = styled('div')` + align-items: flex-start; + font-size: 16px; + border: 1px solid ${DesignTokenColors.primary600}; + border-radius: 16px; + display: flex; + margin-bottom: 0; + padding: ${isAndroidSizeWide() ? '12px 12px 0 12px' : '12px'}; +`; + +export const TextFieldForm = styled('form')` + height: 95%; + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +export const TextFieldWrapper = styled('div')` + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +const Title = styled('div')` + font-size: 18px; + font-weight: bold; + line-height: 24.55px; + margin: 0; + text-align: left; + padding-left: 16px; + fontFamily: 'Nunito, sans-serif', + +`; + +export const VoterAvatarImg = styled('img')` + border-radius: 6px; + width: 43px; + height: 43px; + display: block; + margin-left: 14px; + +`; + +export const UserInfoWrapper = styled('div')` + display: flex; + align-items: center; + margin-top: 25px; +`; + +export const UserInfoText = styled('div')` + margin-left: 24px; + fontFamily: 'Nunito, sans-serif', +`; +export const UserName = styled('div')` + color: DesignTokenColors.neutralUI900; + font-size: 18px; + font-weight: 600; + line-height: 25px; + fontFamily: 'Nunito, Arial, sans-serif', +`; + +export const VisibilityText = styled('div')` + color: DesignTokenColors.neutral900; + font-size: 13px; + line-height: 20px; + margin-top: 4px; +`; + +export const OpinionButtonsWrapper = styled('div')` + display: flex; + justify-content: space-around; + padding: 12px 0; +`; + +export const OpinionButton = styled(Button)` + border-radius: 16px; + flex: 1; + fontFamily: 'Nunito, sans-serif', + font-size: 16px; + font-weight: 400; + font-weight: bold; + line-height: 22px; + margin: 0 4px; + text-transform: capitalize; + color: ${(props) => (props.selected ? DesignTokenColors.whiteUI : DesignTokenColors.primary600)}; + background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : 'transparent')}; + &:hover { + background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : 'transparent')}; + background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : DesignTokenColors.primary50)}; + } +`; +export const radioGroup = styled('div')` + display: flex; + justify-content: space-around; + padding: 25px; + background-color: red; +`; +export default withTheme(withStyles(templateBStyles)(ModalDisplayTemplateB)); From da486292596bbb09bf9394b64e8d17d356dcb166 Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Sun, 19 Jan 2025 17:43:57 -0800 Subject: [PATCH 2/8] [WV-659] & [WV-660] Apply fixes and improve accessibility in ActivityPostPublicDropdown.jsx Apply fixes and improve accessibility in ActivityPostPublicDropdown.jsx --- .../Activity/ActivityPostPublicDropdown.jsx | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/js/components/Activity/ActivityPostPublicDropdown.jsx b/src/js/components/Activity/ActivityPostPublicDropdown.jsx index 3c8d5d3a2..2d3222c7e 100644 --- a/src/js/components/Activity/ActivityPostPublicDropdown.jsx +++ b/src/js/components/Activity/ActivityPostPublicDropdown.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Select, MenuItem, FormControl } from '@mui/material'; +import { Select, MenuItem, FormControl, Typography } from '@mui/material'; import { withStyles } from '@mui/styles'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; @@ -9,20 +9,27 @@ const ActivityPostPublicDropdown = (props) => { const { visibilityIsPublic, onVisibilityChange, classes } = props; const handleVisibilityChange = (event) => { - const value = event.target.value; + const { value } = event.target; onVisibilityChange(value === 'Public'); }; return ( -
- Opinion visible to: - + +
+ + Opinion visible to: + - -
+
+ ); }; @@ -42,22 +49,21 @@ ActivityPostPublicDropdown.propTypes = { classes: PropTypes.object.isRequired, }; -const styles = (theme) => ({ +const styles = () => ({ + formControl: { + width: '100%', + }, container: { display: 'flex', alignItems: 'center', - borderRadius: '4px', }, label: { fontFamily: 'Poppins', fontSize: '13px', fontWeight: '400', lineHeight: '19.5px', - textAlign: 'left', - color: 'var(--Neutral-900, #2A2A2C)', - }, - formControl: { - minWidth: '120px', + color: DesignTokenColors.neutralUI900, + marginRight: '8px', }, selectVisibility: { fontFamily: 'Nunito', From 4e11c04d8fa394e68fa3b8e9a596eb96654e7da2 Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Tue, 21 Jan 2025 14:12:24 -0800 Subject: [PATCH 3/8] [WV-659] VoterPositionEntryAndDisplay modal when you click "What's your opinion?" Fix all styles, Add mobile styles including for Iphone SE, Remove font-familly, Move down styles in file, Remove icon svg use MUI instead --- src/img/global/icons/drop-down-icon.svg | 3 - .../Activity/ActivityPostPublicDropdown.jsx | 38 ++++-- .../VoterPositionEditNameAndPhotoModal.jsx | 76 +++++------ .../VoterPositionEntryAndDisplay.jsx | 9 +- .../Widgets/ModalDisplayTemplateB.jsx | 124 ++++++++++++------ 5 files changed, 155 insertions(+), 95 deletions(-) delete mode 100644 src/img/global/icons/drop-down-icon.svg diff --git a/src/img/global/icons/drop-down-icon.svg b/src/img/global/icons/drop-down-icon.svg deleted file mode 100644 index d756f3768..000000000 --- a/src/img/global/icons/drop-down-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/js/components/Activity/ActivityPostPublicDropdown.jsx b/src/js/components/Activity/ActivityPostPublicDropdown.jsx index 2d3222c7e..6f46b977c 100644 --- a/src/js/components/Activity/ActivityPostPublicDropdown.jsx +++ b/src/js/components/Activity/ActivityPostPublicDropdown.jsx @@ -49,32 +49,34 @@ ActivityPostPublicDropdown.propTypes = { classes: PropTypes.object.isRequired, }; -const styles = () => ({ +const styles = (theme) => ({ formControl: { width: '100%', }, container: { - display: 'flex', alignItems: 'center', + display: 'flex', }, label: { + color: DesignTokenColors.neutralUI900, fontFamily: 'Poppins', fontSize: '13px', fontWeight: '400', lineHeight: '19.5px', - color: DesignTokenColors.neutralUI900, marginRight: '8px', + [theme.breakpoints.down('md')]: { + display: 'none', + }, }, selectVisibility: { - fontFamily: 'Nunito', + border: 'none', + boxShadow: 'none', + color: DesignTokenColors.neutralUI900, fontSize: '16px', fontWeight: '400', lineHeight: '21.82px', - color: DesignTokenColors.neutralUI900, - padding: '0 8px', - border: 'none', - boxShadow: 'none', outline: 'none', + padding: 0, '&:focus': { outline: 'none', boxShadow: 'none', @@ -82,13 +84,29 @@ const styles = () => ({ '& .MuiOutlinedInput-notchedOutline': { border: 'none', }, + [theme.breakpoints.down('sm')]: { + padding: 0, + }, + [theme.breakpoints.down('xs')]: { + padding: '0 32px 0 0', + fontSize: '14px', + }, }, menuItem: { - fontFamily: 'Nunito', + color: DesignTokenColors.neutralUI900, fontSize: '16px', fontWeight: '400', lineHeight: '21.82px', - color: DesignTokenColors.neutralUI900, + padding: 0, + [theme.breakpoints.down('xs')]: { + fontSize: '14px', + }, + }, + outlinedInputRoot: { + padding: 0, + '& .MuiSelect-select': { + padding: '0 !important', + }, }, }); diff --git a/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx b/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx index 03c2879be..38031a195 100644 --- a/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx +++ b/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx @@ -5,44 +5,6 @@ import PropTypes from 'prop-types'; import { Close as CloseIcon } from '@mui/icons-material'; import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; -const styles = { - modalContent: { - padding: '20px', - }, - uploadSection: { - alignItems: 'center', - border: `3px dashed ${DesignTokenColors.neutral100}`, - borderRadius: '8px', - display: 'flex', - flexDirection: 'column', - marginBottom: '20px', - padding: '20px', - }, - profilePhoto: { - alignItems: 'center', - backgroundColor: DesignTokenColors.neutral50, - borderRadius: '50%', - display: 'flex', - height: '80px', - justifyContent: 'center', - marginBottom: '10px', - width: '80px', - }, - formField: { - color: DesignTokenColors.neutral100, - marginBottom: '15px', - }, - a: { - color: DesignTokenColors.primary500, - }, - closeButton: { - color: DesignTokenColors.neutral100, - position: 'absolute', - right: '8px', - top: '8px', - }, -}; - const VoterPositionEditNameAndPhotoModal = ({ show, toggleModal, classes }) => { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); @@ -166,5 +128,43 @@ VoterPositionEditNameAndPhotoModal.propTypes = { toggleModal: PropTypes.func.isRequired, classes: PropTypes.object.isRequired, }; +const styles = { + modalContent: { + padding: '20px', + }, + uploadSection: { + alignItems: 'center', + border: `3px dashed ${DesignTokenColors.neutral100}`, + borderRadius: '8px', + display: 'flex', + flexDirection: 'column', + marginBottom: '20px', + padding: '20px', + }, + profilePhoto: { + alignItems: 'center', + backgroundColor: DesignTokenColors.neutral50, + borderRadius: '50%', + display: 'flex', + height: '80px', + justifyContent: 'center', + marginBottom: '10px', + width: '80px', + }, + formField: { + color: DesignTokenColors.neutral100, + marginBottom: '15px', + }, + a: { + color: DesignTokenColors.primary500, + }, + closeButton: { + color: DesignTokenColors.neutral100, + position: 'absolute', + right: '8px', + top: '8px', + }, +}; + export default withStyles(styles)(VoterPositionEditNameAndPhotoModal); diff --git a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx index fd2e69def..e165fbbc7 100644 --- a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx +++ b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx @@ -13,7 +13,7 @@ import { avatarGeneric } from '../../utils/applicationUtils'; import ModalDisplayTemplateB, { templateBStyles, TextFieldDiv, TextFieldForm, TextFieldWrapper, VoterAvatarImg, - UserInfoWrapper, UserInfoText, UserName, VisibilityText, editIcon, + UserInfoWrapper, UserInfoText, UserName, VisibilityText, editIcon, StyledRadioGroup, } from '../Widgets/ModalDisplayTemplateB'; // import ActivityPostPublicToggle from '../Activity/ActivityPostPublicToggle'; import ActivityPostPublicDropdown from '../Activity/ActivityPostPublicDropdown'; @@ -146,7 +146,7 @@ const VoterPositionEntryAndDisplay = (props) => { /> - { label="Neutral" classes={{ root: classes.radioLabel }} /> - + { color="primary" classes={{ root: classes.saveButtonRoot }} type="submit" - disabled={!statementText} + // disabled={!statementText} // Commented out to allow saving without statement + disabled={!selectedOpinion} > {activityTidbitIdCheck ? 'Add opinion' : 'Save Changes'} diff --git a/src/js/components/Widgets/ModalDisplayTemplateB.jsx b/src/js/components/Widgets/ModalDisplayTemplateB.jsx index c195e44db..5b5d157ae 100644 --- a/src/js/components/Widgets/ModalDisplayTemplateB.jsx +++ b/src/js/components/Widgets/ModalDisplayTemplateB.jsx @@ -10,9 +10,8 @@ import { isAndroid, isCordova } from '../../common/utils/isCordovaOrWebApp'; import { renderLog } from '../../common/utils/logging'; import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; - class ModalDisplayTemplateB extends Component { - constructor(props) { + constructor (props) { super(props); this.state = { }; @@ -76,7 +75,7 @@ ModalDisplayTemplateB.propTypes = { export const templateBStyles = (theme) => ({ dialogTitle: { padding: isAndroid() ? 8 : 'inherit', - fontFamily: 'Nunito, Arial, sans-serif', + margin: 0, }, opposingLabel: { margin: '0 8px', @@ -84,26 +83,25 @@ export const templateBStyles = (theme) => ({ }, editIcon: { alignItems: 'center', - backgroundColor: '#E6F3FF', + backgroundColor: DesignTokenColors.primary50, borderRadius: '50%', - color: '#848484', + color: DesignTokenColors.neutral400, cursor: 'pointer', display: 'flex', fontSize: '24px', - marginTop: '25px', + margin: '25px 0 0 -20px', padding: '5px', position: 'relative', }, - dialogPaper: { border: `1px solid ${DesignTokenColors.neutral100}`, borderRadius: '30px', boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', marginTop: hasIPhoneNotch() ? 68 : 48, minHeight: isAndroid() ? '257px' : '200px', - width: '90%', maxWidth: '600px', top: '0', + width: '90%', transform: isAndroid() ? 'translate(0%, -18%)' : 'translate(0%, -20%)', [theme.breakpoints.down('xs')]: { minWidth: '95%', @@ -114,7 +112,14 @@ export const templateBStyles = (theme) => ({ height: '70%', margin: '0 auto', transform: 'translate(0%, -30%)', - fontFamily: 'Nunito, sans-serif', + }, + [theme.breakpoints.up('sm')]: { + maxWidth: '500px', + width: '80%', + }, + [theme.breakpoints.up('md')]: { + maxWidth: '600px', + width: '70%', }, }, dialogPaperAdditionTall: { @@ -125,23 +130,21 @@ export const templateBStyles = (theme) => ({ }, display: 'flex', flexDirection: 'column', - height: '100%', justifyContent: 'space-between', - padding: '16px', + height: '100%', closeButton: { height: '24px', position: 'absolute', right: '16px', width: '24.55px', textAlign: 'left', - fontFamily: 'Nunito, sans-serif', }, formStyles: { width: '100%', }, formControl: { - width: '100%', marginTop: 16, + width: '100%', }, inputMultiline: { fontSize: 20, @@ -166,14 +169,13 @@ export const templateBStyles = (theme) => ({ }, saveButtonRoot: { borderRadius: '30px', - marginTop: '18px', - height: '40px', color: 'white', - width: '100%', fontWeight: 600, fontSize: 16, + height: '40px', + width: '100%', lineHeight: '24px', - + margin: '16px 0 0 0 ', '&.Mui-disabled': { backgroundColor: DesignTokenColors.neutral100, color: DesignTokenColors.whiteUI, @@ -181,12 +183,15 @@ export const templateBStyles = (theme) => ({ }, }, radioLabel: { - margin: '0 16px', - fontSize: '16px', + alignItems: 'center', + color: DesignTokenColors.neutral900, + display: 'flex', fontWeight: '400', lineHeight: '22px', - fontFamily: 'Nunito, Arial, sans-serif', - color: DesignTokenColors.neutral900, + margin: 0, + [theme.breakpoints.down('sm')]: { + fontSize: '12px !important', + }, }, radioRoot: { color: DesignTokenColors.neutral900, @@ -197,17 +202,29 @@ export const templateBStyles = (theme) => ({ }); const DialogContentInnerWrapper = styled('div')` - height: 100%; display: flex; flex-direction: column; + height: 100%; justify-content: space-between; + + @media (max-width: 600px) { + min-height: 48px; + } + + @media (min-width: 960px) { + min-height: 64px; + } `; const DialogTitleInnerWrapper = styled('div')` + align-items: center; display: flex; justify-content: space-between; - align-items: center; min-height: 56px; + + @media ${(props) => props.theme.breakpoints.down('sm')} { + min-height: 48px; + } `; export const horizontalEllipsis = '\u2026'; @@ -240,38 +257,44 @@ const Title = styled('div')` font-size: 18px; font-weight: bold; line-height: 24.55px; - margin: 0; + margin: 16px 0; text-align: left; - padding-left: 16px; - fontFamily: 'Nunito, sans-serif', - -`; + padding-left: 20px; + @media (max-width: 600px) { + font-size: 16px; + line-height: 22px; + } + + @media (min-width: 960px) { + font-size: 20px; + line-height: 28px; + } +`; export const VoterAvatarImg = styled('img')` border-radius: 6px; width: 43px; height: 43px; display: block; margin-left: 14px; - + @media (max-width: 600px) { + margin-left: 0px; + } `; export const UserInfoWrapper = styled('div')` display: flex; - align-items: center; margin-top: 25px; `; export const UserInfoText = styled('div')` - margin-left: 24px; - fontFamily: 'Nunito, sans-serif', -`; + padding-left: 16px; + `; export const UserName = styled('div')` color: DesignTokenColors.neutralUI900; font-size: 18px; font-weight: 600; line-height: 25px; - fontFamily: 'Nunito, Arial, sans-serif', `; export const VisibilityText = styled('div')` @@ -290,24 +313,45 @@ export const OpinionButtonsWrapper = styled('div')` export const OpinionButton = styled(Button)` border-radius: 16px; flex: 1; - fontFamily: 'Nunito, sans-serif', font-size: 16px; font-weight: 400; - font-weight: bold; line-height: 22px; margin: 0 4px; text-transform: capitalize; + + @media (max-width: 600px) { + font-size: 14px; + line-height: 20px; + } + color: ${(props) => (props.selected ? DesignTokenColors.whiteUI : DesignTokenColors.primary600)}; background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : 'transparent')}; + &:hover { background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : 'transparent')}; background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : DesignTokenColors.primary50)}; } `; -export const radioGroup = styled('div')` +export const StyledRadioGroup = styled('div')` display: flex; - justify-content: space-around; - padding: 25px; - background-color: red; + flex-direction: row; + justify-content: flex-start; + align-items: center; + gap: 16px; + margin-bottom: 10px; + padding: 0; + width: 100%; + + @media (max-width: 600px) { + align-items: flex-start; + display: flex; + flex-direction: column; + gap: 8px; + } + + @media (max-width: 375px) { + align-items: flex-start; + gap: 8px; +} `; export default withTheme(withStyles(templateBStyles)(ModalDisplayTemplateB)); From 1c6908670474e1263b500b08dd6d2d5b83389edb Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Thu, 23 Jan 2025 18:15:59 -0800 Subject: [PATCH 4/8] fix dropdown height and radiogroup fix dropdown height on iphone SE and fix bug with radiogroup --- .../Activity/ActivityPostPublicDropdown.jsx | 10 +++- .../VoterPositionEntryAndDisplay.jsx | 20 +++----- .../Widgets/ModalDisplayTemplateB.jsx | 50 +++++++++---------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/js/components/Activity/ActivityPostPublicDropdown.jsx b/src/js/components/Activity/ActivityPostPublicDropdown.jsx index 6f46b977c..44775e9dc 100644 --- a/src/js/components/Activity/ActivityPostPublicDropdown.jsx +++ b/src/js/components/Activity/ActivityPostPublicDropdown.jsx @@ -30,6 +30,9 @@ const ActivityPostPublicDropdown = (props) => { disableUnderline IconComponent={ArrowDropDownIcon} aria-label="Select visibility for your opinion" + MenuProps={{ + classes: { paper: classes.menuPaper }, + }} > Public @@ -97,11 +100,16 @@ const styles = (theme) => ({ fontSize: '16px', fontWeight: '400', lineHeight: '21.82px', - padding: 0, + padding: '10px', [theme.breakpoints.down('xs')]: { fontSize: '14px', }, }, + menuPaper: { + '& .MuiMenu-list': { + padding: 0, + }, + }, outlinedInputRoot: { padding: 0, '& .MuiSelect-select': { diff --git a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx index e165fbbc7..03e2cacba 100644 --- a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx +++ b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; -import { Button, InputBase, Radio, RadioGroup, FormControlLabel, FormControl } from '@mui/material'; +import { Button, InputBase, Radio, FormControlLabel, RadioGroup } from '@mui/material'; import { withStyles } from '@mui/styles'; import PropTypes from 'prop-types'; import { Edit as EditIcon } from '@mui/icons-material'; @@ -13,7 +13,7 @@ import { avatarGeneric } from '../../utils/applicationUtils'; import ModalDisplayTemplateB, { templateBStyles, TextFieldDiv, TextFieldForm, TextFieldWrapper, VoterAvatarImg, - UserInfoWrapper, UserInfoText, UserName, VisibilityText, editIcon, StyledRadioGroup, + UserInfoWrapper, UserInfoText, UserName, } from '../Widgets/ModalDisplayTemplateB'; // import ActivityPostPublicToggle from '../Activity/ActivityPostPublicToggle'; import ActivityPostPublicDropdown from '../Activity/ActivityPostPublicDropdown'; @@ -85,19 +85,10 @@ const VoterPositionEntryAndDisplay = (props) => { } }, [initialFocusSet]); - const onBlurInput = () => { - restoreStylesAfterCordovaKeyboard('VoterPositionEntryAndDisplay'); - }; - - const onFocusInput = () => { prepareForCordovaKeyboard('VoterPositionEntryAndDisplay'); }; - const onPublicToggleChange = (newVisibilityIsPublic) => { - setVisibilityIsPublic(newVisibilityIsPublic); - }; - const saveActivityPost = (e) => { e.preventDefault(); const visibilitySetting = visibilityIsPublic ? 'SHOW_PUBLIC' : 'FRIENDS_ONLY'; @@ -132,7 +123,7 @@ const VoterPositionEntryAndDisplay = (props) => { /> @@ -146,10 +137,11 @@ const VoterPositionEntryAndDisplay = (props) => { /> - { label="Neutral" classes={{ root: classes.radioLabel }} /> - + ({ margin: '0 8px', padding: '4px 8px', }, - editIcon: { + styledEditIcon: { alignItems: 'center', backgroundColor: DesignTokenColors.primary50, borderRadius: '50%', @@ -100,7 +100,7 @@ export const templateBStyles = (theme) => ({ marginTop: hasIPhoneNotch() ? 68 : 48, minHeight: isAndroid() ? '257px' : '200px', maxWidth: '600px', - top: '0', + top: '50px', width: '90%', transform: isAndroid() ? 'translate(0%, -18%)' : 'translate(0%, -20%)', [theme.breakpoints.down('xs')]: { @@ -182,6 +182,27 @@ export const templateBStyles = (theme) => ({ opacity: 0.6, }, }, + radioGroup: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + gap: '16px', + marginBottom: '10px', + padding: '0', + width: '100%', + + '@media (max-width: 600px)': { + alignItems: 'flex-start', + flexDirection: 'column', + gap: '8px', + }, + + '@media (max-width: 375px)': { + alignItems: 'flex-start', + gap: '8px', + }, + }, radioLabel: { alignItems: 'center', color: DesignTokenColors.neutral900, @@ -332,26 +353,5 @@ export const OpinionButton = styled(Button)` background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : DesignTokenColors.primary50)}; } `; -export const StyledRadioGroup = styled('div')` - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - gap: 16px; - margin-bottom: 10px; - padding: 0; - width: 100%; - - @media (max-width: 600px) { - align-items: flex-start; - display: flex; - flex-direction: column; - gap: 8px; - } - @media (max-width: 375px) { - align-items: flex-start; - gap: 8px; -} -`; export default withTheme(withStyles(templateBStyles)(ModalDisplayTemplateB)); From bbbacaee8de103fc3e4573b8a27493306daa83e6 Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Fri, 24 Jan 2025 10:44:17 -0800 Subject: [PATCH 5/8] [WV-660]Enhance VoterPositionEditNameAndPhotoModal - Updated modal structure to mirror SettingsProfile.jsx design - Integrated components: SettingsProfilePicture, SettingsWidgetAccountType, SettingsWidgetFirstLastName, SettingsWidgetOrganizationDescription, and SettingsWidgetOrganizationWebsite - Added styled-components for cleaner layout and responsiveness - Fixed eslint issues across the file --- .../VoterPositionEditNameAndPhotoModal.jsx | 174 ++++++------------ 1 file changed, 59 insertions(+), 115 deletions(-) diff --git a/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx b/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx index 38031a195..a84cb0b72 100644 --- a/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx +++ b/src/js/components/PositionItem/VoterPositionEditNameAndPhotoModal.jsx @@ -1,45 +1,35 @@ -import React, { useState } from 'react'; -import { Dialog, DialogTitle, DialogContent, Button, TextField, IconButton } from '@mui/material'; +import React, { useEffect, useState } from 'react'; +import { Dialog, DialogTitle, DialogContent, IconButton } from '@mui/material'; import { withStyles } from '@mui/styles'; import PropTypes from 'prop-types'; import { Close as CloseIcon } from '@mui/icons-material'; +import VoterStore from '../../stores/VoterStore'; +import SettingsProfilePicture from '../Settings/SettingsProfilePicture'; +import SettingsWidgetFirstLastName from '../Settings/SettingsWidgetFirstLastName'; +import SettingsWidgetOrganizationWebsite from '../Settings/SettingsWidgetOrganizationWebsite'; +import SettingsWidgetOrganizationDescription from '../Settings/SettingsWidgetOrganizationDescription'; +import SettingsWidgetAccountType from '../Settings/SettingsWidgetAccountType'; import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; const VoterPositionEditNameAndPhotoModal = ({ show, toggleModal, classes }) => { - const [firstName, setFirstName] = useState(''); - const [lastName, setLastName] = useState(''); - const [website, setWebsite] = useState(''); - const [description, setDescription] = useState(''); - const [photo, setPhoto] = useState(null); + const [voter, setVoter] = useState(null); - const handlePhotoUpload = (event) => { - const file = event.target.files[0]; - if (file && file.type.startsWith('image/')) { - const reader = new FileReader(); - reader.onload = (e) => setPhoto(e.target.result); - reader.readAsDataURL(file); - } else { - alert('Please upload a valid image file.'); + useEffect(() => { + if (show) { + // Fetch voter data when modal is displayed + const voterData = VoterStore.getVoter(); + setVoter(voterData); } - }; + }, [show]); - const handleSave = () => { - if (!firstName.trim() || !lastName.trim()) { - alert('First and Last name are required.'); - return; - } - if (website && !website.startsWith('http')) { - alert('Please enter a valid URL starting with http or https.'); - return; - } - // Add logic to save details - toggleModal(); - }; + if (!voter) { + return null; // Prevent rendering until voter data is available + } return ( - Name & Photo Settings + Edit Profile {

- We are serious about protecting your information. We are non-profit and never sell information. + We are serious about protecting your information. We are a non-profit and will never sell your data. {' '} - + Frequently Asked Questions.

-
-
- {photo ? Profile : 'Upload'} -
- + {/* Profile picture */} + + + {/* First and last name */} + + + {/* Website */} + + + {/* Organization description */} + + + {/* Account type */} + -
- setFirstName(e.target.value)} - fullWidth - variant="outlined" - className={classes.formField} - /> - setLastName(e.target.value)} - fullWidth - variant="outlined" - className={classes.formField} - /> - - setWebsite(e.target.value)} - fullWidth - variant="outlined" - className={classes.formField} - /> - setDescription(e.target.value)} - fullWidth - variant="outlined" - className={classes.formField} - multiline - rows={3} - /> -
); @@ -128,33 +95,11 @@ VoterPositionEditNameAndPhotoModal.propTypes = { toggleModal: PropTypes.func.isRequired, classes: PropTypes.object.isRequired, }; + const styles = { modalContent: { padding: '20px', }, - uploadSection: { - alignItems: 'center', - border: `3px dashed ${DesignTokenColors.neutral100}`, - borderRadius: '8px', - display: 'flex', - flexDirection: 'column', - marginBottom: '20px', - padding: '20px', - }, - profilePhoto: { - alignItems: 'center', - backgroundColor: DesignTokenColors.neutral50, - borderRadius: '50%', - display: 'flex', - height: '80px', - justifyContent: 'center', - marginBottom: '10px', - width: '80px', - }, - formField: { - color: DesignTokenColors.neutral100, - marginBottom: '15px', - }, a: { color: DesignTokenColors.primary500, }, @@ -166,5 +111,4 @@ const styles = { }, }; - export default withStyles(styles)(VoterPositionEditNameAndPhotoModal); From 7c193c7531cc3fb340593b09e72ecd34cf367801 Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Sat, 25 Jan 2025 16:02:23 -0800 Subject: [PATCH 6/8] remove useState --- src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx index 03e2cacba..66cf35f7f 100644 --- a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx +++ b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx @@ -54,7 +54,7 @@ const VoterPositionEntryAndDisplay = (props) => { setVoterPhotoUrlMedium(voter.voter_photo_url_medium); setVoterName(voter.full_name || 'Anonymous'); }; - const [selectedOpinion, setSelectedOpinion] = useState('Neutral'); + const [selectedOpinion, setSelectedOpinion] = useState(''); const handleOpinionChange = (event) => { setSelectedOpinion(event.target.value); From b6972a1c5c0639a819d0e96b0f9396c9889f1376 Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Thu, 6 Feb 2025 00:06:01 -0800 Subject: [PATCH 7/8] [WV-659] & [WV-660] add two modals [TEAM REVIEW] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] --- .../Politician/PoliticianEndorsementsList.jsx | 8 ++- .../components/Activity/ActivityPostAdd.jsx | 24 ++++----- .../VoterPositionEntryAndDisplay.jsx | 49 ++++++++++++++++--- .../Widgets/ModalDisplayTemplateB.jsx | 29 +++++++++-- 4 files changed, 85 insertions(+), 25 deletions(-) diff --git a/src/js/common/components/Politician/PoliticianEndorsementsList.jsx b/src/js/common/components/Politician/PoliticianEndorsementsList.jsx index ad81871b6..c843aebf6 100644 --- a/src/js/common/components/Politician/PoliticianEndorsementsList.jsx +++ b/src/js/common/components/Politician/PoliticianEndorsementsList.jsx @@ -19,6 +19,7 @@ import { CampaignSubSectionTitleWrapper, } from '../Style/CampaignDetailsStyles'; import PoliticianStore from '../../stores/PoliticianStore'; +import VoterPositionEntryAndDisplay2 from '../../../components/PositionItem/VoterPositionEntryAndDisplay'; const STARTING_NUMBER_OF_POSITIONS_TO_DISPLAY = 2; const NUMBER_OF_POSITIONS_TO_ADD_WHEN_MORE_CLICKED = 10; @@ -112,7 +113,7 @@ class PoliticianEndorsementsList extends Component { render () { renderLog('PoliticianEndorsementsList'); // Set LOG_RENDER_EVENTS to log all renders const { politicianWeVoteId, hideEncouragementToEndorse, showTitle } = this.props; - const { filteredPositionList, numberOfPositionsToDisplay, politicianName } = this.state; + const { filteredPositionList, numberOfPositionsToDisplay, politicianName, showOpinionModal } = this.state; // console.log('PoliticianEndorsementsList render numberOfPositionsToDisplay:', numberOfPositionsToDisplay); const showTitleAndPositionsToShow = showTitle && (filteredPositionList && filteredPositionList.length > 0); const listTitleHtml = showTitleAndPositionsToShow && ( @@ -178,6 +179,11 @@ class PoliticianEndorsementsList extends Component { /> )} + {/* Render the modal */} + ); } diff --git a/src/js/components/Activity/ActivityPostAdd.jsx b/src/js/components/Activity/ActivityPostAdd.jsx index 50f44eed1..03a1806c5 100644 --- a/src/js/components/Activity/ActivityPostAdd.jsx +++ b/src/js/components/Activity/ActivityPostAdd.jsx @@ -10,13 +10,13 @@ import VoterStore from '../../stores/VoterStore'; import { avatarGeneric } from '../../utils/applicationUtils'; import { cordovaNewsPaddingTop } from '../../utils/cordovaOffsets'; -const VoterPositionEntryAndDisplay = React.lazy(() => import(/* webpackChunkName: 'VoterPositionEntryAndDisplay' */ '../PositionItem/VoterPositionEntryAndDisplay')); +const ActivityPostModal = React.lazy(() => import(/* webpackChunkName: 'ActivityPostModal' */ './ActivityPostModal')); class ActivityPostAdd extends Component { constructor (props) { super(props); this.state = { - showVoterPositionEntryAndDisplay: false, + showActivityPostModal: false, statementText: '', }; this.updateStatementTextToBeSaved = this.updateStatementTextToBeSaved.bind(this); @@ -62,11 +62,11 @@ class ActivityPostAdd extends Component { }); } - toggleVoterPositionEntryAndDisplay = () => { - const { showVoterPositionEntryAndDisplay } = this.state; - // console.log('toggleVoterPositionEntryAndDisplay showVoterPositionEntryAndDisplay:', showVoterPositionEntryAndDisplay); + toggleActivityPostModal = () => { + const { showActivityPostModal } = this.state; + // console.log('toggleActivityPostModal showActivityPostModal:', showActivityPostModal); this.setState({ - showVoterPositionEntryAndDisplay: !showVoterPositionEntryAndDisplay, + showActivityPostModal: !showActivityPostModal, }); } @@ -80,7 +80,7 @@ class ActivityPostAdd extends Component { renderLog('ActivityPostAdd'); // Set LOG_RENDER_EVENTS to log all renders const { classes, externalUniqueId, activityTidbitWeVoteId } = this.props; const { - showVoterPositionEntryAndDisplay, + showActivityPostModal, voterPhotoUrlMedium, statementText, } = this.state; @@ -137,19 +137,19 @@ class ActivityPostAdd extends Component { inputRef={(tag) => { this.textarea = tag; }} multiline name="statementText" - onClick={this.toggleVoterPositionEntryAndDisplay} + onClick={this.toggleActivityPostModal} onFocus={this.handleFocus} placeholder={statementPlaceholderText} rows="1" /> - {showVoterPositionEntryAndDisplay && ( + {showActivityPostModal && ( }> - )} diff --git a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx index 66cf35f7f..b8f7c559d 100644 --- a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx +++ b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx @@ -4,7 +4,7 @@ import { withStyles } from '@mui/styles'; import PropTypes from 'prop-types'; import { Edit as EditIcon } from '@mui/icons-material'; import ActivityActions from '../../actions/ActivityActions'; -import { prepareForCordovaKeyboard, restoreStylesAfterCordovaKeyboard } from '../../common/utils/cordovaUtils'; +import { prepareForCordovaKeyboard } from '../../common/utils/cordovaUtils'; import { isAndroid } from '../../common/utils/isCordovaOrWebApp'; import { renderLog } from '../../common/utils/logging'; import ActivityStore from '../../stores/ActivityStore'; @@ -13,14 +13,14 @@ import { avatarGeneric } from '../../utils/applicationUtils'; import ModalDisplayTemplateB, { templateBStyles, TextFieldDiv, TextFieldForm, TextFieldWrapper, VoterAvatarImg, - UserInfoWrapper, UserInfoText, UserName, + UserInfoWrapper, UserInfoText, UserName, OptionBlockWrapper, CommentContainer, InputBox, } from '../Widgets/ModalDisplayTemplateB'; // import ActivityPostPublicToggle from '../Activity/ActivityPostPublicToggle'; import ActivityPostPublicDropdown from '../Activity/ActivityPostPublicDropdown'; import VoterPositionEditNameAndPhotoModal from './VoterPositionEditNameAndPhotoModal'; const VoterPositionEntryAndDisplay = (props) => { - const { activityTidbitWeVoteId, classes, externalUniqueId, show, toggleModal } = props; + const { activityTidbitWeVoteId, classes, externalUniqueId, toggleModal } = props; // useState used for state variables const [visibilityIsPublic, setVisibilityIsPublic] = useState(false); @@ -107,7 +107,38 @@ const VoterPositionEntryAndDisplay = (props) => { const dialogTitleText = activityTidbitIdCheck ? 'Create post' : 'Edit post'; const statementPlaceholderText = 'What\'s on your mind?'; const rowsToShow = isAndroid() ? 4 : 6; + const [showModal, setShowModal] = useState(false); + const toggleLocalModal = () => { + setShowModal((prev) => !prev); // Toggle the modal + }; + const OpinionBlock = ({ onClick }) => ( + + + + + + + {/* Open modal when input is clicked */} + + + + ); + + OpinionBlock.propTypes = { + onClick: PropTypes.func.isRequired, + }; const textFieldJSX = ( { classes={{ root: classes.saveButtonRoot }} type="submit" // disabled={!statementText} // Commented out to allow saving without statement - disabled={!selectedOpinion} + disabled={selectedOpinion === 'Neutral' && (!statementText || statementText.trim() === '')} // Disable if Neutral and no text > {activityTidbitIdCheck ? 'Add opinion' : 'Save Changes'} @@ -194,9 +225,9 @@ const VoterPositionEntryAndDisplay = (props) => { <> {dialogTitleText}} - show={show} + show={showModal} textFieldJSX={textFieldJSX} - toggleModal={toggleModal} + toggleModal={toggleLocalModal} /> {isEditModalOpen && ( { toggleModal={handleEditModalClose} /> )} + ); }; @@ -212,7 +248,6 @@ VoterPositionEntryAndDisplay.propTypes = { activityTidbitWeVoteId: PropTypes.string, classes: PropTypes.object, externalUniqueId: PropTypes.string, - show: PropTypes.bool, toggleModal: PropTypes.func.isRequired, }; diff --git a/src/js/components/Widgets/ModalDisplayTemplateB.jsx b/src/js/components/Widgets/ModalDisplayTemplateB.jsx index 0c63e8534..4f0594c0c 100644 --- a/src/js/components/Widgets/ModalDisplayTemplateB.jsx +++ b/src/js/components/Widgets/ModalDisplayTemplateB.jsx @@ -1,5 +1,5 @@ import { Close } from '@mui/icons-material'; -import { Dialog, DialogContent, DialogTitle, Divider, IconButton, Button, RadioGroup } from '@mui/material'; +import { Dialog, DialogContent, DialogTitle, Divider, IconButton, Button } from '@mui/material'; import withStyles from '@mui/styles/withStyles'; import withTheme from '@mui/styles/withTheme'; import PropTypes from 'prop-types'; @@ -89,7 +89,7 @@ export const templateBStyles = (theme) => ({ cursor: 'pointer', display: 'flex', fontSize: '24px', - margin: '25px 0 0 -20px', + margin: '19px 0 0 -20px', padding: '5px', position: 'relative', }, @@ -227,6 +227,7 @@ const DialogContentInnerWrapper = styled('div')` flex-direction: column; height: 100%; justify-content: space-between; + margin-top:25px; @media (max-width: 600px) { min-height: 48px; @@ -293,11 +294,10 @@ const Title = styled('div')` } `; export const VoterAvatarImg = styled('img')` - border-radius: 6px; + border-radius: 50%; width: 43px; height: 43px; display: block; - margin-left: 14px; @media (max-width: 600px) { margin-left: 0px; } @@ -305,7 +305,6 @@ export const VoterAvatarImg = styled('img')` export const UserInfoWrapper = styled('div')` display: flex; - margin-top: 25px; `; export const UserInfoText = styled('div')` @@ -353,5 +352,25 @@ export const OpinionButton = styled(Button)` background-color: ${(props) => (props.selected ? DesignTokenColors.primary600 : DesignTokenColors.primary50)}; } `; +export const OptionBlockWrapper = styled.div` +margin-bottom: 10px; +padding: 10px 0; +`; +export const CommentContainer = styled.div` + align-items: center; + background-color: ${DesignTokenColors.whiteUI}; + border: 1px solid ${DesignTokenColors.neutralUI300}; + border-radius: 16px; + display: flex; + padding: 8px 12px; +`; +export const InputBox = styled.input` +border: none; +color: ${DesignTokenColors.neutral900}; +flex-grow: 1; +font-size: 14px; +outline: none; +padding: 5px; +`; export default withTheme(withStyles(templateBStyles)(ModalDisplayTemplateB)); From 4b4b844c845c76acfef17481ddb3f5c9179623e7 Mon Sep 17 00:00:00 2001 From: Kateryna Stetsenko Date: Thu, 6 Feb 2025 09:47:30 -0800 Subject: [PATCH 8/8] [WV-659] & [WV-660] add 2 modals [TEAM REVIEW] --- .../VoterPositionEntryAndDisplay.jsx | 20 ++++++++-------- .../Widgets/ModalDisplayTemplateB.jsx | 24 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx index b8f7c559d..5a2d52a5a 100644 --- a/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx +++ b/src/js/components/PositionItem/VoterPositionEntryAndDisplay.jsx @@ -114,17 +114,17 @@ const VoterPositionEntryAndDisplay = (props) => { }; const OpinionBlock = ({ onClick }) => ( + + + + - - - - {/* Open modal when input is clicked */}