diff --git a/package.json b/package.json index d7a4a2f8..671f8089 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "next-transpile-modules": "^8.0.0", "prop-types": "^15.7.2", "react": "^17.0.2", + "react-color": "^2.19.3", "react-dom": "^17.0.2", "react-firebaseui": "^5.0.2", "react-relay": "^12.0.0", diff --git a/src/components/BackgroundColorPicker.js b/src/components/BackgroundColorPicker.js new file mode 100644 index 00000000..884da4f9 --- /dev/null +++ b/src/components/BackgroundColorPicker.js @@ -0,0 +1,71 @@ +import React, { useCallback, useState } from 'react' +import { makeStyles } from '@material-ui/core/styles' +import { SketchPicker } from 'react-color' +import { Typography } from '@material-ui/core' +import PropTypes from 'prop-types' + +const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + flexWrap: 'wrap', + }, + gridList: { + display: 'flex', + flexDirection: 'row', + width: '100%', + }, + previewContainer: { + marginTop: 0, + margin: theme.spacing(2), + width: '100%', + height: '60%', + }, + header: { + paddingLeft: 0, + }, + divider: { + marginBottom: 10, + }, +})) +const BackgroundColorPicker = ({ user, onBackgroundColorSelection }) => { + const classes = useStyles() + const [selectedColor, setSelectedColor] = useState( + user.backgroundColor || '#000' + ) + + const onColorChanged = useCallback( + (color) => { + setSelectedColor(color.hex) + onBackgroundColorSelection(color.hex) + }, + [onBackgroundColorSelection] + ) + + return ( +
+ Select your color +
+ +
+
+
+ ) +} + +BackgroundColorPicker.propTypes = { + user: PropTypes.shape({ + backgroundColor: PropTypes.string.isRequired, + }).isRequired, + onBackgroundColorSelection: PropTypes.func.isRequired, +} + +export default BackgroundColorPicker diff --git a/src/components/BackgroundColorPicker.stories.jsx b/src/components/BackgroundColorPicker.stories.jsx new file mode 100644 index 00000000..ea98bcef --- /dev/null +++ b/src/components/BackgroundColorPicker.stories.jsx @@ -0,0 +1,37 @@ +import React from 'react' +import { makeStyles } from '@material-ui/core/styles' +import blue from '@material-ui/core/colors/blue' +import BackgroundColorPicker from './BackgroundColorPicker' + +export default { + title: 'Components/BackgroundColorPicker', + component: BackgroundColorPicker, +} + +const useStyles = makeStyles(() => ({ + templateContainer: { + background: blue['200'], + height: 700, + }, +})) + +// eslint-disable-next-line react/jsx-props-no-spreading +const Template = (args) => { + const classes = useStyles() + return ( +
+ +
+ ) +} + +export const normal = Template.bind({}) +normal.args = { + user: { + backgroundColor: '#138', + }, + onBackgroundColorSelection: () => { + // eslint-disable-next-line no-console + console.log('onBackgroundColorSelection') + }, +} diff --git a/src/components/__tests__/BackgroundColorPicker.test.js b/src/components/__tests__/BackgroundColorPicker.test.js new file mode 100644 index 00000000..26435999 --- /dev/null +++ b/src/components/__tests__/BackgroundColorPicker.test.js @@ -0,0 +1,40 @@ +import React from 'react' +import { shallow } from 'enzyme' +import { SketchPicker } from 'react-color' + +jest.mock('react-color') + +const mockOnBackgroundColorSelection = jest.fn() +const mockProps = { + user: { + backgroundColor: '#FF0000', + }, + onBackgroundColorSelection: mockOnBackgroundColorSelection, +} + +afterEach(() => { + jest.clearAllMocks() +}) + +describe('Background color picker component', () => { + it('renders without error', () => { + const BackgroundColorPicker = + require('src/components/BackgroundColorPicker').default + expect(() => + shallow() + ).not.toThrow() + }) + + it('calls onBackgroundColorSelection callback when the color changes', () => { + const BackgroundColorPicker = + require('src/components/BackgroundColorPicker').default + const wrapper = shallow() + + // Mock that our color picker fires its change callback + const colorData = { + hex: '#CDCDCD', + } + wrapper.find(SketchPicker).prop('onChangeComplete')(colorData) + expect(mockOnBackgroundColorSelection).toHaveBeenCalledWith('#CDCDCD') + }) +}) diff --git a/src/components/groupImpactComponents/GroupGoalNotification.js b/src/components/groupImpactComponents/GroupGoalNotification.js index bc39b9e0..e4626731 100644 --- a/src/components/groupImpactComponents/GroupGoalNotification.js +++ b/src/components/groupImpactComponents/GroupGoalNotification.js @@ -7,6 +7,7 @@ import { ArrowForwardIos } from '@material-ui/icons' import { GROUP_IMPACT_SIDEBAR_STATE } from 'src/utils/constants' import gtag from 'ga-gtag' import Handlebars from 'handlebars' +import moment from 'moment' import Notification from '../Notification' const useStyles = makeStyles((theme) => ({ @@ -64,6 +65,7 @@ const GroupGoalNotification = ({ onGoalStarted, impactTitle, impactCountPerMetric, + dateStarted, }) => { const impactTitleTemplate = Handlebars.compile(impactTitle) const impactTitleCompiled = impactTitleTemplate({ @@ -93,6 +95,10 @@ const GroupGoalNotification = ({ onGoalStarted() }, [mode, onGoalStarted]) + const dateStartedMoment = dateStarted && moment(dateStarted).format('l') + const startingString = `${ + mode === GROUP_IMPACT_SIDEBAR_STATE.COMPLETED ? 'COMPLETED' : 'GOAL STARTED' + }${dateStarted ? ` - Week of ${dateStartedMoment}` : ''}` return (
- {mode === GROUP_IMPACT_SIDEBAR_STATE.COMPLETED - ? 'COMPLETED' - : 'GOAL STARTED'} - : {impactTitleCompiled} + {startingString}: {impactTitleCompiled} } buttons={ @@ -151,6 +154,11 @@ GroupGoalNotification.propTypes = { onGoalStarted: PropTypes.func.isRequired, impactTitle: PropTypes.string.isRequired, impactCountPerMetric: PropTypes.number.isRequired, + dateStarted: PropTypes.string, +} + +GroupGoalNotification.defaultProps = { + dateStarted: null, } export default GroupGoalNotification diff --git a/src/components/groupImpactComponents/GroupGoalNotification.stories.jsx b/src/components/groupImpactComponents/GroupGoalNotification.stories.jsx index 95d3c5dc..f099df95 100644 --- a/src/components/groupImpactComponents/GroupGoalNotification.stories.jsx +++ b/src/components/groupImpactComponents/GroupGoalNotification.stories.jsx @@ -22,4 +22,5 @@ started.args = { mode: GROUP_IMPACT_SIDEBAR_STATE.NEW, impactTitle: 'Fund {{count}} visits from a community healthworker', impactCountPerMetric: 3, + dateStarted: '2020-01-10T10:00:00.000Z', } diff --git a/src/components/groupImpactComponents/GroupImpact.js b/src/components/groupImpactComponents/GroupImpact.js index de291171..3dd31c4a 100644 --- a/src/components/groupImpactComponents/GroupImpact.js +++ b/src/components/groupImpactComponents/GroupImpact.js @@ -58,8 +58,14 @@ const GroupImpact = ({ user }) => { GROUP_IMPACT_SIDEBAR_STATE.NORMAL ) const [sidebarOpen, setSidebarOpen] = useState(false) - const { id, dollarGoal, dollarProgressFromSearch, impactMetric } = - groupImpactMetric + const { + id, + dollarGoal, + dollarProgressFromSearch, + impactMetric, + dateStarted, + dateExpires, + } = groupImpactMetric const { impactTitle, impactCountPerMetric, whyValuableDescription } = impactMetric @@ -192,6 +198,7 @@ const GroupImpact = ({ user }) => { onNextGoal={beginNewGoal} onGoalStarted={onGoalStarted} impactCountPerMetric={impactCountPerMetric} + dateStarted={dateExpires ? dateStarted : null} />
@@ -215,6 +222,8 @@ GroupImpact.propTypes = { whyValuableDescription: PropTypes.string.isRequired, impactCountPerMetric: PropTypes.number.isRequired, }), + dateStarted: PropTypes.string, + dateExpires: PropTypes.string, }).isRequired, groupImpactMetricCount: PropTypes.number, }).isRequired, @@ -260,6 +269,8 @@ GroupImpactWrapper.propTypes = { whyValuableDescription: PropTypes.string.isRequired, impactCountPerMetric: PropTypes.number, }), + dateStarted: PropTypes.string, + dateExpires: PropTypes.string, }), groupImpactMetricCount: PropTypes.number, }).isRequired, diff --git a/src/components/groupImpactComponents/GroupImpact.stories.jsx b/src/components/groupImpactComponents/GroupImpact.stories.jsx index 0109df75..aee9c967 100644 --- a/src/components/groupImpactComponents/GroupImpact.stories.jsx +++ b/src/components/groupImpactComponents/GroupImpact.stories.jsx @@ -154,6 +154,8 @@ standardView.args = { 'Community health workers provide quality health care to those who might not otherwise have access.', impactCountPerMetric: 3, }, + dateStarted: '2020-01-10T10:00:00.000Z', + dateExpires: '2020-07-10T10:00:00.000Z', }, }, }, diff --git a/src/components/groupImpactComponents/GroupImpactContainer.js b/src/components/groupImpactComponents/GroupImpactContainer.js index a3df758b..b327c15c 100644 --- a/src/components/groupImpactComponents/GroupImpactContainer.js +++ b/src/components/groupImpactComponents/GroupImpactContainer.js @@ -16,6 +16,8 @@ export default createFragmentContainer(GroupImpact, { whyValuableDescription impactCountPerMetric } + dateStarted + dateExpires } groupImpactMetricCount } diff --git a/src/components/groupImpactComponents/GroupImpactSidebar.js b/src/components/groupImpactComponents/GroupImpactSidebar.js index 10473400..6db8ac0a 100644 --- a/src/components/groupImpactComponents/GroupImpactSidebar.js +++ b/src/components/groupImpactComponents/GroupImpactSidebar.js @@ -30,6 +30,7 @@ import SearchIcon from '@material-ui/icons/Search' import TabIcon from '@material-ui/icons/Tab' import ToggleButton from '@material-ui/lab/ToggleButton' import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup' +import moment from 'moment' import VerticalLinearProgress from '../VerticalLinearProgress' import GroupImpactLeaderboard from './GroupImpactLeaderboard' import GroupImpactContributionWidget from './GroupImpactContributionWidget' @@ -253,8 +254,16 @@ const GroupImpactSidebar = ({ setSelectedMode(newValue) event.stopPropagation() } - const { dollarProgress, dollarGoal, dollarProgressFromSearch, impactMetric } = - displayingOldGoal ? lastGroupImpactMetric : groupImpactMetric + + const { + dollarProgress, + dollarGoal, + dollarProgressFromSearch, + impactMetric, + dateStarted, + dateExpires, + } = displayingOldGoal ? lastGroupImpactMetric : groupImpactMetric + const { impactTitle, whyValuableDescription, impactCountPerMetric } = impactMetric const classes = useStyles() @@ -406,7 +415,7 @@ const GroupImpactSidebar = ({
- GROUP GOAL + {`${dateExpires ? 'WEEKLY ' : ''}GROUP GOAL`} {groupImpactSidebarState ? ( @@ -422,6 +431,16 @@ const GroupImpactSidebar = ({ )}
+ {dateExpires && ( + + `${days > 0 ? `${days }days`}`` + + )} + />} {impactTitleCompiled} {absoluteProgress}% diff --git a/yarn.lock b/yarn.lock index e563b474..400fc5a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2192,6 +2192,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== +"@icons/material@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" + integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -11296,6 +11301,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -11401,7 +11411,7 @@ lodash.uniq@4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.0.1, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -11572,6 +11582,11 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== +material-colors@^1.2.1: + version "1.2.6" + resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" + integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== + material-design-lite@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/material-design-lite/-/material-design-lite-1.3.0.tgz#d004ce3fee99a1eeb74a78b8a325134a5f1171d3" @@ -13364,7 +13379,7 @@ prop-types@15.7.2, prop-types@^15.7.0, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -13615,6 +13630,19 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-color@^2.19.3: + version "2.19.3" + resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d" + integrity sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA== + dependencies: + "@icons/material" "^0.2.4" + lodash "^4.17.15" + lodash-es "^4.17.15" + material-colors "^1.2.1" + prop-types "^15.5.10" + reactcss "^1.2.0" + tinycolor2 "^1.4.1" + react-docgen-typescript@^2.1.1: version "2.2.2" resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c" @@ -13767,6 +13795,13 @@ react@^17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" +reactcss@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" + integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A== + dependencies: + lodash "^4.0.1" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -15280,10 +15315,10 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.16.tgz#669b75e86b4295fdcc1bb0498de9ac1af6fd51a9" integrity sha512-qImOD23aDfnIDNqlG1NOehdB9IYsn1V9oByPjKY1nakv2MQYCEMyX033/q+aEtYCpmYK1cv2+NTmlH+ra6GA5A== -tab-ads@^1.1.19: - version "1.1.19" - resolved "https://registry.yarnpkg.com/tab-ads/-/tab-ads-1.1.19.tgz#73d189c5e669b22679abdb7fc903a9be30994115" - integrity sha512-8GDDkM83SFJ0HgfdBq7oFwYkJVNFeslWRTdhQ7e3PiHwvgH49X2dm/rlmgBlU28j1hozSy526/5Jxyx3ZUMqgw== +tab-ads@^1.1.20: + version "1.1.20" + resolved "https://registry.yarnpkg.com/tab-ads/-/tab-ads-1.1.20.tgz#b3ef70a03127f746171daf81fa7d6461c9767977" + integrity sha512-dagHnOnf4827hQRmDZ+thvldFac5CuY7pEVSsVO/iJ+02xYc7/g2wn3d4wyQUifXjwf8AJBVCpEerckZAKANgA== dependencies: lodash "^4.17.21" @@ -15501,6 +15536,11 @@ tiny-warning@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +tinycolor2@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"