diff --git a/client/src/api/boards.js b/client/src/api/boards.js index 21488dac..c8ae9202 100755 --- a/client/src/api/boards.js +++ b/client/src/api/boards.js @@ -8,11 +8,12 @@ import { transformAttachment } from './attachments'; const createBoard = (projectId, data, headers) => socket.post(`/projects/${projectId}/boards`, data, headers); +// TODO: remove and use createBoard instead const importBoard = (projectId, data, headers) => http.post( `/projects/${projectId}/imports/boards?name=${data.name}&position=${data.position}`, { - file: data.file, + file: data.import.file, }, headers, ); diff --git a/client/src/components/Boards/AddPopup.jsx b/client/src/components/Boards/AddPopup.jsx deleted file mode 100755 index 5556eb23..00000000 --- a/client/src/components/Boards/AddPopup.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; -import { useTranslation } from 'react-i18next'; -import { Button, Form, Menu } from 'semantic-ui-react'; -import { withPopup } from '../../lib/popup'; -import { Input, Popup, FilePicker } from '../../lib/custom-ui'; - -import { useForm } from '../../hooks'; - -import styles from './AddPopup.module.scss'; - -const AddStep = React.memo(({ onCreate, onImport, onClose }) => { - const [t] = useTranslation(); - - const [data, handleFieldChange] = useForm({ - name: '', - file: null, - }); - - const [selectedFile, setSelectedFile] = useState(null); - - const nameField = useRef(null); - - const handleSubmit = useCallback(() => { - const cleanData = { - ...data, - type: 'kanban', - name: data.name.trim(), - }; - - if (!cleanData.name) { - nameField.current.select(); - return; - } - - if (data.file) { - onImport(cleanData); - } else { - onCreate(cleanData); - } - - onClose(); - }, [onClose, data, onImport, onCreate]); - - const handleFileSelect = useCallback( - (file) => { - handleFieldChange(null, { - name: 'file', - value: file, - }); - setSelectedFile(file); - }, - [handleFieldChange, setSelectedFile], - ); - - useEffect(() => { - nameField.current.focus(); - }, []); - - return ( - <> - - {t('common.createBoard', { - context: 'title', - })} - - -
- - - - - {selectedFile - ? selectedFile.name - : t('common.uploadTrelloFile', { - context: 'title', - })} - - - -
+ +
+ + ); +}); + +AddStep.propTypes = { + onCreate: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, +}; + +export default withPopup(AddStep); diff --git a/client/src/components/Boards/AddPopup/AddPopup.module.scss b/client/src/components/Boards/AddPopup/AddPopup.module.scss new file mode 100644 index 00000000..481fe714 --- /dev/null +++ b/client/src/components/Boards/AddPopup/AddPopup.module.scss @@ -0,0 +1,31 @@ +:global(#app) { + .field { + margin-bottom: 8px; + } + + .importButton { + background: transparent; + box-shadow: none; + color: #6b808c; + float: right; + font-weight: normal; + margin-right: 0; + max-width: 49%; + overflow: hidden; + text-align: left; + text-decoration: underline; + text-overflow: ellipsis; + transition: none; + white-space: nowrap; + + &:hover { + background: rgba(9, 30, 66, 0.08); + color: #092d42; + } + } + + .importButtonIcon { + font-size: 13px; + text-decoration: none; + } +} diff --git a/client/src/components/Boards/AddPopup/ImportStep.jsx b/client/src/components/Boards/AddPopup/ImportStep.jsx new file mode 100644 index 00000000..d56bf2a2 --- /dev/null +++ b/client/src/components/Boards/AddPopup/ImportStep.jsx @@ -0,0 +1,51 @@ +import React, { useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; +import { Button } from 'semantic-ui-react'; +import { FilePicker, Popup } from '../../../lib/custom-ui'; + +import styles from './ImportStep.module.scss'; + +const ImportStep = React.memo(({ onSelect, onBack }) => { + const [t] = useTranslation(); + + const handleFileSelect = useCallback( + (type, file) => { + onSelect({ + type, + file, + }); + + onBack(); + }, + [onSelect, onBack], + ); + + return ( + <> + + {t('common.importBoard', { + context: 'title', + })} + + + handleFileSelect('trello', file)} accept=".json"> + - - )} - - ) : ( - // eslint-disable-next-line react/jsx-props-no-spreading - + const itemsNode = items.map((item, index) => ( + + {({ innerRef, draggableProps, dragHandleProps }) => ( + // eslint-disable-next-line react/jsx-props-no-spreading +
+
+ {item.isPersisted ? ( + <> + {item.name} - - )} -
+ + {canEdit && ( + handleUpdate(item.id, data)} + onDelete={() => handleDelete(item.id)} + > + + + )} + + ) : ( + // eslint-disable-next-line react/jsx-props-no-spreading + + {item.name} + + )}
- )} -
- )); - - return ( -
-
- - - {({ innerRef, droppableProps, placeholder }) => ( - // eslint-disable-next-line react/jsx-props-no-spreading -
- {itemsNode} - {placeholder} - {canEdit && ( - -
- )} -
-
+ )} + + )); + + return ( +
+
+ + + {({ innerRef, droppableProps, placeholder }) => ( + // eslint-disable-next-line react/jsx-props-no-spreading +
+ {itemsNode} + {placeholder} + {canEdit && ( + +
+ )} +
+
- ); - }, -); +
+ ); +}); Boards.propTypes = { items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types currentId: PropTypes.string, canEdit: PropTypes.bool.isRequired, onCreate: PropTypes.func.isRequired, - onImport: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, diff --git a/client/src/constants/EntryActionTypes.js b/client/src/constants/EntryActionTypes.js index 754a007c..5844a96a 100755 --- a/client/src/constants/EntryActionTypes.js +++ b/client/src/constants/EntryActionTypes.js @@ -76,7 +76,6 @@ export default { /* Boards */ BOARD_IN_CURRENT_PROJECT_CREATE: `${PREFIX}/BOARD_IN_CURRENT_PROJECT_CREATE`, - TRELLO_BOARD_IN_CURRENT_PROJECT_IMPORT: `${PREFIX}/TRELLO_BOARD_IN_CURRENT_PROJECT_IMPORT`, BOARD_CREATE_HANDLE: `${PREFIX}/BOARD_CREATE_HANDLE`, BOARD_FETCH: `${PREFIX}/BOARD_FETCH`, BOARD_UPDATE: `${PREFIX}/BOARD_UPDATE`, diff --git a/client/src/containers/BoardsContainer.js b/client/src/containers/BoardsContainer.js index ffee0996..a8bbf2e3 100755 --- a/client/src/containers/BoardsContainer.js +++ b/client/src/containers/BoardsContainer.js @@ -21,7 +21,6 @@ const mapDispatchToProps = (dispatch) => bindActionCreators( { onCreate: entryActions.createBoardInCurrentProject, - onImport: entryActions.importTrelloBoardInCurrentProject, onUpdate: entryActions.updateBoard, onMove: entryActions.moveBoard, onDelete: entryActions.deleteBoard, diff --git a/client/src/entry-actions/boards.js b/client/src/entry-actions/boards.js index b651878d..638f49ba 100755 --- a/client/src/entry-actions/boards.js +++ b/client/src/entry-actions/boards.js @@ -7,13 +7,6 @@ const createBoardInCurrentProject = (data) => ({ }, }); -const importTrelloBoardInCurrentProject = (data) => ({ - type: EntryActionTypes.TRELLO_BOARD_IN_CURRENT_PROJECT_IMPORT, - payload: { - data, - }, -}); - const handleBoardCreate = (board) => ({ type: EntryActionTypes.BOARD_CREATE_HANDLE, payload: { @@ -67,7 +60,6 @@ const handleBoardDelete = (board) => ({ export default { createBoardInCurrentProject, - importTrelloBoardInCurrentProject, handleBoardCreate, fetchBoard, updateBoard, diff --git a/client/src/locales/en/core.js b/client/src/locales/en/core.js index 62b8b176..900499ae 100644 --- a/client/src/locales/en/core.js +++ b/client/src/locales/en/core.js @@ -92,8 +92,10 @@ export default { filterByLabels_title: 'Filter By Labels', filterByMembers_title: 'Filter By Members', fromComputer_title: 'From Computer', + fromTrello: 'From Trello', general: 'General', hours: 'Hours', + importBoard_title: 'Import Board', invalidCurrentPassword: 'Invalid current password', labels: 'Labels', language: 'Language', @@ -143,7 +145,6 @@ export default { time: 'Time', timer: 'Timer', title: 'Title', - uploadTrelloFile_title: 'Import Board from Trello', userActions_title: 'User Actions', userAddedThisCardToList: '<0>{{user}}<1> added this card to {{list}}', userLeftNewCommentToCard: '{{user}} left a new comment «{{comment}}» to <2>{{card}}', @@ -202,7 +203,7 @@ export default { editTitle_title: 'Edit Title', editUsername_title: 'Edit Username', hideDetails: 'Hide details', - importTrelloBoard: 'Import Trello Board', + import: 'Import', leaveBoard: 'Leave board', leaveProject: 'Leave project', logOut_title: 'Log Out', diff --git a/client/src/models/Board.js b/client/src/models/Board.js index 53992b36..6be5c92c 100755 --- a/client/src/models/Board.js +++ b/client/src/models/Board.js @@ -124,11 +124,7 @@ export default class extends Model { break; case ActionTypes.BOARD_CREATE__SUCCESS: Board.withId(payload.localId).delete(); - - Board.upsert({ - ...payload.board, - isFetching: false, - }); + Board.upsert(payload.board); break; case ActionTypes.BOARD_FETCH__FAILURE: diff --git a/client/src/sagas/core/services/boards.js b/client/src/sagas/core/services/boards.js index a3ad3f24..eebb74af 100644 --- a/client/src/sagas/core/services/boards.js +++ b/client/src/sagas/core/services/boards.js @@ -1,3 +1,4 @@ +import omit from 'lodash/omit'; import { call, put, select } from 'redux-saga/effects'; import { goToBoard, goToProject } from './router'; @@ -7,6 +8,51 @@ import actions from '../../../actions'; import api from '../../../api'; import { createLocalId } from '../../../utils/local-id'; +export function* createBoard(projectId, data) { + const isImport = !!data.import; + + const nextData = { + ...data, + position: yield select(selectors.selectNextBoardPosition, projectId), + }; + + const localId = yield call(createLocalId); + + yield put( + actions.createBoard({ + ...(isImport ? omit(nextData, 'import') : nextData), + projectId, + id: localId, + }), + ); + + let board; + let boardMemberships; + + try { + ({ + item: board, + included: { boardMemberships }, + } = yield call(request, isImport ? api.importBoard : api.createBoard, projectId, nextData)); + } catch (error) { + yield put(actions.createBoard.failure(localId, error)); + return; + } + + yield put(actions.createBoard.success(localId, board, boardMemberships)); + yield call(goToBoard, board.id); +} + +export function* createBoardInCurrentProject(data) { + const { projectId } = yield select(selectors.selectPath); + + yield call(createBoard, projectId, data); +} + +export function* handleBoardCreate(board) { + yield put(actions.handleBoardCreate(board)); +} + export function* fetchBoard(id) { yield put(actions.fetchBoard(id)); @@ -60,59 +106,6 @@ export function* fetchBoard(id) { ); } -export function* createBoard(projectId, data) { - const isImport = data && data.file; - const nextData = { - ...data, - position: yield select(selectors.selectNextBoardPosition, projectId), - }; - - const localId = yield call(createLocalId); - - yield put( - actions.createBoard({ - ...nextData, - projectId, - id: localId, - }), - ); - - let board; - let boardMemberships; - - try { - ({ - item: board, - included: { boardMemberships }, - } = yield call(request, isImport ? api.importBoard : api.createBoard, projectId, nextData)); - } catch (error) { - yield put(actions.createBoard.failure(localId, error)); - return; - } - - yield put(actions.createBoard.success(localId, board, boardMemberships)); - yield call(goToBoard, board.id); - if (isImport) { - yield call(fetchBoard, board.id); - } -} - -export function* createBoardInCurrentProject(data) { - const { projectId } = yield select(selectors.selectPath); - - yield call(createBoard, projectId, data); -} - -export function* importTrelloBoardInCurrentProject(data) { - const { projectId } = yield select(selectors.selectPath); - - yield call(createBoard, projectId, data); -} - -export function* handleBoardCreate(board) { - yield put(actions.handleBoardCreate(board)); -} - export function* updateBoard(id, data) { yield put(actions.updateBoard(id, data)); @@ -173,7 +166,6 @@ export function* handleBoardDelete(board) { export default { createBoard, createBoardInCurrentProject, - importTrelloBoardInCurrentProject, handleBoardCreate, fetchBoard, updateBoard, diff --git a/client/src/sagas/core/watchers/boards.js b/client/src/sagas/core/watchers/boards.js index 06139059..ad9f2615 100644 --- a/client/src/sagas/core/watchers/boards.js +++ b/client/src/sagas/core/watchers/boards.js @@ -8,9 +8,6 @@ export default function* boardsWatchers() { takeEvery(EntryActionTypes.BOARD_IN_CURRENT_PROJECT_CREATE, ({ payload: { data } }) => services.createBoardInCurrentProject(data), ), - takeEvery(EntryActionTypes.TRELLO_BOARD_IN_CURRENT_PROJECT_IMPORT, ({ payload: { data } }) => - services.importTrelloBoardInCurrentProject(data), - ), takeEvery(EntryActionTypes.BOARD_CREATE_HANDLE, ({ payload: { board } }) => services.handleBoardCreate(board), ),