diff --git a/src/actions/index.js b/src/actions/index.js index 1c5202d0..303559d6 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -15,7 +15,6 @@ import type { // // Action Types // -export const SET_ONLINE_STATUS = 'SET_ONLINE_STATUS'; export const REFRESH_PROJECTS_START = 'REFRESH_PROJECTS_START'; export const REFRESH_PROJECTS_ERROR = 'REFRESH_PROJECTS_ERROR'; export const REFRESH_PROJECTS_FINISH = 'REFRESH_PROJECTS_FINISH'; @@ -50,8 +49,6 @@ export const DELETE_DEPENDENCY = 'DELETE_DEPENDENCY'; export const INSTALL_DEPENDENCIES_START = 'INSTALL_DEPENDENCIES_START'; export const INSTALL_DEPENDENCIES_ERROR = 'INSTALL_DEPENDENCIES_ERROR'; export const INSTALL_DEPENDENCIES_FINISH = 'INSTALL_DEPENDENCIES_FINISH'; -export const REINSTALL_DEPENDENCIES_START = 'REINSTALL_DEPENDENCIES_START'; -export const REINSTALL_DEPENDENCIES_FINISH = 'REINSTALL_DEPENDENCIES_FINISH'; export const REINSTALL_DEPENDENCIES_ERROR = 'REINSTALL_DEPENDENCIES_ERROR'; export const UNINSTALL_DEPENDENCIES_START = 'UNINSTALL_DEPENDENCIES_START'; export const UNINSTALL_DEPENDENCIES_ERROR = 'UNINSTALL_DEPENDENCIES_ERROR'; @@ -66,9 +63,6 @@ export const IMPORT_EXISTING_PROJECT_START = 'IMPORT_EXISTING_PROJECT_START'; export const IMPORT_EXISTING_PROJECT_ERROR = 'IMPORT_EXISTING_PROJECT_ERROR'; export const IMPORT_EXISTING_PROJECT_FINISH = 'IMPORT_EXISTING_PROJECT_FINISH'; export const SHOW_DELETE_PROJECT_PROMPT = 'SHOW_DELETE_PROJECT_PROMPT'; -export const START_DELETING_PROJECT = 'START_DELETING_PROJECT'; -export const FINISH_DELETING_PROJECT = 'FINISH_DELETING_PROJECT'; -export const DELETE_PROJECT_ERROR = 'DELETE_PROJECT_ERROR'; export const SHOW_RESET_STATE_PROMPT = 'SHOW_RESET_STATE_PROMPT'; export const RESET_ALL_STATE = 'RESET_ALL_STATE'; @@ -83,9 +77,17 @@ export const SHOW_APP_SETTINGS = 'SHOW_APP_SETTINGS'; export const SAVE_APP_SETTINGS_START = 'SAVE_APP_SETTINGS_START'; export const CHANGE_DEFAULT_PROJECT_PATH = 'CHANGE_DEFAULT_PROJECT_PATH'; -// Status text for loading screen +// App status +export const START_DELETING_PROJECT = 'START_DELETING_PROJECT'; +export const FINISH_DELETING_PROJECT = 'FINISH_DELETING_PROJECT'; +export const DELETE_PROJECT_ERROR = 'DELETE_PROJECT_ERROR'; +export const REINSTALL_DEPENDENCIES_START = 'REINSTALL_DEPENDENCIES_START'; +export const REINSTALL_DEPENDENCIES_FINISH = 'REINSTALL_DEPENDENCIES_FINISH'; export const SET_STATUS_TEXT = 'SET_STATUS_TEXT'; export const RESET_STATUS_TEXT = 'RESET_STATUS_TEXT'; +export const SET_ONLINE_STATUS = 'SET_ONLINE_STATUS'; +export const SET_INFO_BAR_STRING = 'SET_INFO_BAR_STRING'; +export const SET_INCORRECT_NODE = 'SET_INCORRECT_NODE'; // // @@ -96,6 +98,16 @@ export const setOnlineStatus = (onlineStatus: boolean) => ({ onlineStatus, }); +export const setInfoBarString = (string: string | null) => ({ + type: SET_INFO_BAR_STRING, + infoBarString: string, +}); + +export const setIncorrectNode = (bool: boolean) => ({ + type: SET_INCORRECT_NODE, + incorrectNode: bool, +}); + export const addProject = ( project: ProjectInternal, projectHomePath: string, diff --git a/src/components/App/App.js b/src/components/App/App.js index 4ee82648..c3f0be2d 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -6,6 +6,7 @@ import { Scrollbars } from 'react-custom-scrollbars'; import { COLORS } from '../../constants'; import { getSelectedProjectId } from '../../reducers/projects.reducer'; +import { getInfoBarState } from '../../reducers/app-status.reducer'; import IntroScreen from '../IntroScreen'; import Sidebar from '../Sidebar'; @@ -19,6 +20,7 @@ import Initialization from '../Initialization'; import LoadingScreen from '../LoadingScreen'; import FeedbackButton from '../FeedbackButton'; import OnlineChecker from '../OnlineChecker'; +import InfoBar from '../InfoBar'; import { isMac } from '../../services/platform.service'; import type { Project } from '../../types'; @@ -26,17 +28,19 @@ import type { Project } from '../../types'; type Props = { selectedProjectId: ?Project, modalVisible: boolean, + infoBarString: ?string, }; class App extends PureComponent { render() { - const { selectedProjectId, modalVisible } = this.props; + const { selectedProjectId, modalVisible, infoBarString } = this.props; return ( {wasSuccessfullyInitialized => wasSuccessfullyInitialized && ( + {infoBarString && {infoBarString}} {isMac && } @@ -93,6 +97,7 @@ const MainContent = styled.div` const mapStateToProps = state => ({ selectedProjectId: getSelectedProjectId(state), modalVisible: !!state.modal, + infoBarString: getInfoBarState(state), }); export default connect(mapStateToProps)(App); diff --git a/src/components/AppSettingsModal/AppSettingsModal.js b/src/components/AppSettingsModal/AppSettingsModal.js index c26adf42..4551cc9b 100644 --- a/src/components/AppSettingsModal/AppSettingsModal.js +++ b/src/components/AppSettingsModal/AppSettingsModal.js @@ -123,6 +123,7 @@ export class AppSettingsModal extends PureComponent { projectType ); }} + handleIncorrectNodeVersion={() => console.log('write func')} /> diff --git a/src/components/AppSettingsModal/__snapshots__/AppSettingsModal.test.js.snap b/src/components/AppSettingsModal/__snapshots__/AppSettingsModal.test.js.snap index aeb5c768..00ad59d5 100644 --- a/src/components/AppSettingsModal/__snapshots__/AppSettingsModal.test.js.snap +++ b/src/components/AppSettingsModal/__snapshots__/AppSettingsModal.test.js.snap @@ -44,6 +44,7 @@ exports[`AppSettingsModal component should render 1`] = ` spacing={30} > diff --git a/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js b/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js index 46b86fcf..1e3b80dd 100644 --- a/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js +++ b/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js @@ -10,7 +10,10 @@ import { getDefaultProjectPath, } from '../../reducers/app-settings.reducer'; import { getById } from '../../reducers/projects.reducer'; -import { getOnlineState } from '../../reducers/app-status.reducer'; +import { + getOnlineState, + getIncorrectNodeState, +} from '../../reducers/app-status.reducer'; import { getOnboardingCompleted } from '../../reducers/onboarding-status.reducer'; import { getProjectNameSlug } from '../../services/create-project.service'; import { checkIfProjectExists } from '../../services/create-project.service'; @@ -73,7 +76,10 @@ type Props = { addProject: Dispatch, createNewProjectCancel: Dispatch, createNewProjectFinish: Dispatch, + setInfoBarString: Dispatch, + setIncorrectNode: Dispatch, isOnline: boolean, + incorrectNode: boolean, }; type State = { @@ -188,6 +194,16 @@ export class CreateNewProjectWizard extends PureComponent { } }; + handleIncorrectNodeVersion = (str: string | null) => { + const { setInfoBarString, setIncorrectNode } = this.props; + setInfoBarString(str); + if (str) { + setIncorrectNode(true); + } else { + setIncorrectNode(false); + } + }; + handleSubmit = () => { const currentStepIndex = FORM_STEPS.indexOf(this.state.currentStep); const nextStep = FORM_STEPS[currentStepIndex + 1]; @@ -254,6 +270,7 @@ export class CreateNewProjectWizard extends PureComponent { createNewProjectCancel, projectHomePath, isOnline, + incorrectNode, } = this.props; const { projectName, @@ -305,6 +322,8 @@ export class CreateNewProjectWizard extends PureComponent { hasBeenSubmitted={status !== 'filling-in-form'} isProjectNameTaken={isProjectNameTaken} isOnline={isOnline} + incorrectNode={incorrectNode} + handleIncorrectNodeVersion={this.handleIncorrectNodeVersion} /> } backface={ @@ -330,12 +349,15 @@ const mapStateToProps = state => ({ isOnboardingCompleted: getOnboardingCompleted(state), settings: getAppSettings(state), isOnline: getOnlineState(state), + incorrectNode: getIncorrectNodeState(state), }); const mapDispatchToProps = { addProject: actions.addProject, createNewProjectCancel: actions.createNewProjectCancel, createNewProjectFinish: actions.createNewProjectFinish, + setInfoBarString: actions.setInfoBarString, + setIncorrectNode: actions.setIncorrectNode, }; export default connect( diff --git a/src/components/CreateNewProjectWizard/MainPane.js b/src/components/CreateNewProjectWizard/MainPane.js index fbaf6ff3..c004576b 100644 --- a/src/components/CreateNewProjectWizard/MainPane.js +++ b/src/components/CreateNewProjectWizard/MainPane.js @@ -30,6 +30,8 @@ type Props = { focusField: (field: ?Field) => void, handleSubmit: () => Promise | void, isOnline: boolean, + incorrectNode: boolean, + handleIncorrectNodeVersion: (str: string | null) => void, }; class MainPane extends PureComponent { @@ -67,7 +69,12 @@ class MainPane extends PureComponent { } renderConditionalSteps(currentStepIndex: number) { - const { activeField, projectType, projectIcon } = this.props; + const { + activeField, + projectType, + projectIcon, + handleIncorrectNodeVersion, + } = this.props; const steps: Array = []; let lastIndex = 2; @@ -89,6 +96,7 @@ class MainPane extends PureComponent { onProjectTypeSelect={selectedProjectType => this.updateProjectType(selectedProjectType) } + handleIncorrectNodeVersion={handleIncorrectNodeVersion} /> @@ -143,6 +151,7 @@ class MainPane extends PureComponent { isProjectNameTaken, handleSubmit, isOnline, + incorrectNode, } = this.props; const { lastIndex, steps } = this.renderConditionalSteps(currentStepIndex); @@ -178,6 +187,7 @@ class MainPane extends PureComponent { isDisabled={ isProjectNameTaken || !projectName || + incorrectNode || this.isSubmitDisabled(currentStepIndex, lastIndex) } isOnline={isOnline} diff --git a/src/components/CreateNewProjectWizard/__tests__/__snapshots__/CreateNewProjectWizard.test.js.snap b/src/components/CreateNewProjectWizard/__tests__/__snapshots__/CreateNewProjectWizard.test.js.snap index b3686a50..a402115a 100644 --- a/src/components/CreateNewProjectWizard/__tests__/__snapshots__/CreateNewProjectWizard.test.js.snap +++ b/src/components/CreateNewProjectWizard/__tests__/__snapshots__/CreateNewProjectWizard.test.js.snap @@ -32,6 +32,7 @@ exports[`CreateNewProjectWizard component should render TwoPaneModal 1`] = ` activeField="projectName" currentStepIndex={0} focusField={[Function]} + handleIncorrectNodeVersion={[Function]} handleSubmit={[Function]} hasBeenSubmitted={false} isProjectNameTaken={false} diff --git a/src/components/InfoBar/InfoBar.js b/src/components/InfoBar/InfoBar.js new file mode 100644 index 00000000..01362c13 --- /dev/null +++ b/src/components/InfoBar/InfoBar.js @@ -0,0 +1,53 @@ +// @flow +import React, { PureComponent } from 'react'; +import styled from 'styled-components'; + +import { RAW_COLORS, Z_INDICES } from '../../constants'; + +type Props = { + children: string, +}; + +class InfoBar extends PureComponent { + render() { + const { children } = this.props; + return ( + + + + + +

{children}

+
+ ); + } +} + +const Container = styled.div` + width: 100vw; + display: flex; + justify-content: center; + position: fixed; + left: 0; + top: 0; + z-index: ${Z_INDICES.infoBanner}; + background-color: ${RAW_COLORS.transparentWhite[100]}; + padding: 16px; + align-content: center; + box-shadow: 0px 2px 2px 0px ${RAW_COLORS.transparentBlack[900]}; +`; + +const SVG = styled.svg` + width: 16px; + height: 16px; + fill: ${RAW_COLORS.red[500]}; + margin: 2px 8px; +`; + +export default InfoBar; diff --git a/src/components/InfoBar/index.js b/src/components/InfoBar/index.js new file mode 100644 index 00000000..9edc2732 --- /dev/null +++ b/src/components/InfoBar/index.js @@ -0,0 +1,2 @@ +// @flow +export { default } from './InfoBar'; diff --git a/src/components/OnlineChecker/OnlineChecker.js b/src/components/OnlineChecker/OnlineChecker.js index ecd8f632..246f80d7 100644 --- a/src/components/OnlineChecker/OnlineChecker.js +++ b/src/components/OnlineChecker/OnlineChecker.js @@ -5,8 +5,6 @@ */ import React from 'react'; import { connect } from 'react-redux'; -import styled from 'styled-components'; -import { RAW_COLORS, Z_INDICES } from '../../constants'; import * as actions from '../../actions'; import { getOnlineState } from '../../reducers/app-status.reducer'; @@ -16,64 +14,42 @@ import type { Dispatch } from '../../actions/types'; type Props = { isOnline: boolean, setOnlineStatus: Dispatch, + setInfoBarString: Dispatch, }; +const OFFLINE_STRING = + 'You are currently offline, some functions will not be available'; + class OnlineChecker extends React.PureComponent { componentDidMount() { window.addEventListener('online', this.check); window.addEventListener('offline', this.check); } + + handleInfoBarString = (str: string | null) => { + this.props.setInfoBarString(str); + }; + check = () => { - this.props.setOnlineStatus(navigator.onLine); + const { setOnlineStatus } = this.props; + setOnlineStatus(navigator.onLine); + if (navigator.onLine) { + this.handleInfoBarString(null); + } else { + this.handleInfoBarString(OFFLINE_STRING); + } }; + componentWillUnmount() { window.removeEventListener('online', this.check); window.removeEventListener('offline', this.check); } + render() { - const { isOnline } = this.props; - if (!isOnline) { - return ( - - - - - -

You are currently offline, some functions will not be available

-
- ); - } return null; } } -const InfoBar = styled.div` - width: 100vw; - display: flex; - justify-content: center; - position: fixed; - left: 0; - top: 0; - z-index: ${Z_INDICES.infoBanner}; - background-color: ${RAW_COLORS.transparentWhite[100]}; - padding: 16px; - align-content: center; - box-shadow: 0px 2px 2px 0px ${RAW_COLORS.transparentBlack[900]}; -`; - -const SVG = styled.svg` - width: 16px; - height: 16px; - fill: ${RAW_COLORS.red[500]}; - margin: 2px 8px; -`; - const mapStateToProps = state => ({ isOnline: getOnlineState(state), }); @@ -82,5 +58,6 @@ export default connect( mapStateToProps, { setOnlineStatus: actions.setOnlineStatus, + setInfoBarString: actions.setInfoBarString, } )(OnlineChecker); diff --git a/src/components/ProjectTypeSelection/ProjectTypeSelection.js b/src/components/ProjectTypeSelection/ProjectTypeSelection.js index 4443725e..a4b0bb75 100644 --- a/src/components/ProjectTypeSelection/ProjectTypeSelection.js +++ b/src/components/ProjectTypeSelection/ProjectTypeSelection.js @@ -10,12 +10,59 @@ import type { ProjectType } from '../../types'; type Props = { projectType: ?ProjectType, onProjectTypeSelect: (projectType: ProjectType) => void, + handleIncorrectNodeVersion: (str: string | null) => void, }; +const INCORRECT_NODE_STRING = + 'You do not have the correct version of node installed, node version required:'; + class ProjectTypeSelection extends PureComponent { + componentDidMount() { + const { projectType, handleIncorrectNodeVersion } = this.props; + if (projectType && !this.testNodeVersion(projectType)) { + const [project] = mapProjectTypeToComponent.filter( + x => x.type === projectType + ); + handleIncorrectNodeVersion(`${INCORRECT_NODE_STRING} ${project.nodeReq}`); + } + } + + testNodeVersion = (projectType: ProjectType) => { + const currentNode = window.process.env.npm_config_node_version + .split('.') + .map(Number); + const [project] = mapProjectTypeToComponent.filter( + x => x.type === projectType + ); + const requiredNode = project.nodeReq.split('.').map(Number); + + for (let i = 0; i < currentNode.length; i += 1) { + if (currentNode[i] > requiredNode[i]) { + return true; + } + if (currentNode[i] < requiredNode[i]) { + return false; + } + } + + return true; + }; + select = (ev: SyntheticEvent<*>, projectType: ProjectType) => { ev.preventDefault(); - this.props.onProjectTypeSelect(projectType); + const { onProjectTypeSelect, handleIncorrectNodeVersion } = this.props; + + const [project] = mapProjectTypeToComponent.filter( + x => x.type === projectType + ); + + if (!this.testNodeVersion(projectType)) { + handleIncorrectNodeVersion(`${INCORRECT_NODE_STRING} ${project.nodeReq}`); + } else { + handleIncorrectNodeVersion(null); + } + + onProjectTypeSelect(projectType); }; render() { @@ -51,16 +98,19 @@ const mapProjectTypeToComponent = [ type: 'create-react-app', Component: ReactIcon, caption: 'Vanilla React', + nodeReq: '8.16.0', }, { type: 'gatsby', Component: GatsbyIcon, caption: 'Gatsby', + nodeReq: '8.0.0', }, { type: 'nextjs', Component: NextjsIcon, caption: 'Next.js', + nodeReq: '10.0.0', }, ]; diff --git a/src/reducers/app-status.reducer.js b/src/reducers/app-status.reducer.js index e121fdc4..b31f5fea 100644 --- a/src/reducers/app-status.reducer.js +++ b/src/reducers/app-status.reducer.js @@ -8,6 +8,8 @@ import { SET_STATUS_TEXT, RESET_STATUS_TEXT, SET_ONLINE_STATUS, + SET_INFO_BAR_STRING, + SET_INCORRECT_NODE, } from '../actions'; import type { Action } from '../actions/types'; @@ -16,12 +18,15 @@ type State = { blockingActionActive: boolean, statusText: string, onlineStatus: boolean, + infoBarString: ?String, }; export const initialState = { blockingActionActive: false, statusText: 'Please wait...', onlineStatus: navigator.onLine, + infoBarString: null, + incorrectNode: false, }; export default (state: State = initialState, action: Action = {}) => { @@ -63,6 +68,19 @@ export default (state: State = initialState, action: Action = {}) => { ...state, onlineStatus: action.onlineStatus, }; + + case SET_INFO_BAR_STRING: + return { + ...state, + infoBarString: action.infoBarString, + }; + + case SET_INCORRECT_NODE: + return { + ...state, + incorrectNode: action.incorrectNode, + }; + default: return initialState; } @@ -81,3 +99,8 @@ export const getReinstallingActive = (state: any) => state.appStatus.reinstallingActive; export const getOnlineState = (state: any) => state.appStatus.onlineStatus; + +export const getInfoBarState = (state: any) => state.appStatus.infoBarString; + +export const getIncorrectNodeState = (state: any) => + state.appStatus.incorrectNode;