diff --git a/src/api/communicators.json b/src/api/communicators.json index d092ea107..c76acc5ed 100644 --- a/src/api/communicators.json +++ b/src/api/communicators.json @@ -6,6 +6,7 @@ "author": "Cboard Team", "email": "support@cboard.io", "rootBoard": "root", - "boards": ["root"] + "boards": ["root"], + "defaultBoardBlackList": [] } ] diff --git a/src/components/Account/Login/Login.actions.js b/src/components/Account/Login/Login.actions.js index cf96125bf..be0290a34 100644 --- a/src/components/Account/Login/Login.actions.js +++ b/src/components/Account/Login/Login.actions.js @@ -15,6 +15,7 @@ import { } from '../../App/App.actions'; import { getVoiceURI } from '../../../i18n'; import { isCordova, isElectron } from '../../../cordova-util'; +import { isRemoteIdChecker } from '../../../helpers'; export function loginSuccess(payload) { return dispatch => { @@ -147,7 +148,10 @@ export function login({ email, password, activatedData }, type = 'local') { ); if (loginData.communicators && loginData.communicators.length) { - currentCommunicator = loginData.communicators[0]; + const lastRemoteSavedCommunicatorIndex = + loginData.communicators.length - 1; + currentCommunicator = + loginData.communicators[lastRemoteSavedCommunicatorIndex]; //use the latest communicator } const localBoardsIds = []; @@ -166,13 +170,12 @@ export function login({ email, password, activatedData }, type = 'local') { .map(async id => { let board = null; try { - board = await API.getBoard(id); + if (isRemoteIdChecker(id)) board = await API.getBoard(id); } catch (e) {} return board; }) .filter(b => b !== null) ); - dispatch(addBoards(apiBoards)); if (type === 'local') { dispatch( diff --git a/src/components/Board/Board.actions.js b/src/components/Board/Board.actions.js index f06d8db12..9c11e2578 100644 --- a/src/components/Board/Board.actions.js +++ b/src/components/Board/Board.actions.js @@ -38,14 +38,15 @@ import { DOWNLOAD_IMAGES_FAILURE, DOWNLOAD_IMAGES_STARTED, DOWNLOAD_IMAGE_SUCCESS, - DOWNLOAD_IMAGE_FAILURE + DOWNLOAD_IMAGE_FAILURE, + REMOVE_BOARDS_FROM_LIST, + UNMARK_SHOULD_CREATE_API_BOARD, + SHORT_ID_MAX_LENGTH } from './Board.constants'; import API from '../../api'; import { - updateApiCommunicator, - createApiCommunicator, replaceBoardCommunicator, upsertCommunicator, getApiMyCommunicators, @@ -53,16 +54,15 @@ import { upsertApiCommunicator, updateDefaultBoardsIncluded, addDefaultBoardIncluded, - createCommunicator, - changeCommunicator + verifyAndUpsertCommunicator, + concatDefaultBoardIdToBlacklist } from '../Communicator/Communicator.actions'; import { isAndroid, writeCvaFile } from '../../cordova-util'; import { DEFAULT_BOARDS } from '../../helpers'; import history from './../../history'; import { improvePhraseAbortController } from '../../api/api'; -import shortid from 'shortid'; -const BOARDS_PAGE_LIMIT = 100; +const BOARDS_PAGE_LIMIT = 1000; export function importBoards(boards) { return { @@ -102,18 +102,9 @@ export function changeDefaultBoard(selectedBoardNameOnJson) { if (userData.email && activeCommunicator.email !== userData?.email) { // Create a new communicator for the user if it doesn't exist - const newCommunicator = { - ...activeCommunicator, - boards: [...activeCommunicator.boards], - author: userData.name, - email: userData.email, - id: shortid.generate() - }; - dispatch(createCommunicator(newCommunicator)); - dispatch(changeCommunicator(newCommunicator.id)); - return newCommunicator; + dispatch(verifyAndUpsertCommunicator(activeCommunicator)); } - return activeCommunicator; + return getActiveCommunicator(getState); }; const activeCommunicator = checkUserCommunicator(); @@ -164,10 +155,14 @@ export function changeDefaultBoard(selectedBoardNameOnJson) { const switchActiveBoard = homeBoardId => { if (homeBoardId) { + const storeBoards = getState().board.boards; + const board = storeBoards.find(board => board.id === homeBoardId); + if (!board) return null; const goTo = `/board/${homeBoardId}`; dispatch(switchBoard(homeBoardId)); history.replace(goTo); + return true; } }; @@ -196,7 +191,9 @@ export function changeDefaultBoard(selectedBoardNameOnJson) { dispatch(editCommunicator(communicatorWithRootBoardReplaced)); if (userData?.role) { - dispatch(upsertApiCommunicator(communicatorWithRootBoardReplaced)); + dispatch( + upsertApiCommunicator(communicatorWithRootBoardReplaced) + ).catch(console.error); } }; @@ -209,9 +206,7 @@ export function changeDefaultBoard(selectedBoardNameOnJson) { homeBoardId }); - switchActiveBoard(homeBoardId); - - replaceHomeBoard(homeBoardId); + if (switchActiveBoard(homeBoardId)) replaceHomeBoard(homeBoardId); }; } @@ -278,8 +273,19 @@ export function previousBoard() { } export function toRootBoard() { - return { - type: TO_ROOT_BOARD + return (dispatch, getState) => { + const navHistory = getState().board.navHistory; + const firstBoardOnHistory = navHistory[0]; + const allBoardsIds = getState().board.boards.map(board => board.id); + + if (!firstBoardOnHistory || !allBoardsIds.includes(firstBoardOnHistory)) { + return null; + } + history.replace(firstBoardOnHistory); + dispatch({ + type: TO_ROOT_BOARD + }); + return firstBoardOnHistory; }; } @@ -519,14 +525,15 @@ export function getApiMyBoards() { } export function createApiBoard(boardData, boardId) { - return dispatch => { + return async dispatch => { dispatch(createApiBoardStarted()); boardData = { ...boardData, isPublic: false }; return API.createBoard(boardData) - .then(res => { + .then(async res => { + await dispatch(concatDefaultBoardIdToBlacklist(boardId)); dispatch(createApiBoardSuccess(res, boardId)); return res; }) @@ -538,7 +545,7 @@ export function createApiBoard(boardData, boardId) { } export function updateApiBoard(boardData) { - return dispatch => { + return async dispatch => { dispatch(updateApiBoardStarted()); return API.updateBoard(boardData) .then(res => { @@ -689,13 +696,12 @@ function getFileNameFromUrl(url) { export function updateApiObjectsNoChild( parentBoard, - createCommunicator = false, createParentBoard = false ) { - return (dispatch, getState) => { + return async (dispatch, getState) => { //create - update parent board const action = createParentBoard ? createApiBoard : updateApiBoard; - return dispatch(action(parentBoard, parentBoard.id)) + return await dispatch(action(parentBoard, parentBoard.id)) .then(res => { const updatedParentBoardId = res.id; //add new boards to the active communicator @@ -722,12 +728,9 @@ export function updateApiObjectsNoChild( comm.activeBoardId = updatedParentBoardId; dispatch(upsertCommunicator(comm)); } - const caction = createCommunicator - ? createApiCommunicator - : updateApiCommunicator; - return dispatch(caction(comm, comm.id)) - .then(() => { - dispatch(updateApiMarkedBoards()); + return dispatch(upsertApiCommunicator(comm)) + .then(async () => { + await dispatch(updateApiMarkedBoards()); return updatedParentBoardId; }) .catch(e => { @@ -740,34 +743,74 @@ export function updateApiObjectsNoChild( }; } export function updateApiMarkedBoards() { - return (dispatch, getState) => { + return async (dispatch, getState) => { const allBoards = [...getState().board.boards]; - for (let i = 0; i < allBoards.length; i++) { + for await (const board of allBoards) { + const boardsIds = getState().board.boards?.map(board => board.id); + if (!boardsIds.includes(board.id)) return; + if ( - allBoards[i].id.length > 14 && - allBoards[i].hasOwnProperty('email') && - allBoards[i].email === getState().app.userData.email && - allBoards[i].hasOwnProperty('markToUpdate') && - allBoards[i].markToUpdate + board.id.length > 14 && + board.hasOwnProperty('email') && + board.email === getState().app.userData.email && + board.hasOwnProperty('markToUpdate') && + board.markToUpdate ) { - dispatch(updateApiBoard(allBoards[i])) - .then(() => { - dispatch(unmarkBoard(allBoards[i].id)); - return; - }) - .catch(e => { - throw new Error(e.message); - }); + try { + await dispatch(updateApiBoard(board)); + dispatch(unmarkBoard(board.id)); + } catch (e) { + throw new Error(e.message); + } + } + if (board.id.length < SHORT_ID_MAX_LENGTH && board.shouldCreateBoard) { + const state = getState(); + + // TODO - translate name using intl in a redux action + //name: intl.formatMessage({ id: allBoards[i].nameKey }) + const extractName = () => { + const splitedNameKey = board.nameKey.split('.'); + const NAMEKEY_LAST_INDEX = splitedNameKey.length - 1; + return splitedNameKey[NAMEKEY_LAST_INDEX]; + }; + const name = board.name ?? extractName(); + let boardData = { + ...board, + author: state.app.userData.name, + email: state.app.userData.email, + hidden: false, + locale: state.lang, + name + }; + delete boardData.shouldCreateBoard; + dispatch(unmarkShouldCreateBoard(boardData.id)); + + dispatch(updateBoard(boardData)); + try { + const boardId = await dispatch( + updateApiObjectsNoChild(boardData, true) + ); + dispatch( + replaceBoard({ ...boardData }, { ...boardData, id: boardId }) + ); + } catch (err) { + console.log(err.message); + } } } - return; + }; +} + +function unmarkShouldCreateBoard(boardId) { + return { + type: UNMARK_SHOULD_CREATE_API_BOARD, + boardId }; } export function updateApiObjects( childBoard, parentBoard, - createCommunicator = false, createParentBoard = false ) { return (dispatch, getState) => { @@ -819,10 +862,7 @@ export function updateApiObjects( comm.activeBoardId = updatedParentBoardId; dispatch(upsertCommunicator(comm)); } - const caction = createCommunicator - ? createApiCommunicator - : updateApiCommunicator; - return dispatch(caction(comm, comm.id)) + return dispatch(upsertApiCommunicator(comm)) .then(() => { dispatch(updateApiMarkedBoards()); return updatedParentBoardId; @@ -840,3 +880,19 @@ export function updateApiObjects( }); }; } + +export function removeBoardsFromList(blacklist = [], rootBoard) { + return (dispatch, getState) => { + const actualBoardId = getState().board.activeBoardId; + if (blacklist.includes(actualBoardId)) { + history.replace(rootBoard); + dispatch(switchBoard(rootBoard)); + const rootBoardFinded = dispatch(toRootBoard()); + if (!rootBoardFinded || blacklist.includes(rootBoard)) return; + } + dispatch({ + type: REMOVE_BOARDS_FROM_LIST, + blacklist + }); + }; +} diff --git a/src/components/Board/Board.constants.js b/src/components/Board/Board.constants.js index 7d820d648..9b3937e79 100644 --- a/src/components/Board/Board.constants.js +++ b/src/components/Board/Board.constants.js @@ -3,6 +3,7 @@ export const ADD_BOARDS = 'cboard/Board/ADD_BOARDS'; export const CREATE_BOARD = 'cboard/Board/CREATE_BOARD'; export const UPDATE_BOARD = 'cboard/Board/UPDATE_BOARD'; export const DELETE_BOARD = 'cboard/Board/DELETE_BOARD'; +export const REMOVE_BOARDS_FROM_LIST = 'cboard/Board/REMOVE_BOARDS_FROM_LIST'; export const CHANGE_BOARD = 'cboard/Board/CHANGE_BOARD'; export const REPLACE_BOARD = 'cboard/Board/REPLACE_BOARD'; export const SWITCH_BOARD = 'cboard/Board/SWITCH_BOARD'; @@ -19,6 +20,8 @@ export const CHANGE_IMPROVED_PHRASE = 'cboard/Board/CHANGE_IMPROVED_PHRASE'; export const CHANGE_LIVE_MODE = 'cboard/Board/CHANGE_LIVE_MODE'; export const HISTORY_REMOVE_BOARD = 'cboard/Board/HISTORY_REMOVE_BOARD'; export const UNMARK_BOARD = 'cboard/Board/UNMARK_BOARD'; +export const UNMARK_SHOULD_CREATE_API_BOARD = + 'cboard/Board/UNMARK_SHOULD_CREATE_API_BOARD'; export const CREATE_API_BOARD_SUCCESS = 'cboard/Board/CREATE_API_BOARD_SUCCESS'; export const CREATE_API_BOARD_FAILURE = 'cboard/Board/CREATE_API_BOARD_FAILURE'; export const CREATE_API_BOARD_STARTED = 'cboard/Board/CREATE_API_BOARD_STARTED'; @@ -41,3 +44,4 @@ export const DOWNLOAD_IMAGE_SUCCESS = 'cboard/Board/DOWNLOAD_IMAGE_SUCCESS'; export const DOWNLOAD_IMAGE_FAILURE = 'cboard/Board/DOWNLOAD_IMAGE_FAILURE'; export const DEFAULT_ROWS_NUMBER = 5; export const DEFAULT_COLUMNS_NUMBER = 5; +export const SHORT_ID_MAX_LENGTH = 14; diff --git a/src/components/Board/Board.container.js b/src/components/Board/Board.container.js index 615d820db..21f17788b 100644 --- a/src/components/Board/Board.container.js +++ b/src/components/Board/Board.container.js @@ -50,9 +50,8 @@ import { changeDefaultBoard } from './Board.actions'; import { - upsertCommunicator, - changeCommunicator, - addBoardCommunicator + addBoardCommunicator, + verifyAndUpsertCommunicator } from '../Communicator/Communicator.actions'; import { disableTour } from '../App/App.actions'; import TileEditor from './TileEditor'; @@ -71,6 +70,7 @@ import { IS_BROWSING_FROM_APPLE_TOUCH, IS_BROWSING_FROM_SAFARI } from '../../constants'; +import { ALL_DEFAULT_BOARDS, isRemoteIdChecker } from '../../helpers'; //import { isAndroid } from '../../cordova-util'; const ogv = require('ogv'); @@ -274,9 +274,14 @@ export class BoardContainer extends Component { if (!boardExists) { // try the root board - boardExists = boards.find(b => b.id === communicator.rootBoard); + const homeBoard = communicator.rootBoard; + boardExists = boards.find(b => b.id === homeBoard); if (!boardExists) { - boardExists = boards.find(b => b.id !== ''); + if (isRemoteIdChecker(homeBoard)) + boardExists = this.tryRemoteBoard(homeBoard); + if (!boardExists) + boardExists = this.addDefaultBoardIfnecessary(homeBoard); + if (!boardExists) boardExists = boards.find(b => b.id !== ''); } } const boardId = boardExists.id; @@ -486,14 +491,12 @@ export class BoardContainer extends Component { const { userData, communicator, - upsertCommunicator, - changeCommunicator, replaceBoard, updateApiObjectsNoChild, - lang + lang, + verifyAndUpsertCommunicator } = this.props; - var createCommunicator = false; var createBoard = false; // Loggedin user? if ('name' in userData && 'email' in userData) { @@ -508,20 +511,13 @@ export class BoardContainer extends Component { locale: lang }; //check if user has an own communicator - let communicatorData = { ...communicator }; if (communicator.email !== userData.email) { - //need to create a new communicator - communicatorData = { + const communicatorData = { ...communicator, - author: userData.name, - email: userData.email, boards: boardData.id === 'root' ? ['root'] : ['root', boardData.id], - rootBoard: 'root', - id: shortid.generate() + rootBoard: 'root' }; - upsertCommunicator(communicatorData); - changeCommunicator(communicatorData.id); - createCommunicator = true; + verifyAndUpsertCommunicator(communicatorData); } //check if we have to create a copy of the board if (boardData.id.length < 14) { @@ -535,16 +531,15 @@ export class BoardContainer extends Component { updateBoard(boardData); } //api updates - updateApiObjectsNoChild(boardData, createCommunicator, createBoard) - .then(boardId => { - if (createBoard) { - replaceBoard({ ...boardData }, { ...boardData, id: boardId }); - } - this.props.history.replace(`/board/${boardId}`); - }) - .catch(err => { - console.log(err.message); - }); + const boardId = await updateApiObjectsNoChild( + boardData, + + createBoard + ); + if (createBoard) { + replaceBoard({ ...boardData }, { ...boardData, id: boardId }); + } + this.props.history.replace(`/board/${boardId}`); } catch (err) { console.log(err.message); } finally { @@ -633,6 +628,7 @@ export class BoardContainer extends Component { }; if (tile.loadBoard && !tile.linkedBoard) { createBoard(boardData); + //TODO use verifyAndUpsertCommunicator before addBoardCommunicator addBoardCommunicator(boardData.id); } @@ -877,8 +873,21 @@ export class BoardContainer extends Component { }; if (tile.loadBoard) { + const loadBoardFinder = loadBoardSearched => { + const findBoardOnStore = boardId => + this.props.boards.find(b => b.id === boardId); + + const nextBoard = findBoardOnStore(loadBoardSearched); + if (nextBoard) return nextBoard; + if ( + ALL_DEFAULT_BOARDS.map(({ id }) => id).includes(loadBoardSearched) + ) { + const nextBoard = this.addDefaultBoardIfnecessary(loadBoardSearched); + if (nextBoard) return nextBoard; + } + }; const nextBoard = - boards.find(b => b.id === tile.loadBoard) || + loadBoardFinder(tile.loadBoard) || // If the board id is invalid, try falling back to a board // with the right name. boards.find(b => b.name === tile.label); @@ -1018,8 +1027,7 @@ export class BoardContainer extends Component { communicator, board, intl, - upsertCommunicator, - changeCommunicator, + verifyAndUpsertCommunicator, updateApiObjectsNoChild, updateApiObjects, replaceBoard, @@ -1044,7 +1052,6 @@ export class BoardContainer extends Component { editedTiles = _editedTiles; } - var createCommunicator = false; var createParentBoard = false; var createChildBoard = false; var childBoardData = null; @@ -1084,18 +1091,8 @@ export class BoardContainer extends Component { locale: lang }; //check if user has an own communicator - let communicatorData = { ...communicator }; if (communicator.email !== userData.email) { - //need to create a new communicator - communicatorData = { - ...communicator, - author: userData.name, - email: userData.email, - id: shortid.generate() - }; - upsertCommunicator(communicatorData); - changeCommunicator(communicatorData.id); - createCommunicator = true; + verifyAndUpsertCommunicator(communicator); } //check for a new own board if (tile && tile.loadBoard && !tile.linkedBoard) { @@ -1130,7 +1127,7 @@ export class BoardContainer extends Component { //api updates if (tile && tile.type === 'board') { //child becomes parent - updateApiObjectsNoChild(childBoardData, createCommunicator, true) + updateApiObjectsNoChild(childBoardData, true) .then(parentBoardId => { switchBoard(parentBoardId); this.props.history.replace(`/board/${parentBoardId}`, []); @@ -1141,11 +1138,7 @@ export class BoardContainer extends Component { }); } else { if (!createChildBoard) { - updateApiObjectsNoChild( - parentBoardData, - createCommunicator, - createParentBoard - ) + updateApiObjectsNoChild(parentBoardData, createParentBoard) .then(parentBoardId => { if (createParentBoard) { replaceBoard( @@ -1160,12 +1153,7 @@ export class BoardContainer extends Component { this.setState({ isSaving: false }); }); } else { - updateApiObjects( - childBoardData, - parentBoardData, - createCommunicator, - createParentBoard - ) + updateApiObjects(childBoardData, parentBoardData, createParentBoard) .then(parentBoardId => { if (createParentBoard) { /* Here the parentBoardData is not updated with the values @@ -1212,9 +1200,23 @@ export class BoardContainer extends Component { } handleCopyRemoteBoard = async () => { - const { intl, showNotification } = this.props; + const { intl, showNotification, history, switchBoard } = this.props; try { - await this.createBoardsRecursively(this.state.copyPublicBoard); + const copiedBoard = await this.createBoardsRecursively( + this.state.copyPublicBoard + ); + if (!copiedBoard?.id) { + throw new Error('Board not copied correctly'); + } + switchBoard(copiedBoard.id); + history.replace(`/board/${copiedBoard.id}`, []); + const translatedBoard = this.translateBoard(copiedBoard); + this.setState({ + translatedBoard, + isSaving: false, + copyPublicBoard: false, + blockedPrivateBoard: false + }); showNotification(intl.formatMessage(messages.boardCopiedSuccessfully)); } catch (err) { console.log(err.message); @@ -1225,27 +1227,24 @@ export class BoardContainer extends Component { async createBoardsRecursively(board, records) { const { createBoard, - switchBoard, addBoardCommunicator, - upsertCommunicator, - changeCommunicator, - history, communicator, userData, updateApiObjectsNoChild, boards, - intl + intl, + verifyAndUpsertCommunicator } = this.props; //prevent shit if (!board) { - return; + return null; } if (records) { //get the list of next boards in records let nextBoardsRecords = records.map(entry => entry.next); if (nextBoardsRecords.includes(board.id)) { - return; + return null; } } @@ -1271,33 +1270,25 @@ export class BoardContainer extends Component { } createBoard(newBoard); if (!records) { + verifyAndUpsertCommunicator(communicator); addBoardCommunicator(newBoard.id); } + if (!records) { + records = [{ prev: board.id, next: newBoard.id }]; + } else { + records.push({ prev: board.id, next: newBoard.id }); + } + this.updateBoardReferences(board, newBoard, records); + // Loggedin user? if ('name' in userData && 'email' in userData) { this.setState({ isSaving: true }); - let createCommunicator = false; - if (communicator.email !== userData.email) { - //need to create a new communicator - const communicatorData = { - ...communicator, - author: userData.name, - email: userData.email, - id: shortid.generate() - }; - upsertCommunicator(communicatorData); - changeCommunicator(communicatorData.id); - createCommunicator = true; - } + try { - const boardId = await updateApiObjectsNoChild( - newBoard, - createCommunicator, - true - ); + const boardId = await updateApiObjectsNoChild(newBoard, true); newBoard = { ...newBoard, id: boardId @@ -1306,43 +1297,29 @@ export class BoardContainer extends Component { console.log(err.message); } } - if (!records) { - records = [{ prev: board.id, next: newBoard.id }]; - switchBoard(newBoard.id); - history.replace(`/board/${newBoard.id}`, []); - const translatedBoard = this.translateBoard(newBoard); - this.setState({ - translatedBoard, - isSaving: false, - copyPublicBoard: false, - blockedPrivateBoard: false - }); - } else { - records.push({ prev: board.id, next: newBoard.id }); - } - this.updateBoardReferences(board, newBoard, records); if (board.tiles.length < 1) { - return; + return newBoard; } //return condition - board.tiles.forEach(async tile => { + for (const tile of board.tiles) { if (tile.loadBoard && !tile.linkedBoard) { try { const nextBoard = await API.getBoard(tile.loadBoard); - this.createBoardsRecursively(nextBoard, records); + await this.createBoardsRecursively(nextBoard, records); } catch (err) { if (err.response.status === 404) { //look for this board in available boards const localBoard = boards.find(b => b.id === tile.loadBoard); if (localBoard) { - this.createBoardsRecursively(localBoard, records); + await this.createBoardsRecursively(localBoard, records); } } } } - }); + } + return newBoard; } updateBoardReferences(board, newBoard, records) { @@ -1552,6 +1529,18 @@ export class BoardContainer extends Component { : []; }; + addDefaultBoardIfnecessary = boardId => { + const { boards, addBoards } = this.props; + if (!boards.find(b => b.id === boardId)) { + const board = ALL_DEFAULT_BOARDS.find(({ id }) => id === boardId); + if (board) { + addBoards([board]); + return board; + } + return; + } + }; + render() { const { navHistory, @@ -1774,8 +1763,6 @@ const mapDispatchToProps = { showNotification, hideNotification, deactivateScanner, - upsertCommunicator, - changeCommunicator, addBoardCommunicator, updateApiObjects, updateApiObjectsNoChild, @@ -1784,7 +1771,8 @@ const mapDispatchToProps = { disableTour, createApiBoard, upsertApiBoard, - changeDefaultBoard + changeDefaultBoard, + verifyAndUpsertCommunicator }; export default connect( diff --git a/src/components/Board/Board.reducer.js b/src/components/Board/Board.reducer.js index b7db1bd49..713f39ce4 100644 --- a/src/components/Board/Board.reducer.js +++ b/src/components/Board/Board.reducer.js @@ -36,7 +36,10 @@ import { GET_API_MY_BOARDS_STARTED, DOWNLOAD_IMAGES_STARTED, DOWNLOAD_IMAGE_SUCCESS, - DOWNLOAD_IMAGE_FAILURE + DOWNLOAD_IMAGE_FAILURE, + REMOVE_BOARDS_FROM_LIST, + UNMARK_SHOULD_CREATE_API_BOARD, + SHORT_ID_MAX_LENGTH } from './Board.constants'; import { LOGOUT, LOGIN_SUCCESS } from '../Account/Login/Login.constants'; @@ -249,7 +252,13 @@ function boardReducer(state = initialState, action) { board => action.boardId.indexOf(board.id) === -1 ) }; - + case REMOVE_BOARDS_FROM_LIST: + return { + ...state, + boards: state.boards.filter( + board => !action.blacklist?.includes(board.id) + ) + }; case CREATE_TILE: return { ...state, @@ -289,6 +298,15 @@ function boardReducer(state = initialState, action) { : { ...board, markToUpdate: false } ) }; + case UNMARK_SHOULD_CREATE_API_BOARD: + return { + ...state, + boards: state.boards.map(board => + board.id !== action.boardId + ? board + : { ...board, shouldCreateBoard: false } + ) + }; case CHANGE_OUTPUT: return { ...state, @@ -313,6 +331,12 @@ function boardReducer(state = initialState, action) { ) { creadBoards[i].markToUpdate = true; } + + const shouldCreateBoard = + creadBoards[i].id.length < SHORT_ID_MAX_LENGTH; + if (shouldCreateBoard) { + creadBoards[i].shouldCreateBoard = true; + } } } } diff --git a/src/components/Board/__tests__/Board.actions.test.js b/src/components/Board/__tests__/Board.actions.test.js index c9743e001..bda1fc74f 100644 --- a/src/components/Board/__tests__/Board.actions.test.js +++ b/src/components/Board/__tests__/Board.actions.test.js @@ -132,11 +132,21 @@ describe('actions', () => { expect(actions.previousBoard()).toEqual(expectedAction); }); - it('should create an action to REPLACE_ME', () => { + it('should create an action to REPLACE_ME', async () => { const expectedAction = { type: types.TO_ROOT_BOARD }; - expect(actions.toRootBoard()).toEqual(expectedAction); + const store = mockStore({ + ...initialState, + board: { + ...initialState.board, + navHistory: ['12345678901234567'], + boards: [{ ...mockBoard, id: '12345678901234567' }] + } + }); + await store.dispatch(actions.toRootBoard()); + const toRootBoardAction = store.getActions()[0]; + expect(toRootBoardAction).toEqual(expectedAction); }); it('should create an action to REPLACE_ME', () => { @@ -318,7 +328,10 @@ describe('actions', () => { boardId: '12345678901234567', type: 'cboard/Board/CREATE_API_BOARD_SUCCESS' }; - expect(actions[1]).toEqual(dataResp); + const successAction = actions.find( + action => action.type === types.CREATE_API_BOARD_SUCCESS + ); + expect(successAction).toEqual(dataResp); expect(data).toEqual(mockBoard); }) .catch(e => { diff --git a/src/components/Communicator/Communicator.actions.js b/src/components/Communicator/Communicator.actions.js index 57e0373bc..5dec5e3a0 100644 --- a/src/components/Communicator/Communicator.actions.js +++ b/src/components/Communicator/Communicator.actions.js @@ -17,10 +17,16 @@ import { UPDATE_API_COMMUNICATOR_STARTED, GET_API_MY_COMMUNICATORS_SUCCESS, GET_API_MY_COMMUNICATORS_FAILURE, - GET_API_MY_COMMUNICATORS_STARTED + GET_API_MY_COMMUNICATORS_STARTED, + SYNC_COMMUNICATORS } from './Communicator.constants'; import { defaultCommunicatorID } from './Communicator.reducer'; import API from '../../api'; +import shortid from 'shortid'; +import { removeBoardsFromList, switchBoard } from '../Board/Board.actions'; +import { ALL_DEFAULT_BOARDS } from '../../helpers'; +import moment from 'moment'; +import history from './../../history'; export function importCommunicator(communicator) { return { @@ -51,24 +57,27 @@ export function upsertCommunicator(communicator) { } export function upsertApiCommunicator(communicator) { - return (dispatch, getState) => { + return async (dispatch, getState) => { const { communicator: { communicators } } = getState(); - const SHORT_ID_MAX_LENGTH = 14; + const SHORT_ID_MAX_LENGTH = 15; // If the communicator is not on the local state return - if (!communicators.find(c => c.id === communicator.id)) return; + if (!communicators.find(c => c.id === communicator.id)) + return Promise.reject({ + message: 'Communicator not found on local state' + }); - communicator.id.length < SHORT_ID_MAX_LENGTH || - communicator.id === defaultCommunicatorID + return communicator.id.length < SHORT_ID_MAX_LENGTH || + communicator.id === defaultCommunicatorID ? dispatch(createApiCommunicator(communicator, communicator.id)).catch( error => { - console.error(error); + throw new Error(error); } ) : dispatch(updateApiCommunicator(communicator)).catch(error => { - console.error(error); + throw new Error(error); }); }; } @@ -175,22 +184,87 @@ export function updateApiCommunicatorFailure(message) { }; } +export function verifyAndUpsertCommunicator( + communicator, + needToChangeCommunicator = true +) { + return (dispatch, getState) => { + const { + app: { userData } + } = getState(); + + const getActiveCommunicator = getState => { + return getState().communicator.communicators.find( + c => c.id === getState().communicator.activeCommunicatorId + ); + }; + + const updatedCommunicatorData = communicator.hasOwnProperty('id') + ? { ...communicator } + : { ...getActiveCommunicator(getState) }; + + if ( + 'name' in userData && + 'email' in userData && + communicator.email !== userData.email + ) { + //need to create a new communicator + updatedCommunicatorData.author = userData.name; + updatedCommunicatorData.email = userData.email; + updatedCommunicatorData.id = shortid.generate(); + updatedCommunicatorData.boards = [...communicator.boards]; + + if (!!communicator.defaultBoardsIncluded) { + updatedCommunicatorData.defaultBoardsIncluded = communicator.defaultBoardsIncluded.map( + item => ({ ...item }) + ); + } + } + + dispatch(upsertCommunicator(updatedCommunicatorData)); + + if (needToChangeCommunicator) + dispatch(changeCommunicator(updatedCommunicatorData.id)); + + return updatedCommunicatorData; + }; +} + /* * Thunk functions */ export function getApiMyCommunicators() { - return dispatch => { + return async (dispatch, getState) => { dispatch(getApiMyCommunicatorsStarted()); - return API.getCommunicators() - .then(res => { - dispatch(getApiMyCommunicatorsSuccess(res)); - return res; - }) - .catch(err => { - dispatch(getApiMyCommunicatorsFailure(err.message)); - throw new Error(err.message); - }); + try { + const res = await API.getCommunicators(); + dispatch(getApiMyCommunicatorsSuccess(res)); + if (res?.data && res.data.length) { + try { + await dispatch(syncCommunicators(res.data)); + } catch (e) { + console.error(e); + } + const activeCommunicator = + res.data.find( + communicator => + communicator.id === getState().communicator.activeCommunicator + ) ?? res.data[res.data.length - 1]; + const defaultBoardBlackList = activeCommunicator?.defaultBoardBlackList; + dispatch( + removeBoardsFromList( + defaultBoardBlackList, + activeCommunicator.rootBoard + ) + ); + } + + return res; + } catch (err) { + dispatch(getApiMyCommunicatorsFailure(err.message)); + throw new Error(err.message); + } }; } @@ -214,7 +288,7 @@ export function createApiCommunicator(communicatorData, communicatorId) { } export function updateApiCommunicator(communicatorData) { - return dispatch => { + return async dispatch => { dispatch(updateApiCommunicatorStarted()); return API.updateCommunicator(communicatorData) .then(res => { @@ -241,3 +315,109 @@ export function updateDefaultBoardsIncluded(boardAlreadyIncludedData) { defaultBoardsIncluded: boardAlreadyIncludedData }; } + +export function syncCommunicators(remoteCommunicators) { + const reconcileCommunicators = (local, remote) => { + if (local.lastEdited && remote.lastEdited) { + if (moment(local.lastEdited).isAfter(remote.lastEdited)) { + return local; + } + if (moment(local.lastEdited).isBefore(remote.lastEdited)) { + return remote; + } + if (moment(local.lastEdited).isSame(remote.lastEdited)) { + return remote; + } + } + return remote; + }; + const getActiveCommunicator = getState => { + return getState().communicator.communicators.find( + c => c.id === getState().communicator.activeCommunicatorId + ); + }; + + return async (dispatch, getState) => { + const localCommunicators = getState().communicator.communicators; + const updatedCommunicators = [...localCommunicators]; + + for (const remote of remoteCommunicators) { + const localIndex = localCommunicators.findIndex( + local => local.id === remote.id + ); + + if (localIndex !== -1) { + // If the communicator exists locally, reconcile the two + const reconciled = reconcileCommunicators( + localCommunicators[localIndex], + remote + ); + if (reconciled === localCommunicators[localIndex]) { + // Local is more recent, update the server + try { + const res = await dispatch( + updateApiCommunicator(localCommunicators[localIndex]) + ); + updatedCommunicators[localIndex] = res; + } catch (e) { + console.error(e); + } + } else { + updatedCommunicators[localIndex] = reconciled; + } + } else { + // If the communicator does not exist locally, add it + updatedCommunicators.push(remote); + } + } + + const activeCommunicatorId = getActiveCommunicator(getState).id ?? null; + const lastRemoteSavedCommunicatorId = remoteCommunicators[0].id ?? null; //The last communicator saved on the server + const needToChangeActiveCommunicator = + activeCommunicatorId !== lastRemoteSavedCommunicatorId && + updatedCommunicators.length && + lastRemoteSavedCommunicatorId && + updatedCommunicators.findIndex( + communicator => communicator.id === lastRemoteSavedCommunicatorId + ) !== -1; + + dispatch({ + type: SYNC_COMMUNICATORS, + communicators: updatedCommunicators, + activeCommunicatorId: needToChangeActiveCommunicator + ? lastRemoteSavedCommunicatorId + : activeCommunicatorId + }); + + if (needToChangeActiveCommunicator) { + const newActiveCommunicator = getActiveCommunicator(getState); + const rootBoard = newActiveCommunicator.rootBoard; + dispatch(switchBoard(rootBoard)); + history.replace(rootBoard); + } + }; +} + +export function concatDefaultBoardIdToBlacklist(boardId) { + const getActiveCommunicator = getState => { + return getState().communicator.communicators.find( + c => c.id === getState().communicator.activeCommunicatorId + ); + }; + return (dispatch, getState) => { + if (!ALL_DEFAULT_BOARDS.map(({ id }) => id).includes(boardId)) return; + const updatedCommunicatorData = { ...getActiveCommunicator(getState) }; + + const concatBoardIdIfNecessary = () => { + if (!updatedCommunicatorData?.defaultBoardBlackList.includes(boardId)) + return updatedCommunicatorData?.defaultBoardBlackList.concat(boardId); + return updatedCommunicatorData?.defaultBoardBlackList; + }; + + updatedCommunicatorData.defaultBoardBlackList = updatedCommunicatorData?.defaultBoardBlackList + ? concatBoardIdIfNecessary() + : [boardId]; + + return dispatch(verifyAndUpsertCommunicator(updatedCommunicatorData)); + }; +} diff --git a/src/components/Communicator/Communicator.constants.js b/src/components/Communicator/Communicator.constants.js index 3caa635b6..6b681e9a2 100644 --- a/src/components/Communicator/Communicator.constants.js +++ b/src/components/Communicator/Communicator.constants.js @@ -31,3 +31,4 @@ export const GET_API_MY_COMMUNICATORS_FAILURE = 'cboard/Communicator/GET_API_MY_COMMUNICATORS_FAILURE'; export const GET_API_MY_COMMUNICATORS_STARTED = 'cboard/Communicator/GET_API_MY_COMMUNICATORS_STARTED'; +export const SYNC_COMMUNICATORS = 'Communicator/SYNC_COMMUNICATORS'; diff --git a/src/components/Communicator/Communicator.reducer.js b/src/components/Communicator/Communicator.reducer.js index cadca19ef..5b19818b2 100644 --- a/src/components/Communicator/Communicator.reducer.js +++ b/src/components/Communicator/Communicator.reducer.js @@ -19,9 +19,11 @@ import { UPDATE_API_COMMUNICATOR_STARTED, GET_API_MY_COMMUNICATORS_SUCCESS, GET_API_MY_COMMUNICATORS_FAILURE, - GET_API_MY_COMMUNICATORS_STARTED + GET_API_MY_COMMUNICATORS_STARTED, + SYNC_COMMUNICATORS } from './Communicator.constants'; import { LOGIN_SUCCESS, LOGOUT } from '../Account/Login/Login.constants'; +import moment from 'moment'; export const defaultCommunicatorID = 'cboard_default'; const initialState = { @@ -55,9 +57,13 @@ function communicatorReducer(state = initialState, action) { }; case CREATE_COMMUNICATOR: + const newCommunicator = { + ...action.payload, + lastEdited: moment().format() + }; return { ...state, - communicators: state.communicators.concat(action.payload) + communicators: state.communicators.concat(newCommunicator) }; case EDIT_COMMUNICATOR: @@ -67,7 +73,11 @@ function communicatorReducer(state = initialState, action) { let newState = { ...state }; if (communicatorIndex >= 0) { - newState.communicators[communicatorIndex] = action.payload; + const updatedCommunicator = { + ...action.payload, + lastEdited: moment().format() + }; + newState.communicators[communicatorIndex] = updatedCommunicator; } return newState; @@ -96,6 +106,7 @@ function communicatorReducer(state = initialState, action) { if (index !== -1) { const updatedCommunicators = [...state.communicators]; updatedCommunicators[index].boards.push(action.boardId); + updatedCommunicators[index].lastEdited = moment().format(); return { ...state, communicators: updatedCommunicators @@ -112,6 +123,7 @@ function communicatorReducer(state = initialState, action) { const bindex = activeCommunicator.boards.indexOf(action.boardId); if (bindex !== -1) { dupdatedCommunicators[index].boards.splice(bindex, 1); + dupdatedCommunicators[index].lastEdited = moment().format(); return { ...state, communicators: dupdatedCommunicators @@ -135,6 +147,7 @@ function communicatorReducer(state = initialState, action) { 1, action.nextBoardId ); + updatedCommunicators[index].lastEdited = moment().format(); return { ...state, communicators: updatedCommunicators @@ -163,6 +176,7 @@ function communicatorReducer(state = initialState, action) { updatedCommunicators[ index ].defaultBoardsIncluded = defaultBoardsIncluded; + updatedCommunicators[index].lastEdited = moment().format(); return { ...state, @@ -179,6 +193,7 @@ function communicatorReducer(state = initialState, action) { const updatedCommunicators = [...state.communicators]; updatedCommunicators[index].defaultBoardsIncluded = action.defaultBoardsIncluded; + updatedCommunicators[index].lastEdited = moment().format(); return { ...state, @@ -199,7 +214,11 @@ function communicatorReducer(state = initialState, action) { : state.activeCommunicatorId, communicators: state.communicators.map(communicator => communicator.id === action.communicatorId - ? { ...communicator, id: action.communicator.id } + ? { + ...communicator, + id: action.communicator.id, + lastEdited: action.communicator.lastEdited + } : communicator ) }; @@ -216,7 +235,15 @@ function communicatorReducer(state = initialState, action) { case UPDATE_API_COMMUNICATOR_SUCCESS: return { ...state, - isFetching: false + isFetching: false, + communicators: state.communicators.map(communicator => + communicator.id === action.communicator.id + ? { + ...communicator, + lastEdited: action.communicator.lastEdited + } + : communicator + ) }; case UPDATE_API_COMMUNICATOR_FAILURE: return { @@ -229,25 +256,9 @@ function communicatorReducer(state = initialState, action) { isFetching: true }; case GET_API_MY_COMMUNICATORS_SUCCESS: - let flag = false; - const myCommunicators = [...state.communicators]; - for (let i = 0; i < action.communicators.data.length; i++) { - for (let j = 0; j < myCommunicators.length; j++) { - if (myCommunicators[j].id === action.communicators.data[i].id) { - myCommunicators[j].boards = action.communicators.data[i].boards; - flag = true; - break; - } - } - if (!flag) { - myCommunicators.push(action.communicators.data[i]); - flag = false; - } - } return { ...state, - isFetching: false, - communicators: myCommunicators + isFetching: false }; case GET_API_MY_COMMUNICATORS_FAILURE: return { @@ -259,6 +270,12 @@ function communicatorReducer(state = initialState, action) { ...state, isFetching: true }; + case SYNC_COMMUNICATORS: + return { + ...state, + communicators: action.communicators, + activeCommunicatorId: action.activeCommunicatorId + }; default: return state; } diff --git a/src/components/Communicator/CommunicatorDialog/CommunicatorDialog.container.js b/src/components/Communicator/CommunicatorDialog/CommunicatorDialog.container.js index e0a91a520..3891f1ae2 100644 --- a/src/components/Communicator/CommunicatorDialog/CommunicatorDialog.container.js +++ b/src/components/Communicator/CommunicatorDialog/CommunicatorDialog.container.js @@ -6,12 +6,10 @@ import { injectIntl } from 'react-intl'; import shortid from 'shortid'; import API from '../../../api'; import { - createCommunicator, - editCommunicator, - changeCommunicator, deleteBoardCommunicator, addBoardCommunicator, - upsertCommunicator + verifyAndUpsertCommunicator, + upsertApiCommunicator } from '../Communicator.actions'; import { deleteBoard, deleteApiBoard } from '../../Board/Board.actions'; import { showNotification } from '../../Notifications/Notifications.actions'; @@ -239,8 +237,7 @@ class CommunicatorDialogContainer extends React.Component { const { createBoard, addBoardCommunicator, - upsertCommunicator, - changeCommunicator, + verifyAndUpsertCommunicator, currentCommunicator, userData, updateApiObjectsNoChild, @@ -282,33 +279,24 @@ class CommunicatorDialogContainer extends React.Component { } createBoard(newBoard); if (!records) { + verifyAndUpsertCommunicator({ ...currentCommunicator }); addBoardCommunicator(newBoard.id); } + if (!records) { + records = [{ prev: board.id, next: newBoard.id }]; + } else { + records.push({ prev: board.id, next: newBoard.id }); + } + this.updateBoardReferences(board, newBoard, records); + // Loggedin user? if ('name' in userData && 'email' in userData) { - let createCommunicator = false; - if (currentCommunicator.email !== userData.email) { - //need to create a new communicator - const communicatorData = { - ...currentCommunicator, - author: userData.name, - email: userData.email, - id: shortid.generate() - }; - upsertCommunicator(communicatorData); - changeCommunicator(communicatorData.id); - createCommunicator = true; - } try { this.setState({ loading: true }); - const boardId = await updateApiObjectsNoChild( - newBoard, - createCommunicator, - true - ); + const boardId = await updateApiObjectsNoChild(newBoard, true); newBoard = { ...newBoard, id: boardId @@ -321,23 +309,17 @@ class CommunicatorDialogContainer extends React.Component { }); } } - if (!records) { - records = [{ prev: board.id, next: newBoard.id }]; - } else { - records.push({ prev: board.id, next: newBoard.id }); - } - this.updateBoardReferences(board, newBoard, records); if (board.tiles.length < 1) { return; } //return condition - board.tiles.forEach(async tile => { + for (const tile of board.tiles) { if (tile.loadBoard && !tile.linkedBoard) { try { const nextBoard = await API.getBoard(tile.loadBoard); - this.createBoardsRecursively(nextBoard, records); + await this.createBoardsRecursively(nextBoard, records); } catch (err) { if (err.response.status === 404) { //look for this board in available boards @@ -345,12 +327,12 @@ class CommunicatorDialogContainer extends React.Component { b => b.id === tile.loadBoard ); if (localBoard) { - this.createBoardsRecursively(localBoard, records); + await this.createBoardsRecursively(localBoard, records); } } } } - }); + } } updateBoardReferences(board, newBoard, records) { @@ -433,10 +415,9 @@ class CommunicatorDialogContainer extends React.Component { async updateCommunicatorBoards(boards) { const { userData, - communicators, currentCommunicator, - changeCommunicator, - editCommunicator + verifyAndUpsertCommunicator, + upsertApiCommunicator } = this.props; const updatedCommunicatorData = { @@ -444,15 +425,15 @@ class CommunicatorDialogContainer extends React.Component { boards: boards.map(cb => cb.id) }; - if (communicators.findIndex(c => c.id === currentCommunicator.id) >= 0) { - editCommunicator(updatedCommunicatorData); - changeCommunicator(updatedCommunicatorData.id); + const upsertedCommunicator = verifyAndUpsertCommunicator( + updatedCommunicatorData + ); - // Loggedin user? - if ('name' in userData && 'email' in userData) { - try { - await API.updateCommunicator(updatedCommunicatorData); - } catch (err) {} + if ('name' in userData && 'email' in userData) { + try { + await upsertApiCommunicator(upsertedCommunicator); + } catch (err) { + console.error('Error upserting communicator', err); } } } @@ -495,27 +476,24 @@ class CommunicatorDialogContainer extends React.Component { async setRootBoard(board) { const { userData, - communicators, currentCommunicator, - changeCommunicator, - editCommunicator + verifyAndUpsertCommunicator, + upsertApiCommunicator } = this.props; const updatedCommunicatorData = { ...currentCommunicator, rootBoard: board.id }; - - if (communicators.findIndex(c => c.id === currentCommunicator.id) >= 0) { - editCommunicator(updatedCommunicatorData); - changeCommunicator(updatedCommunicatorData.id); - - // Loggedin user? + const upsertedCommunicator = verifyAndUpsertCommunicator( + updatedCommunicatorData + ); + try { if ('name' in userData && 'email' in userData) { - try { - await API.updateCommunicator(updatedCommunicatorData); - } catch (err) {} + await upsertApiCommunicator(upsertedCommunicator); } + } catch (err) { + console.error('Error upserting communicator', err); } } @@ -528,10 +506,11 @@ class CommunicatorDialogContainer extends React.Component { showNotification, deleteBoard, communicators, - editCommunicator, + verifyAndUpsertCommunicator, deleteApiBoard, userData, - intl + intl, + upsertApiCommunicator } = this.props; deleteBoard(board.id); @@ -541,21 +520,27 @@ class CommunicatorDialogContainer extends React.Component { await deleteApiBoard(board.id); } catch (err) {} } - communicators.forEach(async comm => { + for await (const comm of communicators) { if (comm.boards.includes(board.id)) { - editCommunicator({ + const filteredCommunicator = { ...comm, boards: comm.boards.filter(b => b !== board.id) - }); + }; + + const upsertedCommunicator = verifyAndUpsertCommunicator( + filteredCommunicator + ); - // Loggedin user? if ('name' in userData && 'email' in userData) { try { - await API.updateCommunicator(comm); - } catch (err) {} + await upsertApiCommunicator(upsertedCommunicator); + } catch (err) { + console.error('Error upserting communicator', err); + } } } - }); + } + const sBoards = this.state.boards; const index = sBoards.findIndex(b => board.id === b.id); sBoards.splice(index, 1); @@ -646,9 +631,6 @@ const mapStateToProps = ({ board, communicator, language, app }, ownProps) => { }; const mapDispatchToProps = { - createCommunicator, - editCommunicator, - changeCommunicator, addBoards, replaceBoard, showNotification, @@ -658,10 +640,11 @@ const mapDispatchToProps = { createBoard, updateBoard, addBoardCommunicator, - upsertCommunicator, updateApiObjectsNoChild, updateApiBoard, - disableTour + disableTour, + verifyAndUpsertCommunicator, + upsertApiCommunicator }; export default connect( diff --git a/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.container.js b/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.container.js index dd7dab4d2..364672b22 100644 --- a/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.container.js +++ b/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.container.js @@ -12,12 +12,9 @@ import { import { showNotification } from '../../Notifications/Notifications.actions'; import { importCommunicator, - createCommunicator, deleteCommunicator, - changeCommunicator, - upsertCommunicator + verifyAndUpsertCommunicator } from '../Communicator.actions'; -import API from '../../../api'; class CommunicatorContainer extends React.Component { constructor(props) { @@ -37,17 +34,29 @@ class CommunicatorContainer extends React.Component { } editCommunicatorTitle = async name => { + const { + currentCommunicator, + verifyAndUpsertCommunicator, + upsertApiCommunicator, + userData + } = this.props; + const updatedCommunicatorData = { - ...this.props.currentCommunicator, + ...currentCommunicator, name }; - const communicatorData = await API.updateCommunicator( + const upsertedCommunicator = verifyAndUpsertCommunicator( updatedCommunicatorData ); - this.props.upsertCommunicator(communicatorData); - this.props.changeCommunicator(communicatorData.id); + if ('name' in userData && 'email' in userData) { + try { + await upsertApiCommunicator(upsertedCommunicator); + } catch (err) { + console.error('Error upserting communicator', err); + } + } }; render() { @@ -102,10 +111,8 @@ const mapStateToProps = ( const mapDispatchToProps = { importCommunicator, - createCommunicator, + verifyAndUpsertCommunicator, deleteCommunicator, - changeCommunicator, - upsertCommunicator, showNotification, switchBoard, replaceBoard, diff --git a/src/components/Communicator/__tests__/Communicator.reducer.test.js b/src/components/Communicator/__tests__/Communicator.reducer.test.js index 1b6703f41..1cee3ea57 100644 --- a/src/components/Communicator/__tests__/Communicator.reducer.test.js +++ b/src/components/Communicator/__tests__/Communicator.reducer.test.js @@ -21,6 +21,7 @@ import { GET_API_MY_COMMUNICATORS_STARTED } from '../Communicator.constants'; import { LOGIN_SUCCESS, LOGOUT } from '../../Account/Login/Login.constants'; +import moment from 'moment'; let mockComm, defaultCommunicatorID, initialState; describe('reducer', () => { @@ -32,7 +33,8 @@ describe('reducer', () => { email: 'anything@cboard.io', id: '123', name: "Cboard's Communicator", - rootBoard: '1' + rootBoard: '1', + lastEdited: moment().format() }; defaultCommunicatorID = 'cboard_default'; initialState = { @@ -124,7 +126,8 @@ describe('reducer', () => { }); it('should handle updateApiCommunicatorSuccess', () => { const updateApiCommunicatorSuccess = { - type: UPDATE_API_COMMUNICATOR_SUCCESS + type: UPDATE_API_COMMUNICATOR_SUCCESS, + communicator: initialState }; expect( communicatorReducer(initialState, updateApiCommunicatorSuccess) diff --git a/src/components/Settings/Import/Import.container.js b/src/components/Settings/Import/Import.container.js index 28fdfd8e0..e3b213b1b 100644 --- a/src/components/Settings/Import/Import.container.js +++ b/src/components/Settings/Import/Import.container.js @@ -6,8 +6,8 @@ import shortid from 'shortid'; import { addBoards, changeBoard } from '../../Board/Board.actions'; import { - upsertCommunicator, - changeCommunicator + upsertApiCommunicator, + verifyAndUpsertCommunicator } from '../../Communicator/Communicator.actions'; import { switchBoard } from '../../Board/Board.actions'; import { showNotification } from '../../Notifications/Notifications.actions'; @@ -97,7 +97,7 @@ export class ImportContainer extends PureComponent { return response; } catch (err) { console.log(err.message); - return board + return board; } }) ); @@ -121,7 +121,12 @@ export class ImportContainer extends PureComponent { } async addBoardsToCommunicator(boards) { - const { userData, currentCommunicator } = this.props; + const { + currentCommunicator, + verifyAndUpsertCommunicator, + userData, + upsertApiCommunicator + } = this.props; const communicatorBoards = new Set( currentCommunicator.boards.concat(boards.map(b => b.id)) @@ -131,16 +136,17 @@ export class ImportContainer extends PureComponent { boards: Array.from(communicatorBoards) }; - if (userData && userData.authToken && userData.authToken.length) { + const upsertedCommunicator = verifyAndUpsertCommunicator( + communicatorModified + ); + + if ('name' in userData && 'email' in userData) { try { - communicatorModified = await API.updateCommunicator(communicatorModified); + await upsertApiCommunicator(upsertedCommunicator); } catch (err) { - console.log(err.message); + console.error('Error upserting communicator', err); } } - - this.props.upsertCommunicator(communicatorModified); - this.props.changeCommunicator(communicatorModified.id); } async handleImportClick(e, doneCallback) { @@ -229,9 +235,9 @@ const mapDispatchToProps = { addBoards, changeBoard, switchBoard, - upsertCommunicator, - changeCommunicator, - showNotification + showNotification, + verifyAndUpsertCommunicator, + upsertApiCommunicator }; export default connect( diff --git a/src/helpers.js b/src/helpers.js index 6ac74ee2f..06770683d 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -5,6 +5,16 @@ export const DEFAULT_BOARDS = { advanced: boards.advanced, picSeePal: picSeePal }; +let advancedCopy = JSON.parse(JSON.stringify(DEFAULT_BOARDS.advanced)); +let picSeePalCopy = JSON.parse(JSON.stringify(DEFAULT_BOARDS.picSeePal)); + +export const ALL_DEFAULT_BOARDS = [...advancedCopy, ...picSeePalCopy]; + +export const SHORT_ID_MAX_LENGTH = 14; + +export const isRemoteIdChecker = id => { + return !(id.length < SHORT_ID_MAX_LENGTH); +}; export const dataURLtoFile = (dataurl, filename, checkExtension = false) => { // https://stackoverflow.com/a/38936042