diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..faa9fb2a Binary files /dev/null and b/.DS_Store differ diff --git a/client/package.json b/client/package.json index b88258fa..a3fa8e95 100644 --- a/client/package.json +++ b/client/package.json @@ -32,6 +32,7 @@ "react-router-redux": "^4.0.8", "react-scripts": "1.1.5", "react-share": "^2.0.0", + "react-spring": "^5.8.0", "redux": "^4.0.0", "redux-logger": "^3.0.6", "redux-persist": "^5.10.0", diff --git a/client/src/components/AutoSuggestInput.js b/client/src/components/AutoSuggestInput.js index 994c1908..fab16d4d 100644 --- a/client/src/components/AutoSuggestInput.js +++ b/client/src/components/AutoSuggestInput.js @@ -68,7 +68,7 @@ class AutoSuggestInput extends Component { type: 'text', value: this.state.address, onChange: this.onChange, - autoFocus: true, + autoFocus: false, placeholder: 'Search your address' }; diff --git a/client/src/components/Banner.js b/client/src/components/Banner.js new file mode 100644 index 00000000..23139d80 --- /dev/null +++ b/client/src/components/Banner.js @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Row, Col } from 'react-bootstrap'; + +const Banner = props => ( + + +
{props.children}
+ +
+); + +Banner.propTypes = { + children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired +}; + +export default Banner; diff --git a/client/src/components/HeroCTA.js b/client/src/components/HeroCTA.js index 3a62f6da..496f5bc7 100644 --- a/client/src/components/HeroCTA.js +++ b/client/src/components/HeroCTA.js @@ -1,59 +1,17 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { Grid, Row, Col, Button } from 'react-bootstrap'; -import AutoSuggestInput from './AutoSuggestInput'; +import { Grid } from 'react-bootstrap'; -const HeroCTA = ({ - openMap, - firebaseSearchAddressFlow, - clearInitialSearchResults, - firebaseCampaigns, - router -}) => ( +const HeroCTA = () => ( -
- - +
+
-

NEED RECYCLING?

-

Recruit, Request, Recycle

- - -

- We have a mission to change Denver's low recycling rate by making it easy for you - and your neighbors to petition your landlord for recyling for your building. -

- -
+

NEED RECYCLING IN YOUR APARTMENT OR CONDO?

+
Recruit, Request, Recycle
- - - - -
- -
- -
- - -
+
+
); -HeroCTA.propTypes = { - openMap: PropTypes.func.isRequired, - clearInitialSearchResults: PropTypes.func.isRequired, - firebaseCampaigns: PropTypes.shape({}).isRequired, - firebaseSearchAddressFlow: PropTypes.func.isRequired, - router: PropTypes.shape({}).isRequired -}; - export default HeroCTA; diff --git a/client/src/components/HowItWorks/HowItWorks.js b/client/src/components/HowItWorks/HowItWorks.js new file mode 100644 index 00000000..bcb7fba6 --- /dev/null +++ b/client/src/components/HowItWorks/HowItWorks.js @@ -0,0 +1,189 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Grid, Row, Col } from 'react-bootstrap'; +import { connect } from 'react-redux'; +import AutoSuggestInput from '../AutoSuggestInput'; +import { + firebaseSearchAddressFlow, + clearInitialSearchResults +} from '../../redux/actions/firebaseInitialSearch'; + +// import screenShot from '../../images/screen-shot.png'; +// import list from '../../images/list.png'; +// import letter from '../../images/letter.png'; +// import smallList from '../../images/small-list.png'; +// import guideline1 from '../../images/guildline1.png'; +// import guideline2 from '../../images/guideline2.png'; + +import HeroCTA from '../HeroCTA'; +import Steps from './Steps'; +import Footer from '../Footer/Footer'; +import HowItWorksStepHeaderContent from './HowItWorksStepHeaderContent'; +import HowItWorksStepContent from './HowItWorksStepContent'; + +const stepsData = [ + { + title: 'Create or Join a Campaign', + icon: , + prevStepBtn: , + nextStepBtn: , + content: [ + { h3: 'Start by searching for your condo or apartment building address' }, + { + p: + `Enter your condo or apartment building address in the search bar below to find out${' '}` + + `if there is a recycling campaign already active for your building. Then, follow${' '}` + + `the on screen instructions to either create a brand new campaign or sign the${' '}` + + 'online petition for an existing one.' + } + ] + }, + { + title: 'Recruit Your Neighbors', + icon: , + prevStepBtn: , + nextStepBtn: , + content: [ + { h3: 'Power in Numbers' }, + { + p: + `Now it's time to spread the word about your building's new recycling campaign to${' '}` + + `your neighbors. Gathering signatures from your fellow tenants let's your landlord${' '}` + + 'know just how important recycling services are to your community!' + }, + { h3: 'Tools' }, + { + ul: [ + { + li: + `Print and post this petition in a public space in your apartment building, such${' '}` + + 'as a laundry or mail room.' + } + ] + } + ] + }, + { + title: 'Request Recyling From Your Landlord', + icon: , + prevStepBtn: , + nextStepBtn: , + content: [ + { + p: + `Submit the letter to your landlord along with the petition signatures. If${' '}` + + 'possible, bring other neighbors along; there is great strength in numbers.' + } + ] + }, + { + title: 'Recycle!', + icon: , + prevStepBtn: , + nextStepBtn: , + content: [ + { + p: + `Congratulations for getting recycling services for your building! Best practice${' '}` + + `shows that posting guidelines will help your neighbors recycle correctly. When a${' '}` + + `recycling bin is too contaminated your complex will either be given 1 week to${' '}` + + `clean out the recycling bin or be charged extra to take it to a landfill as trash.${' '}` + + `Common contaminants in the recycing bin include plastic bags because they jam up${' '}` + + `the machine and disposable ware such as red solo cups, disposable cutlery and${' '}` + + 'disposable plates.' + } + ] + } +]; + +const HowItWorks = props => { + let currentStep = 0; + if (props.location.state && props.location.action === 'PUSH') { + currentStep = props.location.state.currentStep; + } + + const steps = stepsData.map((stepData, index) => ({ + headerContent: ( + + ), + content: , + prevStepBtn: stepData.prevStepBtn, + nextStepBtn: stepData.nextStepBtn + })); + + return ( +
+ +
+
+

+ Follow these easy steps! +

+
+
+ + + + + + +

Ready? Enter your address!

+ +
+ + +
+ Start a campaign to bring recycling to your property! Search your address below to get + started. +
+ +
+ + + + + +
+ +
+
+ ); +}; + +HowItWorks.defaultProps = { + location: { + state: { + currentStep: 0 + } + } +}; + +HowItWorks.propTypes = { + router: PropTypes.shape({}).isRequired, + location: PropTypes.shape({ + state: PropTypes.shape({ + currentStep: PropTypes.number + }), + action: PropTypes.string.isRequired + }).isRequired, + firebaseSearchAddressFlow: PropTypes.func.isRequired, + clearInitialSearchResults: PropTypes.func.isRequired +}; + +export default connect( + null, + { + firebaseSearchAddressFlow, + clearInitialSearchResults + } +)(HowItWorks); diff --git a/client/src/components/HowItWorks/HowItWorksStepContent.js b/client/src/components/HowItWorks/HowItWorksStepContent.js new file mode 100644 index 00000000..8816acd6 --- /dev/null +++ b/client/src/components/HowItWorks/HowItWorksStepContent.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Row, Col, Grid } from 'react-bootstrap'; + +const buildContents = (content, lvl) => { + const result = []; + for (let i = 0; i < content.length; i += 1) { + const keys = Object.keys(content[i]); + if (keys && keys[0]) { + const DynamicTag = `${keys[0]}`; + const value = content[i][DynamicTag]; + if (React.isValidElement(value) || typeof value !== 'object') { + result.push( + {value} + ); + } else if (Array.isArray(value)) { + const newElem = ( + + {buildContents(value, lvl + 1)} + + ); + result.push(newElem); + } + } + } + return result; +}; + +const HowItWorksStepContent = ({ content }) => { + const result = buildContents(content, 0); + return ( + + + +
{result}
+ +
+
+ ); +}; + +HowItWorksStepContent.propTypes = { + content: PropTypes.arrayOf(PropTypes.shape({})).isRequired +}; + +export default HowItWorksStepContent; diff --git a/client/src/components/HowItWorks/HowItWorksStepHeaderContent.js b/client/src/components/HowItWorks/HowItWorksStepHeaderContent.js new file mode 100644 index 00000000..f2ccf731 --- /dev/null +++ b/client/src/components/HowItWorks/HowItWorksStepHeaderContent.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const HowItWorksStepHeaderContent = ({ title, icon, stepIndex, priority, stacked }) => { + const classes = stacked ? 'step-header-content stacked' : 'step-header-content'; + const HeaderTag = `h${priority}`; + return ( +
+ {icon} + {`${stepIndex + 1}. ${title}`} +
+ ); +}; + +HowItWorksStepHeaderContent.defaultProps = { + icon: null, + stacked: true, + priority: 2 +}; + +HowItWorksStepHeaderContent.propTypes = { + title: PropTypes.string.isRequired, + icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + stepIndex: PropTypes.number.isRequired, + priority: PropTypes.number, + stacked: PropTypes.bool +}; + +export default HowItWorksStepHeaderContent; diff --git a/client/src/components/HowItWorks/PulsedStepSelectorButton.js b/client/src/components/HowItWorks/PulsedStepSelectorButton.js new file mode 100644 index 00000000..b64508a9 --- /dev/null +++ b/client/src/components/HowItWorks/PulsedStepSelectorButton.js @@ -0,0 +1,77 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Spring, animated } from 'react-spring'; +import { TimingAnimation, Easing } from 'react-spring/dist/addons.cjs'; + +class PulsedStepSelectorButton extends Component { + constructor(props) { + super(props); + this.state = { + pulseOpacity: 1.0, + pulseColor: 'rgba(249, 199, 100, 1)' + }; + } + + componentDidMount() { + this.pulseInterval = setInterval(() => { + if (this.state.pulseOpacity === 1.0) { + this.setState({ pulseOpacity: 0.8, pulseColor: 'rgba(255,255,255,1)' }); + } else { + this.setState({ pulseOpacity: 1.0, pulseColor: 'rgba(249,199,100,1)' }); + } + }, this.props.pulseDelay); + } + + componentWillUnmount() { + clearInterval(this.pulseInterval); + } + + render() { + const { onClick, stepNum, vertical } = this.props; + const { pulseOpacity, pulseColor } = this.state; + let classes = `step-selector-btn-${stepNum}`; + if (vertical) { + classes += ' vertical'; + } + const animationDuration = pulseOpacity === 1.0 ? 300 : 600; + return ( + + ); + } +} + +PulsedStepSelectorButton.defaultProps = { + pulseDelay: 600, + vertical: false +}; + +PulsedStepSelectorButton.propTypes = { + onClick: PropTypes.func.isRequired, + stepNum: PropTypes.number.isRequired, + pulseDelay: PropTypes.number, + vertical: PropTypes.bool +}; + +export default PulsedStepSelectorButton; diff --git a/client/src/components/HowItWorks/StepHeaderNavButton.js b/client/src/components/HowItWorks/StepHeaderNavButton.js new file mode 100644 index 00000000..9f7752ef --- /dev/null +++ b/client/src/components/HowItWorks/StepHeaderNavButton.js @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const StepHeaderNavButton = ({ + children, + onClick, + targetStep, + isReachableByKeyboard, + isDisabled +}) => { + const classes = isDisabled ? 'step-header-nav disabled' : 'step-header-nav'; + if (!children) { + return null; + } + return ( + + ); +}; + +StepHeaderNavButton.propTypes = { + children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]).isRequired, + onClick: PropTypes.func.isRequired, + targetStep: PropTypes.number.isRequired, + isReachableByKeyboard: PropTypes.bool.isRequired, + isDisabled: PropTypes.bool.isRequired +}; + +export default StepHeaderNavButton; diff --git a/client/src/components/HowItWorks/StepSelectorButton.js b/client/src/components/HowItWorks/StepSelectorButton.js new file mode 100644 index 00000000..e2cbe687 --- /dev/null +++ b/client/src/components/HowItWorks/StepSelectorButton.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const StepSelectorButton = ({ onClick, stepNum, isSelected, isHighlighted, vertical }) => { + let classes = `step-selector-btn-${stepNum}`; + if (isSelected) { + classes += ' selected'; + } else { + classes += ' not-selected'; + } + if (isHighlighted) { + classes += ' highlighted'; + } + if (vertical) { + classes += ' vertical'; + } + return ( + + ); +}; + +StepSelectorButton.defaultProps = { + vertical: false +}; + +StepSelectorButton.propTypes = { + onClick: PropTypes.func.isRequired, + stepNum: PropTypes.number.isRequired, + isSelected: PropTypes.bool.isRequired, + isHighlighted: PropTypes.bool.isRequired, + vertical: PropTypes.bool +}; + +export default StepSelectorButton; diff --git a/client/src/components/HowItWorks/Steps.js b/client/src/components/HowItWorks/Steps.js new file mode 100644 index 00000000..772c4056 --- /dev/null +++ b/client/src/components/HowItWorks/Steps.js @@ -0,0 +1,186 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Spring, animated } from 'react-spring'; +import { TimingAnimation, Easing } from 'react-spring/dist/addons.cjs'; + +// Components +import StepsMainNav from './StepsMainNav'; +import StepHeaderNavButton from './StepHeaderNavButton'; + +class Steps extends Component { + constructor(props) { + super(props); + this.state = { + currentStep: props.currentStep, + autoSlide: props.autoSlide + }; + } + + componentDidMount() { + const { autoSlide, currentStep } = this.state; + const { steps, autoSlideDelay } = this.props; + if (autoSlide) { + this.autoSlideInterval = setInterval(() => { + if (autoSlide) { + const nextStep = (currentStep + 1) % steps.length; + this.setState({ currentStep: nextStep }); + } + }, autoSlideDelay); + } + } + + componentWillUnmount() { + if (this.autoSlideInterval) { + clearInterval(this.autoSlideInterval); + } + } + + goToStep = (i, stopAutoSliding = true) => { + if (i < this.props.steps.length && i > -1) { + if (stopAutoSliding) { + this.setState({ currentStep: i, autoSlide: false }); + } else { + this.setState({ currentStep: i }); + } + } + }; + + createSteps = () => { + const { vertical, steps } = this.props; + const numSteps = steps.length; + const stepsComponents = []; + const currentStep = this.state.currentStep; + for (let i = 0; i < numSteps; i += 1) { + let springTo = { opacity: 0.2 }; + let springFrom = { opacity: 1 }; + if (!vertical) { + springTo.height = 0; + springFrom.height = 'auto'; + } + if (currentStep === i) { + springTo = { opacity: 1 }; + springFrom = { opacity: 0.2 }; + if (!vertical) { + springTo.height = 'auto'; + springFrom.height = 200; + } + } + const step = ( + + {style => ( + +
+ {steps[i].prevStepBtn && ( + + {steps[i].prevStepBtn} + + )} + + {steps[i].headerContent} + + {steps[i].nextStepBtn && ( + + {steps[i].nextStepBtn} + + )} +
+ + {steps[i].content} +
+ )} +
+ ); + stepsComponents.push(step); + } + return stepsComponents; + }; + + render() { + let containerClasses = 'steps-container'; + let springTo; + const { currentStep } = this.state; + const { height, pulseNextStep, vertical, steps } = this.props; + if (vertical) { + containerClasses += ' vertical'; + springTo = { left: 0, top: `-${currentStep * 100}%` }; + } else { + springTo = { left: `-${currentStep * 100}%`, top: 0 }; + } + return ( +
+ + + + {style => ( + + {this.createSteps()} + + )} + + + {vertical ?
: null} +
+ ); + } +} + +Steps.defaultProps = { + currentStep: 0, + vertical: false, + pulseNextStep: true, + autoSlide: false, + autoSlideDelay: 4000, + height: 'auto', + steps: { + prevStepBtn: null, + nextStepBtn: null, + headerContent: null + } +}; + +Steps.propTypes = { + currentStep: PropTypes.number, + vertical: PropTypes.bool, + height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + pulseNextStep: PropTypes.bool, + autoSlide: PropTypes.bool, + autoSlideDelay: PropTypes.number, + steps: PropTypes.arrayOf( + PropTypes.shape({ + content: PropTypes.node.isRequired, + headerContent: PropTypes.node, + prevStepBtn: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), + nextStepBtn: PropTypes.oneOfType([PropTypes.node, PropTypes.string]) + }) + ).isRequired +}; + +export default Steps; diff --git a/client/src/components/HowItWorks/StepsMainNav.js b/client/src/components/HowItWorks/StepsMainNav.js new file mode 100644 index 00000000..950459c4 --- /dev/null +++ b/client/src/components/HowItWorks/StepsMainNav.js @@ -0,0 +1,74 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +// Components +import StepSelectorButton from './StepSelectorButton'; +import PulsedStepSelectorButton from './PulsedStepSelectorButton'; + +const StepsMainNav = ({ + currentStep, + totalNumSteps, + vertical, + pulseNextStep, + pulseDelay, + onClick +}) => { + let containerClasses = 'step-selectors-container'; + if (vertical) { + containerClasses += ' vertical'; + } + + const stepSelectorBtns = []; + stepSelectorBtns.push(
); + for (let i = 0; i < totalNumSteps; i += 1) { + const stepSpacerClasses = i < currentStep ? 'spacer-line highlighted' : 'spacer-line'; + if (pulseNextStep && i === currentStep + 1) { + stepSelectorBtns.push( + { + onClick(i); + }} + vertical={vertical} + /> + ); + } else { + stepSelectorBtns.push( + { + onClick(i); + }} + vertical={vertical} + /> + ); + } + if (i !== totalNumSteps - 1) { + stepSelectorBtns.push(
); + } + } + stepSelectorBtns.push(
); + + return
{stepSelectorBtns}
; +}; + +StepsMainNav.defaultProps = { + vertical: false, + pulseNextStep: true, + pulseDelay: 600 +}; + +StepsMainNav.propTypes = { + currentStep: PropTypes.number.isRequired, + totalNumSteps: PropTypes.number.isRequired, + onClick: PropTypes.func.isRequired, + vertical: PropTypes.bool, + pulseNextStep: PropTypes.bool, + pulseDelay: PropTypes.number +}; +export default StepsMainNav; diff --git a/client/src/components/Informational/InfoAndLinks.js b/client/src/components/Informational/InfoAndLinks.js index 60e6fa8a..ec757425 100644 --- a/client/src/components/Informational/InfoAndLinks.js +++ b/client/src/components/Informational/InfoAndLinks.js @@ -1,10 +1,8 @@ import React from 'react'; -import RecyclingInfo from './RecyclingInfo'; import PropManagerLinks from './PropManagerLinks'; const InfoAndLinks = () => (
-
diff --git a/client/src/components/Informational/RecyclingInfo.js b/client/src/components/Informational/RecyclingInfo.js index d1c60092..708ae469 100644 --- a/client/src/components/Informational/RecyclingInfo.js +++ b/client/src/components/Informational/RecyclingInfo.js @@ -1,24 +1,65 @@ import React from 'react'; -import { Link } from 'react-router'; -import { Col, PageHeader } from 'react-bootstrap'; +import { Row, Col } from 'react-bootstrap'; +import SingleGraphBar from '../SingleGraphBar'; const RecyclingInfo = () => ( - -
- - Denver's recycling rate is only 23%, while the national average is 35%. - -

- The City of Denver only provides recycling service to single-family residential homes and - buildings with seven or fewer units. Furthermore, multi-family building managers are not - mandated to provide recycling service, but you and your neighbors can request recycling - service from your landlord today! -

- - LEARN MORE - -
- + + +
+
+
+
+ + Colorado buries more than + + $265M + worth of resources every year. +
+
+ +
+ Be a part of the solution! We can help you and your neighbors request recycling from + your landlord in 3 easy steps. +
+
+ +
+
+ Denver's recycling rate: + +
+ +
+ National recycling rate: + +
+ +
+ San Francisco's recycling rate: + +
+
+
+ +
); export default RecyclingInfo; diff --git a/client/src/components/Navigation/Navbar.js b/client/src/components/Navigation/Navbar.js index f525c8be..bf6f1dc2 100644 --- a/client/src/components/Navigation/Navbar.js +++ b/client/src/components/Navigation/Navbar.js @@ -31,7 +31,7 @@ const NavBar = ({ homeText = props.location.pathname === '/' ? (homeText = 'RE:IMAGINE DENVER') : (homeText = 'HOME'); return ( - + @@ -45,6 +45,9 @@ const NavBar = ({ WHY + + HOW IT WORKS + Property Manager Resources diff --git a/client/src/components/SingleGraphBar.js b/client/src/components/SingleGraphBar.js new file mode 100644 index 00000000..4f2d6924 --- /dev/null +++ b/client/src/components/SingleGraphBar.js @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Transition, animated } from 'react-spring'; +import { TimingAnimation, Easing } from 'react-spring/dist/addons'; + +const SingleGraphBar = ({ num, denom, barColor, bgColor, thickness }) => { + if (barColor && barColor.length > 0) { + return ( +
+
+ + {style => } + +
{`${(num / denom) * 100} %`}
+
+
+
+ ); + } + return ( +
+ + {style => } + + +
+ {`${(num / denom) * 100} %`} +
+
+ ); +}; + +SingleGraphBar.defaultProps = { + barColor: '', + bgColor: 'transparent' +}; + +SingleGraphBar.propTypes = { + num: PropTypes.number.isRequired, + denom: PropTypes.number.isRequired, + barColor: PropTypes.string, + bgColor: PropTypes.string, + thickness: PropTypes.number.isRequired +}; + +export default SingleGraphBar; diff --git a/client/src/components/StepByStep.js b/client/src/components/StepByStep.js index 74a31483..b87abc6e 100644 --- a/client/src/components/StepByStep.js +++ b/client/src/components/StepByStep.js @@ -1,30 +1,118 @@ import React from 'react'; -import { PageHeader } from 'react-bootstrap'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router'; +import { Row, Col, Grid } from 'react-bootstrap'; +import Steps from './HowItWorks/Steps'; +import HowItWorksStepHeaderContent from './HowItWorks/HowItWorksStepHeaderContent'; +import HowItWorksStepContent from './HowItWorks/HowItWorksStepContent'; -const StepByStep = () => ( -
- - HOW DOES THIS WORK? - -
-
- -

1. CREATE

-
-
- -

2. RECRUIT

-
-
- -

3. REQUEST

-
-
- -

4. RECYCLE

-
+const shortStepsData = [ + { + title: 'Create or Join a Campaign', + icon: , + content: [ + { + div: ( +

+ Look up your address{' '} + + {' '} + here{' '} + {' '} + to create a new recycling campaign for your building or join an existing one! +

+ ) + } + ] + }, + { + title: 'Recruit Your Neighbors', + icon: , + content: [ + { + p: 'We provide you with the resources to gather the support of your building community.' + } + ] + }, + { + title: 'Request Recyling From Your Landlord', + icon: , + content: [ + { + p: 'Submit your petition for recycling to your landlord and hope for the best!' + } + ] + }, + { + title: 'Recycle!', + icon: , + content: [ + { + p: + "That's it! Hopefully your landlord agrees to provide recycling services for you and " + + 'your community. Enjoy your convenient recycling!' + } + ] + } +]; + +const HowItWorksVerticalStep = ({ title, icon, content, stepIndex }) => ( +
+
+ +
+ + More Details +
); +HowItWorksVerticalStep.defaultProps = { + icon: null +}; + +HowItWorksVerticalStep.propTypes = { + title: PropTypes.string.isRequired, + icon: PropTypes.node, + content: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + stepIndex: PropTypes.number.isRequired +}; + +const StepByStep = () => { + const steps = shortStepsData.map((stepData, index) => { + const data = { ...stepData, stepIndex: index }; + return { + content: + }; + }); + return ( + + + +

HOW DOES THIS WORK?

+ +
+ + + + + + +
+ ); +}; + export default StepByStep; diff --git a/client/src/components/TenantOrPMChoice.js b/client/src/components/TenantOrPMChoice.js new file mode 100644 index 00000000..46a29327 --- /dev/null +++ b/client/src/components/TenantOrPMChoice.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Grid, Row, Col } from 'react-bootstrap'; +import { Link } from 'react-router'; + +const TenantOrPMChoice = () => ( + + + +
+

Ready? Let's improve recycling together!

+
+ +
+ + +
+

+ You deserve convenient recycling. Apartment or condo managers are not required to + provide recycling services for residents, but there is something you can do. Organize + your neighbors and request recycling from your buliding manager. Together we can make + Denver a sustainability leader! +

+
+ +
+ + +
+
+ I'm a tenant + I'm a property manager +
+
+ +
+
+); + +export default TenantOrPMChoice; diff --git a/client/src/containers/Home.js b/client/src/containers/Home.js index 3d44af5c..6d220a6e 100644 --- a/client/src/containers/Home.js +++ b/client/src/containers/Home.js @@ -1,93 +1,31 @@ -import React, { Component } from 'react'; +import React from 'react'; import { Grid } from 'react-bootstrap'; import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; import HeroCTA from '../components/HeroCTA'; -import CampaignsMap from '../components/CampaignsMap'; +// import CampaignsMap from '../components/CampaignsMap'; import StepByStep from '../components/StepByStep'; -import InfoAndLinks from '../components/Informational/InfoAndLinks'; +import TenantOrPMChoice from '../components/TenantOrPMChoice'; import Footer from '../components/Footer/Footer'; -import { - firebaseSearchAddressFlow, - clearInitialSearchResults -} from '../redux/actions/firebaseInitialSearch'; -import { openMap, closeMap } from '../redux/actions/googleMap'; +import Banner from '../components/Banner'; -class Home extends Component { - constructor(props) { - super(props); - this.state = {}; - } - render() { - /* eslint no-shadow: */ - const { - firebaseCampaigns, - firebaseSearchAddressFlow, - googleMap: { isOpen }, - router - } = this.props; - const { campaigns } = firebaseCampaigns; - return ( -
- - - - - - -
-
- ); - } -} - -Home.defaultProps = { - firebaseCampaigns: PropTypes.shape({ - campaigns: PropTypes.arrayOf() - }) -}; - -Home.propTypes = { - firebaseSearchAddressFlow: PropTypes.func.isRequired, - firebaseCampaigns: PropTypes.shape({ - loading: PropTypes.bool.isRequired, - loaded: PropTypes.bool.isRequired, - campaigns: PropTypes.arrayOf( - PropTypes.shape({ - address: PropTypes.string.isRequired, - campaignId: PropTypes.string.isRequired, - createdAt: PropTypes.shape({}).isRequired, - latLng: PropTypes.shape({ - _lat: PropTypes.number.isRequired, - _long: PropTypes.number.isRequired - }).isRequired - }).isRequired - ) - }).isRequired, - googleMap: PropTypes.shape({ - isOpen: PropTypes.bool.isRequired - }).isRequired, - openMap: PropTypes.func.isRequired, - closeMap: PropTypes.func.isRequired, - router: PropTypes.shape({}).isRequired -}; +const Home = () => ( +
+ + + +
+ Most Denver apartments don't have recycling on site. +
+
We can help you get recycling at your building.
+
+
+ + +
+
+); export default connect( - ({ initialSearch, googleMap, firebaseCampaigns }) => ({ - ...initialSearch, - googleMap, - firebaseCampaigns - }), - { - firebaseSearchAddressFlow, - clearInitialSearchResults, - openMap, - closeMap - } + null, + null )(Home); diff --git a/client/src/images/guideline2.png b/client/src/images/guideline2.png new file mode 100644 index 00000000..b85648fc Binary files /dev/null and b/client/src/images/guideline2.png differ diff --git a/client/src/images/guildline1.png b/client/src/images/guildline1.png new file mode 100644 index 00000000..c707d795 Binary files /dev/null and b/client/src/images/guildline1.png differ diff --git a/client/src/images/letter.png b/client/src/images/letter.png new file mode 100644 index 00000000..6f093eea Binary files /dev/null and b/client/src/images/letter.png differ diff --git a/client/src/images/list.png b/client/src/images/list.png new file mode 100644 index 00000000..241c45aa Binary files /dev/null and b/client/src/images/list.png differ diff --git a/client/src/images/screen-shot.png b/client/src/images/screen-shot.png new file mode 100644 index 00000000..66b7c16d Binary files /dev/null and b/client/src/images/screen-shot.png differ diff --git a/client/src/images/small-list.png b/client/src/images/small-list.png new file mode 100644 index 00000000..e26e7462 Binary files /dev/null and b/client/src/images/small-list.png differ diff --git a/client/src/index.js b/client/src/index.js index e85a5e6b..cfbcc3e0 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -34,7 +34,7 @@ export const history = syncHistoryWithStore(browserHistory, store); render( - + window.scrollTo(0, 0)} history={history} routes={routes} /> , document.getElementById('root') diff --git a/client/src/routes.js b/client/src/routes.js index cf7da916..d71e1e56 100644 --- a/client/src/routes.js +++ b/client/src/routes.js @@ -7,12 +7,12 @@ import NewCampaign from './containers/NewCampaign'; import CreateCampaignStep1 from './components/CreateCampaignStep1'; import CreateCampaignStep2 from './components/CreateCampaignStep2'; import CreateCampaignStep3 from './components/CreateCampaignStep3'; +import HowItWorks from './components/HowItWorks/HowItWorks'; import CampaignContainer from './containers/CampaignContainer'; import RequestRecyclingTips from './components/Informational/RequestRecyclingTips'; import DenverInfo from './components/Informational/DenverInfo'; import ManagerResources from './components/Informational/ManagerResources'; import DenverLearnMore from './components/Informational/DenverLearnMore'; -import Instructions from './components/Instructions'; import Collaboration from './components/Informational/Collaboration'; import PrivacyPolicy from './components/Footer/PrivacyPolicy'; import NotFound from './components/UtilComponents/NotFound'; @@ -72,8 +72,8 @@ export default ( getComponent={(location, callback) => callback(null, PrivacyPolicy)} /> callback(null, Instructions)} + path="/how-does-this-work" + getComponent={(location, callback) => callback(null, HowItWorks)} /> callback(null, NotFound)} /> diff --git a/client/src/static/assets/crushed_can.png b/client/src/static/assets/crushed_can.png new file mode 100644 index 00000000..9e38a1ef Binary files /dev/null and b/client/src/static/assets/crushed_can.png differ diff --git a/client/src/stylesheets/base/_colors.scss b/client/src/stylesheets/base/_colors.scss index 3ef14228..b8ddf113 100644 --- a/client/src/stylesheets/base/_colors.scss +++ b/client/src/stylesheets/base/_colors.scss @@ -26,6 +26,7 @@ $step2_imgage: '../static/assets/people.png'; $step3_imgage: '../static/assets/request.png'; $step4_imgage: '../static/assets/recycle.png'; $link_arrow: '../static/assets/arrow_right.png'; +$crushed_can: '../static/assets/crushed_can.png'; //BUTTONS $twitter-blue: #1da1f2; diff --git a/client/src/stylesheets/base/_fonts.scss b/client/src/stylesheets/base/_fonts.scss index 96c3d84e..1c27a5ef 100644 --- a/client/src/stylesheets/base/_fonts.scss +++ b/client/src/stylesheets/base/_fonts.scss @@ -11,11 +11,42 @@ $body_stack: "Open Sans", "Segoe UI", Tahoma, sans-serif; color: $mainwhite; } + +@mixin display-72-med () { + @include font($display_stack, 72px, 500); + line-height: normal; +} + +@mixin display-48-med () { + @include font($display_stack, 48px, 500); + line-height: normal; +} + +@mixin display-48-thin () { + @include font($display_stack, 48px, 300); + line-height: normal; +} + +@mixin display-36-thin () { + @include font($display_stack, 36px, 300); + line-height: normal; +} + @mixin display-36-med () { @include font($display_stack, 36px, 500); line-height: normal; } +@mixin display-32-thin () { + @include font($display_stack, 32px, 300); + line-height: normal; +} + +@mixin display-28-med () { + @include font($display_stack, 28px, 500); + line-height: 1.25; +} + @mixin display-24-med () { @include font($display_stack, 24px, 500); line-height: 1.25; @@ -26,11 +57,21 @@ $body_stack: "Open Sans", "Segoe UI", Tahoma, sans-serif; line-height: 1.25; } +@mixin display-24-thin () { + @include font($display_stack, 24px, 300); + line-height: 1.25; +} + @mixin display-20-reg () { @include font($display_stack, 20px, 400); line-height: 1.33; } +@mixin display-20-thin () { + @include font($display_stack, 20px, 300); + line-height: 1.33; +} + @mixin body-16-semibold-tt () { @include font($body_stack, 16px, 600); text-transform: uppercase; diff --git a/client/src/stylesheets/components/_Banner.scss b/client/src/stylesheets/components/_Banner.scss new file mode 100644 index 00000000..46f47c41 --- /dev/null +++ b/client/src/stylesheets/components/_Banner.scss @@ -0,0 +1,19 @@ +@import "../base/colors"; + +.row.banner-row { + background-color: $light-blue-background; + + .banner-content { + padding: 1rem 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: #164c5f; + + strong { + font-weight: 600; + } + + } +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_HeroCTA.scss b/client/src/stylesheets/components/_HeroCTA.scss index 0226b6d5..02a21ab1 100644 --- a/client/src/stylesheets/components/_HeroCTA.scss +++ b/client/src/stylesheets/components/_HeroCTA.scss @@ -6,10 +6,10 @@ color: $mainwhite; h1 { - font-size: 70px; + font-size: 60px; letter-spacing: 3px; text-shadow: 30px 30px 30px 30px #000; - margin: 10px 0; + margin: 15px 15px; } h2, @@ -19,7 +19,7 @@ margin: 10px 0; } - h2 { + .font-italic { @include font('Dancing Script', 50px, regular); } @@ -29,30 +29,29 @@ } .hero-wrapper { position: relative; + height: 100%; background-image: url($desktop-hero-image); + background-position: center; background-repeat: no-repeat; background-size: cover; - box-sizing: padding-box; + box-sizing: border-box; + background-attachment: fixed; .opacity-div { background-color: rgba(0, 0, 0, 0.35); - padding: 2rem 150px; + padding: 5rem 12%; width: 100vw; + height: 60vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } @media #{$information-phone} { background-image: url($mobile-hero-image); } - - .tinted { - position: absolute; - height: 100%; - width: 100%; - top: 0; - right: 0; - background-color: rgba(0, 0, 0, 0.25); - } } .hero-search { padding-top: 0px; @@ -76,16 +75,30 @@ padding: 0 0 50px 0; } } + + .hero-page-header { + h1 { + font-size: 48px; + } + + .font-italic { + @include font('Dancing Script', 48px, regular); + } + } } @media screen and (max-width: 550px) { .hero-page-header { h1 { - font-size: 40px; + font-size: 32px; + } + + .font-italic { + @include font('Dancing Script', 32px, regular); } h2 { - font-size: 30px; + font-size: 28px; } h3 { diff --git a/client/src/stylesheets/components/_Home.scss b/client/src/stylesheets/components/_Home.scss index 463e3ec8..15c21952 100644 --- a/client/src/stylesheets/components/_Home.scss +++ b/client/src/stylesheets/components/_Home.scss @@ -15,7 +15,7 @@ color: $mainwhite; border-bottom: none; margin-bottom: 5px; - + h1 { font-size: 24px; font-weight: 300; @@ -80,3 +80,26 @@ } } } + +.how-it-works-banner { + background: $light-gray; +} + +.banner-content { + > div { + @include display-24-thin; + color: #164c5f; + text-align: center; + } + + > div:first-child { + margin-bottom: 0.5rem; + @include display-24-reg; + color: #164c5f; + } +} + + + + + diff --git a/client/src/stylesheets/components/_HowItWorks.scss b/client/src/stylesheets/components/_HowItWorks.scss new file mode 100644 index 00000000..6293f28a --- /dev/null +++ b/client/src/stylesheets/components/_HowItWorks.scss @@ -0,0 +1,29 @@ +@import "../utils/placeholders"; +@import "../base/colors"; +@import '../base/fonts'; + +.how-it-works-banner { + background: $light-gray; + display: flex; + justify-content: center; + align-items: center; + padding: 1rem; +} + +.blue-color { + color: $dark-blue; +} + +.how-it-works-container { + .search-container { + padding-bottom: 4rem; + padding-top: 3.5rem; + margin-top: 4.5rem; + background: rgba(255,255,255,0.25); + + .call-to-action { + text-align: center; + padding-bottom: 1rem; + } + } +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_HowItWorksStepContent.scss b/client/src/stylesheets/components/_HowItWorksStepContent.scss new file mode 100644 index 00000000..51058445 --- /dev/null +++ b/client/src/stylesheets/components/_HowItWorksStepContent.scss @@ -0,0 +1,50 @@ +.how-it-works-step-content-wrapper { + h3 { + @include display-20-reg; + margin-top: 1.2rem; + } + + p { + margin-top: 0.7rem; + opacity: 0.8; + + em { + font-style: italic; + } + } + + p:first-child { + margin-top: 1.2rem; + } + + ul { + margin: 0.7rem 0 1rem 1.2rem; + + > li { + opacity: 0.8; + list-style: initial; + } + } +} + +@media only screen and (min-width: 480px) { + .how-it-works-step-content-wrapper { + + h3 { + @include display-24-reg; + margin-top: 2.5rem; + } + + p { + margin-top: 1rem; + } + + p:first-child { + margin-top: 2.5rem; + } + + ul { + margin: 1rem 0 1.5rem 1.2rem; + } + } +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_HowItWorksStepHeaderContent.scss b/client/src/stylesheets/components/_HowItWorksStepHeaderContent.scss new file mode 100644 index 00000000..4ef1a590 --- /dev/null +++ b/client/src/stylesheets/components/_HowItWorksStepHeaderContent.scss @@ -0,0 +1,65 @@ +.step-header-content { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + i.how-icon { + font-size: 2rem; + padding: 0 1rem; + } + + h2 { + @include display-24-med; + text-align: center; + } + + h3 { + @include display-20-thin; + text-align: left; + } + + &.stacked { + flex-direction: column; + i.how-icon { + padding: 1rem; + } + } +} + +@media only screen and (min-width: 480px) { + .step-header-content { + i.how-icon { + font-size: 3rem; + } + + h2 { + @include display-36-thin; + } + + h3 { + @include display-24-thin; + text-align: left; + } + } +} + +@media only screen and (min-width: 720px) { + .step-header-content { + i.how-icon { + font-size: 4rem; + } + + h3 { + @include display-32-thin; + } + } +} + +@media only screen and (min-width: 820px) { + .step-header-content { + h3 { + @include display-36-thin; + } + } +} diff --git a/client/src/stylesheets/components/_RecyclingInfo.scss b/client/src/stylesheets/components/_RecyclingInfo.scss new file mode 100644 index 00000000..958bbaee --- /dev/null +++ b/client/src/stylesheets/components/_RecyclingInfo.scss @@ -0,0 +1,125 @@ +@import "../base/colors"; + +.recycling-info { + display: flex; + flex-direction: column; + align-items: center; + margin: 4rem 0 0 0; + + + .text-content { + display: flex; + flex-direction: column; + align-items: stretch; + flex: 1; + text-align: center; + max-width: 500px; + + @include body-16; + font-style: italic; + + > div:first-child { + margin-bottom: 2rem; + font-style: italic; + @include display-24-thin; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + // align-self: center; + + > div { + max-width: 400px; + display: flex; + flex-direction: column; + flex: 1 + } + + span { + align-self: flex-end; + } + + span:first-child { + align-self: flex-start; + } + + strong { + font-weight: 500; + } + + span.large { + align-self: center; + @include display-72-med; + color: $maingreen; + } + } + } + + .bar-graphs { + flex: 1; + margin-top: 2rem; + padding: 1rem 1rem; + background: rgba(0,0,0,0.2);//$main-blue; + max-width: 500px; + align-self: stretch; + // padding: 2rem 0 0 0; + + } + + + + .bar-and-label { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-bottom: 1rem; + + } + + .bar-label { + color: white; + white-space: nowrap; + @include display-24-thin; + margin-right: 0; + margin-bottom: 0.5rem; + } + + @media only screen and (min-width: 480px) { + // .bar-and-label { + // flex-direction: row; + // align-items: center; + // } + + // .bar-label { + // margin-top: 0; + // margin-right: 1rem; + // } + } + + +} + +@media only screen and (min-width: 992px) { + .recycling-info { + flex-direction: row; + justify-content: center; + align-items: center; + + + + .bar-graphs { + margin-top: 0; + margin-left: 3%; + align-self: center; + // padding: 2rem 0 0 0; + + } + + .text-content { + margin-right: 3%; + } + } + +} + diff --git a/client/src/stylesheets/components/_SingleGraphBar.scss b/client/src/stylesheets/components/_SingleGraphBar.scss new file mode 100644 index 00000000..a363271b --- /dev/null +++ b/client/src/stylesheets/components/_SingleGraphBar.scss @@ -0,0 +1,45 @@ +@import "../base/colors"; + +.outer-bar { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: stretch; + border-radius: 4px; + overflow: hidden; + + .percentage-text { + z-index: 1; + } + + .main-bar { + position: relative; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + @include display-20-thin; + + &.with-bg-img { + background-image: url($crushed_can); + background-repeat: repeat-x; + background-size: contain; + } + + .fill { + position: absolute; + left: 0; + right: 0; + height: 100%; + } + } + + .space { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + @include display-20-thin; + } + +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_StepByStep.scss b/client/src/stylesheets/components/_StepByStep.scss index 967aa60c..d73bc466 100644 --- a/client/src/stylesheets/components/_StepByStep.scss +++ b/client/src/stylesheets/components/_StepByStep.scss @@ -1,40 +1,113 @@ @import "../utils/placeholders"; @import "../base/colors"; -.info-graphic-header { - padding-left: 55px; -} +.step-by-step-container { + margin-bottom: 6rem; + + &.container-fluid { + padding-left: 15px; + padding-right: 15px; + } + + h2.home-section-title { + padding: 0 0; + margin-top: 6rem; + text-align: center; + @include display-24-med; + } -.info-graphic { - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: center; - justify-content: space-around; - background-color: $light-blue-background; + .tinted { + background-color: rgba(255,255,255,0.25); + } - &:hover { - cursor: pointer; + .pad-bottom { + padding-bottom: 6rem; } - .box { - color: $dark-blue; + .inner-content-container { display: flex; + height: 100%; flex-direction: column; - align-items: center; justify-content: center; - margin: 20px; + align-items: center; - i { - font-size: 36px; - margin-bottom: 7.5px; + > div:first-child { + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + max-width: 800px; + padding: 0 0.5rem; + margin-top: 2rem; + } + + a.more-step-details-link { + color: $maingreen; + opacity: 0.7; + align-self: center; + margin-bottom: 2rem; + } + + a.more-step-details-link:hover { + color: $hovergreen; + opacity: 1; } p { - font-size: 16px; - font-weight: bold; + text-align: center; + } + + p:first-child { + margin-top: 0.7rem; + } + } +} + +@media only screen and (min-width: 480px) { + .step-by-step-container { + + .inner-content-container { + padding: 0 1rem; + + a.more-step-details-link { + align-self: flex-end; + } + + p:first-child { + margin-top: 1rem; + } + } + + h2.home-section-title { + text-align: left; + @include display-28-med; + margin-left: 1rem; } } } +@media only screen and (min-width: 750px) { + .step-by-step-container { + .inner-content-container { + p { + font-size: 1.2rem; + } + } + } +} + +@media only screen and (min-width: 1200px) { + .step-by-step-container { + .inner-content-container { + padding: 0 1rem; + + a.more-step-details-link { + align-self: flex-end; + } + p { + align-self: center; + } + } + } +} diff --git a/client/src/stylesheets/components/_StepHeaderNavButton.scss b/client/src/stylesheets/components/_StepHeaderNavButton.scss new file mode 100644 index 00000000..e0640d14 --- /dev/null +++ b/client/src/stylesheets/components/_StepHeaderNavButton.scss @@ -0,0 +1,39 @@ +button.step-header-nav { + background: transparent; + border: none; + margin: 0 1rem; + color: $maingreen; + opacity: 0.8; + cursor: pointer; + user-select: none; + + &:hover { + color: $hovergreen; + opacity: 1; + } + + &:focus { + outline: none; + } + + &.disabled { + opacity: 0.2; + cursor: default; + &:hover { + color: $maingreen; + opacity: 0.2; + } + } +} + +@media only screen and (min-width: 480px) { + button.step-header-nav { + margin: 0 2rem; + } +} + +@media only screen and (min-width: 900px) { + button.step-header-nav { + margin: 0 3rem; + } +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_StepSelectorButton.scss b/client/src/stylesheets/components/_StepSelectorButton.scss new file mode 100644 index 00000000..92bdf93d --- /dev/null +++ b/client/src/stylesheets/components/_StepSelectorButton.scss @@ -0,0 +1,115 @@ +button[class^='step-selector-btn-'], +button[class*=' step-selector-btn-'] { + display: flex; + justify-content: center; + align-items: center; + margin: 1.2rem 0; + padding: 0; + align-self: stretch; + cursor: pointer; + background: transparent; + border: none; + + &:focus { + outline: none; + } + + .circle { + display: flex; + justify-content: center; + align-items: center; + height: 2rem; + width: 2rem; + border: 2px solid white; + border-radius: 2rem; + opacity: 0.7; + } + + &.highlighted { + .circle { + color: $maingreen; + opacity: 0.7; + border: 2px solid $maingreen; + } + } + + &.selected { + .circle { + color: $maingreen; + height: 2.5rem; + width: 2.5rem; + border-radius: 2.5rem; + border: 3px solid $maingreen; + opacity: 1; + font-weight: bold; + } + } + + &.not-selected { + .circle { + &:hover { + opacity: 1 !important; + color: white !important; + border-color: white !important; + } + } + + &.highlighted { + .circle { + &:hover { + color: $hovergreen; + border: 2px solid $hovergreen; + opacity: 1; + } + } + } + } + + &.vertical { + margin: 0 0; + } + + @media only screen and (min-width: 700px) { + &.vertical { + margin: 0 1rem; + } + + .circle { + height: 3rem; + width: 3rem; + border-radius: 3rem; + border: 3px solid white; + } + + &.highlighted { + .circle { + border: 3px solid $maingreen; + } + } + + &.selected { + .circle { + height: 3.5rem; + width: 3.5rem; + border-radius: 3.5rem; + border: 4px solid $maingreen; + } + } + + &.not-selected { + .circle { + &:hover { + border: 3px solid white; + } + } + + &.highlighted { + .circle { + &:hover { + border: 3px solid $hovergreen; + } + } + } + } + } +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_Steps.scss b/client/src/stylesheets/components/_Steps.scss new file mode 100644 index 00000000..d64f406e --- /dev/null +++ b/client/src/stylesheets/components/_Steps.scss @@ -0,0 +1,55 @@ +@import "../base/colors"; + +.steps-container { + position: relative; + overflow: hidden; + margin-top: 1rem; + + .steps-content-container { + width: 100%; + position: relative; + display: flex; + flex-direction: row; + align-items: flex-start; + flex-wrap: nowrap; + + .single-step-content { + display: inline-block; + flex-shrink: 0; + flex-grow: 1; + width: 100%; + + .step-header-wrapper { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + } + } + } + + &.vertical { + display: flex; + flex-direction: row; + + .steps-content-container { + flex-direction: column; + flex-wrap: nowrap; + flex: 10; + } + + .centering-space { + flex: 0; + } + + @media only screen and (min-width: 700px) { + .centering-space { + flex: 1; + } + } + + .single-step-content { + height: 100%; + } + } +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_StepsMainNav.scss b/client/src/stylesheets/components/_StepsMainNav.scss new file mode 100644 index 00000000..11f093dc --- /dev/null +++ b/client/src/stylesheets/components/_StepsMainNav.scss @@ -0,0 +1,45 @@ +.step-selectors-container { + display: flex; + flex-direction: row; + align-items: center; + + .half-spacer { + display: flex; + flex: 1; + } + .spacer-line { + display: flex; + flex: 2; + height: 2px; + background: white; + opacity: 0.7; + + &.highlighted { + opacity: 0.7; + background: $maingreen; + } + } + + &.vertical { + display: flex; + flex: 1; + flex-direction: column; + + .spacer-line { + width: 2px; + } + } + + @media only screen and (min-width: 700px) { + .spacer-line { + height: 3px; + } + + &.vertical { + .spacer-line { + width: 3px; + height: auto; + } + } + } +} \ No newline at end of file diff --git a/client/src/stylesheets/components/_TenantOrPMChoice.scss b/client/src/stylesheets/components/_TenantOrPMChoice.scss new file mode 100644 index 00000000..db979f35 --- /dev/null +++ b/client/src/stylesheets/components/_TenantOrPMChoice.scss @@ -0,0 +1,79 @@ +.tenant-or-pm-section { + margin-top: 6rem; + margin-bottom: 6rem; + + h2 { + text-transform: uppercase; + text-align: center; + @include display-24-med; + } + + p { + margin: 2rem 0; + text-align: center; + } + + .buttons-wrapper { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + } + + a { + background-color: rgba(0, 199, 138, 0.8); + cursor: pointer; + border: none; + padding: 0.5rem 1.5rem; + margin: 0.5rem 1rem; + border-radius: 0.3rem; + text-align: center; + white-space: nowrap; + color: white; + } + + a:hover { + background-color: rgba(0, 199, 138, 1); + } + + a:focus { + outline: none; + } + + a:active { + background-color: rgba(0, 199, 138, 0.5); + } + } + + @media only screen and (min-width: 480px) { + h2 { + text-align: left; + @include display-28-med; + } + + p { + margin: 2rem 0; + text-align: left; + } + } + + @media only screen and (min-width: 550px) { + .buttons-wrapper { + > div { + flex-direction: row; + align-items: center; + } + + a { + min-width: 240px; + } + } + } + +} \ No newline at end of file diff --git a/client/src/stylesheets/main.scss b/client/src/stylesheets/main.scss index 0e3ad352..f771bfdd 100644 --- a/client/src/stylesheets/main.scss +++ b/client/src/stylesheets/main.scss @@ -33,6 +33,17 @@ 'components/ToolList', 'components/Footer', 'components/UpdateCampaignModal', + 'components/HowItWorks', + 'components/Steps', + 'components/StepsMainNav', + 'components/StepSelectorButton', + 'components/StepHeaderNavButton', + 'components/HowItWorksStepHeaderContent', + 'components/HowItWorksStepContent', + 'components/SingleGraphBar', + 'components/Banner', + 'components/TenantOrPMChoice', + 'components/RecyclingInfo', 'components/AdminAddSigantureModal', 'components/NotFound'; diff --git a/client/yarn.lock b/client/yarn.lock index 036aa611..294d2627 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -9,6 +9,12 @@ core-js "^2.5.7" regenerator-runtime "^0.12.0" +"@babel/runtime@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c" + dependencies: + regenerator-runtime "^0.12.0" + "@firebase/app-types@0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.3.2.tgz#a92dc544290e2893bd8c02a81e684dae3d8e7c85" @@ -6301,6 +6307,12 @@ react-share@^2.0.0: jsonp "^0.2.1" prop-types "^15.5.8" +react-spring@^5.8.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-5.8.0.tgz#52d8206487692b99c223377c252acea645d77307" + dependencies: + "@babel/runtime" "^7.0.0" + react-transition-group@^2.0.0, react-transition-group@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874" diff --git a/cypress/integration/how_it_works_page_spec.js b/cypress/integration/how_it_works_page_spec.js new file mode 100644 index 00000000..c631fdfe --- /dev/null +++ b/cypress/integration/how_it_works_page_spec.js @@ -0,0 +1,56 @@ +describe('The how does it work page', () => { + beforeEach(function() { + cy.visit('http://localhost:3000/how-does-this-work'); + }); + + it('Shows first step on page load', () => { + cy.get('.steps-content-container').then($el => { + expect($el).to.have.css('left', '0px'); + }); + }); + + describe('Clicking Step 1', () => { + it('Sets left position of steps-content-container to make 1st step visible', () => { + cy.get('.step-selector-btn-0').click(); + cy.wait(700); + cy.get('.steps-content-container').then($el => { + expect($el).to.have.css('left', '0px'); + }); + }); + }); + + describe('Clicking Step 2', () => { + it('Sets left position of steps-content-container to make 2nd step visible', () => { + cy.get('.step-selector-btn-1').click(); + cy.wait(700); + cy.get('.steps-content-container').then($el => { + const width = $el.css('width'); + expect($el).to.have.css('left', '-' + width); + }); + }); + }); + + describe('Clicking Step 3', () => { + it('Sets left position of steps-content-container to make 3rd step visible', () => { + cy.get('.step-selector-btn-2').click(); + cy.wait(700); + cy.get('.steps-content-container').then($el => { + let width = $el.css('width'); + width = parseInt(width) * 2; + expect($el).to.have.css('left', '-' + width + 'px'); + }); + }); + }); + + describe('Clicking Step 4', () => { + it('Sets left position of steps-content-container to make 4th step visible', () => { + cy.get('.step-selector-btn-3').click(); + cy.wait(700); + cy.get('.steps-content-container').then($el => { + let width = $el.css('width'); + width = parseInt(width) * 3; + expect($el).to.have.css('left', '-' + width + 'px'); + }); + }); + }); +}); diff --git a/cypress/integration/landing_page_spec.js b/cypress/integration/landing_page_spec.js index 33a4fd22..04b16266 100644 --- a/cypress/integration/landing_page_spec.js +++ b/cypress/integration/landing_page_spec.js @@ -45,6 +45,14 @@ describe('The landing page', () => { cy.url().should('include', '/tips-for-requesting'); }); }); + + // WHO WE ARE LINK + describe('Who Are We link', () => { + it('Navigates to Who Are We page', () => { + cy.contains('WHO WE ARE').click(); + cy.url().should('include', '/who-are-we'); + }); + }); // WILL NEED TO ADDRESS CORS ISSUES TO TEST AUTH describe('Check login navigation toggle', () => { @@ -57,53 +65,6 @@ describe('The landing page', () => { }); }); - // WHO WE ARE LINK - describe('Who Are We link', () => { - it('Navigates to Who Are We page', () => { - cy.contains('WHO WE ARE').click(); - cy.url().should('include', '/who-are-we'); - }); - }); - - // MAIN SEARCH FEATURES - describe('Main Search Block', () => { - // SEARCH ADDRESS BAR - describe('When Searching an Address', () => { - it('Should fill out search box and click search and visit choose campaign page', () => { - cy.get('.search_input') - .type('Denver') - .should('have.value', 'Denver') - .then(() => { - cy.get('.search_button').click(() => { - cy.visit('/choose-campaign'); - }); - }); - }); - }); - // MAP MODAL - describe('Explore the Map Modal', () => { - it('Opens the map modal', () => { - cy.contains('Explore Nearby Campaigns').click({ isOpen: true }); - }); - }); - }); - - //BOTTOM FEATURES - describe('Bottom Features', () => { - describe('Wait, But Why?', () => { - it('Navigates to Learn More', () => { - cy.contains('LEARN MORE').click(); - cy.url().should('include', '/denver-learn-more'); - }); - }); - describe('Tips and Resources', () => { - it('Navigates to Manager Resources', () => { - cy.contains('TIPS AND RESOURCES').click(); - cy.url().should('include', '/manager-resources'); - }); - }); - }); - //FOOTER describe('Footer', () => { describe('Privacy Policy', () => { diff --git a/cypress/integration/start_campaign_spec.js b/cypress/integration/start_campaign_spec.js index a5f45c15..20c90d33 100644 --- a/cypress/integration/start_campaign_spec.js +++ b/cypress/integration/start_campaign_spec.js @@ -1,11 +1,9 @@ describe('Starting a new campaign', () => { it('prompts the user with about recycling', () => { - cy.visit('http://localhost:3000'); - cy.contains('NEED RECYCLING?'); + cy.visit('http://localhost:3000/how-does-this-work'); + cy.contains('NEED RECYCLING IN YOUR APARTMENT OR CONDO?'); cy.contains('Recruit, Request, Recycle'); - cy.contains( - 'We have a mission to change Denver\'s low recycling rate by making it easy for you and your neighbors to petition your landlord for recyling for your building.' - ); + cy.contains('Follow these easy steps!'); }); describe('When Searching an Address', () => {