diff --git a/.circleci/config.yml b/.circleci/config.yml index f5e8c390c..eb0b8d9b7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,7 +38,7 @@ jobs: test: docker: # image for running tests - - image: cypress/browsers:node16.14.0-chrome99-ff97 + - image: cypress/browsers:node18.12.0-chrome103-ff107 environment: - DB_NAME=lunch_test - DB_USER=lunch_test diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 959db8a94..000000000 --- a/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# http://editorconfig.org - -root = true - -[*] - -# Change these settings to your own preference -indent_style = space -indent_size = 2 - -# We recommend you to keep these unchanged -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -# editorconfig-tools is unable to ignore longs strings or urls -max_line_length = null diff --git a/.env.sample b/.env.sample index 2d528adad..271166b07 100644 --- a/.env.sample +++ b/.env.sample @@ -11,7 +11,7 @@ SUPERUSER_EMAIL= # Credentials for your Google Developer app GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= -GOOGLE_CLIENT_APIKEY= # optional +GOOGLE_CLIENT_APIKEY= GOOGLE_SERVER_APIKEY= # Google Analytics ID diff --git a/.eslintrc.js b/.eslintrc.js index 279053a6e..fab7bdc30 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,7 +10,7 @@ // ESLint configuration // http://eslint.org/docs/user-guide/configuring module.exports = { - parser: 'babel-eslint', + parser: '@babel/eslint-parser', extends: [ 'airbnb', @@ -77,6 +77,7 @@ module.exports = { 'key-spacing': 0, 'no-confusing-arrow': 0, 'react/jsx-quotes': 0, + 'react/jsx-props-no-spreading': 0, 'max-len': 0, 'jsx-quotes': [ 2, @@ -88,6 +89,13 @@ module.exports = { 'react/forbid-prop-types': 'off', 'react/destructuring-assignment': 'off', + 'react/function-component-definition': ['error', { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function' + }], + 'react/static-property-placement': 'off', + 'import/no-relative-packages': 'off', + 'import/no-import-module-exports': 'off' }, settings: { diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..d8335613d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npm test && npx lint-staged diff --git a/.mocharc.js b/.mocharc.js new file mode 100644 index 000000000..25b965d1b --- /dev/null +++ b/.mocharc.js @@ -0,0 +1,5 @@ +module.exports = { + require: ['@babel/register', './test/setup'], + exit: true, + file: './test/mocha-setup', +}; diff --git a/.stylelintrc.js b/.stylelintrc.js index 32eef2b23..b94a4ff48 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -7,13 +7,15 @@ * LICENSE.txt file in the root directory of this source tree. */ +const lowerKebabCase = /^[a-z][a-zA-Z0-9]+$/; + // stylelint configuration // https://stylelint.io/user-guide/configuration/ module.exports = { // The standard config based on a handful of CSS style guides // https://github.com/stylelint/stylelint-config-standard - extends: 'stylelint-config-standard', + extends: 'stylelint-config-standard-scss', plugins: [ // stylelint plugin to sort CSS rules content with specified order @@ -22,13 +24,10 @@ module.exports = { ], rules: { - 'at-rule-no-unknown': [ - true, - { - ignoreAtRules: ['include', 'mixin'], - }, - ], + 'at-rule-no-unknown': null, + 'scss/at-rule-no-unknown': true, 'declaration-empty-line-before': null, + 'keyframes-name-pattern': lowerKebabCase, 'number-leading-zero': 'never', 'property-no-unknown': [true, { ignoreProperties: [ @@ -38,6 +37,7 @@ module.exports = { 'overflow-anchor' ], }], + 'selector-class-pattern': lowerKebabCase, 'selector-pseudo-class-no-unknown': [ true, diff --git a/Dockerfile b/Dockerfile index 8ac264ff4..57bdba022 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.17.0 +FROM node:18.14.0 # Set a working directory WORKDIR /usr/src/app diff --git a/Dockerfile.local b/Dockerfile.local index fad02977a..84d17439b 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,4 +1,4 @@ -FROM node:16.17.0 +FROM node:18.14.0 # Set a working directory WORKDIR /app diff --git a/cypress/support/helpers/addRestaurant.js b/cypress/support/helpers/addRestaurant.js index 02e12c5f9..b8e3ce813 100644 --- a/cypress/support/helpers/addRestaurant.js +++ b/cypress/support/helpers/addRestaurant.js @@ -5,10 +5,10 @@ export default () => { const lat = 37.7955703; const lng = -122.39332079999997; const name = 'Ferry Building Marketplace'; - const place_id = 'ChIJWTGPjmaAhYARxz6l1hOj92w'; + const placeId = 'ChIJWTGPjmaAhYARxz6l1hOj92w'; cy.request('POST', `${Cypress.env('subdomain')}api/restaurants`, { name, - place_id, + placeId, address, lat, lng diff --git a/cypress/support/helpers/deleteRestaurant.js b/cypress/support/helpers/deleteRestaurant.js index 0afb44214..eddc779a1 100644 --- a/cypress/support/helpers/deleteRestaurant.js +++ b/cypress/support/helpers/deleteRestaurant.js @@ -2,7 +2,7 @@ export default () => { cy.visit('/'); cy.get('button.RestaurantDropdown-toggle').click(); - cy.get('ul.RestaurantDropdown-menu li').contains('Delete').should('be.visible').click(); + cy.get('.RestaurantDropdown-menu a').contains('Delete').should('be.visible').click(); cy.get('body').should('have.attr', 'class', 'modal-open'); cy.get('.modal-footer .btn-primary').contains('Delete').click(); }; diff --git a/db/migrations/20160303171210-AddGoogleParamsToRestaurants.js b/db/migrations/20160303171210-AddGoogleParamsToRestaurants.js index 3beb92703..1aead39d0 100644 --- a/db/migrations/20160303171210-AddGoogleParamsToRestaurants.js +++ b/db/migrations/20160303171210-AddGoogleParamsToRestaurants.js @@ -1,6 +1,4 @@ -const Promise = require('bluebird'); - -exports.up = (queryInterface, Sequelize) => Promise.all( +exports.up = (queryInterface, Sequelize) => Promise.all([ queryInterface.addColumn('restaurants', 'place_id', { type: Sequelize.STRING, unique: true @@ -11,10 +9,10 @@ exports.up = (queryInterface, Sequelize) => Promise.all( queryInterface.addColumn('restaurants', 'lng', { type: Sequelize.FLOAT }) -); +]); -exports.down = queryInterface => Promise.all( +exports.down = queryInterface => Promise.all([ queryInterface.removeColumn('restaurants', 'place_id'), queryInterface.removeColumn('restaurants', 'lat'), queryInterface.removeColumn('restaurants', 'lng') -); +]); diff --git a/db/migrations/20160308134329-AddTimestampsToRestaurants.js b/db/migrations/20160308134329-AddTimestampsToRestaurants.js index a33b55b54..e04a53bae 100644 --- a/db/migrations/20160308134329-AddTimestampsToRestaurants.js +++ b/db/migrations/20160308134329-AddTimestampsToRestaurants.js @@ -1,6 +1,4 @@ -const Promise = require('bluebird'); - -exports.up = (queryInterface, Sequelize) => Promise.all( +exports.up = (queryInterface, Sequelize) => Promise.all([ queryInterface.addColumn('restaurants', 'created_at', { type: Sequelize.DATE, allowNull: false @@ -9,9 +7,9 @@ exports.up = (queryInterface, Sequelize) => Promise.all( type: Sequelize.DATE, allowNull: false }) -); +]); -exports.down = queryInterface => Promise.all( +exports.down = queryInterface => Promise.all([ queryInterface.removeColumn('restaurants', 'created_at'), queryInterface.removeColumn('restaurants', 'updated_at') -); +]); diff --git a/db/migrations/20160308134819-AddTimestampsToVotes.js b/db/migrations/20160308134819-AddTimestampsToVotes.js index 9849c7366..28b851580 100644 --- a/db/migrations/20160308134819-AddTimestampsToVotes.js +++ b/db/migrations/20160308134819-AddTimestampsToVotes.js @@ -1,6 +1,4 @@ -const Promise = require('bluebird'); - -exports.up = (queryInterface, Sequelize) => Promise.all( +exports.up = (queryInterface, Sequelize) => Promise.all([ queryInterface.addColumn('votes', 'created_at', { type: Sequelize.DATE, allowNull: false @@ -9,9 +7,9 @@ exports.up = (queryInterface, Sequelize) => Promise.all( type: Sequelize.DATE, allowNull: false }) -); +]); -exports.down = queryInterface => Promise.all( +exports.down = queryInterface => Promise.all([ queryInterface.removeColumn('votes', 'created_at'), queryInterface.removeColumn('votes', 'updated_at') -); +]); diff --git a/db/migrations/20160308134835-AddTimestampsToUsers.js b/db/migrations/20160308134835-AddTimestampsToUsers.js index 8e20e9859..31f2bcf22 100644 --- a/db/migrations/20160308134835-AddTimestampsToUsers.js +++ b/db/migrations/20160308134835-AddTimestampsToUsers.js @@ -1,6 +1,4 @@ -const Promise = require('bluebird'); - -exports.up = (queryInterface, Sequelize) => Promise.all( +exports.up = (queryInterface, Sequelize) => Promise.all([ queryInterface.addColumn('users', 'created_at', { type: Sequelize.DATE, allowNull: false @@ -9,9 +7,9 @@ exports.up = (queryInterface, Sequelize) => Promise.all( type: Sequelize.DATE, allowNull: false }) -); +]); -exports.down = queryInterface => Promise.all( +exports.down = queryInterface => Promise.all([ queryInterface.removeColumn('users', 'created_at'), queryInterface.removeColumn('users', 'updated_at') -); +]); diff --git a/db/migrations/20170309225630-CreateTeams.js b/db/migrations/20170309225630-CreateTeams.js index 65c5d5466..ee0e496c3 100644 --- a/db/migrations/20170309225630-CreateTeams.js +++ b/db/migrations/20170309225630-CreateTeams.js @@ -1,7 +1,5 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => { - const Team = db.sequelize.define('team', { + const Team = queryInterface.sequelize.define('team', { name: Sequelize.STRING, }, { underscored: true diff --git a/db/migrations/20170309230545-AddTeamIdToRestaurants.js b/db/migrations/20170309230545-AddTeamIdToRestaurants.js index f762ef2f8..9f2b8d618 100644 --- a/db/migrations/20170309230545-AddTeamIdToRestaurants.js +++ b/db/migrations/20170309230545-AddTeamIdToRestaurants.js @@ -1,7 +1,5 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => { - const Team = db.sequelize.define('team', { + const Team = queryInterface.sequelize.define('team', { name: Sequelize.STRING, }, { underscored: true diff --git a/db/migrations/20170309232003-AddTeamIdToTags.js b/db/migrations/20170309232003-AddTeamIdToTags.js index 2620184ac..04ec5a91f 100644 --- a/db/migrations/20170309232003-AddTeamIdToTags.js +++ b/db/migrations/20170309232003-AddTeamIdToTags.js @@ -1,7 +1,5 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => { - const Team = db.sequelize.define('team', { + const Team = queryInterface.sequelize.define('team', { name: Sequelize.STRING, }, { underscored: true diff --git a/db/migrations/20170309233227-ChangeTagNameUniqueness.js b/db/migrations/20170309233227-ChangeTagNameUniqueness.js index d0c0322c3..a05075472 100644 --- a/db/migrations/20170309233227-ChangeTagNameUniqueness.js +++ b/db/migrations/20170309233227-ChangeTagNameUniqueness.js @@ -1,10 +1,8 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => queryInterface.changeColumn('tags', 'name', { type: Sequelize.STRING, allowNull: false, unique: false -}).then(() => db.sequelize.query('ALTER TABLE tags DROP CONSTRAINT IF EXISTS tags_name_key;')); +}).then(() => queryInterface.sequelize.query('ALTER TABLE tags DROP CONSTRAINT IF EXISTS tags_name_key;')); exports.down = (queryInterface, Sequelize) => queryInterface.changeColumn('tags', 'name', { type: Sequelize.STRING, diff --git a/db/migrations/20170310215129-ChangeUserGoogleIdNull.js b/db/migrations/20170310215129-ChangeUserGoogleIdNull.js index 8e05e3758..256248ed6 100644 --- a/db/migrations/20170310215129-ChangeUserGoogleIdNull.js +++ b/db/migrations/20170310215129-ChangeUserGoogleIdNull.js @@ -1,17 +1,15 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => queryInterface.changeColumn('users', 'google_id', { type: Sequelize.STRING }); exports.down = (queryInterface, Sequelize) => { - const User = db.sequelize.define('user', { + const User = queryInterface.sequelize.define('user', { google_id: Sequelize.STRING, }, { underscored: true }); - User.destroy({ where: { google_id: null } }).then(() => queryInterface.changeColumn('users', 'google_id', { + return User.destroy({ where: { google_id: null } }).then(() => queryInterface.changeColumn('users', 'google_id', { type: Sequelize.STRING, allowNull: false })); diff --git a/db/migrations/20170310220402-CreateRoles.js b/db/migrations/20170310220402-CreateRoles.js index 5af7f3e54..b4c01d678 100644 --- a/db/migrations/20170310220402-CreateRoles.js +++ b/db/migrations/20170310220402-CreateRoles.js @@ -1,7 +1,5 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => { - const User = db.sequelize.define('user', { + const User = queryInterface.sequelize.define('user', { google_id: Sequelize.STRING, name: Sequelize.STRING, email: Sequelize.STRING @@ -9,7 +7,7 @@ exports.up = (queryInterface, Sequelize) => { underscored: true }); - const Team = db.sequelize.define('team', { + const Team = queryInterface.sequelize.define('team', { name: Sequelize.STRING, }, { underscored: true @@ -61,7 +59,7 @@ exports.up = (queryInterface, Sequelize) => { }) .then(() => User.findAll()) .then((users) => Team.findOne({ where: { name: 'Lab Zero' } }).then(team => { - const Role = db.sequelize.define('role', { + const Role = queryInterface.sequelize.define('role', { type: { allowNull: false, type: Sequelize.ENUM('guest', 'member', 'owner'), @@ -101,4 +99,4 @@ exports.up = (queryInterface, Sequelize) => { })); }; -exports.down = queryInterface => queryInterface.dropTable('roles').then(() => db.sequelize.query('DROP TYPE enum_roles_type')); +exports.down = queryInterface => queryInterface.dropTable('roles').then(() => queryInterface.sequelize.query('DROP TYPE enum_roles_type')); diff --git a/db/migrations/20170310221346-AddSuperuserToUsers.js b/db/migrations/20170310221346-AddSuperuserToUsers.js index 0e99ff8cd..03e243243 100644 --- a/db/migrations/20170310221346-AddSuperuserToUsers.js +++ b/db/migrations/20170310221346-AddSuperuserToUsers.js @@ -1,11 +1,9 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => queryInterface.addColumn('users', 'superuser', { allowNull: false, type: Sequelize.BOOLEAN, defaultValue: false }).then(() => { - const User = db.sequelize.define('user', { + const User = queryInterface.sequelize.define('user', { google_id: Sequelize.STRING, name: Sequelize.STRING, email: Sequelize.STRING, diff --git a/db/migrations/20170317163119-AddTeamIdToDecisions.js b/db/migrations/20170317163119-AddTeamIdToDecisions.js index 51d68fd10..5fd76ff2c 100644 --- a/db/migrations/20170317163119-AddTeamIdToDecisions.js +++ b/db/migrations/20170317163119-AddTeamIdToDecisions.js @@ -1,7 +1,5 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => { - const Team = db.sequelize.define('team', { + const Team = queryInterface.sequelize.define('team', { name: Sequelize.STRING, slug: Sequelize.STRING(63) }, { diff --git a/db/migrations/20170317172934-ChangeTeamSlugAllowNull.js b/db/migrations/20170317172934-ChangeTeamSlugAllowNull.js index c05a2c5da..aa7be92f5 100644 --- a/db/migrations/20170317172934-ChangeTeamSlugAllowNull.js +++ b/db/migrations/20170317172934-ChangeTeamSlugAllowNull.js @@ -1,7 +1,5 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => { - const Team = db.sequelize.define('team', { + const Team = queryInterface.sequelize.define('team', { name: Sequelize.STRING, slug: Sequelize.STRING(63) }, { diff --git a/db/migrations/20170328222012-AddLatLngAndAddressToTeams.js b/db/migrations/20170328222012-AddLatLngAndAddressToTeams.js index 9a299bc61..c7d7b5aa2 100644 --- a/db/migrations/20170328222012-AddLatLngAndAddressToTeams.js +++ b/db/migrations/20170328222012-AddLatLngAndAddressToTeams.js @@ -1,6 +1,4 @@ -const Promise = require('bluebird'); - -exports.up = (queryInterface, Sequelize) => Promise.all( +exports.up = (queryInterface, Sequelize) => Promise.all([ queryInterface.addColumn('teams', 'lat', { type: Sequelize.DOUBLE }), @@ -10,10 +8,10 @@ exports.up = (queryInterface, Sequelize) => Promise.all( queryInterface.addColumn('teams', 'address', { type: Sequelize.STRING }) -); +]); -exports.down = queryInterface => Promise.all( +exports.down = queryInterface => Promise.all([ queryInterface.removeColumn('teams', 'lat'), queryInterface.removeColumn('teams', 'lng'), queryInterface.removeColumn('teams', 'address') -); +]); diff --git a/db/migrations/20170328223830-ChangeLatLngAddressAllowNull.js b/db/migrations/20170328223830-ChangeLatLngAddressAllowNull.js index a877930b0..5223b38b7 100644 --- a/db/migrations/20170328223830-ChangeLatLngAddressAllowNull.js +++ b/db/migrations/20170328223830-ChangeLatLngAddressAllowNull.js @@ -1,8 +1,5 @@ -const Promise = require('bluebird'); -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => { - const Team = db.sequelize.define('team', { + const Team = queryInterface.sequelize.define('team', { name: Sequelize.STRING, slug: Sequelize.STRING(63), lat: Sequelize.DOUBLE, @@ -18,7 +15,7 @@ exports.up = (queryInterface, Sequelize) => { lng: -122.399991 }, { where: { slug: 'labzero' } - }).then(() => Promise.all( + }).then(() => Promise.all([ queryInterface.changeColumn('teams', 'lat', { type: Sequelize.DOUBLE, allowNull: false, @@ -27,10 +24,10 @@ exports.up = (queryInterface, Sequelize) => { type: Sequelize.DOUBLE, allowNull: false, }) - )); + ])); }; -exports.down = (queryInterface, Sequelize) => Promise.all( +exports.down = (queryInterface, Sequelize) => Promise.all([ queryInterface.changeColumn('teams', 'lat', { allowNull: true, type: Sequelize.DOUBLE @@ -39,4 +36,4 @@ exports.down = (queryInterface, Sequelize) => Promise.all( allowNull: true, type: Sequelize.DOUBLE }) -); +]); diff --git a/db/migrations/20170403143257-AddLocalAuthAttributesToUsers.js b/db/migrations/20170403143257-AddLocalAuthAttributesToUsers.js index 676740f4b..d9b07f2d7 100644 --- a/db/migrations/20170403143257-AddLocalAuthAttributesToUsers.js +++ b/db/migrations/20170403143257-AddLocalAuthAttributesToUsers.js @@ -1,6 +1,4 @@ -const Promise = require('bluebird'); - -exports.up = (queryInterface, Sequelize) => Promise.all( +exports.up = (queryInterface, Sequelize) => Promise.all([ queryInterface.addColumn('users', 'encrypted_password', { type: Sequelize.STRING }), @@ -21,13 +19,13 @@ exports.up = (queryInterface, Sequelize) => Promise.all( queryInterface.addColumn('users', 'confirmation_sent_at', { type: Sequelize.DATE }) -); +]); -exports.down = queryInterface => Promise.all( +exports.down = queryInterface => Promise.all([ queryInterface.removeColumn('users', 'encrypted_password'), queryInterface.removeColumn('users', 'reset_password_token'), queryInterface.removeColumn('users', 'reset_password_sent_at'), queryInterface.removeColumn('users', 'confirmation_token'), queryInterface.removeColumn('users', 'confirmed_at'), queryInterface.removeColumn('users', 'confirmation_sent_at') -); +]); diff --git a/db/migrations/20170403172215-ChangeRestaurantPlaceIdUniqueness.js b/db/migrations/20170403172215-ChangeRestaurantPlaceIdUniqueness.js index 734a4019e..055e97362 100644 --- a/db/migrations/20170403172215-ChangeRestaurantPlaceIdUniqueness.js +++ b/db/migrations/20170403172215-ChangeRestaurantPlaceIdUniqueness.js @@ -1,9 +1,7 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => queryInterface.changeColumn('restaurants', 'place_id', { type: Sequelize.STRING, unique: false -}).then(() => db.sequelize.query('ALTER TABLE restaurants DROP CONSTRAINT restaurants_place_id_key;')); +}).then(() => queryInterface.sequelize.query('ALTER TABLE restaurants DROP CONSTRAINT restaurants_place_id_key;')); exports.down = (queryInterface, Sequelize) => queryInterface.changeColumn('restaurants', 'place_id', { type: Sequelize.STRING, diff --git a/db/migrations/20170417043528-ChangeUserEmailUniqueness.js b/db/migrations/20170417043528-ChangeUserEmailUniqueness.js index c5306d52c..6ee39b54a 100644 --- a/db/migrations/20170417043528-ChangeUserEmailUniqueness.js +++ b/db/migrations/20170417043528-ChangeUserEmailUniqueness.js @@ -1,5 +1,3 @@ -const db = require('../../src/models/db'); - exports.up = (queryInterface, Sequelize) => queryInterface.changeColumn('users', 'email', { type: Sequelize.STRING, allowNull: false, @@ -10,4 +8,4 @@ exports.down = (queryInterface, Sequelize) => queryInterface.changeColumn('users type: Sequelize.STRING, allowNull: true, unique: false -}).then(() => db.sequelize.query('ALTER TABLE users DROP CONSTRAINT IF EXISTS email_unique_idx;')); +}).then(() => queryInterface.sequelize.query('ALTER TABLE users DROP CONSTRAINT IF EXISTS email_unique_idx;')); diff --git a/db/migrations/20180115184107-AddSortDurationToTeams.js b/db/migrations/20180115184107-AddSortDurationToTeams.js index e087ccac8..ff08c8ed0 100644 --- a/db/migrations/20180115184107-AddSortDurationToTeams.js +++ b/db/migrations/20180115184107-AddSortDurationToTeams.js @@ -1,13 +1,9 @@ module.exports = { - up: (queryInterface, Sequelize) => { - queryInterface.addColumn('teams', 'sort_duration', { - type: Sequelize.INTEGER, - allowNull: false, - defaultValue: 28 - }); - }, + up: (queryInterface, Sequelize) => queryInterface.addColumn('teams', 'sort_duration', { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 28 + }), - down: (queryInterface) => { - queryInterface.removeColumn('teams', 'sort_duration'); - } + down: (queryInterface) => queryInterface.removeColumn('teams', 'sort_duration'), }; diff --git a/db/migrations/20180503213015-AddIndicesToAppropriateColumns.js b/db/migrations/20180503213015-AddIndicesToAppropriateColumns.js index 6ac9ad454..d72a7d20f 100644 --- a/db/migrations/20180503213015-AddIndicesToAppropriateColumns.js +++ b/db/migrations/20180503213015-AddIndicesToAppropriateColumns.js @@ -1,27 +1,27 @@ module.exports = { - up: (queryInterface) => { - queryInterface.addIndex('decisions', ['created_at']); - queryInterface.addIndex('decisions', ['restaurant_id']); - queryInterface.addIndex('restaurants_tags', ['restaurant_id']); - queryInterface.addIndex('restaurants_tags', ['tag_id']); - queryInterface.addIndex('roles', ['team_id']); - queryInterface.addIndex('roles', ['user_id']); - queryInterface.addIndex('teams', ['created_at']); - queryInterface.addIndex('votes', ['created_at']); - queryInterface.addIndex('votes', ['restaurant_id']); - queryInterface.addIndex('votes', ['user_id']); - }, + up: (queryInterface) => Promise.all([ + queryInterface.addIndex('decisions', ['created_at']), + queryInterface.addIndex('decisions', ['restaurant_id']), + queryInterface.addIndex('restaurants_tags', ['restaurant_id']), + queryInterface.addIndex('restaurants_tags', ['tag_id']), + queryInterface.addIndex('roles', ['team_id']), + queryInterface.addIndex('roles', ['user_id']), + queryInterface.addIndex('votes', ['created_at']), + queryInterface.addIndex('teams', ['created_at']), + queryInterface.addIndex('votes', ['restaurant_id']), + queryInterface.addIndex('votes', ['user_id']), + ]), - down: (queryInterface) => { - queryInterface.removeIndex('decisions', 'decisions_created_at'); - queryInterface.removeIndex('decisions', 'decisions_restaurant_id'); - queryInterface.removeIndex('restaurants_tags', 'restaurants_tags_restaurant_id'); - queryInterface.removeIndex('restaurants_tags', 'restaurants_tags_tag_id'); - queryInterface.removeIndex('roles', 'roles_team_id'); - queryInterface.removeIndex('roles', 'roles_user_id'); - queryInterface.removeIndex('teams', 'teams_created_at'); - queryInterface.removeIndex('votes', 'votes_created_at'); - queryInterface.removeIndex('votes', 'votes_restaurant_id'); - queryInterface.removeIndex('votes', 'votes_user_id'); - } + down: (queryInterface) => Promise.all([ + queryInterface.removeIndex('decisions', 'decisions_created_at'), + queryInterface.removeIndex('decisions', 'decisions_restaurant_id'), + queryInterface.removeIndex('restaurants_tags', 'restaurants_tags_restaurant_id'), + queryInterface.removeIndex('restaurants_tags', 'restaurants_tags_tag_id'), + queryInterface.removeIndex('roles', 'roles_team_id'), + queryInterface.removeIndex('roles', 'roles_user_id'), + queryInterface.removeIndex('teams', 'teams_created_at'), + queryInterface.removeIndex('votes', 'votes_created_at'), + queryInterface.removeIndex('votes', 'votes_restaurant_id'), + queryInterface.removeIndex('votes', 'votes_user_id'), + ]) }; diff --git a/db/migrations/20230214171101-ChangeUnderscoresToCamelCase.js b/db/migrations/20230214171101-ChangeUnderscoresToCamelCase.js new file mode 100644 index 000000000..b3154bfdd --- /dev/null +++ b/db/migrations/20230214171101-ChangeUnderscoresToCamelCase.js @@ -0,0 +1,95 @@ +module.exports = { + up: (queryInterface) => queryInterface.sequelize.transaction(transaction => Promise.all([ + queryInterface.renameTable('restaurants_tags', 'restaurantsTags', { transaction }) + ]).then(() => Promise.all([ + queryInterface.renameColumn('decisions', 'restaurant_id', 'restaurantId', { transaction }), + queryInterface.renameColumn('decisions', 'team_id', 'teamId', { transaction }), + queryInterface.renameColumn('decisions', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('decisions', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('invitations', 'confirmed_at', 'confirmedAt', { transaction }, { transaction }), + queryInterface.renameColumn('invitations', 'confirmation_token', 'confirmationToken', { transaction }), + queryInterface.renameColumn('invitations', 'confirmation_sent_at', 'confirmationSentAt', { transaction }), + queryInterface.renameColumn('invitations', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('invitations', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('restaurants', 'place_id', 'placeId', { transaction }), + queryInterface.renameColumn('restaurants', 'team_id', 'teamId', { transaction }), + queryInterface.renameColumn('restaurants', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('restaurants', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('restaurantsTags', 'restaurant_id', 'restaurantId', { transaction }), + queryInterface.renameColumn('restaurantsTags', 'tag_id', 'tagId', { transaction }), + queryInterface.renameColumn('restaurantsTags', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('restaurantsTags', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('roles', 'user_id', 'userId', { transaction }), + queryInterface.renameColumn('roles', 'team_id', 'teamId', { transaction }), + queryInterface.renameColumn('roles', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('roles', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('tags', 'team_id', 'teamId', { transaction }), + queryInterface.renameColumn('tags', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('tags', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('teams', 'default_zoom', 'defaultZoom', { transaction }), + queryInterface.renameColumn('teams', 'sort_duration', 'sortDuration', { transaction }), + queryInterface.renameColumn('teams', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('teams', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('users', 'google_id', 'googleId', { transaction }), + queryInterface.renameColumn('users', 'encrypted_password', 'encryptedPassword', { transaction }), + queryInterface.renameColumn('users', 'reset_password_token', 'resetPasswordToken', { transaction }), + queryInterface.renameColumn('users', 'reset_password_sent_at', 'resetPasswordSentAt', { transaction }), + queryInterface.renameColumn('users', 'confirmation_token', 'confirmationToken', { transaction }), + queryInterface.renameColumn('users', 'confirmation_sent_at', 'confirmationSentAt', { transaction }), + queryInterface.renameColumn('users', 'confirmed_at', 'confirmedAt', { transaction }), + queryInterface.renameColumn('users', 'name_changed', 'nameChanged', { transaction }), + queryInterface.renameColumn('users', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('users', 'updated_at', 'updatedAt', { transaction }), + queryInterface.renameColumn('votes', 'user_id', 'userId', { transaction }), + queryInterface.renameColumn('votes', 'restaurant_id', 'restaurantId', { transaction }), + queryInterface.renameColumn('votes', 'created_at', 'createdAt', { transaction }), + queryInterface.renameColumn('votes', 'updated_at', 'updatedAt', { transaction }), + ]))), + + down: (queryInterface) => queryInterface.sequelize.transaction(transaction => Promise.all([ + queryInterface.renameTable('restaurantsTags', 'restaurants_tags', { transaction }) + ]).then(() => Promise.all([ + queryInterface.renameColumn('decisions', 'restaurantId', 'restaurant_id', { transaction }), + queryInterface.renameColumn('decisions', 'teamId', 'team_id', { transaction }), + queryInterface.renameColumn('decisions', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('decisions', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('invitations', 'confirmedAt', 'confirmed_at', { transaction }), + queryInterface.renameColumn('invitations', 'confirmationToken', 'confirmation_token', { transaction }), + queryInterface.renameColumn('invitations', 'confirmationSentAt', 'confirmation_sent_at', { transaction }), + queryInterface.renameColumn('invitations', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('invitations', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('restaurants', 'placeId', 'place_id', { transaction }), + queryInterface.renameColumn('restaurants', 'teamId', 'team_id', { transaction }), + queryInterface.renameColumn('restaurants', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('restaurants', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('restaurants_tags', 'restaurantId', 'restaurant_id', { transaction }), + queryInterface.renameColumn('restaurants_tags', 'tagId', 'tag_id', { transaction }), + queryInterface.renameColumn('restaurants_tags', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('restaurants_tags', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('roles', 'userId', 'user_id', { transaction }), + queryInterface.renameColumn('roles', 'teamId', 'team_id', { transaction }), + queryInterface.renameColumn('roles', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('roles', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('tags', 'teamId', 'team_id', { transaction }), + queryInterface.renameColumn('tags', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('tags', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('teams', 'defaultZoom', 'default_zoom', { transaction }), + queryInterface.renameColumn('teams', 'sortDuration', 'sort_duration', { transaction }), + queryInterface.renameColumn('teams', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('teams', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('users', 'googleId', 'google_id', { transaction }), + queryInterface.renameColumn('users', 'encryptedPassword', 'encrypted_password', { transaction }), + queryInterface.renameColumn('users', 'resetPasswordToken', 'reset_password_token', { transaction }), + queryInterface.renameColumn('users', 'resetPasswordSentAt', 'reset_password_sent_at', { transaction }), + queryInterface.renameColumn('users', 'confirmationToken', 'confirmation_token', { transaction }), + queryInterface.renameColumn('users', 'confirmationSentAt', 'confirmation_sent_at', { transaction }), + queryInterface.renameColumn('users', 'confirmedAt', 'confirmed_at', { transaction }), + queryInterface.renameColumn('users', 'nameChanged', 'name_changed', { transaction }), + queryInterface.renameColumn('users', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('users', 'updatedAt', 'updated_at', { transaction }), + queryInterface.renameColumn('votes', 'userId', 'user_id', { transaction }), + queryInterface.renameColumn('votes', 'restaurantId', 'restaurant_id', { transaction }), + queryInterface.renameColumn('votes', 'createdAt', 'created_at', { transaction }), + queryInterface.renameColumn('votes', 'updatedAt', 'updated_at', { transaction }), + ]))) +}; diff --git a/db/seeds/20180108175137-superuser.js b/db/seeds/20180108175137-superuser.js index 990c6a9a5..3a606e2b7 100644 --- a/db/seeds/20180108175137-superuser.js +++ b/db/seeds/20180108175137-superuser.js @@ -11,11 +11,11 @@ module.exports = { function createUser(encryptedPassword) { return queryInterface.bulkInsert('users', [{ name, - encrypted_password: encryptedPassword, + encryptedPassword, superuser: true, email: process.env.SUPERUSER_EMAIL, - created_at: now, - updated_at: now + createdAt: now, + updatedAt: now }], {}); } diff --git a/package.json b/package.json index 13ca7df22..25a2ddf2b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "engines": { - "node": ">=16.13.1", + "node": ">=18.12.0", "npm": ">=5.0" }, "browserslist": [ @@ -14,72 +14,70 @@ ], "dependencies": { "@babel/polyfill": "^7.0.0", - "bcrypt": "^5.0.1", - "bluebird": "^3.5.1", + "@googlemaps/js-api-loader": "^1.15.1", + "@honeybadger-io/js": "^5.0.0", + "@reduxjs/toolkit": "^1.9.2", + "bcrypt": "^5.1.0", "body-parser": "^1.18.3", + "bootstrap": "^5.2.3", "classnames": "^2.2.6", "common-password": "^0.1.2", "compression": "^1.6.1", "connect-flash": "^0.1.1", - "connect-session-sequelize": "^4.1.0", + "connect-session-sequelize": "^7.1.5", "cookie-parser": "^1.4.3", "core-js": "^2.5.4", "cors": "^2.8.3", + "dayjs": "^1.11.7", "dotenv": "^2.0.0", "eventemitter3": "^1.2.0", "express": "^4.17.3", - "express-jwt": "^5.3.1", + "express-jwt": "^8.4.1", "express-session": "^1.15.2", "express-sslify": "^1.2.0", - "express-ws": "^3.0.0", + "express-ws": "^5.0.2", "fastclick": "^1.0.6", - "fbjs": "^0.8.4", - "fetch-mock": "^5.9.4", - "google-map-react": "^1.1.2", + "fbjs": "^3.0.4", + "fetch-mock": "^9.11.0", + "google-map-react": "^2.2.0", "history": "^4.7.2", - "honeybadger": "^1.1.3", "immutability-helper": "^2.1.2", - "isomorphic-fetch": "^2.2.1", - "isomorphic-style-loader": "^4.0.0", + "isomorphic-fetch": "^3.0.0", + "isomorphic-style-loader": "^5.3.2", "jsonwebtoken": "^9.0.0", "method-override": "^2.3.8", - "mocha-junit-reporter": "^1.17.0", - "moment": "^2.29.2", - "morgan": "^1.8.1", - "node-fetch": "^2.6.7", + "mocha-junit-reporter": "^2.2.0", + "morgan": "^1.10.0", + "node-fetch": "^2.6.9", "normalizr": "^3.2.2", - "passport": "^0.4.0", + "passport": "^0.6.0", "passport-google-oauth20": "^1.0.0", "passport-local": "^1.0.0", - "pg": "^8.8.0", - "pretty-error": "^2.1.1", - "prop-types": "^15.6.2", + "pg": "^8.9.0", + "pretty-error": "^3.0.4", + "prop-types": "^15.8.1", "query-string": "^6.1.0", - "react": "^16.5.2", - "react-addons-shallow-compare": "^15.4.2", - "react-autosuggest": "^9.3.1", - "react-bootstrap": "^0.31.0", - "react-dom": "^16.5.2", - "react-flip-move": "^3.0.1", - "react-geosuggest": "^2.7.0", - "react-intl": "^2.3.0", - "react-redux": "^5.0.6", - "react-scroll": "^1.7.6", - "react-transition-group": "^1.2.0", - "redux": "^3.7.2", - "redux-devtools-extension": "^2.13.2", - "redux-logger": "^3.0.6", - "redux-thunk": "^2.2.0", - "request": "^2.71.0", + "react": "^16.14.0", + "react-autosuggest": "^10.0.2", + "react-bootstrap": "^2.7.0", + "react-dom": "^16.14.0", + "react-flip-move": "^3.0.5", + "react-flip-toolkit": "^7.0.17", + "react-geosuggest": "^2.14.1", + "react-icons": "^4.7.1", + "react-intl": "^6.2.7", + "react-redux": "^8.0.5", + "react-scroll": "^1.8.9", + "react-transition-group": "^4.4.5", + "redux": "^4.2.1", "reselect": "^2.3.0", "reserved-usernames": "^1.0.3", - "resolve-url-loader": "^2.0.2", "robust-websocket": "^0.2.1", "rotating-file-stream": "^1.2.1", "sendgrid": "^5.2.3", - "sequelize": "^4.38.1", - "sequelize-cli": "^4.1.0", - "serialize-javascript": "^1.5.0", + "sequelize": "^6.28.0", + "sequelize-cli": "^6.6.0", + "serialize-javascript": "^6.0.1", "source-map-support": "^0.5.9", "sqlite3": "^5.0.11", "universal-router": "^8.1.0", @@ -87,23 +85,24 @@ "whatwg-fetch": "^3.0.0" }, "devDependencies": { - "@babel/core": "^7.0.0", - "@babel/node": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-react-constant-elements": "^7.0.0", - "@babel/plugin-transform-react-inline-elements": "^7.0.0", - "@babel/preset-env": "^7.1.0", - "@babel/preset-flow": "^7.0.0", - "@babel/preset-react": "^7.0.0", - "@babel/register": "^7.0.0", - "assets-webpack-plugin": "^3.5.1", + "@babel/core": "^7.20.12", + "@babel/eslint-parser": "^7.19.1", + "@babel/node": "^7.20.7", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.20.11", + "@babel/plugin-transform-react-constant-elements": "^7.20.2", + "@babel/plugin-transform-react-inline-elements": "^7.18.6", + "@babel/preset-env": "^7.20.2", + "@babel/preset-flow": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@babel/register": "^7.18.9", + "@redux-devtools/extension": "^3.2.5", + "assets-webpack-plugin": "^7.1.1", "autoprefixer": "^9.1.5", "babel-core": "^7.0.0-bridge.0", - "babel-eslint": "^9.0.0", - "babel-loader": "^8.0.0", - "babel-plugin-istanbul": "^4.1.5", + "babel-loader": "^9.1.0", + "babel-plugin-istanbul": "^6.1.1", "babel-plugin-react-transform": "^2.0.2", "babel-plugin-transform-react-remove-prop-types": "^0.4.18", "babel-preset-env": "^1.5.2", @@ -112,107 +111,86 @@ "babel-preset-stage-2": "^6.24.1", "babel-template": "^6.25.0", "babel-types": "^6.25.0", - "bootstrap-sass": "^3.3.7", - "browser-sync": "2.26.7", + "browser-sync": "2.27.11", "chai": "4.1.2", - "chokidar": "^2.0.4", + "chokidar": "^3.5.3", "cross-env": "^5.0.1", - "css-loader": "^1.0.0", + "css-loader": "^6.7.3", "custom-event-polyfill": "^0.3.0", - "cypress": "^10.7.0", + "cypress": "^12.5.1", "del": "^2.2.2", - "editorconfig-tools": "^0.1.1", - "enzyme": "^3.6.0", - "enzyme-adapter-react-16": "^1.0.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.7", "es6-promise": "^4.1.0", - "eslint": "^5.6.0", - "eslint-config-airbnb": "^17.1.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-loader": "^2.1.1", - "eslint-plugin-css-modules": "^2.9.1", - "eslint-plugin-flowtype": "^2.50.1", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-jsx-a11y": "^6.1.1", - "eslint-plugin-react": "^7.11.1", - "extend": "^3.0.0", - "file-loader": "^2.0.0", + "eslint": "^8.34.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-import-resolver-node": "^0.3.7", + "eslint-plugin-css-modules": "^2.11.0", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.32.2", + "file-loader": "^6.2.0", "git-repository": "^0.1.4", "glob": "^7.1.3", - "husky": "^1.0.0-rc.15", + "husky": "^8.0.3", "identity-obj-proxy": "^3.0.0", - "json-loader": "^0.5.4", - "lint-staged": "^8.1.4", - "mkdirp": "^0.5.1", - "mocha": "^5.0.0", - "node-sass": "7.0.0", - "npm-run-all": "^4.1.2", - "null-loader": "^0.1.1", - "nyc": "^11.4.1", - "opn-cli": "^3.1.0", - "pixrem": "^4.0.1", - "pleeease-filters": "^4.0.0", - "postcss": "^7.0.36", - "postcss-calc": "^6.0.1", - "postcss-color-function": "^4.0.1", - "postcss-custom-media": "^7.0.3", - "postcss-custom-properties": "^8.0.5", - "postcss-custom-selectors": "^5.1.2", - "postcss-flexbugs-fixes": "^4.1.0", - "postcss-global-import": "^1.0.0", - "postcss-import": "^12.0.0", - "postcss-loader": "^3.0.0", - "postcss-media-minmax": "^4.0.0", - "postcss-nested": "^4.1.0", - "postcss-nesting": "^7.0.0", - "postcss-pseudoelements": "^5.0.0", - "postcss-selector-matches": "^4.0.0", - "postcss-selector-not": "^4.0.0", + "json-loader": "^0.5.7", + "lint-staged": "^13.1.0", + "mkdirp": "^2.1.3", + "mocha": "^10.2.0", + "npm-run-all": "^4.1.5", + "null-loader": "^4.0.1", + "nyc": "^15.1.0", + "open-cli": "^7.1.0", + "postcss": "^8.4.21", + "postcss-loader": "^7.0.2", "proxyquire": "^1.7.11", "raw-loader": "^0.5.1", "react-deep-force-update": "^2.1.3", - "react-dev-utils": "^5.0.2", + "react-dev-utils": "^12.0.1", "react-error-overlay": "^4.0.1", - "react-test-renderer": "^16.5.2", + "react-test-renderer": "^16.14.0", "redux-mock-store": "^1.5.1", "rimraf": "^2.6.2", - "sass-loader": "^6.0.6", + "sass": "^1.58.0", + "sass-loader": "^13.2.0", "sequelize-mock": "^0.7.0", "sinon": "^13.0.1", "style-loader": "^0.13.2", - "stylelint": "^9.5.0", - "stylelint-config-standard": "^18.2.0", - "stylelint-order": "^1.0.0", - "supertest": "^3.0.0", - "svg-url-loader": "^2.3.2", - "url-loader": "^1.1.1", - "webpack": "^4.19.1", - "webpack-assets-manifest": "^3.0.2", - "webpack-bundle-analyzer": "^3.0.2", - "webpack-dev-middleware": "^3.3.0", - "webpack-hot-middleware": "^2.24.2", - "webpack-node-externals": "^1.7.2", - "workbox-webpack-plugin": "^3.2.0" + "stylelint": "^14.16.1", + "stylelint-config-standard-scss": "^6.1.0", + "stylelint-order": "^6.0.2", + "supertest": "^6.3.3", + "svg-url-loader": "^8.0.0", + "url-loader": "^4.1.1", + "webpack": "^5.75.0", + "webpack-assets-manifest": "^5.1.0", + "webpack-bundle-analyzer": "^4.8.0", + "webpack-cli": "^5.0.1", + "webpack-dev-middleware": "^6.0.1", + "webpack-hot-middleware": "^2.25.3", + "webpack-node-externals": "^3.0.0", + "workbox-webpack-plugin": "^6.5.4" }, "lint-staged": { - "ignore": [ - "package.json" + "*.{js,jsx}": [ + "eslint --no-ignore --fix", + "git add --force" ], - "linters": { - "*.{js,jsx}": [ - "eslint --no-ignore --fix", - "git add --force" - ], - "*.{json,md,graphql}": [ - "git add --force" - ], - "*.{css,less,styl,scss,sass,sss}": [ - "stylelint --fix", - "git add --force" - ] - } + "*.{json,md,graphql}": [ + "git add --force" + ], + "*.{css,less,styl,scss,sass,sss}": [ + "stylelint --fix", + "git add --force" + ] + }, + "nyc": { + "sourceMap": false, + "instrument": false }, "scripts": { - "precommit": "npm run test && lint-staged", "lint-js": "eslint --ignore-path .gitignore --ignore-pattern \"!**/.*\" .", "lint-css": "stylelint \"src/**/*.{css,less,styl,scss,sass,sss}\"", "lint": "npm run lint-js && npm run lint-css", @@ -225,10 +203,11 @@ "test-ci": "npm run test-file-ci \"./src/**/*.test.js\"", "test-watch": "npm run test --watch --notify", "test-cover": "nyc npm run test", - "coverage": "npm run test-cover && opn coverage/lcov-report/index.html", + "coverage": "npm run test-cover && open-cli coverage/lcov-report/index.html", "db:create": "./node_modules/.bin/sequelize db:create", "db:seed:all": "./node_modules/.bin/sequelize db:seed:all", "db:migrate": "./node_modules/.bin/sequelize db:migrate", + "db:migrate:undo": "./node_modules/.bin/sequelize db:migrate:undo", "db:migrate:undo:all": "./node_modules/.bin/sequelize db:migrate:undo:all", "db:drop": "./node_modules/.bin/sequelize db:drop", "debug": "npm run start -- --inspect", @@ -245,10 +224,11 @@ "copy": "babel-node tools/run copy", "bundle": "babel-node tools/run bundle", "build": "babel-node tools/run build", - "build-stats": "npm run build --release --analyse", + "build-stats": "npm run build -- --release --analyse", "deploy": "babel-node tools/run deploy", "render": "babel-node tools/run render", "serve": "babel-node tools/run runServer || true", - "start": "babel-node tools/run start" + "start": "babel-node tools/run start", + "prepare": "husky install" } } \ No newline at end of file diff --git a/src/actions/decisions.js b/src/actions/decisions.js index 0310f6827..89083e271 100644 --- a/src/actions/decisions.js +++ b/src/actions/decisions.js @@ -79,7 +79,7 @@ export const decisionsDeleted = (decisions, userId) => ({ }); export const decide = (restaurantId, daysAgo) => dispatch => { - const payload = { daysAgo, restaurant_id: restaurantId }; + const payload = { daysAgo, restaurantId }; dispatch(postDecision(restaurantId)); return fetch('/api/decisions', { credentials, diff --git a/src/actions/restaurants.js b/src/actions/restaurants.js index 2c358361d..028fb3b3f 100644 --- a/src/actions/restaurants.js +++ b/src/actions/restaurants.js @@ -212,7 +212,7 @@ export function fetchRestaurantsIfNeeded() { export function addRestaurant(name, placeId, address, lat, lng) { const payload = { - name, place_id: placeId, address, lat, lng + name, placeId, address, lat, lng }; return (dispatch) => { dispatch(postRestaurant(payload)); diff --git a/src/actions/tests/decisions.test.js b/src/actions/tests/decisions.test.js index 0c7382962..33b232b68 100644 --- a/src/actions/tests/decisions.test.js +++ b/src/actions/tests/decisions.test.js @@ -88,7 +88,7 @@ describe('actions/decisions', () => { store.dispatch(decisions.decide(restaurantId)); expect(fetchMock.lastCall()[0]).to.eq('/api/decisions'); - expect(fetchMock.lastCall()[1].body).to.eq(JSON.stringify({ restaurant_id: 1 })); + expect(fetchMock.lastCall()[1].body).to.eq(JSON.stringify({ restaurantId: 1 })); }); }); diff --git a/src/actions/tests/restaurants.test.js b/src/actions/tests/restaurants.test.js index 2312d44c7..bb428cc80 100644 --- a/src/actions/tests/restaurants.test.js +++ b/src/actions/tests/restaurants.test.js @@ -90,7 +90,7 @@ describe('actions/restaurants', () => { expect(actions[0].type).to.eq('POST_RESTAURANT'); expect(actions[0].restaurant).to.eql({ name: 'Lab Zero', - place_id: '12345', + placeId: '12345', address: '123 Main', lat: 50, lng: 100, @@ -104,7 +104,7 @@ describe('actions/restaurants', () => { expect(fetchMock.lastCall()[0]).to.eq('/api/restaurants'); expect(fetchMock.lastCall()[1].body).to.eq(JSON.stringify({ name, - place_id: placeId, + placeId, address, lat, lng diff --git a/src/actions/tests/teams.test.js b/src/actions/tests/teams.test.js index 6dc97dbf0..aa824440d 100644 --- a/src/actions/tests/teams.test.js +++ b/src/actions/tests/teams.test.js @@ -51,8 +51,8 @@ describe('actions/teams', () => { foo: 'bar', roles: [{ id: 1, - team_id: 2, - user_id: 3 + teamId: 2, + userId: 3 }] }; fetchMock.mock('*', { diff --git a/src/actions/tests/users.test.js b/src/actions/tests/users.test.js index 2f43778d7..a5d92c336 100644 --- a/src/actions/tests/users.test.js +++ b/src/actions/tests/users.test.js @@ -198,7 +198,7 @@ describe('actions/users', () => { it('fetches user with full url', () => { store.dispatch(proxyUsers.removeUser(id, team)); - expect(fetchMock.lastCall()[0]).to.eq(`//${team.slug}.lunch.pink/api/users/${id}`); + expect(fetchMock.lastCall()[0]).to.eq(`http://${team.slug}.lunch.pink/api/users/${id}`); }); }); diff --git a/src/actions/users.js b/src/actions/users.js index 4fdef8860..d4c6d2574 100644 --- a/src/actions/users.js +++ b/src/actions/users.js @@ -1,3 +1,4 @@ +import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; import ActionTypes from '../constants/ActionTypes'; import { credentials, jsonHeaders, processResponse } from '../core/ApiClient'; import { getCurrentUser } from '../selectors/user'; @@ -88,8 +89,12 @@ export function removeUser(id, team) { dispatch(deleteUser(id, team, isSelf)); let url = `/api/users/${id}`; const host = state.host; + let protocol = 'http:'; + if (canUseDOM) { + protocol = window.location.protocol; + } if (team) { - url = `//${team.slug}.${host}${url}`; + url = `${protocol}//${team.slug}.${host}${url}`; } return fetch(url, { credentials: team ? 'include' : credentials, diff --git a/src/api/main/teams.js b/src/api/main/teams.js index b3df9e703..8e3229d88 100644 --- a/src/api/main/teams.js +++ b/src/api/main/teams.js @@ -66,7 +66,7 @@ export default () => { name, slug, roles: [{ - user_id: req.user.id, + userId: req.user.id, type: 'owner' }] }, { include: [Role] }); @@ -107,7 +107,7 @@ export default () => { const message409 = 'Could not update team. Its new URL might already exist.'; let fieldCount = 0; - const allowedFields = [{ name: 'default_zoom', type: 'number' }]; + const allowedFields = [{ name: 'defaultZoom', type: 'number' }]; if (hasRole(req.user, req.team, 'owner')) { allowedFields.push({ name: 'address', @@ -125,7 +125,7 @@ export default () => { name: 'slug', type: 'string' }, { - name: 'sort_duration', + name: 'sortDuration', type: 'number' }); } @@ -153,8 +153,8 @@ export default () => { if (filteredPayload.slug && oldSlug !== filteredPayload.slug) { req.flash('success', 'Team URL has been updated.'); return req.session.save(async () => { - const teamRoles = await Role.findAll({ where: { team_id: req.team.get('id') } }); - const userIds = teamRoles.map(r => r.get('user_id')); + const teamRoles = await Role.findAll({ where: { teamId: req.team.get('id') } }); + const userIds = teamRoles.map(r => r.get('userId')); const recipients = await User.findAll({ where: { id: userIds } }); // returns a promise but we're not going to wait to see if it succeeds. diff --git a/src/api/main/user.js b/src/api/main/user.js index f02c14ac9..ef003fcdd 100644 --- a/src/api/main/user.js +++ b/src/api/main/user.js @@ -4,7 +4,6 @@ import getUserPasswordUpdates from '../../helpers/getUserPasswordUpdates'; import { User } from '../../models'; import loggedIn from '../helpers/loggedIn'; - export default () => { const router = new Router(); @@ -57,7 +56,7 @@ export default () => { } if (filteredPayload.name) { if (req.user.get('name') !== filteredPayload.name) { - filteredPayload.name_changed = true; + filteredPayload.namedChanged = true; } } await req.user.update(filteredPayload); diff --git a/src/api/team/decisions.js b/src/api/team/decisions.js index 13067ac1e..d2e77cd52 100644 --- a/src/api/team/decisions.js +++ b/src/api/team/decisions.js @@ -1,5 +1,5 @@ import { Router } from 'express'; -import moment from 'moment'; +import dayjs from 'dayjs'; import { DataTypes } from '../../models/db'; import { Decision } from '../../models'; import checkTeamRole from '../helpers/checkTeamRole'; @@ -16,11 +16,11 @@ export default () => { checkTeamRole(), async (req, res, next) => { try { - const opts = { where: { team_id: req.team.id } }; + const opts = { where: { teamId: req.team.id } }; const days = parseInt(req.query.days, 10); if (!Number.isNaN(days)) { - opts.where.created_at = { - [DataTypes.Op.gt]: moment().subtract(days, 'days').toDate() + opts.where.createdAt = { + [DataTypes.Op.gt]: dayjs().subtract(days, 'days').toDate() }; } @@ -37,15 +37,15 @@ export default () => { loggedIn, checkTeamRole(), async (req, res, next) => { - const restaurantId = parseInt(req.body.restaurant_id, 10); + const restaurantId = parseInt(req.body.restaurantId, 10); try { - const destroyOpts = { where: { team_id: req.team.id } }; + const destroyOpts = { where: { teamId: req.team.id } }; const daysAgo = parseInt(req.body.daysAgo, 10); let MaybeScopedDecision = Decision; if (daysAgo > 0) { - destroyOpts.where.created_at = { - [DataTypes.Op.gt]: moment().subtract(daysAgo, 'days').subtract(12, 'hours').toDate(), - [DataTypes.Op.lt]: moment().subtract(daysAgo, 'days').add(12, 'hours').toDate(), + destroyOpts.where.createdAt = { + [DataTypes.Op.gt]: dayjs().subtract(daysAgo, 'days').subtract(12, 'hours').toDate(), + [DataTypes.Op.lt]: dayjs().subtract(daysAgo, 'days').add(12, 'hours').toDate(), }; } else { MaybeScopedDecision = MaybeScopedDecision.scope('fromToday'); @@ -56,11 +56,11 @@ export default () => { try { const createOpts = { - restaurant_id: restaurantId, - team_id: req.team.id + restaurantId, + teamId: req.team.id }; if (daysAgo > 0) { - createOpts.created_at = moment().subtract(daysAgo, 'days').toDate(); + createOpts.createdAt = dayjs().subtract(daysAgo, 'days').toDate(); } const obj = await Decision.create(createOpts); @@ -82,8 +82,8 @@ export default () => { checkTeamRole(), async (req, res, next) => { try { - const decisions = await Decision.scope('fromToday').findAll({ where: { team_id: req.team.id } }); - await Decision.scope('fromToday').destroy({ where: { team_id: req.team.id } }); + const decisions = await Decision.scope('fromToday').findAll({ where: { teamId: req.team.id } }); + await Decision.scope('fromToday').destroy({ where: { teamId: req.team.id } }); req.wss.broadcast(req.team.id, decisionsDeleted(decisions, req.user.id)); res.status(204).send(); diff --git a/src/api/team/restaurantTags.js b/src/api/team/restaurantTags.js index 15c08d243..360600546 100644 --- a/src/api/team/restaurantTags.js +++ b/src/api/team/restaurantTags.js @@ -17,7 +17,7 @@ export default () => { loggedIn, checkTeamRole(), async (req, res, next) => { - const restaurantId = parseInt(req.params.restaurant_id, 10); + const restaurantId = parseInt(req.params.restaurantId, 10); const alreadyAddedError = () => { const error = { message: 'Could not add tag to restaurant. Is it already added?' }; res.status(409).json({ error: true, data: error }); @@ -26,13 +26,13 @@ export default () => { Tag.findOrCreate({ where: { name: req.body.name.toLowerCase().trim(), - team_id: req.team.id + teamId: req.team.id } - }).spread(async tag => { + }).then(async ([tag]) => { try { await RestaurantTag.create({ - restaurant_id: restaurantId, - tag_id: tag.id + restaurantId, + tagId: tag.id }); const json = tag.toJSON(); json.restaurant_count = 1; @@ -51,8 +51,8 @@ export default () => { const id = parseInt(req.body.id, 10); try { const obj = await RestaurantTag.create({ - restaurant_id: restaurantId, - tag_id: id + restaurantId, + tagId: id }); const json = obj.toJSON(); @@ -72,9 +72,9 @@ export default () => { checkTeamRole(), async (req, res, next) => { const id = parseInt(req.params.id, 10); - const restaurantId = parseInt(req.params.restaurant_id, 10); + const restaurantId = parseInt(req.params.restaurantId, 10); try { - await RestaurantTag.destroy({ where: { restaurant_id: restaurantId, tag_id: id } }); + await RestaurantTag.destroy({ where: { restaurantId, tagId: id } }); req.wss.broadcast(req.team.id, deletedTagFromRestaurant(restaurantId, id, req.user.id)); res.status(204).send(); } catch (err) { diff --git a/src/api/team/restaurants.js b/src/api/team/restaurants.js index 9dc4d75cb..26a41f0fd 100644 --- a/src/api/team/restaurants.js +++ b/src/api/team/restaurants.js @@ -1,5 +1,5 @@ import { Router } from 'express'; -import request from 'request'; +import fetch from 'node-fetch'; import { Restaurant, Vote, Tag } from '../../models'; import checkTeamRole from '../helpers/checkTeamRole'; import loggedIn from '../helpers/loggedIn'; @@ -22,7 +22,7 @@ export default () => { checkTeamRole(), async (req, res, next) => { try { - const all = await Restaurant.findAllWithTagIds({ team_id: req.team.id }); + const all = await Restaurant.findAllWithTagIds({ teamId: req.team.id }); res.status(200).json({ error: false, data: all }); } catch (err) { @@ -35,30 +35,28 @@ export default () => { checkTeamRole(), async (req, res, next) => { try { - const r = await Restaurant.findById(parseInt(req.params.id, 10)); + const r = await Restaurant.findByPk(parseInt(req.params.id, 10)); - if (r === null || r.team_id !== req.team.id) { + if (r === null || r.teamId !== req.team.id) { notFound(res); } else { - request(`https://maps.googleapis.com/maps/api/place/details/json?key=${apikey}&placeid=${r.place_id}`, - (error, response, body) => { - if (!error && response.statusCode === 200) { - const json = JSON.parse(body); - if (json.status !== 'OK') { - const newError = { - message: `Could not get info for restaurant. Google might have - removed its entry. Try removing it and adding it to Lunch again.` - }; - res.status(404).json({ error: true, newError }); - } else if (json.result && json.result.url) { - res.redirect(json.result.url); - } else { - res.redirect(`https://www.google.com/maps/place/${r.name}, ${r.address}`); - } - } else { - next(error); - } - }); + const response = await fetch(`https://maps.googleapis.com/maps/api/place/details/json?key=${apikey}&placeid=${r.placeId}`); + const json = await response.json(); + if (response.ok) { + if (json.status !== 'OK') { + const newError = { + message: `Could not get info for restaurant. Google might have +removed its entry. Try removing it and adding it to Lunch again.` + }; + res.status(404).json({ error: true, newError }); + } else if (json.result && json.result.url) { + res.redirect(json.result.url); + } else { + res.redirect(`https://www.google.com/maps/place/${r.name}, ${r.address}`); + } + } else { + next(json); + } } } catch (err) { next(err); @@ -71,8 +69,7 @@ export default () => { checkTeamRole(), async (req, res, next) => { const { - // eslint-disable-next-line camelcase - name, place_id, lat, lng + name, placeId, lat, lng } = req.body; let { address } = req.body; @@ -81,11 +78,11 @@ export default () => { try { const obj = await Restaurant.create({ name, - place_id, + placeId, address, lat, lng, - team_id: req.team.id, + teamId: req.team.id, votes: [], tags: [] }, { include: [Vote, Tag] }); @@ -111,8 +108,8 @@ export default () => { Restaurant.update( { name }, - { fields: ['name'], where: { id, team_id: req.team.id }, returning: true } - ).spread((count, rows) => { + { fields: ['name'], where: { id, teamId: req.team.id }, returning: true } + ).then((count, rows) => { if (count === 0) { notFound(res); } else { @@ -133,7 +130,7 @@ export default () => { async (req, res, next) => { const id = parseInt(req.params.id, 10); try { - const count = await Restaurant.destroy({ where: { id, team_id: req.team.id } }); + const count = await Restaurant.destroy({ where: { id, teamId: req.team.id } }); if (count === 0) { notFound(res); } else { @@ -145,6 +142,6 @@ export default () => { } } ) - .use('/:restaurant_id/votes', voteApi()) - .use('/:restaurant_id/tags', restaurantTagApi()); + .use('/:restaurantId/votes', voteApi()) + .use('/:restaurantId/tags', restaurantTagApi()); }; diff --git a/src/api/team/tags.js b/src/api/team/tags.js index 209219bea..a5f5daba3 100644 --- a/src/api/team/tags.js +++ b/src/api/team/tags.js @@ -14,7 +14,7 @@ export default () => { checkTeamRole(), async (req, res, next) => { try { - const all = await Tag.scope('orderedByRestaurant').findAll({ where: { team_id: req.team.id } }); + const all = await Tag.scope('orderedByRestaurant').findAll({ where: { teamId: req.team.id } }); res.status(200).send({ error: false, data: all }); } catch (err) { next(err); @@ -28,7 +28,7 @@ export default () => { async (req, res, next) => { const id = parseInt(req.params.id, 10); try { - const count = await Tag.destroy({ where: { id, team_id: req.team.id } }); + const count = await Tag.destroy({ where: { id, teamId: req.team.id } }); if (count === 0) { res.status(404).json({ error: true, data: { message: 'Tag not found.' } }); } else { diff --git a/src/api/team/users.js b/src/api/team/users.js index f273a4b17..477404836 100644 --- a/src/api/team/users.js +++ b/src/api/team/users.js @@ -20,12 +20,12 @@ export default () => { if (currentUser.id === targetId) { return getRole(currentUser, team); } - return Role.findOne({ where: { team_id: team.id, user_id: targetId } }); + return Role.findOne({ where: { teamId: team.id, userId: targetId } }); }; const hasOtherOwners = async (team, id) => { - const allTeamRoles = await Role.findAll({ where: { team_id: team.id } }); - return allTeamRoles.some(role => role.type === 'owner' && role.user_id !== id); + const allTeamRoles = await Role.findAll({ where: { teamId: team.id } }); + return allTeamRoles.some(role => role.type === 'owner' && role.userId !== id); }; const getExtraAttributes = (req) => { @@ -37,7 +37,7 @@ export default () => { const canChangeUser = async (user, roleToChange, target, team, noOtherOwners) => { let currentUserRole; - if (user.id === roleToChange.user_id) { + if (user.id === roleToChange.userId) { currentUserRole = roleToChange; } else { currentUserRole = getRole(user, team); @@ -46,8 +46,8 @@ export default () => { if (user.superuser) { allowed = true; } else if (currentUserRole.type === 'owner') { - if (user.id === roleToChange.user_id) { - const otherOwners = await hasOtherOwners(team, roleToChange.user_id); + if (user.id === roleToChange.userId) { + const otherOwners = await hasOtherOwners(team, roleToChange.userId); if (otherOwners) { allowed = true; } else { @@ -75,7 +75,7 @@ export default () => { include: { attributes: [], model: Role, - where: { team_id: req.team.id } + where: { teamId: req.team.id } } }); @@ -112,7 +112,7 @@ export default () => { if (hasRole(userToAdd, req.team, undefined, true)) { return res.status(409).json({ error: true, data: { message: 'User already exists on this team.' } }); } - await Role.create({ team_id: req.team.id, user_id: userToAdd.id, type }); + await Role.create({ teamId: req.team.id, userId: userToAdd.id, type }); // returns a promise but we're not going to wait to see if it succeeds. transporter.sendMail({ @@ -141,10 +141,10 @@ Happy Lunching!` let newUser = await User.create({ email, name, - reset_password_token: resetPasswordToken, - reset_password_sent_at: new Date(), + resetPasswordToken, + resetPasswordSentAt: new Date(), roles: [{ - team_id: req.team.id, + teamId: req.team.id, type }] }, { include: [Role] }); @@ -192,15 +192,13 @@ Happy Lunching!` const roleToChange = await getRoleToChange(req.user, id, req.team); if (roleToChange) { - const allowed = await canChangeUser( - req.user, roleToChange, req.body.type, req.team, () => res.status(403).json({ - error: true, - data: { - message: `You cannot demote yourself if you are the only owner. + const allowed = await canChangeUser(req.user, roleToChange, req.body.type, req.team, () => res.status(403).json({ + error: true, + data: { + message: `You cannot demote yourself if you are the only owner. Grant ownership to another user first.` - } - }) - ); + } + })); // in case of error response within canChangeUser if (typeof allowed !== 'boolean') { @@ -233,15 +231,13 @@ Happy Lunching!` const roleToDelete = await getRoleToChange(req.user, id, req.team); if (roleToDelete) { - const allowed = await canChangeUser( - req.user, roleToDelete, undefined, req.team, () => res.status(403).json({ - error: true, - data: { - message: `You cannot remove yourself if you are the only owner. + const allowed = await canChangeUser(req.user, roleToDelete, undefined, req.team, () => res.status(403).json({ + error: true, + data: { + message: `You cannot remove yourself if you are the only owner. Transfer ownership to another user first.` - } - }) - ); + } + })); // in case of error response within canChangeUser if (typeof allowed !== 'boolean') { diff --git a/src/api/team/votes.js b/src/api/team/votes.js index 4bcb0b5ac..4ed34cd55 100644 --- a/src/api/team/votes.js +++ b/src/api/team/votes.js @@ -18,15 +18,15 @@ export default () => { loggedIn, checkTeamRole(), async (req, res, next) => { - const restaurantId = parseInt(req.params.restaurant_id, 10); + const restaurantId = parseInt(req.params.restaurantId, 10); try { const result = await sequelize.transaction(async (t) => { const count = await Vote.recentForRestaurantAndUser(restaurantId, req.user.id, t); if (count === 0) { return Vote.create({ - restaurant_id: restaurantId, - user_id: req.user.id + restaurantId, + userId: req.user.id }, { transaction: t }); } return '409'; @@ -55,14 +55,14 @@ export default () => { const id = parseInt(req.params.id, 10); try { - const count = await Vote.destroy({ where: { id, user_id: req.user.id } }); + const count = await Vote.destroy({ where: { id, userId: req.user.id } }); if (count === 0) { notFound(res); } else { req.wss.broadcast( req.team.id, - voteDeleted(parseInt(req.params.restaurant_id, 10), req.user.id, id) + voteDeleted(parseInt(req.params.restaurantId, 10), req.user.id, id) ); res.status(204).send(); } diff --git a/src/api/tests/decisions.test.js b/src/api/tests/decisions.test.js index 0faa8d195..5430619b9 100644 --- a/src/api/tests/decisions.test.js +++ b/src/api/tests/decisions.test.js @@ -102,10 +102,10 @@ describe('api/team/decisions', () => { it('looks for decisions within past 5 days', () => { expect(findAllSpy.calledWith({ where: { - created_at: { + createdAt: { gt: match.date, }, - team_id: 77 + teamId: 77 } })).to.be.true; }); @@ -166,19 +166,19 @@ describe('api/team/decisions', () => { beforeEach(() => { destroySpy = spy(DecisionMock, 'destroy'); createSpy = spy(DecisionMock, 'create'); - return request(app).post('/').send({ restaurant_id: 1 }); + return request(app).post('/').send({ restaurantId: 1 }); }); it('deletes any prior decisions', () => { expect(destroySpy.calledWith({ - where: { team_id: 77 } + where: { teamId: 77 } })).to.be.true; }); it('creates new decision', () => { expect(createSpy.calledWith({ - restaurant_id: 1, - team_id: 77 + restaurantId: 1, + teamId: 77 })).to.be.true; }); }); @@ -189,26 +189,26 @@ describe('api/team/decisions', () => { beforeEach(() => { destroySpy = spy(DecisionMock, 'destroy'); createSpy = spy(DecisionMock, 'create'); - return request(app).post('/').send({ daysAgo: 1, restaurant_id: 1 }); + return request(app).post('/').send({ daysAgo: 1, restaurantId: 1 }); }); it('deletes any prior decisions', () => { expect(destroySpy.calledWith({ where: { - created_at: { + createdAt: { lt: match.date, gt: match.date, }, - team_id: 77, + teamId: 77, }, })).to.be.true; }); it('creates new decision', () => { expect(createSpy.calledWith({ - created_at: match.date, - restaurant_id: 1, - team_id: 77 + createdAt: match.date, + restaurantId: 1, + teamId: 77 })).to.be.true; }); }); @@ -224,7 +224,7 @@ describe('api/team/decisions', () => { }) }); - request(app).post('/').send({ restaurant_id: 1 }).then(r => { + request(app).post('/').send({ restaurantId: 1 }).then(r => { response = r; done(); }); @@ -310,7 +310,7 @@ describe('api/team/decisions', () => { it('finds decisions', () => { expect(findAllSpy.calledWith({ - where: { team_id: 77 } + where: { teamId: 77 } })).to.be.true; }); }); diff --git a/src/api/tests/teams.test.js b/src/api/tests/teams.test.js index 7bee2dfe2..a115befc6 100644 --- a/src/api/tests/teams.test.js +++ b/src/api/tests/teams.test.js @@ -290,7 +290,7 @@ describe('api/main/teams', () => { name: 'Lab Zero', slug: 'labzero', roles: [{ - user_id: 231, + userId: 231, type: 'owner' }] })).to.be.true; @@ -473,7 +473,7 @@ describe('api/main/teams', () => { update: updateSpy })); - return request(app).patch('/1').send({ default_zoom: 15, id: 123 }); + return request(app).patch('/1').send({ defaultZoom: 15, id: 123 }); }); it('updates team', () => { @@ -560,7 +560,7 @@ describe('api/main/teams', () => { describe('success', () => { let response; beforeEach((done) => { - request(app).patch('/1').send({ default_zoom: 15 }).then(r => { + request(app).patch('/1').send({ defaultZoom: 15 }).then(r => { response = r; done(); }); @@ -615,7 +615,7 @@ describe('api/main/teams', () => { get: () => {}, update: stub().throws('Oh No') })); - request(app).patch('/1').send({ default_zoom: 15 }).then((r) => { + request(app).patch('/1').send({ defaultZoom: 15 }).then((r) => { response = r; done(); }); diff --git a/src/api/tests/user.test.js b/src/api/tests/user.test.js index d9e6d69b7..99c124938 100644 --- a/src/api/tests/user.test.js +++ b/src/api/tests/user.test.js @@ -134,7 +134,7 @@ describe('api/main/user', () => { }), '../../helpers/getUserPasswordUpdates': mockEsmodule({ default: () => Promise.resolve({ - encrypted_password: 'drowssapdoog' + encryptedPassword: 'drowssapdoog' }) }) }); @@ -143,15 +143,15 @@ describe('api/main/user', () => { }); it('updates with password updates, not password', () => { - expect(updateSpy.calledWith({ encrypted_password: 'drowssapdoog' })).to.be.true; + expect(updateSpy.calledWith({ encryptedPassword: 'drowssapdoog' })).to.be.true; }); }); describe('with new name', () => { beforeEach(() => request(app).patch('/').send({ name: 'New Name' })); - it('sets name_changed', () => { - expect(updateSpy.calledWith({ name: 'New Name', name_changed: true })).to.be.true; + it('sets namedChanged', () => { + expect(updateSpy.calledWith({ name: 'New Name', namedChanged: true })).to.be.true; }); }); diff --git a/src/api/tests/users.test.js b/src/api/tests/users.test.js index 0d1bc717c..842a49fda 100644 --- a/src/api/tests/users.test.js +++ b/src/api/tests/users.test.js @@ -344,8 +344,8 @@ describe('api/team/users', () => { it('creates team role for user', () => { expect(createSpy.calledWith({ - team_id: 77, - user_id: 2, + teamId: 77, + userId: 2, type: 'member' })).to.be.true; }); @@ -386,10 +386,10 @@ describe('api/team/users', () => { expect(createStub.calledWith({ email: 'foo@bar.com', name: 'Jeffrey', - reset_password_token: match.string, - reset_password_sent_at: match.date, + resetPasswordToken: match.string, + resetPasswordSentAt: match.date, roles: [{ - team_id: 77, + teamId: 77, type: 'member' }] })).to.be.true; @@ -529,8 +529,8 @@ describe('api/team/users', () => { beforeEach(() => { role = { update: spy(), - user_id: user.id, - team_id: team.id, + userId: user.id, + teamId: team.id, type: 'owner' }; path = `/${user.id}`; @@ -554,7 +554,7 @@ describe('api/team/users', () => { beforeEach((done) => { findAllStub = stub(RoleMock, 'findAll').callsFake(() => Promise.resolve([role, { type: 'member', - user_id: 2 + userId: 2 }])); request(app).patch(path).send(payload).then((r) => { response = r; @@ -563,7 +563,7 @@ describe('api/team/users', () => { }); it('finds all roles', () => { - expect(findAllStub.calledWith({ where: { team_id: team.id } })).to.be.true; + expect(findAllStub.calledWith({ where: { teamId: team.id } })).to.be.true; }); it('returns 403', () => { @@ -580,7 +580,7 @@ describe('api/team/users', () => { beforeEach(() => { stub(RoleMock, 'findAll').callsFake(() => Promise.resolve([role, { type: 'owner', - user_id: 2 + userId: 2 }])); return request(app).patch(path).send(payload); }); @@ -602,8 +602,8 @@ describe('api/team/users', () => { it('queries role on team', () => { expect(findOneSpy.calledWith({ where: { - team_id: team.id, - user_id: 2 + teamId: team.id, + userId: 2 } })).to.be.true; }); @@ -616,14 +616,14 @@ describe('api/team/users', () => { let role; beforeEach(() => { currentUserRole = { - user_id: user.id, - team_id: team.id, + userId: user.id, + teamId: team.id, type: 'owner' }; role = { update: spy(), - user_id: otherUserId, - team_id: team.id + userId: otherUserId, + teamId: team.id }; otherUserId = 2; path = `/${otherUserId}`; @@ -679,14 +679,14 @@ describe('api/team/users', () => { let role; beforeEach(() => { currentUserRole = { - user_id: user.id, - team_id: team.id, + userId: user.id, + teamId: team.id, type: 'member' }; role = { update: spy(), - user_id: otherUserId, - team_id: team.id + userId: otherUserId, + teamId: team.id }; otherUserId = 2; path = `/${otherUserId}`; @@ -791,8 +791,8 @@ describe('api/team/users', () => { let userToChange; beforeEach((done) => { currentUserRole = { - user_id: user.id, - team_id: team.id, + userId: user.id, + teamId: team.id, type: 'member' }; userToChange = { @@ -800,8 +800,8 @@ describe('api/team/users', () => { }; stub(RoleMock, 'findOne').callsFake(() => Promise.resolve({ update: () => Promise.resolve(), - user_id: userToChange.id, - team_id: team.id, + userId: userToChange.id, + teamId: team.id, type: 'guest' })); stub(UserMock, 'findOne').callsFake(() => Promise.resolve(userToChange)); @@ -872,8 +872,8 @@ describe('api/team/users', () => { let userToDelete; beforeEach((done) => { currentUserRole = { - user_id: user.id, - team_id: team.id, + userId: user.id, + teamId: team.id, type: 'member' }; userToDelete = { @@ -881,8 +881,8 @@ describe('api/team/users', () => { }; roleToDestroy = { destroy: spy(() => Promise.resolve()), - user_id: userToDelete.id, - team_id: team.id, + userId: userToDelete.id, + teamId: team.id, type: 'guest' }; stub(RoleMock, 'findOne').callsFake(() => Promise.resolve(roleToDestroy)); diff --git a/src/client.js b/src/client.js index 66ff47645..bdfc16dc9 100644 --- a/src/client.js +++ b/src/client.js @@ -44,7 +44,9 @@ window.App.state.host = host; if (!subdomain) { // escape domain periods to not appear as regex wildcards - const subdomainMatch = window.location.host.match(`^(.*)\\.${host.replace(/\./g, '\\.')}`); + const subdomainMatch = window.location.host.match( + `^(.*)\\.${host.replace(/\./g, '\\.')}` + ); if (subdomainMatch) { subdomain = subdomainMatch[1]; } @@ -60,18 +62,19 @@ const context = { // https://github.com/kriasoft/isomorphic-style-loader insertCss: (...styles) => { // eslint-disable-next-line no-underscore-dangle - const removeCss = styles.map(x => x._insertCss()); + const removeCss = styles.map((x) => x._insertCss()); return () => { - removeCss.forEach(f => f()); + removeCss.forEach((f) => f()); }; }, // Universal HTTP client fetch: createFetch(fetch, { baseUrl: window.App.apiUrl, }), + googleApiKey: window.App.googleApiKey, // Initialize a new Redux store // http://redux.js.org/docs/basics/UsageWithReact.html - store + store, }; const container = document.getElementById('app'); @@ -182,7 +185,7 @@ async function onLocationChange(location, action) { if (window.ga) { window.ga('send', 'pageview', createPath(location)); } - }, + } ); } catch (error) { if (__DEV__) { diff --git a/src/components/AddUserForm/AddUserForm.js b/src/components/AddUserForm/AddUserForm.js index ea88bf106..b4556778b 100644 --- a/src/components/AddUserForm/AddUserForm.js +++ b/src/components/AddUserForm/AddUserForm.js @@ -1,13 +1,9 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { intlShape } from 'react-intl'; -import Button from 'react-bootstrap/lib/Button'; -import Col from 'react-bootstrap/lib/Col'; -import ControlLabel from 'react-bootstrap/lib/ControlLabel'; -import FormControl from 'react-bootstrap/lib/FormControl'; -import FormGroup from 'react-bootstrap/lib/FormGroup'; -import HelpBlock from 'react-bootstrap/lib/HelpBlock'; -import Row from 'react-bootstrap/lib/Row'; +import Button from 'react-bootstrap/Button'; +import Col from 'react-bootstrap/Col'; +import Form from 'react-bootstrap/Form'; +import Row from 'react-bootstrap/Row'; import { globalMessageDescriptor as gm } from '../../helpers/generateMessageDescriptor'; class AddUserForm extends Component { @@ -16,7 +12,7 @@ class AddUserForm extends Component { hasGuestRole: PropTypes.bool.isRequired, hasMemberRole: PropTypes.bool.isRequired, hasOwnerRole: PropTypes.bool.isRequired, - intl: intlShape.isRequired, + intl: PropTypes.shape().isRequired, }; static defaultState = { @@ -25,14 +21,18 @@ class AddUserForm extends Component { type: 'member' }; - state = Object.assign({}, AddUserForm.defaultState); + constructor(props) { + super(props); + + this.state = ({ ...AddUserForm.defaultState }); + } handleChange = field => event => this.setState({ [field]: event.target.value }); handleSubmit = (event) => { event.preventDefault(); this.props.addUserToTeam(this.state); - this.setState(Object.assign({}, AddUserForm.defaultState)); + this.setState({ ...AddUserForm.defaultState }); }; render() { @@ -48,27 +48,27 @@ class AddUserForm extends Component {

Add User

- - + + Name - + - - - - + + + Email - + - - - - + + + Type - + - {f(gm('guestRole'))}} {hasMemberRole && } {hasOwnerRole && } - + - + Members can add new users and remove guests. {hasOwnerRole - && ' Owners can manage all user roles and manage overall team information.' - } - - + && ' Owners can manage all user roles and manage overall team information.'} + + - + Please tell the user you are inviting to check their spam folder if they don’t receive anything shortly. - +
); diff --git a/src/components/AddUserForm/AddUserForm.test.js b/src/components/AddUserForm/AddUserForm.test.js index 695d0d303..3a209016f 100644 --- a/src/components/AddUserForm/AddUserForm.test.js +++ b/src/components/AddUserForm/AddUserForm.test.js @@ -31,6 +31,7 @@ describe('AddUserForm', () => { }; }); + /* eslint-disable jsx-a11y/control-has-associated-label */ describe('the options for the User Type form', () => { it('includes an option for guest if the hasGuestRole prop is true', () => { props.hasGuestRole = true; diff --git a/src/components/App.js b/src/components/App.js index 8649fc6ea..177ba050e 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -7,10 +7,13 @@ * LICENSE.txt file in the root directory of this source tree. */ +import StyleContext from 'isomorphic-style-loader/StyleContext'; import PropTypes from 'prop-types'; import React, { Children } from 'react'; import { Provider as ReduxProvider } from 'react-redux'; +import { Loader } from '@googlemaps/js-api-loader'; import IntlProviderContainer from './IntlProvider/IntlProviderContainer'; +import GoogleMapsLoaderContext from './GoogleMapsLoaderContext/GoogleMapsLoaderContext'; const ContextType = { // Enables critical path CSS rendering @@ -18,8 +21,10 @@ const ContextType = { insertCss: PropTypes.func.isRequired, // Universal HTTP client fetch: PropTypes.func.isRequired, + googleApiKey: PropTypes.string.isRequired, pathname: PropTypes.string.isRequired, query: PropTypes.object, + store: PropTypes.object.isRequired, // Integrate Redux // http://redux.js.org/docs/basics/UsageWithReact.html ...ReduxProvider.childContextTypes, @@ -55,6 +60,20 @@ class App extends React.PureComponent { static childContextTypes = ContextType; + constructor(props) { + super(props); + + this.loaderContextValue = { + loader: new Loader({ + apiKey: this.props.context.googleApiKey, + version: 'weekly', + libraries: ['places', 'geocoder'] + }) + }; + + this.styleContextValue = { insertCss: props.context.insertCss }; + } + getChildContext() { return this.props.context; } @@ -63,9 +82,17 @@ class App extends React.PureComponent { // NOTE: If you need to add or modify header, footer etc. of the app, // please do that inside the Layout component. return ( - - {Children.only(this.props.children)} - + + + + + {Children.only(this.props.children)} + + + + ); } } diff --git a/src/components/ChangeTeamURLModal/ChangeTeamURLModal.js b/src/components/ChangeTeamURLModal/ChangeTeamURLModal.js index b871b1109..a0a1441f8 100644 --- a/src/components/ChangeTeamURLModal/ChangeTeamURLModal.js +++ b/src/components/ChangeTeamURLModal/ChangeTeamURLModal.js @@ -1,16 +1,14 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import withStyles from 'isomorphic-style-loader/lib/withStyles'; -import Col from 'react-bootstrap/lib/Col'; -import ControlLabel from 'react-bootstrap/lib/ControlLabel'; -import FormControl from 'react-bootstrap/lib/FormControl'; -import FormGroup from 'react-bootstrap/lib/FormGroup'; -import InputGroup from 'react-bootstrap/lib/InputGroup'; -import Modal from 'react-bootstrap/lib/Modal'; -import ModalBody from 'react-bootstrap/lib/ModalBody'; -import ModalFooter from 'react-bootstrap/lib/ModalFooter'; -import Row from 'react-bootstrap/lib/Row'; -import Button from 'react-bootstrap/lib/Button'; +import withStyles from 'isomorphic-style-loader/withStyles'; +import Col from 'react-bootstrap/Col'; +import Form from 'react-bootstrap/Form'; +import InputGroup from 'react-bootstrap/InputGroup'; +import Modal from 'react-bootstrap/Modal'; +import ModalBody from 'react-bootstrap/ModalBody'; +import ModalFooter from 'react-bootstrap/ModalFooter'; +import Row from 'react-bootstrap/Row'; +import Button from 'react-bootstrap/Button'; import { TEAM_SLUG_REGEX } from '../../constants'; import s from './ChangeTeamURLModal.scss'; @@ -20,16 +18,20 @@ class ChangeTeamURLModal extends Component { team: PropTypes.object.isRequired, shown: PropTypes.bool.isRequired, hideModal: PropTypes.func.isRequired, - updateTeam: PropTypes.func.isRequired + updateTeam: PropTypes.func.isRequired, }; - state = { - newSlug: '', - oldSlug: '' - }; + constructor(props) { + super(props); + + this.state = { + newSlug: '', + oldSlug: '', + }; + } - handleChange = field => event => this.setState({ - [field]: event.target.value + handleChange = (field) => (event) => this.setState({ + [field]: event.target.value, }); handleSubmit = () => { @@ -39,7 +41,7 @@ class ChangeTeamURLModal extends Component { updateTeam({ slug: newSlug }).then(() => { window.location.href = `//${newSlug}.${host}/team`; }); - } + }; render() { const { team, shown, hideModal } = this.props; @@ -51,18 +53,22 @@ class ChangeTeamURLModal extends Component {

Be forewarned: {' '} -Changing the team URL frees up the old URL to be used - by other teams. This means that any bookmarks your team members have created for this - team will no longer work. We’ll send out an email notification to all users on - the team that this change has taken place. + Changing the team URL frees up the + old URL to be used by other teams. This means that any bookmarks + your team members have created for this team will no longer work. + We’ll send out an email notification to all users on the team + that this change has taken place. +

+

+ To confirm, please write the current URL of the team in the field + below.

-

To confirm, please write the current URL of the team in the field below.

- - Current team URL + + Current team URL - - .lunch.pink + .lunch.pink - - - New team URL + + + New team URL - - .lunch.pink + .lunch.pink - + - + - + diff --git a/src/components/ConfirmModal/ConfirmModal.test.js b/src/components/ConfirmModal/ConfirmModal.test.js index 9fc35fdf4..51730c7c8 100644 --- a/src/components/ConfirmModal/ConfirmModal.test.js +++ b/src/components/ConfirmModal/ConfirmModal.test.js @@ -1,7 +1,7 @@ /* eslint-env mocha */ /* eslint-disable padded-blocks, no-unused-expressions */ -import Modal from 'react-bootstrap/lib/Modal'; +import Modal from 'react-bootstrap/Modal'; import { expect } from 'chai'; import React from 'react'; import sinon from 'sinon'; diff --git a/src/components/ConfirmModal/ConfirmModalContainer.js b/src/components/ConfirmModal/ConfirmModalContainer.js index e242dc480..79d047f30 100644 --- a/src/components/ConfirmModal/ConfirmModalContainer.js +++ b/src/components/ConfirmModal/ConfirmModalContainer.js @@ -15,7 +15,9 @@ const mapDispatchToProps = dispatch => ({ hideModal: () => dispatch(hideModal('confirm')), }); -const mergeProps = (stateProps, dispatchProps) => Object.assign({}, stateProps, dispatchProps, { +const mergeProps = (stateProps, dispatchProps) => ({ + ...stateProps, + ...dispatchProps, handleSubmit: () => { stateProps.internalHandleSubmit(); dispatchProps.hideModal(); diff --git a/src/components/DeleteTeamModal/DeleteTeamModal.js b/src/components/DeleteTeamModal/DeleteTeamModal.js index a5b5c0560..724b5bf2b 100644 --- a/src/components/DeleteTeamModal/DeleteTeamModal.js +++ b/src/components/DeleteTeamModal/DeleteTeamModal.js @@ -1,16 +1,14 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import withStyles from 'isomorphic-style-loader/lib/withStyles'; -import Col from 'react-bootstrap/lib/Col'; -import ControlLabel from 'react-bootstrap/lib/ControlLabel'; -import FormControl from 'react-bootstrap/lib/FormControl'; -import FormGroup from 'react-bootstrap/lib/FormGroup'; -import InputGroup from 'react-bootstrap/lib/InputGroup'; -import Modal from 'react-bootstrap/lib/Modal'; -import ModalBody from 'react-bootstrap/lib/ModalBody'; -import ModalFooter from 'react-bootstrap/lib/ModalFooter'; -import Row from 'react-bootstrap/lib/Row'; -import Button from 'react-bootstrap/lib/Button'; +import withStyles from 'isomorphic-style-loader/withStyles'; +import Col from 'react-bootstrap/Col'; +import Form from 'react-bootstrap/Form'; +import InputGroup from 'react-bootstrap/InputGroup'; +import Modal from 'react-bootstrap/Modal'; +import ModalBody from 'react-bootstrap/ModalBody'; +import ModalFooter from 'react-bootstrap/ModalFooter'; +import Row from 'react-bootstrap/Row'; +import Button from 'react-bootstrap/Button'; import { TEAM_SLUG_REGEX } from '../../constants'; import s from './DeleteTeamModal.scss'; @@ -20,18 +18,22 @@ class DeleteTeamModal extends Component { team: PropTypes.object.isRequired, shown: PropTypes.bool.isRequired, hideModal: PropTypes.func.isRequired, - deleteTeam: PropTypes.func.isRequired + deleteTeam: PropTypes.func.isRequired, }; - state = { - confirmSlug: '' - }; + constructor(props) { + super(props); + + this.state = { + confirmSlug: '', + }; + } handleChange = (event) => { this.setState({ - confirmSlug: event.target.value + confirmSlug: event.target.value, }); - } + }; handleSubmit = () => { const { deleteTeam, host } = this.props; @@ -39,7 +41,7 @@ class DeleteTeamModal extends Component { deleteTeam().then(() => { window.location.href = `//${host}/teams`; }); - } + }; render() { const { team, shown, hideModal } = this.props; @@ -53,20 +55,22 @@ class DeleteTeamModal extends Component { {' '} {team.name} {' '} -team? + team? {' '} This is irreversible. {' '} - All restaurants and tags will be deleted, - and all users will be unassigned from the team. + All restaurants and tags will + be deleted, and all users will be unassigned from the team. +

+

+ To confirm, please write the URL of the team in the field below.

-

To confirm, please write the URL of the team in the field below.

- - Team URL + + Team URL - - .lunch.pink + .lunch.pink - + - + diff --git a/src/components/GoogleInfoWindow/GoogleInfoWindow.scss b/src/components/GoogleInfoWindow/GoogleInfoWindow.scss index 2ecd2d70f..4235278b6 100644 --- a/src/components/GoogleInfoWindow/GoogleInfoWindow.scss +++ b/src/components/GoogleInfoWindow/GoogleInfoWindow.scss @@ -1,5 +1,5 @@ -@import '../../styles/_mixins.scss'; -@import '../../styles/_variables.scss'; +@import '../../styles/mixins'; +@import '../../styles/variables'; .root { @include info-window; diff --git a/src/components/GoogleMapsLoaderContext/GoogleMapsLoaderContext.js b/src/components/GoogleMapsLoaderContext/GoogleMapsLoaderContext.js new file mode 100644 index 000000000..1e8d57271 --- /dev/null +++ b/src/components/GoogleMapsLoaderContext/GoogleMapsLoaderContext.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const GoogleMapsLoaderContext = createContext(); + +export default GoogleMapsLoaderContext; diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 9ebf1d49f..e59e03753 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import withStyles from 'isomorphic-style-loader/lib/withStyles'; +import withStyles from 'isomorphic-style-loader/withStyles'; import HeaderLoginContainer from '../HeaderLogin/HeaderLoginContainer'; import FlashContainer from '../Flash/FlashContainer'; import MenuContainer from '../Menu/MenuContainer'; @@ -22,33 +22,37 @@ class Header extends Component { flashes: PropTypes.array.isRequired, loggedIn: PropTypes.bool.isRequired, // eslint-disable-next-line react/no-unused-prop-types - path: PropTypes.string + path: PropTypes.string, }; static defaultProps = { - path: PropTypes.string + path: PropTypes.string, }; static getDerivedStateFromProps(nextProps, state) { if (nextProps.path !== state.prevPath) { return { menuOpen: false, - prevPath: nextProps.path + prevPath: nextProps.path, }; } return null; } - state = { - menuOpen: false, - // eslint-disable-next-line react/no-unused-state - prevPath: null - }; + constructor(props) { + super(props); + + this.state = { + menuOpen: false, + // eslint-disable-next-line react/no-unused-state + prevPath: null, + }; + } flashContainers = () => { const { flashes } = this.props; - return flashes.map(flash => ( + return flashes.map((flash) => ( )); - } + }; closeMenu = () => { this.setState({ - menuOpen: false + menuOpen: false, }); - } + }; toggleMenu = () => { - this.setState(prevState => ({ - menuOpen: !prevState.menuOpen + this.setState((prevState) => ({ + menuOpen: !prevState.menuOpen, })); - } + }; render() { const { loggedIn } = this.props; @@ -78,9 +82,7 @@ class Header extends Component {
-
- {this.flashContainers()} -
+
{this.flashContainers()}

@@ -90,18 +92,28 @@ class Header extends Component {

- {loggedIn - ? ( -
- - {menuOpen &&
- ) - : - } + {loggedIn ? ( +
+ + {menuOpen && ( +
+ ) : ( + + )}
); } diff --git a/src/components/Header/Header.scss b/src/components/Header/Header.scss index a28949942..4e1b7d272 100644 --- a/src/components/Header/Header.scss +++ b/src/components/Header/Header.scss @@ -7,8 +7,8 @@ * LICENSE.txt file in the root directory of this source tree. */ -@import '../../styles/_mixins.scss'; -@import '../../styles/_variables.scss'; +@import '../../styles/mixins'; +@import '../../styles/variables'; @keyframes animatedBackground { from { @@ -21,7 +21,7 @@ } @keyframes squashStretch { - from { + 0% { transform: scaleX(1); } @@ -29,7 +29,7 @@ transform: scaleX(1.5); } - to { + 100% { transform: scaleX(1); } } @@ -138,7 +138,7 @@ .menuBackground { @include plain-button; - background: rgba(0, 0, 0, .5); + background: rgb(0 0 0 / 50%); height: 100%; left: 0; position: fixed; diff --git a/src/components/HeaderLogin/HeaderLogin.js b/src/components/HeaderLogin/HeaderLogin.js index d8145d0df..5e509c7ba 100644 --- a/src/components/HeaderLogin/HeaderLogin.js +++ b/src/components/HeaderLogin/HeaderLogin.js @@ -9,8 +9,8 @@ import PropTypes from 'prop-types'; import React from 'react'; -import withStyles from 'isomorphic-style-loader/lib/withStyles'; -import Button from 'react-bootstrap/lib/Button'; +import withStyles from 'isomorphic-style-loader/withStyles'; +import Button from 'react-bootstrap/Button'; import s from './HeaderLogin.scss'; const HeaderLogin = ({ user }) => { @@ -18,7 +18,7 @@ const HeaderLogin = ({ user }) => { if (user.id === undefined) { content = (
-
@@ -29,7 +29,7 @@ const HeaderLogin = ({ user }) => { }; HeaderLogin.propTypes = { - user: PropTypes.object.isRequired + user: PropTypes.object.isRequired, }; export default withStyles(s)(HeaderLogin); diff --git a/src/components/HereMarker/HereMarker.js b/src/components/HereMarker/HereMarker.js index 1fa988603..cc09a9c2d 100644 --- a/src/components/HereMarker/HereMarker.js +++ b/src/components/HereMarker/HereMarker.js @@ -1,5 +1,5 @@ import React from 'react'; -import withStyles from 'isomorphic-style-loader/lib/withStyles'; +import withStyles from 'isomorphic-style-loader/withStyles'; import s from './HereMarker.scss'; const HereMarker = () =>
; diff --git a/src/components/Html.js b/src/components/Html.js index 4e29e67bc..020441c87 100644 --- a/src/components/Html.js +++ b/src/components/Html.js @@ -16,7 +16,6 @@ import config from '../config'; class Html extends Component { static propTypes = { - apikey: PropTypes.string, app: PropTypes.object, // eslint-disable-line title: PropTypes.string.isRequired, ogTitle: PropTypes.string.isRequired, @@ -31,7 +30,6 @@ class Html extends Component { }; static defaultProps = { - apikey: '', styles: [], scripts: [], root: '' @@ -39,7 +37,6 @@ class Html extends Component { render() { const { - apikey, app, title, ogTitle, @@ -55,7 +52,7 @@ class Html extends Component { {config.analytics.googleTrackingId && ( - + <>