diff --git a/package-lock.json b/package-lock.json index f2a717d4e..ccb3c7014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -213,7 +213,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2078,7 +2077,6 @@ "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2121,7 +2119,6 @@ "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -4139,7 +4136,6 @@ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.14.tgz", "integrity": "sha512-heL4S+EawrP61xMXBm59QH6HODsu0gxtZi5JtnXF2r+rghzyU/3Uftlt1ij8rmJh+cFdKTQug1L9KkZB5JgpMQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -4215,7 +4211,6 @@ "version": "5.17.1", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.17.1.tgz", "integrity": "sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==", - "peer": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/core-downloads-tracker": "^5.17.1", @@ -10233,8 +10228,7 @@ "version": "1.54.2", "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.54.2.tgz", "integrity": "sha512-R1PwtDvUfs99cAjfuQ/WpwJ3c92+DAMy9xGApjqlWQMj0FKQabUAys2swfTRNzuYAYJh7NqK2dzcYVNkKLEKUg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@swc/core": { "version": "1.12.1", @@ -10243,7 +10237,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" @@ -11100,7 +11093,6 @@ "version": "17.0.83", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", - "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "^0.16", @@ -11624,7 +11616,6 @@ "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.20.0.tgz", "integrity": "sha512-dGkZFp09aIyoN6HlJah7zJApG/FzN0O/HaTfTkWrOM5GBli9th/9VIfbsT3vx4I9mBdETiYPmgfl4LqDP2p0VQ==", "dev": true, - "peer": true, "dependencies": { "@vitest/snapshot": "^2.1.1", "@wdio/config": "9.20.0", @@ -12551,7 +12542,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -12634,7 +12624,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -13976,7 +13965,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", @@ -15949,8 +15937,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz", "integrity": "sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/d3-timer": { "version": "3.0.1", @@ -16680,8 +16667,7 @@ "version": "0.0.1312386", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/diff": { "version": "8.0.2", @@ -17486,7 +17472,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -17604,7 +17589,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -17754,7 +17738,6 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -17812,7 +17795,6 @@ "integrity": "sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "aria-query": "~5.1.3", "array-includes": "^3.1.8", @@ -17844,7 +17826,6 @@ "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -17878,7 +17859,6 @@ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -18047,7 +18027,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -18516,7 +18495,6 @@ "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.4.3.tgz", "integrity": "sha512-/XxRRR90gNSuNf++w1jOQjhC5LE9Ixf/iAQctVb/miEI3dwzPZTuG27/omoh5REfSLDoPXofM84vAH/ULtz35g==", "dev": true, - "peer": true, "dependencies": { "@vitest/snapshot": "^3.2.4", "deep-eql": "^5.0.2", @@ -19142,7 +19120,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -19636,7 +19613,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -24762,7 +24738,6 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", - "peer": true, "engines": { "node": "*" } @@ -26202,7 +26177,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -26993,7 +26967,6 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -27418,7 +27391,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -27540,7 +27512,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -27726,8 +27697,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", @@ -27773,7 +27743,6 @@ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -27861,7 +27830,6 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -30304,7 +30272,6 @@ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", @@ -31372,8 +31339,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsutils": { "version": "3.21.0", @@ -31844,7 +31810,6 @@ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=12.20" }, @@ -32802,7 +32767,6 @@ "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -33039,7 +33003,6 @@ "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", diff --git a/src/js/common/actions/PoliticianActions.js b/src/js/common/actions/PoliticianActions.js index 5d58a6a5a..1f2327e85 100644 --- a/src/js/common/actions/PoliticianActions.js +++ b/src/js/common/actions/PoliticianActions.js @@ -50,6 +50,15 @@ export default { Dispatcher.dispatch({ type: 'profilePhotoTooBigReset', payload: true }); }, + politicianPoliticalPartySave (politicianWeVoteId, politicalParty) { + Dispatcher.loadEndpoint('politicianSave', + { + political_party: politicalParty, + political_party_changed: true, + politician_we_vote_id: politicianWeVoteId, + }); + }, + politicianRetrieve (politicianWeVoteId, asOwner = false) { if (asOwner) { Dispatcher.loadEndpoint('politicianRetrieveAsOwner', diff --git a/src/js/common/stores/PoliticianStore.js b/src/js/common/stores/PoliticianStore.js index ba38a71ec..de592f199 100644 --- a/src/js/common/stores/PoliticianStore.js +++ b/src/js/common/stores/PoliticianStore.js @@ -99,6 +99,14 @@ class PoliticianStore extends ReduceStore { return ''; } + getPoliticalParty (politicianWeVoteId) { + const politician = this.getState().allCachedPoliticians[politicianWeVoteId] || {}; + if (politician) { + return politician.political_party || ''; + } + return ''; + } + getMostLikelyOfficeDictFromPoliticianWeVoteId (politicianWeVoteId) { const politician = this.getPoliticianByWeVoteId(politicianWeVoteId); // console.log('getMostLikelyOfficeDictFromPoliticianWeVoteId politician:', politician) diff --git a/src/js/components/PoliticianSelfEdit/SettingsPoliticalParty.jsx b/src/js/components/PoliticianSelfEdit/SettingsPoliticalParty.jsx index fdd549476..08fe9cb65 100644 --- a/src/js/components/PoliticianSelfEdit/SettingsPoliticalParty.jsx +++ b/src/js/components/PoliticianSelfEdit/SettingsPoliticalParty.jsx @@ -1,38 +1,116 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; import styled from 'styled-components'; import DesignTokenColors from '../../common/components/Style/DesignTokenColors'; +import PoliticianActions from '../../common/actions/PoliticianActions'; +import PoliticianStore from '../../common/stores/PoliticianStore'; -const SettingsPoliticalParty = () => { +const delayBeforeSavingToAPI = 1500; +const delayBeforeShowingSavedStatus = 3000; +const enterYourOwnPartyText = '-- Enter your own --'; + +const SettingsPoliticalParty = ({ politicianWeVoteId }) => { + // The master list of political party options are in WeVoteServer/wevote_functions/functions.py - candidate_party_display const partyOptions = [ - "Democratic Party", - "Republican Party", - "Green Party", - "Libertarian Party", - "Independent", - "Enter your own" + 'Democrat', + 'Republican', + 'Green', + 'Libertarian', + 'Independent', + 'Nonpartisan', + 'No Party Preference', + 'Peace and Freedom', + 'Working Families', + 'Constitution', + 'No Labels', + enterYourOwnPartyText, ]; - const [selectedParty, setSelectedParty] = useState(""); - const [customParty, setCustomParty] = useState(""); + const [customParty, setCustomParty] = useState(''); + const [savedStatus, setSavedStatus] = useState(''); + const [selectedParty, setSelectedParty] = useState(''); + + const clearStatusTimer = useRef(null); + const savingStatusTimer = useRef(null); + + const updatePoliticalPartyCustom = (event) => { + const politicalPartyValue = event.target.value; + if (event.target.name === 'politicalPartyCustom') { + setSelectedParty(enterYourOwnPartyText); + setCustomParty(politicalPartyValue); + if (savingStatusTimer.current) clearTimeout(savingStatusTimer.current); + savingStatusTimer.current = setTimeout(() => { + // After some time, save to the API server + setSavedStatus('Saving Political Party...'); + PoliticianActions.politicianPoliticalPartySave(politicianWeVoteId, politicalPartyValue); + }, delayBeforeSavingToAPI); + + if (clearStatusTimer.current) clearTimeout(clearStatusTimer.current); + clearStatusTimer.current = setTimeout(() => { + // After some time, show that the data was saved + setSavedStatus('Saved'); + }, delayBeforeShowingSavedStatus); + } + }; + + const updatePoliticalPartyFromDropdown = (event) => { + const politicalPartyValue = event.target.value; + if (event.target.name === 'politicalParty') { + if (politicalPartyValue === enterYourOwnPartyText) { + setSelectedParty(politicalPartyValue); + } else { + setSavedStatus('Saving Political Party...'); + setSelectedParty(politicalPartyValue); + PoliticianActions.politicianPoliticalPartySave(politicianWeVoteId, politicalPartyValue); + // Clear any existing timeout + if (clearStatusTimer.current) clearTimeout(clearStatusTimer.current); + // After some time, show that the data was saved + clearStatusTimer.current = setTimeout(() => { + setSavedStatus('Saved'); + }, delayBeforeShowingSavedStatus); + } + } + }; + + // Cleanup timeout on component unmount + useEffect(() => { + if (politicianWeVoteId && partyOptions) { + const politicalPartyFromAPIServer = PoliticianStore.getPoliticalParty(politicianWeVoteId); + // If politicalPartyFromAPIServer is in the array of partyOptions, update the dropdown with it + if (politicalPartyFromAPIServer && partyOptions.some((option) => option.toLowerCase() === politicalPartyFromAPIServer.toLowerCase())) { + setSelectedParty(politicalPartyFromAPIServer); + } else { + setSelectedParty(enterYourOwnPartyText); + setCustomParty(politicalPartyFromAPIServer); + } + } + + // Clear the timer when the component is unmounted to prevent memory leaks + return () => { + if (clearStatusTimer.current) clearTimeout(clearStatusTimer.current); + if (savingStatusTimer.current) clearTimeout(savingStatusTimer.current); + }; + }, []); return ( - +

Political Party

- + + Select from common political parties or choose the "Enter your own" option. + {/* Custom input only visible if needed */} - {selectedParty === "Enter your own" && ( + {selectedParty === enterYourOwnPartyText && ( setCustomParty(e.target.value)} + onChange={updatePoliticalPartyCustom} /> )} -
+ {savedStatus} + ); }; +SettingsPoliticalParty.propTypes = { + politicianWeVoteId: PropTypes.string, +}; -const Wrapper = styled('div')` - padding: 16px; - max-width: 700px; - color: ${DesignTokenColors.neutralUI900}; +const CustomWrapper = styled('div')` + margin-top: 3px; `; /* Matches Official Statement header container */ @@ -70,14 +151,35 @@ const HeaderContainer = styled('div')` margin-bottom: 4px; `; -/* Uses the same spacing behavior as IntroductionWrapper in Official Statement */ -const Label = styled('label')` +const Input = styled('input')` + width: 100%; + padding: 12px; + margin-top: 8px; + font-size: 16px; + border-radius: 8px; + border: 1px solid ${DesignTokenColors.neutralUI400}; + outline: none; + + &:focus { + border-color: ${DesignTokenColors.primary600}; + } +`; + +const IntroductionText = styled('div')` display: block; - margin-top: 8px; /* Spacing between title and intro text */ + margin-top: 0px; /* Spacing between title and intro text */ margin-bottom: 12px; /* Same spacing as Official Statement intro text */ font-size: 15px; color: ${DesignTokenColors.neutralUI700}; - font-weight: 600; + font-weight: 300; +`; + +const SavedStatus = styled('div')` + display: block; + margin-top: 12px; + font-size: 15px; + color: ${DesignTokenColors.neutralUI700}; + font-weight: 300; `; const Select = styled('select')` @@ -101,22 +203,10 @@ const Select = styled('select')` } `; -const Input = styled('input')` - width: 100%; - padding: 12px; - margin-top: 8px; - font-size: 16px; - border-radius: 8px; - border: 1px solid ${DesignTokenColors.neutralUI400}; - outline: none; - - &:focus { - border-color: ${DesignTokenColors.primary600}; - } -`; - -const CustomWrapper = styled('div')` - margin-top: 20px; +const SettingsPoliticalPartyWrapper = styled('div')` + padding: 16px; + max-width: 700px; + color: ${DesignTokenColors.neutralUI900}; `; export default SettingsPoliticalParty;