From 8f91fb301389b5063269aceef8967a0dc551bc23 Mon Sep 17 00:00:00 2001 From: Oliver Steele Date: Thu, 17 May 2018 15:47:24 -0400 Subject: [PATCH 1/3] Use new OAuth scope, instead of permission --- src/components/sidebar-header.jsx | 4 ++- src/sidebar/sidebar.jsx | 51 +++++++++++++++---------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/components/sidebar-header.jsx b/src/components/sidebar-header.jsx index a6b34c6..024e2af 100644 --- a/src/components/sidebar-header.jsx +++ b/src/components/sidebar-header.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import OlinLogo from '../../assets/olin-logo-beta.svg'; -export const SidebarHeader = props => ( +const SidebarHeader = props => (
@@ -14,3 +14,5 @@ export const SidebarHeader = props => ( SidebarHeader.propTypes = { homeClicked: PropTypes.func.isRequired, }; + +export default SidebarHeader; diff --git a/src/sidebar/sidebar.jsx b/src/sidebar/sidebar.jsx index 3302afa..1b47086 100644 --- a/src/sidebar/sidebar.jsx +++ b/src/sidebar/sidebar.jsx @@ -3,40 +3,39 @@ import React from 'react'; import LabelPane from '../components/label-pane'; -import { SidebarHeader } from '../components/sidebar-header'; +import SidebarHeader from '../components/sidebar-header'; +import { canSignOut, clearAccessToken } from '../data/auth'; import EventActionsPane from './event-actions-pane'; import FilterPane from './filter-pane'; import Footer from './footer'; import GenerateICSPane from './generate-ics-pane'; import LinkPane from './link-pane'; import MarkdownGuide from './markdown-guide'; -import SidebarItemContainer from './sidebar-item-wrapper'; -import { canSignOut, clearAccessToken } from '../data/auth'; +import SidebarItem from './sidebar-item'; const Sidebar = (props) => { const { - account: { permissions }, + account: { scope }, sidebarMode: mode, } = props; + const oauthBaseUrl = `${window.abe_url}/oauth/authorize`; + const oauthUrl = `${oauthBaseUrl}?redirect_uri=${encodeURIComponent(window.location.href)}`; const content = (
- {!permissions.has('view_all_events') && ( -
-

You are viewing the public calendar.

-

- - Sign in - {' '} - to view and add Olin Community events. -

-
+ {!scope.has('community_events:read') && ( + +
+

You are viewing the public calendar.

+

+ Sign in to view and add Olin Community events. +

+
+
)} {mode.LINK_PANE && - permissions.has('add_events') && ( + scope.has('events:create') && ( { )} {mode.EVENT_ACTIONS && - permissions.has('edit_events') && ( + scope.has('events:edit') && ( )} {mode.EVENT_LABELS_PANE && ( - + - + )} {mode.FILTER_PANE && ( // For viewing the calendar - + - + )} {mode.GENERATE_ICS_PANE && ( - + - + )} {mode.MARKDOWN_GUIDE && ( - + - + )}
); From 5d5e9c72cd913aea47499bf8ca416455a7e8b919 Mon Sep 17 00:00:00 2001 From: Oliver Steele Date: Thu, 17 May 2018 15:47:44 -0400 Subject: [PATCH 2/3] Bump line max-len down to 100 --- .eslintrc.yml | 5 +- .../__snapshots__/label-pane.test.jsx.snap | 55 ++++++++++------- src/__test__/auth.test.js | 1 + src/__test__/event_details-page.test.jsx | 12 ++-- src/components/label-pane.jsx | 5 +- src/components/material-button.jsx | 4 +- src/containers/calendar-container.js | 3 +- src/containers/import-container.js | 3 +- src/containers/labels-container.js | 7 ++- src/containers/subscription-container.js | 9 +-- src/data/actions.js | 44 ++++++++----- src/data/encoding.js | 7 ++- src/data/sidebar-modes.js | 5 +- src/pages/add-edit/add-edit-page.jsx | 12 ++-- src/pages/add-edit/location-field.jsx | 61 ++++++++++++++----- src/pages/add-edit/recurrence-selector.jsx | 13 +++- src/pages/calendar/calendar-header.jsx | 57 ++++++++--------- src/pages/calendar/calendar-page.jsx | 6 +- src/pages/details/event-details-page.jsx | 4 +- src/pages/import/import.jsx | 11 ++-- src/pages/labels/labels.jsx | 2 - src/sidebar/filter-pane.jsx | 4 +- src/sidebar/sidebar-item-wrapper.jsx | 18 ------ src/sidebar/sidebar-item.jsx | 18 ++++++ 24 files changed, 226 insertions(+), 140 deletions(-) delete mode 100644 src/sidebar/sidebar-item-wrapper.jsx create mode 100644 src/sidebar/sidebar-item.jsx diff --git a/.eslintrc.yml b/.eslintrc.yml index ff97eea..041ec56 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -11,7 +11,7 @@ globals: process: false # allow webpack `process.env[*]` variables rules: - max-len: [warn, { code: 120 }] + max-len: [warn, { code: 100 }] # exceptions from eslint:recommended: no-unused-vars: [error, argsIgnorePattern: "^_" ] # exceptions from plugin:react/recommended: @@ -65,3 +65,6 @@ overrides: - files: [ "actions.js" ] rules: no-use-before-define: off + - files: [ "index.html.js" ] + rules: + max-len: off diff --git a/src/__test__/__snapshots__/label-pane.test.jsx.snap b/src/__test__/__snapshots__/label-pane.test.jsx.snap index 256a474..6131cf9 100644 --- a/src/__test__/__snapshots__/label-pane.test.jsx.snap +++ b/src/__test__/__snapshots__/label-pane.test.jsx.snap @@ -8,12 +8,14 @@ exports[`LabelPane matches snapshot 1`] = ` type="text/css" > .label.label-label-1.selected{background-color:red} - .label.label-label-1.selected:not(.no-hover):hover{background-color:white;border-color:red;color:red} - .label.label-label-1:not(.selected){background-color:white;border-color:red;color:red} + .label.label-label-1.selected:not(.no-hover):hover, + .label.label-label-1:not(.selected) + {background-color:white;border-color:red;color:red} .label.label-label-1:not(.no-hover):hover{background-color:red;color:white} .label.label-label-2.selected{background-color:green} - .label.label-label-2.selected:not(.no-hover):hover{background-color:white;border-color:green;color:green} - .label.label-label-2:not(.selected){background-color:white;border-color:green;color:green} + .label.label-label-2.selected:not(.no-hover):hover, + .label.label-label-2:not(.selected) + {background-color:white;border-color:green;color:green} .label.label-label-2:not(.no-hover):hover{background-color:green;color:white}
.label.label-label-1.selected{background-color:red} - .label.label-label-1.selected:not(.no-hover):hover{background-color:white;border-color:red;color:red} - .label.label-label-1:not(.selected){background-color:white;border-color:red;color:red} + .label.label-label-1.selected:not(.no-hover):hover, + .label.label-label-1:not(.selected) + {background-color:white;border-color:red;color:red} .label.label-label-1:not(.no-hover):hover{background-color:red;color:white} .label.label-label-2.selected{background-color:green} - .label.label-label-2.selected:not(.no-hover):hover{background-color:white;border-color:green;color:green} - .label.label-label-2:not(.selected){background-color:white;border-color:green;color:green} + .label.label-label-2.selected:not(.no-hover):hover, + .label.label-label-2:not(.selected) + {background-color:white;border-color:green;color:green} .label.label-label-2:not(.no-hover):hover{background-color:green;color:white}
.label.label-id-0.selected{background-color:undefined} - .label.label-id-0.selected:not(.no-hover):hover{background-color:white;border-color:undefined;color:undefined} - .label.label-id-0:not(.selected){background-color:white;border-color:undefined;color:undefined} + .label.label-id-0.selected:not(.no-hover):hover, + .label.label-id-0:not(.selected) + {background-color:white;border-color:undefined;color:undefined} .label.label-id-0:not(.no-hover):hover{background-color:black;color:white} .label.label-id-4.selected{background-color:undefined} - .label.label-id-4.selected:not(.no-hover):hover{background-color:white;border-color:undefined;color:undefined} - .label.label-id-4:not(.selected){background-color:white;border-color:undefined;color:undefined} + .label.label-id-4.selected:not(.no-hover):hover, + .label.label-id-4:not(.selected) + {background-color:white;border-color:undefined;color:undefined} .label.label-id-4:not(.no-hover):hover{background-color:black;color:white} .label.label-id-3.selected{background-color:undefined} - .label.label-id-3.selected:not(.no-hover):hover{background-color:white;border-color:undefined;color:undefined} - .label.label-id-3:not(.selected){background-color:white;border-color:undefined;color:undefined} + .label.label-id-3.selected:not(.no-hover):hover, + .label.label-id-3:not(.selected) + {background-color:white;border-color:undefined;color:undefined} .label.label-id-3:not(.no-hover):hover{background-color:black;color:white} .label.label-id-2.selected{background-color:undefined} - .label.label-id-2.selected:not(.no-hover):hover{background-color:white;border-color:undefined;color:undefined} - .label.label-id-2:not(.selected){background-color:white;border-color:undefined;color:undefined} + .label.label-id-2.selected:not(.no-hover):hover, + .label.label-id-2:not(.selected) + {background-color:white;border-color:undefined;color:undefined} .label.label-id-2:not(.no-hover):hover{background-color:black;color:white} .label.label-id-1.selected{background-color:undefined} - .label.label-id-1.selected:not(.no-hover):hover{background-color:white;border-color:undefined;color:undefined} - .label.label-id-1:not(.selected){background-color:white;border-color:undefined;color:undefined} + .label.label-id-1.selected:not(.no-hover):hover, + .label.label-id-1:not(.selected) + {background-color:white;border-color:undefined;color:undefined} .label.label-id-1:not(.no-hover):hover{background-color:black;color:white}
.label.label-label-1.selected{background-color:red} - .label.label-label-1.selected:not(.no-hover):hover{background-color:white;border-color:red;color:red} - .label.label-label-1:not(.selected){background-color:white;border-color:red;color:red} + .label.label-label-1.selected:not(.no-hover):hover, + .label.label-label-1:not(.selected) + {background-color:white;border-color:red;color:red} .label.label-label-1:not(.no-hover):hover{background-color:red;color:white} .label.label-label-2.selected{background-color:green} - .label.label-label-2.selected:not(.no-hover):hover{background-color:white;border-color:green;color:green} - .label.label-label-2:not(.selected){background-color:white;border-color:green;color:green} + .label.label-label-2.selected:not(.no-hover):hover, + .label.label-label-2:not(.selected) + {background-color:white;border-color:green;color:green} .label.label-label-2:not(.no-hover):hover{background-color:green;color:white}
{ test('removes parameters', () => { expect(removeOauthParams('http://localhost/path')).toEqual('http://localhost/path'); diff --git a/src/__test__/event_details-page.test.jsx b/src/__test__/event_details-page.test.jsx index 26b7a65..2e15f37 100644 --- a/src/__test__/event_details-page.test.jsx +++ b/src/__test__/event_details-page.test.jsx @@ -6,7 +6,8 @@ import EventDetailsPage from '../pages/details/event-details-page'; describe('EventDetailsPage', () => { moment.tz.setDefault('EST'); test('loading', () => { - const component = renderer.create( undefined} />); + const jsx = undefined} />; + const component = renderer.create(jsx); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); @@ -19,7 +20,8 @@ describe('EventDetailsPage', () => { end: moment('2018-05-07T10:00:00-05:00'), allDay: false, }; - const component = renderer.create( undefined} />); + const jsx = undefined} />; + const component = renderer.create(jsx); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); @@ -32,7 +34,8 @@ describe('EventDetailsPage', () => { end: moment('2018-05-07T23:59:59-05:00'), allDay: true, }; - const component = renderer.create( undefined} />); + const jsx = undefined} />; + const component = renderer.create(jsx); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); @@ -45,7 +48,8 @@ describe('EventDetailsPage', () => { end: moment('2018-05-08T23:59:59-05:00'), allDay: true, }; - const component = renderer.create( undefined} />); + const jsx = undefined} />; + const component = renderer.create(jsx); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/src/components/label-pane.jsx b/src/components/label-pane.jsx index e489109..8b929cb 100644 --- a/src/components/label-pane.jsx +++ b/src/components/label-pane.jsx @@ -63,8 +63,9 @@ export default class LabelPane extends React.Component { // snapshots. return [ `${sel}.selected{background-color:${color}}`, - `${sel}.selected:not(.no-hover):hover{background-color:white;border-color:${color};color:${color}}`, - `${sel}:not(.selected){background-color:white;border-color:${color};color:${color}}`, + `${sel}.selected:not(.no-hover):hover,`, + `${sel}:not(.selected)`, + `{background-color:white;border-color:${color};color:${color}}`, `${sel}:not(.no-hover):hover{background-color:${color || 'black'};color:white}`, ].join('\n'); } diff --git a/src/components/material-button.jsx b/src/components/material-button.jsx index 9fe3e22..408df36 100644 --- a/src/components/material-button.jsx +++ b/src/components/material-button.jsx @@ -1,5 +1,5 @@ -// This component is an add (+) button following the Material Design style, like the button to create an event in -// Google Calendar +// This component is an add (+) button following the Material Design style, like +// the button to create an event in Google Calendar. import PropTypes from 'prop-types'; import React, { Component } from 'react'; diff --git a/src/containers/calendar-container.js b/src/containers/calendar-container.js index 264f297..82ceedc 100644 --- a/src/containers/calendar-container.js +++ b/src/containers/calendar-container.js @@ -74,6 +74,7 @@ const mapDispatchToProps = dispatch => ({ }); // Connect props to Redux state and actions -const CalendarContainer = connect(mapStateToProps, mapDispatchToProps)(withServerData(CalendarPage)); +const GuardedCalendarPage = withServerData(CalendarPage); +const CalendarContainer = connect(mapStateToProps, mapDispatchToProps)(GuardedCalendarPage); export default CalendarContainer; diff --git a/src/containers/import-container.js b/src/containers/import-container.js index d3fa788..28794e9 100644 --- a/src/containers/import-container.js +++ b/src/containers/import-container.js @@ -1,4 +1,5 @@ -// This container is a sort of middleware between the React import component and the Redux data store +// This container is a sort of middleware between the React import component and +// the Redux data store import { connect } from 'react-redux'; import * as ga from 'react-ga'; diff --git a/src/containers/labels-container.js b/src/containers/labels-container.js index 4cb82d4..0079ade 100644 --- a/src/containers/labels-container.js +++ b/src/containers/labels-container.js @@ -1,6 +1,11 @@ import { connect } from 'react-redux'; import LabelsPage from '../pages/labels/labels'; -import { refreshLabelsIfNeeded, setPageTitlePrefix, setSidebarMode, updateLabel } from '../data/actions'; +import { + refreshLabelsIfNeeded, + setPageTitlePrefix, + setSidebarMode, + updateLabel, +} from '../data/actions'; import SidebarModes from '../data/sidebar-modes'; // Pass data from the Redux state to the React component diff --git a/src/containers/subscription-container.js b/src/containers/subscription-container.js index 4795ebc..855899a 100644 --- a/src/containers/subscription-container.js +++ b/src/containers/subscription-container.js @@ -1,12 +1,9 @@ -// This container is a sort of middleware between the React import component and the Redux data store +// This container is a sort of middleware between the React import component and +// the Redux data store import { connect } from 'react-redux'; import * as ga from 'react-ga'; -import { - setSidebarMode, - toggleSidebarCollapsed, - setPageTitlePrefix, -} from '../data/actions'; +import { setSidebarMode, toggleSidebarCollapsed, setPageTitlePrefix } from '../data/actions'; import SubscriptionEditorPage from '../pages/subscription/subscription'; // This function passes values/objects from the Redux state to the React component as props diff --git a/src/data/actions.js b/src/data/actions.js index 2e37d30..a26d0d8 100644 --- a/src/data/actions.js +++ b/src/data/actions.js @@ -7,6 +7,7 @@ import { push } from 'react-router-redux'; import { setAccessTokenFromResponse } from './auth'; import { decodeEvent } from './encoding'; +/* eslint-disable max-len */ export const ActionTypes = { // General UI DISPLAY_ERROR: 'DISPLAY_ERROR', // Displays an error message (red background) at the top of the screen TODO: Issue #54 @@ -40,6 +41,7 @@ export const ActionTypes = { GENERATE_ICS_FEED: 'GENERATE_ICS_FEED', // Triggers generation of an ICS feed with current filter applied and copies // URL to clipboard }; +/* eslint-emable max-len */ // Sections: // General UI actions @@ -62,7 +64,8 @@ export function displayMessage(message) { } /** - * Display an error message (red background) at the top of the window in a notification bar TODO: Issue #54 + * Display an error message (red background) at the top of the window in a + * notification bar TODO: Issue #54 */ export function displayError(error, message) { return { type: ActionTypes.DISPLAY_ERROR, error, message }; @@ -110,7 +113,7 @@ export function navigateHome() { export function setPageTitlePrefix(newTitle) { return (dispatch, getState) => { let fullTitle; - const pageTitleSuffix = getState().general.pageTitleSuffix; + const { pageTitleSuffix } = getState().general; if (!newTitle || newTitle.length === 0) { fullTitle = pageTitleSuffix; } else if (newTitle.length > 50) { @@ -155,8 +158,9 @@ export function page(direction) { } /** - * Calculates how many of what unit (3 days, 1 week, etc) the currently viewing date should be changed by, based on - * what display mode (month, week, day, etc) the calendar is in + * Calculates how many of what unit (3 days, 1 week, etc) the currently viewing + * date should be changed by, based on what display mode (month, week, day, etc) + * the calendar is in */ function getPageDelta(state) { return state.calendar.currentViewMode.daysVisible > 0 @@ -178,8 +182,8 @@ export function showToday() { /** * Sets the date the calendar should be "centered" around - * @param {Moment} date - the first day to show in a multi-day view, or to be used to determine the week or month to - * show in a week or month view, etc. + * @param {Moment} date - the first day to show in a multi-day view, or to be + * used to determine the week or month to show in a week or month view, etc. */ export function setCurrentlyViewingDate(date) { // Set the first date visible on the calendar @@ -242,7 +246,7 @@ export function fetchAccount() { export function setAccount(account) { const data = { authenticated: account.authenticated, - permissions: new Set(account.permissions), + scope: new Set(account.scope), }; return { type: ActionTypes.SET_ACCOUNT, data }; } @@ -252,9 +256,12 @@ export function setAccount(account) { // ----- Begin general event actions ----- // /** - * Sets the current event so that the entire app can know what's being viewed or edited (esp. the sidebar) + * Sets the current event so that the entire app can know what's being viewed or + * edited (esp. the sidebar) + * * @param {string} id: the ID or SID (series ID?) of the event - * @param {string|null} recId: the date of the recurrence instance (YYYYDDD), if applicable + * @param {string|null} recId: the date of the recurrence instance (YYYYDDD), if + * applicable */ export function setCurrentEventById(id, recId) { return (dispatch, getStore) => { @@ -310,7 +317,8 @@ export function refreshEvents(start, end) { }; } -// FIXME: This is a bit of a hack to trigger a refresh. It should be replaced by some sort of cache invalidation call. +// FIXME: This is a bit of a hack to trigger a refresh. It should be replaced by +// some sort of cache invalidation call. export function refreshEventsForCurrentViewWindow() { return (dispatch, getStore) => { const { currentlyViewingDate } = getStore().calendar; @@ -523,10 +531,12 @@ export function setLabels(labels) { /** * Sets the label filter for the calendar view. - * @param {array} visibleLabels - which labels should be used to filter the events (presence indicates visible, absence - * indicates invisible) - * @param {string|undefined} allNoneDefault - 'all', 'none' or 'default' if that button was clicked in the sidebar - * (triggers reporting of the action to Google Analytics) + * + * @param {array} visibleLabels - which labels should be used to filter the + * events (presence indicates visible, absence indicates invisible) + * @param {string|undefined} allNoneDefault - 'all', 'none' or 'default' if that + * button was clicked in the sidebar (triggers reporting of the action to + * Google Analytics) */ export function setVisibleLabels(visibleLabels, allNoneDefault) { if (allNoneDefault !== undefined) { @@ -540,8 +550,10 @@ export function setVisibleLabels(visibleLabels, allNoneDefault) { } /** - * Toggles whether or not events with a particular label should be shown on the calendar. (An event with the given label - * will still be displayed if another one of its labels is selected for the filter [an OR operator is used].) + * Toggles whether or not events with a particular label should be shown on the + * calendar. (An event with the given label will still be displayed if another + * one of its labels is selected for the filter [an OR operator is used].) + * * @param {string} labelName - the name of the label to toggle */ export function labelVisibilityToggled(labelName) { diff --git a/src/data/encoding.js b/src/data/encoding.js index 6cd447d..eaa3a2b 100644 --- a/src/data/encoding.js +++ b/src/data/encoding.js @@ -18,9 +18,10 @@ const decodeDateTime = value => (moment.isMoment(value) ? value : toLocalDate(va */ export function decodeEvent(data) { useSnakeCaseProperties = 'all_day' in data; + const isDateField = key => key === 'start' || key === 'end'; return _.chain(data) .mapKeys((_value, key) => (key === 'all_day' ? 'allDay' : key)) - .mapValues((value, key) => (key === 'start' || key === 'end' ? decodeDateTime(data[key]) : value)) + .mapValues((value, key) => (isDateField(key) ? decodeDateTime(value) : value)) .value(); } @@ -28,5 +29,7 @@ export function decodeEvent(data) { * Convert an event from this app's internal format to the client-server API format. */ export function encodeEvent(event) { - return useSnakeCaseProperties ? _.mapKeys(event, (_value, key) => (key === 'allDay' ? 'all_day' : key)) : event; + return useSnakeCaseProperties + ? _.mapKeys(event, (_value, key) => (key === 'allDay' ? 'all_day' : key)) + : event; } diff --git a/src/data/sidebar-modes.js b/src/data/sidebar-modes.js index a5d871e..c94e04a 100644 --- a/src/data/sidebar-modes.js +++ b/src/data/sidebar-modes.js @@ -1,5 +1,6 @@ -// This file contains a dictionary of sidebar modes/configurations. Editing it will change which sidebar components/ -// panes appear on each page of the app. Note that the page must still trigger a Redux action to switch states upon +// This file contains a dictionary of sidebar modes/configurations. Editing it +// will change which sidebar components/ panes appear on each page of the app. +// Note that the page must still trigger a Redux action to switch states upon // loading. const SidebarModes = { diff --git a/src/pages/add-edit/add-edit-page.jsx b/src/pages/add-edit/add-edit-page.jsx index 10ec977..b8653dd 100644 --- a/src/pages/add-edit/add-edit-page.jsx +++ b/src/pages/add-edit/add-edit-page.jsx @@ -91,7 +91,8 @@ export default class AddEditEventPage extends React.Component { locationRaw: newProps.eventData.location, }); } - // TODO: If the event is recurring but newProps.match.params.recId is not defined, copy the data to seriesData + // TODO: If the event is recurring but newProps.match.params.recId is not + // defined, copy the data to seriesData } componentWillUnmount() { @@ -138,7 +139,8 @@ export default class AddEditEventPage extends React.Component { eventData.end.endOf('day'); } - // Declare some variables for keeping track of exactly what kind of request we'll be making later + // Declare some variables for keeping track of exactly what kind of + // request we'll be making later let url; let requestMethod; @@ -147,7 +149,8 @@ export default class AddEditEventPage extends React.Component { // Existing // Check if we're editing a single recurrence of a recurring event if (this.state.seriesData) { - // Determine what's different for this event compared to the rest of the events in the series + // Determine what's different for this event compared to the rest of + // the events in the series Object.keys(eventData).forEach((key) => { // Check if this attribute is the same for all events in the series if (_.isEqual(eventData[key], this.state.seriesData[key])) { @@ -196,7 +199,8 @@ export default class AddEditEventPage extends React.Component { const { eventData } = this.state; if (eventData) { - // Save the original start and end times in the series data (to check later if the user changed it) + // Save the original start and end times in the series data (to check + // later if the user changed it) seriesData.start = moment(eventData.start); seriesData.end = moment(eventData.end); } diff --git a/src/pages/add-edit/location-field.jsx b/src/pages/add-edit/location-field.jsx index af4a4ed..389a12e 100644 --- a/src/pages/add-edit/location-field.jsx +++ b/src/pages/add-edit/location-field.jsx @@ -1,5 +1,6 @@ -// This component is a text box for event locations. It tries to parse locations on the Olin campus (input in various -// formats) and stores them in a standard format to aid in analytics later. +// This component is a text box for event locations. It tries to parse locations +// on the Olin campus (input in various formats) and stores them in a standard +// format to aid in analytics later. import * as React from 'react'; @@ -20,13 +21,35 @@ export default class LocationField extends React.Component { this.LIBRARY_MATCHES = ['library', 'lib', 'l']; this.LIBRARY_UPPER_LEVEL_MATCHES = ['library upper level', 'lu', 'lul', 'library ul']; this.LIBRARY_LOWER_LEVEL_MATCHES = ['library lower level', 'll', 'lll', 'library ll']; - this.AUDITORIUM_MATCHES = ['auditorium', 'nordatorium', 'nord', 'mh auditorium', 'milas hall auditorium', - 'milas auditorium', 'mh nord', 'mh nordatorium']; + this.AUDITORIUM_MATCHES = [ + 'auditorium', + 'nordatorium', + 'nord', + 'mh auditorium', + 'milas hall auditorium', + 'milas auditorium', + 'mh nord', + 'mh nordatorium', + ]; this.DINING_HALL_MATCHES = ['dh', 'dining hall']; - this.DINING_HALL_MEZZANINE_MATCHES = ['dh mezz', 'dhm', 'mezz', 'dining hall mezz', 'dining hall mezzanine', - 'cc mezz', 'campus center mezz', 'campus center mezzanine']; - this.MILAS_HALL_MEZZANINE_MATCHES = ['mh mezz', 'mhm', 'milas mezz', 'milas hall mezz', 'milas hall mezz', - 'milas hall mezzanine']; + this.DINING_HALL_MEZZANINE_MATCHES = [ + 'dh mezz', + 'dhm', + 'mezz', + 'dining hall mezz', + 'dining hall mezzanine', + 'cc mezz', + 'campus center mezz', + 'campus center mezzanine', + ]; + this.MILAS_HALL_MEZZANINE_MATCHES = [ + 'mh mezz', + 'mhm', + 'milas mezz', + 'milas hall mezz', + 'milas hall mezz', + 'milas hall mezzanine', + ]; this.GREAT_LAWN_MATCHES = ['gl', 'great lawn', 'lawn']; this.MILAS_HALL_MATCHES = ['mh', 'milas', 'milas hall']; this.CAMPUS_CENTER_MATCHES = ['cc', 'campus center']; @@ -153,7 +176,9 @@ export default class LocationField extends React.Component { } else if (LocationField.stringMatches(buildingString, this.DINING_HALL_MATCHES)) { result.building = 'CC'; result.room = 'Dining Hall'; - } else if (LocationField.stringMatches(buildingString, this.DINING_HALL_MEZZANINE_MATCHES)) { + } else if ( + LocationField.stringMatches(buildingString, this.DINING_HALL_MEZZANINE_MATCHES) + ) { result.building = 'CC'; result.room = 'Dining Hall'; result.suffix = 'Mezzanine'; @@ -164,7 +189,9 @@ export default class LocationField extends React.Component { result.suffix = 'Mezzanine'; result.isOlin = true; return result; - } else if (LocationField.stringMatches(buildingString, this.LARGE_PROJECT_BUILDING_MATCHES)) { + } else if ( + LocationField.stringMatches(buildingString, this.LARGE_PROJECT_BUILDING_MATCHES) + ) { result.building = 'LPB'; result.isOlin = true; return result; @@ -203,7 +230,8 @@ export default class LocationField extends React.Component { } } - result.isOlin = (result.building !== null && result.room !== null && result.suffix !== undefined); + result.isOlin = + result.building !== null && result.room !== null && result.suffix !== undefined; } return result; }; @@ -233,13 +261,18 @@ export default class LocationField extends React.Component { value={this.props.location} onChange={this.textChanged} /> - - + +
diff --git a/src/pages/add-edit/recurrence-selector.jsx b/src/pages/add-edit/recurrence-selector.jsx index 6a890d6..160f6ed 100644 --- a/src/pages/add-edit/recurrence-selector.jsx +++ b/src/pages/add-edit/recurrence-selector.jsx @@ -202,7 +202,11 @@ class EndOptions extends React.Component { render() { const untilDate = this.state.option === 'date' ? ( - + ) : null; return (
@@ -313,7 +317,7 @@ export default class RecurrenceSelector extends React.Component { } endOptionsChanged(value) { - const state = this.state; + const { state } = this; if (value.option === 'date') { state.recurrence.until = value.endDate; delete state.recurrence.count; @@ -333,7 +337,10 @@ export default class RecurrenceSelector extends React.Component { render() { const monthOptions = this.props.recurs.frequency === 'MONTHLY' ? ( - + ) : null; const weekOptions = this.props.recurs.frequency === 'WEEKLY' ? ( diff --git a/src/pages/calendar/calendar-header.jsx b/src/pages/calendar/calendar-header.jsx index 405e966..f2053f5 100644 --- a/src/pages/calendar/calendar-header.jsx +++ b/src/pages/calendar/calendar-header.jsx @@ -3,37 +3,32 @@ import React from 'react'; import MenuIconButton from '../../components/menu-icon-button'; -export default class CalendarHeader extends React.Component { - render() { - const viewOptions = Object.values(this.props.possibleViewModes).map((mode) => { - const className = `calendar-header-view-option${ - mode === this.props.currentViewMode ? ' current' : ''}`; - return ( -
this.props.setViewMode(mode)} - > - {mode.displayName} -
- ); - }); - +const CalendarHeader = (props) => { + const viewOptions = Object.values(props.possibleViewModes).map((mode) => { + const className = `calendar-header-view-option${ + mode === props.currentViewMode ? ' current' : '' + }`; return ( -
- - {this.props.title} -
-
- {viewOptions} -
-
- - - -
-
+
props.setViewMode(mode)}> + {mode.displayName}
); - } -} + }); + + return ( +
+ + {props.title} +
+
{viewOptions}
+
+ + + +
+
+
+ ); +}; + +export default CalendarHeader; diff --git a/src/pages/calendar/calendar-page.jsx b/src/pages/calendar/calendar-page.jsx index 9c57acf..57c4782 100644 --- a/src/pages/calendar/calendar-page.jsx +++ b/src/pages/calendar/calendar-page.jsx @@ -47,7 +47,7 @@ export default class CalendarPage extends React.Component { componentDidUpdate() { if (this.props.labels.visibleLabels) { const labelsStr = this.props.labels.visibleLabels - .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) // sort alphabetically ignoring case + .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) .join(','); const url = `/calendar/${labelsStr}`; if (url !== this.props.location.pathname) { @@ -58,7 +58,9 @@ export default class CalendarPage extends React.Component { } render() { - const currDate = this.props.currentlyViewingDate ? this.props.currentlyViewingDate.format('MMMM D, YYYY') : ''; + const currDate = this.props.currentlyViewingDate + ? this.props.currentlyViewingDate.format('MMMM D, YYYY') + : ''; return (
diff --git a/src/pages/details/event-details-page.jsx b/src/pages/details/event-details-page.jsx index bebcdda..8ed0f6a 100644 --- a/src/pages/details/event-details-page.jsx +++ b/src/pages/details/event-details-page.jsx @@ -44,7 +44,9 @@ export default class EventDetailsPage extends React.Component { {event.end.format(endDateFormat)} ); - const recurrence = event.recurrence && ; + const recurrence = event.recurrence && ( + + ); return (
diff --git a/src/pages/import/import.jsx b/src/pages/import/import.jsx index 9dd4cff..cd36baa 100644 --- a/src/pages/import/import.jsx +++ b/src/pages/import/import.jsx @@ -30,13 +30,13 @@ export default class ImportPage extends React.Component { }; urlChanged = (e) => { - let importData = this.state.importData; - importData = Object.assign(importData, { url: e.currentTarget.value }); + const importData = Object.assign(this.state.importData, { url: e.currentTarget.value }); this.setState({ importData }); }; submitICS = () => { - axios.post(`${window.abe_url}/ics/`, this.state.importData) + axios + .post(`${window.abe_url}/ics/`, this.state.importData) .then( response => this.props.importSuccess(response, this.state.importData), (jqXHR, textStatus, errorThrown) => this.props.importFailed(errorThrown, jqXHR.message), @@ -48,7 +48,10 @@ export default class ImportPage extends React.Component {

- + Import

- {/* TODO: Update label.public to whatever the property ends up being named */} - {/* TODO: Update label.protected to whatever the property ends up being named */} l.default).map(l => l.name); + visibleLabels = Object.values(this.props.possibleLabels) + .filter(l => l.default) + .map(l => l.name); break; default: // Do nothing diff --git a/src/sidebar/sidebar-item-wrapper.jsx b/src/sidebar/sidebar-item-wrapper.jsx deleted file mode 100644 index 25b044d..0000000 --- a/src/sidebar/sidebar-item-wrapper.jsx +++ /dev/null @@ -1,18 +0,0 @@ -// This component "wraps" sidebar panes in a standard header (for consistent UI) - -import React from 'react'; - -export default class SidebarItemWrapper extends React.Component { - render() { - return ( -
-
- {this.props.header} -
-
- {this.props.children} -
-
- ); - } -} diff --git a/src/sidebar/sidebar-item.jsx b/src/sidebar/sidebar-item.jsx new file mode 100644 index 0000000..43b488f --- /dev/null +++ b/src/sidebar/sidebar-item.jsx @@ -0,0 +1,18 @@ +// This component "wraps" sidebar panes in a standard header (for consistent UI) + +import PropTypes from 'prop-types'; +import React from 'react'; + +const SidebarItemWrapper = props => ( +
+
{props.header}
+
{props.children}
+
+); + +SidebarItemWrapper.propTypes = { + header: PropTypes.node.isRequired, + children: PropTypes.node.isRequired, +}; + +export default SidebarItemWrapper; From 4bd6f635a5303c8b23d84ab2c37cbd41ce227b32 Mon Sep 17 00:00:00 2001 From: Oliver Steele Date: Thu, 17 May 2018 16:04:10 -0400 Subject: [PATCH 3/3] More lint --- .eslintrc.yml | 31 +++++--------- ....snap => event-details-page.test.jsx.snap} | 0 ...e.test.jsx => event-details-page.test.jsx} | 0 src/data/actions.js | 8 +++- src/pages/add-edit/location-field.jsx | 40 +++++++++---------- 5 files changed, 35 insertions(+), 44 deletions(-) rename src/__test__/__snapshots__/{event_details-page.test.jsx.snap => event-details-page.test.jsx.snap} (100%) rename src/__test__/{event_details-page.test.jsx => event-details-page.test.jsx} (100%) diff --git a/.eslintrc.yml b/.eslintrc.yml index 041ec56..2172f25 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -11,30 +11,20 @@ globals: process: false # allow webpack `process.env[*]` variables rules: - max-len: [warn, { code: 100 }] - # exceptions from eslint:recommended: + # Intentional exceptions to Airbnb rules: + no-alert: off # currently used in the UI + no-plusplus: [error, allowForLoopAfterthoughts: true] + no-underscore-dangle: [error, allow: [__REDUX_DEVTOOLS_EXTENSION_COMPOSE__]] no-unused-vars: [error, argsIgnorePattern: "^_" ] - # exceptions from plugin:react/recommended: - react/no-find-dom-node: warn - react/prop-types: off # TODO: #123 + react/jsx-no-target-blank: off - # Airbnb style exceptions that we'll eventually fix: - import/prefer-default-export: warn + # Airbnb exceptions that we should fix: + no-console: off no-param-reassign: warn no-restricted-globals: warn - prefer-destructuring: warn - react/forbid-prop-types: warn - react/jsx-no-target-blank: warn - react/no-unused-state: warn - react/prefer-stateless-function: warn + react/no-find-dom-node: warn + react/prop-types: off # TODO: #123 react/require-default-props: warn - react/sort-comp: warn - no-console: off - - # Intentional Airbnb exceptions: - no-alert: off # currently used in the UI - no-plusplus: [error, allowForLoopAfterthoughts: true] - no-underscore-dangle: [error, allow: [__REDUX_DEVTOOLS_EXTENSION_COMPOSE__]] # TODO: #118 accessibility jsx-a11y/anchor-is-valid: off @@ -42,9 +32,6 @@ rules: jsx-a11y/label-has-for: off jsx-a11y/no-static-element-interactions: off - # Fixable exceptions to Airbnb style guide. - react/jsx-indent: off - overrides: # server: - files: [ "index.html.js", "server.js", "webpack.config.js" ] diff --git a/src/__test__/__snapshots__/event_details-page.test.jsx.snap b/src/__test__/__snapshots__/event-details-page.test.jsx.snap similarity index 100% rename from src/__test__/__snapshots__/event_details-page.test.jsx.snap rename to src/__test__/__snapshots__/event-details-page.test.jsx.snap diff --git a/src/__test__/event_details-page.test.jsx b/src/__test__/event-details-page.test.jsx similarity index 100% rename from src/__test__/event_details-page.test.jsx rename to src/__test__/event-details-page.test.jsx diff --git a/src/data/actions.js b/src/data/actions.js index a26d0d8..7a788ec 100644 --- a/src/data/actions.js +++ b/src/data/actions.js @@ -471,6 +471,9 @@ export function eventDeleteFailed(event, error) { /** * Triggers showing the event details page. + * + * This function adds a `recId` property to the event. + * * @param {object} event - the event data of the event to view */ export function viewEvent(event) { @@ -518,15 +521,16 @@ export function fetchLabels() { * @param {object} labels - a dictionary of label names and information */ export function setLabels(labels) { + let data = labels; if (Object.prototype.toString.call(labels) === '[object Array]') { // Convert array to object const labelsMap = {}; labels.forEach((label) => { labelsMap[label.name] = label; }); - labels = labelsMap; + data = labelsMap; } - return { type: ActionTypes.SET_LABELS, data: labels }; + return { type: ActionTypes.SET_LABELS, data }; } /** diff --git a/src/pages/add-edit/location-field.jsx b/src/pages/add-edit/location-field.jsx index 389a12e..f734c39 100644 --- a/src/pages/add-edit/location-field.jsx +++ b/src/pages/add-edit/location-field.jsx @@ -5,6 +5,16 @@ import * as React from 'react'; export default class LocationField extends React.Component { + static stringMatches(str, substrings) { + const s = str.trim(); + for (let i = 0; i < substrings.length; ++i) { + if (s === substrings[i]) { + return true; + } + } + return false; + } + constructor(props) { super(props); this.state = { @@ -12,7 +22,6 @@ export default class LocationField extends React.Component { room: null, suffix: null, isOlin: false, - value: this.props.location, }; // Matching substrings for each building. Should be lowercase. @@ -82,7 +91,7 @@ export default class LocationField extends React.Component { } textChanged = (e) => { - const value = e.target.value; + const { value } = e.target; const parsed = this.tryParseLocationInput(value); const locationObj = { building: parsed.building, @@ -96,12 +105,13 @@ export default class LocationField extends React.Component { } }; - tryParseLocationInput = (string) => { + tryParseLocationInput = (str) => { + let name = str; const result = { isOlin: false, building: null, room: null }; - if (string && string.length > 0) { + if (name && name.length > 0) { // We don't care about case - string = string.toLowerCase(); + name = name.toLowerCase(); // Define regular expressions to use when searching string const buildingRegex = /^\D+/; @@ -109,7 +119,7 @@ export default class LocationField extends React.Component { const suffixRegex = /\D*$/; // Try to determine Olin building - let buildingString = buildingRegex.exec(string); + let buildingString = buildingRegex.exec(name); if (buildingString && buildingString.length > 0) { buildingString = buildingString[0].trim(); @@ -204,15 +214,15 @@ export default class LocationField extends React.Component { } // Try to determine Olin room number - const roomString = roomRegex.exec(string); + const roomString = roomRegex.exec(name); if (roomString && roomString.length > 0) { result.room = roomString[0].trim(); } // Try to determine Olin room suffix (optional) - let suffixString = suffixRegex.exec(string); - if (suffixString && suffixString.length > 0) { - suffixString = suffixString[0]; + const suffixMatch = suffixRegex.exec(name); + if (suffixMatch && suffixMatch.length > 0) { + const suffixString = suffixMatch[0]; // Just return if the regex didn't find anything if (suffixString !== null && suffixString.length > 0) { // The regex found something, so let's parse it @@ -236,16 +246,6 @@ export default class LocationField extends React.Component { return result; }; - static stringMatches(string, substrings) { - string = string.trim(); - for (let i = 0; i < substrings.length; ++i) { - if (string === substrings[i]) { - return true; - } - } - return false; - } - render() { // let svgSrc = (this.state.isOlin) ? '/assets/olin-o.svg' : '/assets/olin-o-slash.svg'; // let oTooltip = (this.state.isOlin) ? 'Recognized Olin location' : 'Location not at Olin';