diff --git a/client/components/Footer.jsx b/client/components/Footer.jsx index 5601895b5..5d171b36d 100644 --- a/client/components/Footer.jsx +++ b/client/components/Footer.jsx @@ -1,7 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'proptypes'; -import { Container, Typography } from '@material-ui/core'; +import Container from '@material-ui/core/Container'; +import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import moment from 'moment'; import { Link } from 'react-router-dom'; @@ -16,6 +17,9 @@ const useStyles = makeStyles(theme => ({ backgroundColor: theme.palette.primary.dark, zIndex: 1, }, + footerSpacing: { + height: theme.footer.height, + }, lastUpdated: { color: theme.palette.text.dark, lineHeight: theme.footer.height, @@ -57,7 +61,7 @@ const Footer = ({ lastUpdated }) => { Privacy Policy -  | Powered by volunteers from Hack for LA | +  | Powered by volunteers from Hack for LA | diff --git a/client/components/Header.jsx b/client/components/Header.jsx index 743f8c059..e0332f605 100644 --- a/client/components/Header.jsx +++ b/client/components/Header.jsx @@ -9,6 +9,7 @@ import { Menu, MenuItem, } from '@material-ui/core'; +import colors from '@theme/colors'; const useStyles = makeStyles(theme => ({ appBar: { @@ -16,19 +17,19 @@ const useStyles = makeStyles(theme => ({ backgroundColor: theme.palette.primary.main, }, link: { - color: 'white', + color: colors.textPrimaryLight, textDecoration: 'none', }, button: { + color: colors.textPrimaryLight, textTransform: 'none', fontFamily: 'Roboto', marginLeft: theme.spacing(1), marginRight: theme.spacing(1), - color: 'white', textDecoration: 'none', }, title: { - ...theme.typography.h1, + ...theme.typography.h3, flexGrow: 1, fontSize: '30px', fontWeight: 'bold', @@ -56,7 +57,7 @@ const Header = () => { return ( - + 311DATA @@ -81,13 +82,13 @@ const Header = () => { horizontal: 'left', }} > - - + + Overview - - + + Compare Two Neighborhoods @@ -98,9 +99,6 @@ const Header = () => { - - - diff --git a/client/components/common/ContentBody/index.jsx b/client/components/common/ContentBody/index.jsx new file mode 100644 index 000000000..7b955f10c --- /dev/null +++ b/client/components/common/ContentBody/index.jsx @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Container from '@material-ui/core/Container'; +import Grid from '@material-ui/core/Grid'; +import sharedLayout from '@theme/layout'; + +const ContentBody = ({ children, maxWidth }) => { + const classes = sharedLayout(); + + return ( + + + + {children} + + + + ); +}; + +ContentBody.defaultProps = { + maxWidth: 'md', +}; + +ContentBody.propTypes = { + children: PropTypes.node.isRequired, + maxWidth: PropTypes.string, +}; + +export default ContentBody; diff --git a/client/components/common/ContentBottom/index.jsx b/client/components/common/ContentBottom/index.jsx new file mode 100644 index 000000000..6e1586f5e --- /dev/null +++ b/client/components/common/ContentBottom/index.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles(theme => ({ + bottomSpacing: { + height: theme.footer.height, + }, +})); + +const ContentBottom = () => { + const classes = useStyles(); + return ( + + {/* an empty grid container with footer height to prevent + * fixed positioned footer from obscuring submit button */} + + ); +}; + +export default ContentBottom; diff --git a/client/components/common/TextHeading/index.jsx b/client/components/common/TextHeading/index.jsx new file mode 100644 index 000000000..d5ce38f8c --- /dev/null +++ b/client/components/common/TextHeading/index.jsx @@ -0,0 +1,41 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; + +const useStyles = makeStyles(theme => ({ + headingBackground: { + background: theme.palette.primary.main, + backgroundPosition: 'top', + height: '15vh', + position: 'relative', + }, + headingOverlayText: { + left: '50%', + color: 'white', + fontSize: '40px', + fontWeight: 'bold', + position: 'absolute', + textAlign: 'center', + top: '50%', + transform: 'translate(-50%, -70%)', + }, +})); + +const TextHeading = ({ children }) => { + const classes = useStyles(); + + return ( +
+
+ {children} +
+
+ ); +}; + +TextHeading.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default TextHeading; diff --git a/client/components/contact/Contact.jsx b/client/components/contact/Contact.jsx index 7625b9729..ed0f25edd 100644 --- a/client/components/contact/Contact.jsx +++ b/client/components/contact/Contact.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { ToastContainer } from 'react-toastify'; -import ContactImage from './ContactImage'; +import TextHeading from '@components/common/TextHeading'; +import ContentBody from '@components/common/ContentBody'; import ContactIntro from './ContactIntro'; import ContactForm from './ContactForm'; @@ -22,9 +23,11 @@ const Contact = () => ( />
- Contact Us - - + Contact Us + + + +
); diff --git a/client/components/contact/ContactForm.jsx b/client/components/contact/ContactForm.jsx index d4cc22af3..2811b77ee 100644 --- a/client/components/contact/ContactForm.jsx +++ b/client/components/contact/ContactForm.jsx @@ -3,22 +3,12 @@ import { useDispatch, useSelector } from 'react-redux'; import { sendGitRequest } from '@reducers/data'; import { showFeedbackSuccess, setErrorModal } from '@reducers/ui'; import { toast } from 'react-toastify'; -import { - Container, - Grid, - Button, - TextField, - CircularProgress, - makeStyles, -} from '@material-ui/core'; +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import CircularProgress from '@material-ui/core/CircularProgress'; import 'react-toastify/dist/ReactToastify.css'; -const useStyles = makeStyles(theme => ({ - footer: { - height: theme.footer.height, - }, -})); - const initialFormValues = { firstName: '', lastName: '', @@ -46,8 +36,6 @@ const toastEmitterSettings = { }; const ContactForm = () => { - const classes = useStyles(); - const dispatch = useDispatch(); // mapStateToProps equivalent. @@ -172,102 +160,95 @@ const ContactForm = () => { validateForm]); return ( - -
- - - - - - - - + + + + + - - - - - - - - - - + + - - - + + + + - - {/* an empty grid container with footer height to prevent - * fixed positioned footer from obscuring submit button */} + + + + + - -
- + + + + + + ); }; diff --git a/client/components/contact/ContactImage.jsx b/client/components/contact/ContactImage.jsx index a6d2738e8..9d2fd4bf1 100644 --- a/client/components/contact/ContactImage.jsx +++ b/client/components/contact/ContactImage.jsx @@ -3,7 +3,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import coverImage from '@assets/contact_bg.png'; -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles(theme => ({ contactImageCover: { height: '25vh', backgroundImage: `url(${coverImage})`, @@ -11,9 +11,9 @@ const useStyles = makeStyles(() => ({ backgroundRepeat: 'no-repeat', backgroundSize: 'cover', position: 'relative', - }, contactImageOverlayText: { + color: theme.palette.background.default, left: '50%', fontSize: '40px', fontWeight: 'bold', diff --git a/client/components/contact/ContactIntro.jsx b/client/components/contact/ContactIntro.jsx index c5f4270d1..36295e4af 100644 --- a/client/components/contact/ContactIntro.jsx +++ b/client/components/contact/ContactIntro.jsx @@ -1,22 +1,30 @@ import React from 'react'; -import { - Grid, -} from '@material-ui/core'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import sharedLayout from '@theme/layout'; -const ContactIntro = () => ( - - -

- {'Don\'t See What You Need?'} -

-

- We want to build a tool that works for you. We are open to suggestions and - feedback and would love the opportunity to get connected. Feel free to input - your information in the contact form below and we will be sure to get back to - you within 2-3 business days. Thank you! -

+const ContactIntro = () => { + const classes = sharedLayout(); + + return ( + + +
+ + {'Don\'t See What You Need?'} + +
+
+ + We want to build a tool that works for you. We are open to suggestions and + feedback and would love the opportunity to get connected. Feel free to input + your information in the contact form below and we will be sure to get back to + you within 2-3 business days. Thank you! + +
+
-
-); + ); +}; export default ContactIntro; diff --git a/client/components/main/About.jsx b/client/components/main/About.jsx index f9ae8c586..9dbe81566 100644 --- a/client/components/main/About.jsx +++ b/client/components/main/About.jsx @@ -1,9 +1,13 @@ import React from 'react'; -import { - makeStyles, - Container, - Grid, -} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import colors from '@theme/colors'; +import sharedLayout from '@theme/layout'; +import TextHeading from '@components/common/TextHeading'; +import ContentBody from '@components/common/ContentBody'; + +// Images import empowerLaLogo from '@assets/empower_la_logo.png'; import hackForLaLogo from '@assets/hack_for_la_logo.png'; import codeForAmericaLogo from '@assets/code_for_america_logo.png'; @@ -16,17 +20,9 @@ import visualizeIcon from '@assets/visualize_icon.png'; // TODO: Revisit adding shared standard styles once those are decided. const useStyles = makeStyles({ root: { - color: 'black', - backgroundColor: 'white', alignItems: 'center', justifyContent: 'center', padding: '2rem', - '& h1': { - fontSize: '2.5rem', - }, - '& h2': { - fontSize: '2.25rem', - }, '& img': { maxWidth: '100%', display: 'block', @@ -36,98 +32,109 @@ const useStyles = makeStyles({ }); const About = () => { - const classes = useStyles(); + const classes = { ...useStyles(), ...sharedLayout() }; return ( <> - - - -

- About - 311 - DATA -

-

- Each day, Los Angelenos report thousands of 311 - requests all across LA to resolve issues such as - illegal dumping and homeless encampments in their - neighborhoods. These requests are then received by - relevant agencies, such as the Police, Building and - Safety, or Department of Transportation. The agency - responds to the request, addresses it, and then closes - it once it is fixed. The expansive amount of data - associated with these 311 requests is available online. - However, it is difficult to make actionable at the neighborhood - level. Thanks to the mayor's Open Data Initiative, - the expansive amount of data associated with these 311 - requests is available online. The mayor has encouraged - us to create apps with this data, and that's where - this project comes in. -

-

Partners

-

+ + About 311DATA + + + + + +

+ + Each day, Los Angelenos report thousands of 311 + requests all across LA to resolve issues such as + illegal dumping and homeless encampments in their + neighborhoods. These requests are then received by + relevant agencies, such as the Police, Building and + Safety, or Department of Transportation. The agency + responds to the request, addresses it, and then closes + it once it is fixed. The expansive amount of data + associated with these 311 requests is available online. + However, it is difficult to make actionable at the neighborhood + level. Thanks to the mayor's Open Data Initiative, + the expansive amount of data associated with these 311 + requests is available online. The mayor has encouraged + us to create apps with this data, and that's where + this project comes in. + +
+
+ + Partners + +
+ To empower local residents and Neighborhood Councils to make informed decisions about how to improve their communities using an easy-to-use application, EmpowerLA partnered with Hack For LA to create the 311 Data project. The 311 Data project makes navigating the wealth of 311 data easier using an open source application built and maintained by volunteers throughout our community. -

+
- - + + {/* Inserting 3 images horizontally here */} + + Empower LA - + Hack for LA - + Code for America -

How it works

+
+ + How it works + +
- + Mobile App -

+ You and other members of your community post reports via the City’s easy-to-use mobile application. -

+
Mobile App -

+ Your reports are consolidated by the City and entered into a central database. All requests are assigned to the appropriate department to resolve. -

+
Mobile App -

+ Once data from each department is sorted, the City then publishes it as raw information. -

+
Mobile App -

+ Our site draws data from the City’s database to create easy-to-view visualizations and files to export. -

+
Mobile App -

+ You now have access to digestable data. Communities are empowered and equipped to identify areas of improvement to uplift and thrive. -

+
- + ); }; diff --git a/client/components/main/Faqs.jsx b/client/components/main/Faqs.jsx index 1cfc50967..9937d500b 100644 --- a/client/components/main/Faqs.jsx +++ b/client/components/main/Faqs.jsx @@ -1,13 +1,11 @@ import React from 'react'; import ReactMarkdown from 'react-markdown'; -import { - makeStyles, - Container, - Box, - List, - ListItem, - Grid, -} from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import sharedLayout from '@theme/layout'; +import TextHeading from '@components/common/TextHeading'; +import ContentBody from '@components/common/ContentBody'; import useContentful from '../../hooks/useContentful'; const query = ` @@ -22,27 +20,9 @@ const query = ` } `; -const useStyles = makeStyles({ - root: { - color: 'black', - backgroundColor: 'white', - padding: '2em', - '& h1': { - fontSize: '2.5em', - }, - '& img': { - maxWidth: '100%', - height: 'auto', - display: 'block', - marginLeft: 'auto', - marginRight: 'auto', - }, - }, -}); - const Faqs = () => { const { data, errors } = useContentful(query); - const classes = useStyles(); + const classes = sharedLayout(); React.useEffect(() => { if (errors) console.log(errors); @@ -50,27 +30,34 @@ const Faqs = () => { return ( <> - { data - && ( - - - -

Frequently Asked Questions

- + + What can we help you with? + + + { data + && ( + + + + Frequently Asked Questions + + +
{ data.faqCollection.items.map(item => ( - {item.question} + + + {item.question} + + + {item.answer} + + ))} - - { data.faqCollection.items.map(item => ( - -

{item.question}

- {item.answer} -
- ))} +
-
- )} + )} + ); }; diff --git a/client/components/main/Privacy.jsx b/client/components/main/Privacy.jsx index 7120e85b9..df68adb58 100644 --- a/client/components/main/Privacy.jsx +++ b/client/components/main/Privacy.jsx @@ -1,10 +1,10 @@ import React from 'react'; import ReactMarkdown from 'react-markdown'; -import { - makeStyles, - Container, - Grid, -} from '@material-ui/core'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import sharedLayout from '@theme/layout'; +import TextHeading from '@components/common/TextHeading'; +import ContentBody from '@components/common/ContentBody'; import useContentful from '../../hooks/useContentful'; const query = ` @@ -18,27 +18,9 @@ const query = ` } `; -const useStyles = makeStyles({ - root: { - color: 'black', - backgroundColor: 'white', - padding: '2em', - '& h1': { - fontSize: '2.5em', - }, - '& img': { - maxWidth: '100%', - height: 'auto', - display: 'block', - marginLeft: 'auto', - marginRight: 'auto', - }, - }, -}); - const Privacy = () => { const { data, errors } = useContentful(query); - const classes = useStyles(); + const classes = sharedLayout(); React.useEffect(() => { if (errors) console.log(errors); @@ -46,17 +28,25 @@ const Privacy = () => { return ( <> - { data + + Privacy Policy + + + + { data && ( - - - -

{data.simplePageCollection.items[0].title}

- {data.simplePageCollection.items[0].body} -
+ + + + {data.simplePageCollection.items[0].title} + + + {data.simplePageCollection.items[0].body} + -
+ )} +
); }; diff --git a/client/index.js b/client/index.js index 2bd7e7aeb..10652b344 100644 --- a/client/index.js +++ b/client/index.js @@ -7,9 +7,9 @@ import { Integrations } from '@sentry/tracing'; import { Provider } from 'react-redux'; import { ThemeProvider } from '@material-ui/core/styles'; import { CssBaseline } from '@material-ui/core'; +import theme from '@theme/theme'; import store from './redux/store'; import App from './App'; -import theme from './theme/theme'; Sentry.init({ dsn: process.env.SENTRY_CLIENT_DSN, @@ -21,6 +21,10 @@ Sentry.init({ tracesSampleRate: 1.0, }); +// Expose theme to debugging console like on mui.com. +// https://v4.mui.com/customization/typography/#default-values +window.theme = theme; + ReactDOM.render( diff --git a/client/theme/colors.js b/client/theme/colors.js index d9d479cd4..02e899c78 100644 --- a/client/theme/colors.js +++ b/client/theme/colors.js @@ -1,21 +1,30 @@ -export const colorPrimaryDark = '#29404F'; -export const colorPrimaryFocus = '#FFB100'; -export const colorSecondaryDark = '#0F181F'; -export const colorSecondaryFocus = '#87C8BC'; -export const colorTextPrimaryDark = '#0F181F'; -export const colorTextPrimaryLight = '#FFFFFF'; -export const colorTextSecondaryDark = '#A8A8A8'; -export const colorTextSecondaryLight = '#ECECEC'; -export const colorTextFocus = '#87C8BC'; - export default { - colorPrimaryDark, - colorPrimaryFocus, - colorSecondaryDark, - colorSecondaryFocus, - colorTextPrimaryDark, - colorTextPrimaryLight, - colorTextSecondaryDark, - colorTextSecondaryLight, - colorTextFocus, + primaryDark: '#192730', + primaryDarkMain: '#29404F', + primaryFocus: '#FFB100', + primaryLight: '#002449', + secondaryDark: '#0F181F', + secondaryFocus: '#87C8BC', + selectedPrimary: 'rgba(129, 123, 123, 0.3)', + textDark: '#C4C4C4', + textPrimaryDark: '#0F181F', + textPrimaryLight: '#FFFFFF', + textSecondaryDark: '#A8A8A8', + textSecondaryLight: '#ECECEC', + textFocus: '#87C8BC', + + requestTypes: { + animalRemains: '#3CB4B2', + bulkyItems: '#DF9286', + graffiti: '#C5E406', + eWaste: '#FF7A93', + homeless: '#15BC76', + illegalDumping: '#A49FD1', + metalHouseholdAppliance: '#C056C8', + multiStreetlight: '#EDAD08', + singleStreetlight: '#79B74E', + waterWaste: '#54ABDE', + feedback: '#F86747', + other: '#F58505', + }, }; diff --git a/client/theme/layout.js b/client/theme/layout.js new file mode 100644 index 000000000..6057b6dcd --- /dev/null +++ b/client/theme/layout.js @@ -0,0 +1,12 @@ +import { makeStyles, createStyles } from '@material-ui/core/styles'; + +export default makeStyles(theme => createStyles({ + // Content pages: Vertical margin just below the TextHeading. + contentMarginTop: { + margin: theme.spacing(5, 0, 1, 0), + }, + // Content pages: Vertical margin added to separate content body. + contentIntroBody: { + margin: theme.spacing(1, 0), + }, +})); diff --git a/client/theme/theme.js b/client/theme/theme.js index 36104146e..f7a7fc666 100644 --- a/client/theme/theme.js +++ b/client/theme/theme.js @@ -1,49 +1,73 @@ -import { createMuiTheme } from '@material-ui/core/styles'; import { - colorPrimaryFocus, - colorPrimaryDark, - colorTextSecondaryDark, - colorTextSecondaryLight, - colorSecondaryFocus, - colorTextPrimaryDark, -} from './colors'; -import gaps from './gaps'; -import borderRadius from './borderRadius'; -import typography from './typography'; + createMuiTheme, +} from '@material-ui/core/styles'; +import colors from '@theme/colors'; +import gaps from '@theme/gaps'; +import borderRadius from '@theme/borderRadius'; +import typography from '@theme/typography'; -const theme = createMuiTheme({ +const isLightTheme = true; + +const commonThemeItems = { gaps, borderRadius, typography, + header: { + height: '62px', + }, + footer: { + height: '40px', + }, +}; + +const lightTheme = createMuiTheme({ + ...commonThemeItems, + palette: { + type: 'light', + selected: { primary: 'rgba(129, 123, 123, 0.3)' }, + primary: { + main: '#002449', + }, + secondary: { + main: '#ffb100', + }, + text: { + primary: '#002449', + }, + }, +}); + +const darkTheme = createMuiTheme({ + ...commonThemeItems, palette: { type: 'dark', primary: { - main: colorPrimaryDark, + main: colors.primaryDark, dark: '#192730', - focus: colorPrimaryFocus, + focus: colors.primaryFocus, }, selected: { primary: 'rgba(129, 123, 123, 0.3)' }, secondary: { - main: colorTextSecondaryDark, - light: colorTextSecondaryLight, + main: colors.textSecondaryDark, + light: colors.textSecondaryLight, }, background: { default: '#0F181F', }, text: { dark: '#C4C4C4', - cyan: colorSecondaryFocus, - primaryDark: colorTextPrimaryDark, - secondaryDark: colorTextSecondaryDark, - secondaryLight: colorTextSecondaryLight, + cyan: colors.secondaryFocus, + primaryDark: colors.textPrimaryDark, + secondaryDark: colors.textSecondaryDark, + secondaryLight: colors.textSecondaryLight, }, }, - header: { - height: '62px', - }, - footer: { - height: '40px', - }, }); -export default theme; +const theme = isLightTheme ? lightTheme : darkTheme; + +export { + lightTheme, + darkTheme, + theme as default, +}; diff --git a/client/theme/typography.js b/client/theme/typography.js index 67d6dca1e..8fbe3dd5e 100644 --- a/client/theme/typography.js +++ b/client/theme/typography.js @@ -4,6 +4,7 @@ const oswald = ['Oswald', 'sans-serif']; const robotoMedium = 500; const robotoRegular = 400; const robotoBold = 700; +const semiBold = 600; export default { button: { @@ -12,16 +13,18 @@ export default { fontFamily: roboto, fontWeight: robotoRegular, h1: { - fontFamily: oswald, - fontSize: 21, + fontFamily: roboto, + fontSize: 40, + fontWeight: robotoBold, }, h2: { - fontSize: 18, + fontSize: 32, fontWeight: robotoMedium, }, h3: { fontFamily: oswald, - fontSize: 17, + fontSize: 21, + fontWeight: semiBold, }, h4: { fontSize: 17, @@ -37,9 +40,10 @@ export default { }, body1: { fontSize: 16, + fontWeight: robotoRegular, }, body2: { - fontSize: 14, + fontSize: 16, fontWeight: robotoRegular, }, body3: { @@ -47,7 +51,8 @@ export default { fontWeight: robotoMedium, }, subtitle1: { - fontSize: 21, + fontSize: 16, + fontWeight: robotoBold, }, subtitle2: { fontSize: 21, diff --git a/client/webpack.config.js b/client/webpack.config.js index 649925695..91ea03efe 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -18,6 +18,8 @@ module.exports = { resolve: { extensions: ['.js', '.jsx'], alias: { + '@root': __dirname, + '@theme': path.resolve(__dirname, 'theme'), '@components': path.resolve(__dirname, 'components'), '@reducers': path.resolve(__dirname, 'redux/reducers'), '@styles': path.resolve(__dirname, 'styles'), @@ -118,4 +120,4 @@ module.exports = { }, }), ], -}; +}; \ No newline at end of file