diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 69d64239d6..d9bbdd9860 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,11 +21,9 @@ YES **Release notes for users (delete if codebase-only change)** - - -**Related issues (delete if you don't know any existing issue(s) that could be related to this PR)** - +**Related issues (delete if you don't know of any)** +Closes # + + + diff --git a/README.md b/README.md index 038e1ed5e8..dfe9d00dda 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Spectrum has been under full-time development since March, 2017. See [the roadma - [Roadmap](https://github.com/withspectrum/spectrum/projects/19) - [Technical](docs/) - [Testing](docs/testing/intro.md) - - [Background Jobs](docs/backend/background-jobs.md) + - [Background Jobs](docs/workers/background-jobs.md) - [Deployment](docs/deployments.md) - [API](docs/backend/api/) - [Fragments](docs/backend/api/fragments.md) diff --git a/api/models/channel.js b/api/models/channel.js index bc244798ed..5f920d9f29 100644 --- a/api/models/channel.js +++ b/api/models/channel.js @@ -10,13 +10,13 @@ const channelsByCommunitiesQuery = (...communityIds: string[]) => db .table('channels') .getAll(...communityIds, { index: 'communityId' }) - .filter(channel => db.not(channel.hasFields('deletedAt'))); + .filter(channel => channel.hasFields('deletedAt').not()); const channelsByIdsQuery = (...channelIds: string[]) => db .table('channels') .getAll(...channelIds) - .filter(channel => db.not(channel.hasFields('deletedAt'))); + .filter(channel => channel.hasFields('deletedAt').not()); const threadsByChannelsQuery = (...channelIds: string[]) => channelsByIdsQuery(...channelIds) diff --git a/api/models/search.js b/api/models/search.js index f1af7e29ba..46d559b3b1 100644 --- a/api/models/search.js +++ b/api/models/search.js @@ -91,6 +91,9 @@ export const getUsersJoinedChannels = (userId: string): Promise> = .table('usersChannels') .getAll(userId, { index: 'userId' }) .filter({ isMember: true }) + .eqJoin('channelId', db.table('channels')) + .filter(row => row('right').hasFields('deletedAt').not()) + .zip() .map(row => row('channelId')) .run(); }; @@ -101,6 +104,9 @@ export const getUsersJoinedCommunities = (userId: string): Promise .table('usersCommunities') .getAll(userId, { index: 'userId' }) .filter({ isMember: true }) + .eqJoin('communityId', db.table('communities')) + .filter(row => row('right').hasFields('deletedAt').not()) + .zip() .map(row => row('communityId')) .run(); }; @@ -112,7 +118,7 @@ export const getUsersJoinedPrivateChannelIds = (userId: string): Promise row('right')('isPrivate').eq(true)) + .filter(row => row('right')('isPrivate').eq(true).and(row('right').hasFields('deletedAt').not())) .without({ left: ['id'] }) .zip() .map(row => row('id')) @@ -126,7 +132,7 @@ export const getUsersJoinedPrivateCommunityIds = (userId: string): Promise row('right')('isPrivate').eq(true)) + .filter(row => row('right')('isPrivate').eq(true).and(row('right').hasFields('deletedAt').not())) .without({ left: ['id'] }) .zip() .map(row => row('id')) diff --git a/api/queries/search/searchThreads.js b/api/queries/search/searchThreads.js index b61c1db4c3..07e5a03f29 100644 --- a/api/queries/search/searchThreads.js +++ b/api/queries/search/searchThreads.js @@ -109,7 +109,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { ); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -168,7 +168,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { .filter(t => t.communityId === searchFilter.communityId); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -239,7 +239,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { .filter(t => t.creatorId === searchFilter.creatorId); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -269,7 +269,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { .filter(t => availableCommunitiesForSearch.indexOf(t.communityId) >= 0); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -282,11 +282,11 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { // first, lets get the channels where the thread results were posted const channelsOfThreads = await getChannels( - searchResultThreads.map(t => t.channelId) + searchResultThreads.map(t => t && t.channelId) ); const communitiesOfThreads = await getCommunities( - searchResultThreads.map(t => t.communityId) + searchResultThreads.map(t => t && t.communityId) ); // see if any channels where thread results were found are private @@ -305,7 +305,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { (!privateCommunityIds || privateCommunityIds.length === 0) ) { return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } else { // otherwise here we know that the user found threads where some of them are @@ -332,6 +332,8 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { // for each thread in the search results, determine if it was posted in // a private channel. if yes, is the current user a member? searchResultThreads = searchResultThreads.filter(thread => { + if (!thread) return null; + if (privateChannelIds.indexOf(thread.channelId) >= 0) { return availablePrivateChannels.indexOf(thread.channelId) >= 0; } @@ -344,7 +346,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { }); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } }; diff --git a/api/queries/thread/rootThread.js b/api/queries/thread/rootThread.js index acecc66889..03e9dc63f3 100644 --- a/api/queries/thread/rootThread.js +++ b/api/queries/thread/rootThread.js @@ -21,7 +21,8 @@ export default async ( ]); // if the channel is private, don't return any thread data - if (channel.isPrivate || community.isPrivate) return null; + if (!channel || !community || channel.isPrivate || community.isPrivate) + return null; return thread; } else { // if the user is signed in, we need to check if the channel is private as well as the user's permission in that channel @@ -38,6 +39,7 @@ export default async ( ]); // if the thread is in a private channel where the user is not a member, don't return any thread data + if (!channel || !community) return null; if ( channel.isPrivate && (!channelPermissions || !channelPermissions.isMember) diff --git a/cypress/integration/thread/action_bar_spec.js b/cypress/integration/thread/action_bar_spec.js index c29d9e385e..1bb1c2deb1 100644 --- a/cypress/integration/thread/action_bar_spec.js +++ b/cypress/integration/thread/action_bar_spec.js @@ -139,7 +139,7 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-notifications-toggle"]').should('be.visible'); cy.get('[data-cy="thread-facebook-button"]').should('not.be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('not.be.visible'); - cy.get('[data-cy="thread-copy-link-button"]').should('not.be.visible'); + cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); cy .get('[data-cy="thread-actions-dropdown-trigger"]') .should('not.be.visible'); diff --git a/mobile/package.json b/mobile/package.json index 9310813152..2e7ae48dec 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -4,7 +4,7 @@ "dependencies": { "@expo/react-native-action-sheet": "^1.0.2", "apollo-cache-inmemory": "^1.1.12", - "apollo-client": "^2.2.8", + "apollo-client": "^2.3.2", "apollo-link": "^1.2.2", "apollo-link-error": "^1.0.9", "apollo-link-http": "^1.5.4", diff --git a/mobile/views/Channel/index.js b/mobile/views/Channel/index.js index bb05f46760..b8634a7280 100644 --- a/mobile/views/Channel/index.js +++ b/mobile/views/Channel/index.js @@ -57,7 +57,8 @@ class Channel extends Component { } else { title = 'Loading channel...'; } - if (navigation.state.params.title === title) return; + const oldTitle = navigation.getParam('title', null); + if (oldTitle && oldTitle === title) return; navigation.setParams({ title }); }; diff --git a/mobile/views/Community/index.js b/mobile/views/Community/index.js index 3f54db93e7..2a989e13d5 100644 --- a/mobile/views/Community/index.js +++ b/mobile/views/Community/index.js @@ -78,7 +78,8 @@ class Community extends Component { } else { title = 'Loading community...'; } - if (navigation.state.params.title === title) return; + const oldTitle = navigation.getParam('title', null); + if (oldTitle && oldTitle === title) return; navigation.setParams({ title }); }; diff --git a/mobile/views/DirectMessageThread/components/DirectMessageThread.js b/mobile/views/DirectMessageThread/components/DirectMessageThread.js index 45ead568ed..08ab8e24e7 100644 --- a/mobile/views/DirectMessageThread/components/DirectMessageThread.js +++ b/mobile/views/DirectMessageThread/components/DirectMessageThread.js @@ -52,7 +52,8 @@ class DirectMessageThread extends Component { let title = directMessageThread ? sentencify(directMessageThread.participants.map(({ name }) => name)) : 'Loading thread...'; - if (navigation.state.params.title === title) return; + const oldTitle = navigation.getParam('title', null); + if (oldTitle && oldTitle === title) return; navigation.setParams({ title }); }; diff --git a/mobile/views/DirectMessageThread/index.js b/mobile/views/DirectMessageThread/index.js index 9668259d4d..ebae979dcf 100644 --- a/mobile/views/DirectMessageThread/index.js +++ b/mobile/views/DirectMessageThread/index.js @@ -1,7 +1,6 @@ // @flow import React from 'react'; import compose from 'recompose/compose'; -import idx from 'idx'; import Text from '../../components/Text'; import { withCurrentUser } from '../../components/WithCurrentUser'; import DirectMessageThread from './components/DirectMessageThread'; @@ -17,8 +16,8 @@ type Props = { class DirectMessageThreadView extends React.Component { render() { - const id = idx(this.props, props => props.navigation.state.params.id); const { currentUser, navigation } = this.props; + const id = navigation.getParam('id', null); if (!id) return Non-existant DM thread; if (!currentUser) return null; diff --git a/mobile/views/TabBar/BaseStack.js b/mobile/views/TabBar/BaseStack.js index 09d44d63b6..371196ef14 100644 --- a/mobile/views/TabBar/BaseStack.js +++ b/mobile/views/TabBar/BaseStack.js @@ -12,26 +12,26 @@ const BaseStack = { Thread: { screen: withMappedNavigationProps(Thread), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - headerTitle: navigation.state.params.title || null, + headerTitle: navigation.getParam('title', null), tabBarVisible: false, }), }, Community: { screen: withMappedNavigationProps(Community), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - headerTitle: navigation.state.params.title || null, + headerTitle: navigation.getParam('title', null), }), }, Channel: { screen: withMappedNavigationProps(Channel), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - headerTitle: navigation.state.params.title || null, + headerTitle: navigation.getParam('title', null), }), }, User: { screen: withMappedNavigationProps(User), navigationOptions: ({ navigation }: NavigationScreenConfigProps) => ({ - headerTitle: navigation.state.params.title || null, + headerTitle: navigation.getParam('title', null), }), }, }; diff --git a/mobile/views/TabBar/DirectMessageStack.js b/mobile/views/TabBar/DirectMessageStack.js index 75278ce3b3..db52e60f7e 100644 --- a/mobile/views/TabBar/DirectMessageStack.js +++ b/mobile/views/TabBar/DirectMessageStack.js @@ -1,6 +1,5 @@ // @flow import { createStackNavigator } from 'react-navigation'; -import idx from 'idx'; import BaseStack from './BaseStack'; import DirectMessages from '../DirectMessages'; import DirectMessageThread from '../DirectMessageThread'; @@ -10,13 +9,13 @@ const DMStack = createStackNavigator( DirectMessages: { screen: DirectMessages, navigationOptions: ({ navigation }) => ({ - headerTitle: idx(navigation, _ => _.state.params.title) || 'Messages', + headerTitle: navigation.getParam('title', 'Messages'), }), }, DirectMessageThread: { screen: DirectMessageThread, navigationOptions: ({ navigation }) => ({ - headerTitle: idx(navigation, _ => _.state.params.title) || '', + headerTitle: navigation.getParam('title', null), tabBarVisible: false, }), }, diff --git a/mobile/views/Thread/index.js b/mobile/views/Thread/index.js index 69b67decc7..0f7260d2f6 100644 --- a/mobile/views/Thread/index.js +++ b/mobile/views/Thread/index.js @@ -59,7 +59,8 @@ class Thread extends Component { } else { title = 'Loading thread...'; } - if (navigation.state.params.title === title) return; + const oldTitle = navigation.getParam('title', null); + if (oldTitle && oldTitle === title) return; navigation.setParams({ title }); }; diff --git a/mobile/views/User/profile.js b/mobile/views/User/profile.js index 16cf372a5d..dc61e0a3d0 100644 --- a/mobile/views/User/profile.js +++ b/mobile/views/User/profile.js @@ -50,7 +50,9 @@ class User extends Component { } else { title = 'Loading user...'; } - if (navigation.state.params.title === title) return; + + const oldTitle = navigation.getParam('title', null); + if (oldTitle && oldTitle === title) return; navigation.setParams({ title }); }; diff --git a/mobile/yarn.lock b/mobile/yarn.lock index 0f33836d07..12e296812a 100644 --- a/mobile/yarn.lock +++ b/mobile/yarn.lock @@ -564,9 +564,9 @@ component-type "^1.2.1" join-component "^1.1.0" -"@types/async@2.0.47": - version "2.0.47" - resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.47.tgz#f49ba1dd1f189486beb6e1d070a850f6ab4bd521" +"@types/async@2.0.49": + version "2.0.49" + resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0" "@types/graphql@0.12.6": version "0.12.6" @@ -732,19 +732,25 @@ apollo-cache@^1.1.7: dependencies: apollo-utilities "^1.0.11" -apollo-client@^2.2.8: - version "2.2.8" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.2.8.tgz#b604d31ab2d2dd00db3105d8793b93ee02ce567e" +apollo-cache@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.9.tgz#90426f25c43bc66ae02808af01194d78fd15ea40" + dependencies: + apollo-utilities "^1.0.13" + +apollo-client@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.3.2.tgz#0c4c06eba0aedc63d2d988f247a9310cb2152c2e" dependencies: "@types/zen-observable" "^0.5.3" - apollo-cache "^1.1.7" + apollo-cache "^1.1.9" apollo-link "^1.0.0" apollo-link-dedup "^1.0.0" - apollo-utilities "^1.0.11" + apollo-utilities "^1.0.13" symbol-observable "^1.0.2" - zen-observable "^0.7.0" + zen-observable "^0.8.0" optionalDependencies: - "@types/async" "2.0.47" + "@types/async" "2.0.49" apollo-link-dedup@^1.0.0: version "1.0.5" @@ -790,6 +796,10 @@ apollo-utilities@^1.0.0, apollo-utilities@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.11.tgz#cd36bfa6e5c04eea2caf0c204a0f38a0ad550802" +apollo-utilities@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.13.tgz#793c858bb42243f7254d3c2961c64a7158e51022" + append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -8122,10 +8132,6 @@ zen-observable-ts@^0.8.9: dependencies: zen-observable "^0.8.0" -zen-observable@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.7.1.tgz#f84075c0ee085594d3566e1d6454207f126411b3" - zen-observable@^0.8.0: version "0.8.8" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.8.tgz#1ea93995bf098754a58215a1e0a7309e5749ec42" diff --git a/package.json b/package.json index bbf681a747..33e7f4de12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Spectrum", - "version": "2.4.8", + "version": "2.4.9", "license": "BSD-3-Clause", "devDependencies": { "babel-cli": "^6.24.1", @@ -51,7 +51,7 @@ "amplitude": "^3.5.0", "amplitude-js": "^4.2.1", "apollo-cache-inmemory": "^1.1.5", - "apollo-client": "2.x", + "apollo-client": "^2.3.2", "apollo-engine": "1.x", "apollo-link": "^1.0.7", "apollo-link-http": "^1.3.2", diff --git a/shared/clients/analytics/raven.js b/shared/clients/analytics/raven.js index 7901511cd3..ac58398113 100644 --- a/shared/clients/analytics/raven.js +++ b/shared/clients/analytics/raven.js @@ -9,6 +9,4 @@ if ( whitelistUrls: [/spectrum\.chat/, /www\.spectrum\.chat/], environment: process.env.NODE_ENV, }).install(); -} else { - console.warn('Raven not enabled locally'); } diff --git a/shared/clients/analytics/setUser.js b/shared/clients/analytics/setUser.js index a6d36395e4..6566910e09 100644 --- a/shared/clients/analytics/setUser.js +++ b/shared/clients/analytics/setUser.js @@ -3,7 +3,6 @@ import type { Amplitude } from './'; export const createSetUser = (amplitude: Amplitude) => (userId: string) => { if (!amplitude) { - console.warn('No amplitude function attached to window'); return; } @@ -13,12 +12,10 @@ export const createSetUser = (amplitude: Amplitude) => (userId: string) => { : process.env.AMPLITUDE_API_KEY_DEVELOPMENT; if (!AMPLITUDE_API_KEY) { - // console.warn(`[Amplitude Dev] Set user ${userId}`); return; } const amplitudePromise = () => { - // console.warn(`[Amplitude] Set user ${userId}`); return amplitude.getInstance().setUserId(userId); }; diff --git a/shared/clients/analytics/track.js b/shared/clients/analytics/track.js index 7b5641be2b..7194f83d3b 100644 --- a/shared/clients/analytics/track.js +++ b/shared/clients/analytics/track.js @@ -6,7 +6,6 @@ export const createTrack = (amplitude: Amplitude, client: AmplitudeClient) => ( eventProperties?: Object = {} ) => { if (!amplitude) { - console.warn('No amplitude function attached to window'); return; } @@ -16,12 +15,10 @@ export const createTrack = (amplitude: Amplitude, client: AmplitudeClient) => ( : process.env.AMPLITUDE_API_KEY_DEVELOPMENT; if (!AMPLITUDE_API_KEY) { - // console.warn(`[Amplitude Dev] Tracking ${eventType}`); return; } const amplitudePromise = () => { - // console.warn(`[Amplitude] Tracking ${eventType}`); return amplitude.getInstance().logEvent(eventType, { ...eventProperties, client, diff --git a/shared/clients/analytics/unsetUser.js b/shared/clients/analytics/unsetUser.js index b560d4d8d9..e530556da9 100644 --- a/shared/clients/analytics/unsetUser.js +++ b/shared/clients/analytics/unsetUser.js @@ -3,7 +3,6 @@ import type { Amplitude } from './'; export const createUnsetUser = (amplitude: Amplitude) => () => { if (!amplitude) { - console.warn('No amplitude function attached to window'); return; } @@ -13,12 +12,10 @@ export const createUnsetUser = (amplitude: Amplitude) => () => { : process.env.AMPLITUDE_API_KEY_DEVELOPMENT; if (!AMPLITUDE_API_KEY) { - // console.warn(`[Amplitude Dev] Unset user`); return; } const amplitudePromise = () => { - // console.warn('[Amplitude] Unset user'); return amplitude.getInstance().setUserId(null); }; diff --git a/src/components/chatInput/index.js b/src/components/chatInput/index.js index ff4f4d3694..a1791e3625 100644 --- a/src/components/chatInput/index.js +++ b/src/components/chatInput/index.js @@ -546,7 +546,9 @@ class ChatInput extends React.Component { } }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; onFocus = () => { @@ -601,7 +603,10 @@ class ChatInput extends React.Component { mediaPreview: reader.result.toString(), isSendingMediaMessage: false, }); - reader.readAsDataURL(blob); + + if (blob) { + reader.readAsDataURL(blob); + } }; render() { diff --git a/src/components/messageGroup/index.js b/src/components/messageGroup/index.js index e1ec336c5d..beb3a3e101 100644 --- a/src/components/messageGroup/index.js +++ b/src/components/messageGroup/index.js @@ -51,10 +51,14 @@ export const AuthorByline = (props: { const { user, roles } = props; return ( - - {user.name}{' '} - {user.username && `@${user.username}`} - + {user.username ? ( + + {user.name}{' '} + {user.username && `@${user.username}`} + + ) : ( + {user.name} + )} {roles && roles.map((role, index) => )} {user.isPro && } diff --git a/src/index.js b/src/index.js index 13c151d32d..40ad4a68c7 100644 --- a/src/index.js +++ b/src/index.js @@ -51,7 +51,7 @@ if (thread) { history.replace(`/thread/${thread}`); } } -if (t && (!existingUser || !existingUser.currentUser)) { +if (t) { const hash = window.location.hash.substr(1); if (hash && hash.length > 1) { history.replace(`/thread/${t}#${hash}`); diff --git a/src/store/index.js b/src/store/index.js index 5c412d1388..048a008b6d 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -17,8 +17,9 @@ export const initStore = ( initialState, { middleware = [], reducers = {} } = {} ) => { + let store; if (initialState) { - return createStore( + store = createStore( getReducers(reducers), initialState, composeEnhancers( @@ -26,7 +27,7 @@ export const initStore = ( ) ); } else { - return createStore( + store = createStore( getReducers(reducers), {}, composeEnhancers( @@ -34,4 +35,13 @@ export const initStore = ( ) ); } + + if (module.hot) { + module.hot.accept('../reducers', () => { + const nextRootReducer = require('../reducers/index'); + store.replaceReducer(nextRootReducer); + }); + } + + return store; }; diff --git a/src/views/communitySettings/components/editForm.js b/src/views/communitySettings/components/editForm.js index 877779a4a1..e49f27f4cc 100644 --- a/src/views/communitySettings/components/editForm.js +++ b/src/views/communitySettings/components/editForm.js @@ -120,6 +120,8 @@ class EditForm extends React.Component { let reader = new FileReader(); let file = e.target.files[0]; + if (!file) return; + this.setState({ isLoading: true, }); @@ -150,6 +152,8 @@ class EditForm extends React.Component { let reader = new FileReader(); let file = e.target.files[0]; + if (!file) return; + this.setState({ isLoading: true, }); @@ -171,7 +175,9 @@ class EditForm extends React.Component { }); }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; save = e => { diff --git a/src/views/dashboard/components/sidebarChannels.js b/src/views/dashboard/components/sidebarChannels.js index 1a57a80ada..62ec57e857 100644 --- a/src/views/dashboard/components/sidebarChannels.js +++ b/src/views/dashboard/components/sidebarChannels.js @@ -63,10 +63,16 @@ class SidebarChannels extends React.Component { slug, } = this.props; - const { isOwner, isModerator } = permissions; + const isOwner = permissions && permissions.isOwner; + const isModerator = permissions && permissions.isModerator; if (community) { - const { isOwner, isModerator } = community.communityPermissions; + const isOwner = + community.communityPermissions && + community.communityPermissions.isOwner; + const isModerator = + community.communityPermissions && + community.communityPermissions.isModerator; const channels = community.channelConnection.edges .map(channel => channel && channel.node) .filter(channel => { diff --git a/src/views/dashboard/components/threadSearch.js b/src/views/dashboard/components/threadSearch.js index 424569f93b..ad8bdfacbc 100644 --- a/src/views/dashboard/components/threadSearch.js +++ b/src/views/dashboard/components/threadSearch.js @@ -56,7 +56,7 @@ class ThreadSearch extends React.Component { open = () => { this.props.dispatch(openSearch()); - this.searchInput.focus(); + this.searchInput && this.searchInput.focus(); }; close = () => { @@ -64,7 +64,7 @@ class ThreadSearch extends React.Component { this.props.dispatch(closeSearch()); this.props.dispatch(setSearchStringVariable('')); } - this.searchInput.blur(); + this.searchInput && this.searchInput.blur(); }; clearClose = () => { diff --git a/src/views/newCommunity/components/createCommunityForm/index.js b/src/views/newCommunity/components/createCommunityForm/index.js index 0732e8c5bd..caa16feedd 100644 --- a/src/views/newCommunity/components/createCommunityForm/index.js +++ b/src/views/newCommunity/components/createCommunityForm/index.js @@ -301,6 +301,8 @@ class CreateCommunityForm extends React.Component { let reader = new FileReader(); let file = e.target.files[0]; + if (!file) return; + if (file.size > 3000000) { return this.setState({ photoSizeError: true, @@ -316,13 +318,17 @@ class CreateCommunityForm extends React.Component { }); }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; setCommunityCover = e => { let reader = new FileReader(); let file = e.target.files[0]; + if (!file) return; + if (file.size > 3000000) { return this.setState({ photoSizeError: true, @@ -338,7 +344,9 @@ class CreateCommunityForm extends React.Component { }); }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; create = e => { diff --git a/src/views/newCommunity/components/editCommunityForm/index.js b/src/views/newCommunity/components/editCommunityForm/index.js index 33f0d1c68f..31439302f1 100644 --- a/src/views/newCommunity/components/editCommunityForm/index.js +++ b/src/views/newCommunity/components/editCommunityForm/index.js @@ -109,6 +109,8 @@ class CommunityWithData extends React.Component { let reader = new FileReader(); let file = e.target.files[0]; + if (!file) return; + this.setState({ isLoading: true, }); @@ -130,13 +132,17 @@ class CommunityWithData extends React.Component { }); }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; setCommunityCover = e => { let reader = new FileReader(); let file = e.target.files[0]; + if (!file) return; + this.setState({ isLoading: true, }); @@ -158,7 +164,9 @@ class CommunityWithData extends React.Component { }); }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; save = e => { diff --git a/src/views/pages/components/nav.js b/src/views/pages/components/nav.js index 8ab195d636..edaa73ae92 100644 --- a/src/views/pages/components/nav.js +++ b/src/views/pages/components/nav.js @@ -18,10 +18,7 @@ import { AuthTab, LogoLink, AuthLink, - PricingLink, - SupportLink, - FeaturesLink, - ExploreLink, + DropdownLink, MenuContainer, MenuOverlay, } from '../style'; @@ -129,50 +126,43 @@ class Nav extends React.Component { - - Features - - Features + + - Pricing - - Pricing + + - Support - - Support + + - Explore - + Explore + {this.props.currentUser ? ( - - {this.props.currentUser.name} - + Return home ) : ( track(events.HOME_PAGE_SIGN_IN_CLICKED)} > - - Sign in - + Log in or sign up )} diff --git a/src/views/pages/style.js b/src/views/pages/style.js index 06a01d2b14..c6f91e4177 100644 --- a/src/views/pages/style.js +++ b/src/views/pages/style.js @@ -552,128 +552,62 @@ export const LogoTab = styled(Tab)` } `; -const DropdownLink = styled(Link)` +export const DropdownLink = styled(Link)` padding: 16px 0; - margin: 0 16px; font-weight: 500; display: flex; + width: 100%; align-items: center; - display: grid; - grid-template-columns: auto auto 1fr auto; - grid-template-rows: 1fr; - grid-template-areas: 'icon label . arrow'; transition: ${Transition.hover.off}; color: ${props => props.selected ? props.theme.text.placeholder : props.theme.brand.alt}; - - > div:last-of-type { - grid-area: arrow; - opacity: 0; - display: ${props => (props.selected ? 'none' : 'inline-block')}; - transition: ${Transition.hover.off}; - } - - > div:first-of-type { - grid-area: icon; - margin-right: 16px; - } - - > span { - grid-area: label; - } + border-radius: 8px; &:hover { transition: ${Transition.hover.on}; color: ${props => props.selected ? props.theme.text.alt : props.theme.brand.default}; - - > div:last-of-type { - opacity: 1; - transition: ${Transition.hover.on}; - } } `; export const LogoLink = styled(DropdownLink)` - grid-area: logo; - padding: 0; color: ${props => props.theme.text.placeholder}; - - > div:last-of-type { - opacity: 1; - display: inline-block; - } + margin-bottom: 16px; &:hover { color: ${props => props.theme.brand.alt}; } `; -export const FeaturesLink = styled(DropdownLink)` - grid-area: features; - border: none; -`; - -export const PricingLink = styled(DropdownLink)` - grid-area: pricing; - border-top: 2px solid ${props => props.theme.bg.border}; -`; - -export const SupportLink = styled(DropdownLink)` - grid-area: support; - border-top: 2px solid ${props => props.theme.bg.border}; -`; - -export const ExploreLink = styled(DropdownLink)` - grid-area: explore; - border-top: 2px solid ${props => props.theme.bg.border}; -`; - export const AuthLink = styled(DropdownLink)` - grid-area: auth; margin: 0; - padding: 16px; + margin-top: 24px; + padding: 16px 0; font-weight: 700; border-top: none; color: ${props => props.theme.text.reverse}; - background-color: ${props => props.theme.brand.alt}; background-image: ${props => Gradient(props.theme.brand.alt, props.theme.brand.default)}; - - > div > div { - box-shadow: 0 0 0 2px ${props => props.theme.bg.default}; - } + justify-content: center; &:hover { color: ${props => props.theme.text.reverse}; text-shadow: 0 0 32px ${props => hexa(props.theme.text.reverse, 0.5)}; } - - > div:first-of-type { - grid-area: icon; - } - - > span { - grid-area: label; - } - - > div:last-of-type { - grid-area: arrow; - } `; export const MenuContainer = styled.div` position: fixed; - display: grid; - grid-template-columns: auto; - grid-template-rows: auto 16px repeat(4, auto) 1fr auto; - grid-template-areas: 'logo' '.' 'features' 'pricing' 'support' 'explore' '.' 'auth'; - align-content: start; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; left: 0; top: 0; bottom: 0; height: 100vh; width: 300px; + padding: 16px; color: ${props => props.theme.brand.alt}; background-color: ${props => props.theme.bg.default}; background-image: ${props => @@ -714,7 +648,7 @@ export const MenuTab = styled.div` } ${MenuContainer} { - display: ${props => (props.open ? 'grid' : 'none')}; + display: ${props => (props.open ? 'flex' : 'none')}; } @media (min-width: 768px) { diff --git a/src/views/userSettings/components/editForm.js b/src/views/userSettings/components/editForm.js index e0b971697e..f4cbc1d566 100644 --- a/src/views/userSettings/components/editForm.js +++ b/src/views/userSettings/components/editForm.js @@ -131,6 +131,8 @@ class UserWithData extends React.Component { let reader = new FileReader(); let file = e.target.files[0]; + if (!file) return; + this.setState({ isLoading: true, }); @@ -177,7 +179,9 @@ class UserWithData extends React.Component { }); }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; setCoverPhoto = e => { @@ -230,7 +234,9 @@ class UserWithData extends React.Component { }); }; - reader.readAsDataURL(file); + if (file) { + reader.readAsDataURL(file); + } }; save = e => { diff --git a/yarn.lock b/yarn.lock index 00f672008a..9a90af7bce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -223,9 +223,9 @@ dependencies: "@types/node" "*" -"@types/async@2.0.47": - version "2.0.47" - resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.47.tgz#f49ba1dd1f189486beb6e1d070a850f6ab4bd521" +"@types/async@2.0.49": + version "2.0.49" + resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0" "@types/blob-util@1.3.3": version "1.3.3" @@ -619,19 +619,25 @@ apollo-cache@^1.1.7: dependencies: apollo-utilities "^1.0.11" -apollo-client@2.x: - version "2.2.8" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.2.8.tgz#b604d31ab2d2dd00db3105d8793b93ee02ce567e" +apollo-cache@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.9.tgz#90426f25c43bc66ae02808af01194d78fd15ea40" + dependencies: + apollo-utilities "^1.0.13" + +apollo-client@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.3.2.tgz#0c4c06eba0aedc63d2d988f247a9310cb2152c2e" dependencies: "@types/zen-observable" "^0.5.3" - apollo-cache "^1.1.7" + apollo-cache "^1.1.9" apollo-link "^1.0.0" apollo-link-dedup "^1.0.0" - apollo-utilities "^1.0.11" + apollo-utilities "^1.0.13" symbol-observable "^1.0.2" - zen-observable "^0.7.0" + zen-observable "^0.8.0" optionalDependencies: - "@types/async" "2.0.47" + "@types/async" "2.0.49" apollo-engine-binary-darwin@0.2018.4-9-gf79380d21: version "0.2018.4-9-gf79380d21" @@ -745,6 +751,10 @@ apollo-utilities@^1.0.0, apollo-utilities@^1.0.1, apollo-utilities@^1.0.11, apol version "1.0.11" resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.11.tgz#cd36bfa6e5c04eea2caf0c204a0f38a0ad550802" +apollo-utilities@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.13.tgz#793c858bb42243f7254d3c2961c64a7158e51022" + app-root-path@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" @@ -11809,3 +11819,7 @@ zen-observable-ts@^0.8.6: zen-observable@^0.7.0: version "0.7.1" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.7.1.tgz#f84075c0ee085594d3566e1d6454207f126411b3" + +zen-observable@^0.8.0: + version "0.8.8" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.8.tgz#1ea93995bf098754a58215a1e0a7309e5749ec42"