1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-18 20:59:44 +02:00

Project managers, board members, auto-update after reconnection, refactoring

This commit is contained in:
Maksim Eltyshev 2021-06-24 01:05:22 +05:00
parent 7956503a46
commit fe91b5241e
478 changed files with 21226 additions and 19495 deletions

14245
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -32,8 +32,7 @@
"extends": [ "extends": [
"airbnb", "airbnb",
"airbnb/hooks", "airbnb/hooks",
"plugin:prettier/recommended", "plugin:prettier/recommended"
"prettier/react"
], ],
"rules": { "rules": {
"import/no-extraneous-dependencies": [ "import/no-extraneous-dependencies": [
@ -58,28 +57,28 @@
"dependencies": { "dependencies": {
"classnames": "^2.2.6", "classnames": "^2.2.6",
"connected-react-router": "^6.8.0", "connected-react-router": "^6.8.0",
"date-fns": "^2.17.0", "date-fns": "^2.22.1",
"dequal": "^2.0.2", "dequal": "^2.0.2",
"history": "^4.10.1", "history": "^4.10.1",
"i18next": "^19.8.7", "i18next": "^20.3.2",
"i18next-browser-languagedetector": "^6.0.1", "i18next-browser-languagedetector": "^6.1.2",
"initials": "^3.1.1", "initials": "^3.1.1",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"node-sass": "^4.14.1", "node-sass": "^6.0.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^17.0.1", "react": "^17.0.1",
"react-beautiful-dnd": "^13.0.0", "react-beautiful-dnd": "^13.0.0",
"react-datepicker": "^3.4.1", "react-datepicker": "^4.1.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-dropzone": "^11.3.1", "react-dropzone": "^11.3.1",
"react-i18next": "^11.8.6", "react-i18next": "^11.11.0",
"react-input-mask": "^2.0.4", "react-input-mask": "^2.0.4",
"react-markdown": "^5.0.3", "react-markdown": "^6.0.2",
"react-redux": "^7.2.2", "react-redux": "^7.2.4",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.2", "react-scripts": "4.0.3",
"react-textarea-autosize": "^8.3.1", "react-textarea-autosize": "^8.3.3",
"redux": "^4.0.5", "redux": "^4.1.0",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-orm": "^0.16.2", "redux-orm": "^0.16.2",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
@ -88,23 +87,23 @@
"sails.io.js": "^1.2.1", "sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.0.3", "semantic-ui-react": "^2.0.3",
"socket.io-client": "^2.3.1", "socket.io-client": "^2.3.1",
"validator": "^13.5.2", "validator": "^13.6.0",
"whatwg-fetch": "^3.5.0" "whatwg-fetch": "^3.5.0"
}, },
"devDependencies": { "devDependencies": {
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.2",
"chai": "^4.3.0", "chai": "^4.3.0",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6", "eslint": "^7.29.0",
"eslint": "^7.19.0",
"eslint-config-airbnb": "^18.2.1", "eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^7.2.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.22.0", "eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-hooks": "^4.2.0",
"jest-enzyme": "^7.1.2", "jest-enzyme": "^7.1.2",
"prettier": "2.2.1", "prettier": "2.3.1",
"react-test-renderer": "^17.0.1" "react-test-renderer": "^17.0.1"
} }
} }

View file

@ -1,23 +1,21 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Events */ export const handleActionCreate = (action) => ({
type: ActionTypes.ACTION_CREATE_HANDLE,
export const createActionReceived = (action) => ({
type: ActionTypes.ACTION_CREATE_RECEIVED,
payload: { payload: {
action, action,
}, },
}); });
export const updateActionReceived = (action) => ({ export const handleActionUpdate = (action) => ({
type: ActionTypes.ACTION_UPDATE_RECEIVED, type: ActionTypes.ACTION_UPDATE_HANDLE,
payload: { payload: {
action, action,
}, },
}); });
export const deleteActionReceived = (action) => ({ export const handleActionDelete = (action) => ({
type: ActionTypes.ACTION_DELETE_RECEIVED, type: ActionTypes.ACTION_DELETE_HANDLE,
payload: { payload: {
action, action,
}, },

View file

@ -1,16 +1,15 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Events */ // eslint-disable-next-line import/prefer-default-export
export const fetchActions = (cardId) => ({
export const fetchActionsRequested = (cardId) => ({ type: ActionTypes.ACTIONS_FETCH,
type: ActionTypes.ACTIONS_FETCH_REQUESTED,
payload: { payload: {
cardId, cardId,
}, },
}); });
export const fetchActionsSucceeded = (cardId, actions, users) => ({ fetchActions.success = (cardId, actions, users) => ({
type: ActionTypes.ACTIONS_FETCH_SUCCEEDED, type: ActionTypes.ACTIONS_FETCH__SUCCESS,
payload: { payload: {
cardId, cardId,
actions, actions,
@ -18,8 +17,8 @@ export const fetchActionsSucceeded = (cardId, actions, users) => ({
}, },
}); });
export const fetchActionsFailed = (cardId, error) => ({ fetchActions.failure = (cardId, error) => ({
type: ActionTypes.ACTIONS_FETCH_FAILED, type: ActionTypes.ACTIONS_FETCH__FAILURE,
payload: { payload: {
cardId, cardId,
error, error,

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createAttachment = (attachment) => ({ export const createAttachment = (attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE, type: ActionTypes.ATTACHMENT_CREATE,
payload: { payload: {
@ -9,6 +7,29 @@ export const createAttachment = (attachment) => ({
}, },
}); });
createAttachment.success = (localId, attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE__SUCCESS,
payload: {
localId,
attachment,
},
});
createAttachment.failure = (localId, error) => ({
type: ActionTypes.ATTACHMENT_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleAttachmentCreate = (attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE_HANDLE,
payload: {
attachment,
},
});
export const updateAttachment = (id, data) => ({ export const updateAttachment = (id, data) => ({
type: ActionTypes.ATTACHMENT_UPDATE, type: ActionTypes.ATTACHMENT_UPDATE,
payload: { payload: {
@ -17,6 +38,28 @@ export const updateAttachment = (id, data) => ({
}, },
}); });
updateAttachment.success = (attachment) => ({
type: ActionTypes.ATTACHMENT_UPDATE__SUCCESS,
payload: {
attachment,
},
});
updateAttachment.failure = (id, error) => ({
type: ActionTypes.ATTACHMENT_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleAttachmentUpdate = (attachment) => ({
type: ActionTypes.ATTACHMENT_UPDATE_HANDLE,
payload: {
attachment,
},
});
export const deleteAttachment = (id) => ({ export const deleteAttachment = (id) => ({
type: ActionTypes.ATTACHMENT_DELETE, type: ActionTypes.ATTACHMENT_DELETE,
payload: { payload: {
@ -24,93 +67,23 @@ export const deleteAttachment = (id) => ({
}, },
}); });
/* Events */ deleteAttachment.success = (attachment) => ({
type: ActionTypes.ATTACHMENT_DELETE__SUCCESS,
export const createAttachmentRequested = (localId, data) => ({
type: ActionTypes.ATTACHMENT_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createAttachmentSucceeded = (localId, attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE_SUCCEEDED,
payload: {
localId,
attachment,
},
});
export const createAttachmentFailed = (localId, error) => ({
type: ActionTypes.ATTACHMENT_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const createAttachmentReceived = (attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE_RECEIVED,
payload: { payload: {
attachment, attachment,
}, },
}); });
export const updateAttachmentRequested = (id, data) => ({ deleteAttachment.failure = (id, error) => ({
type: ActionTypes.ATTACHMENT_UPDATE_REQUESTED, type: ActionTypes.ATTACHMENT_DELETE__FAILURE,
payload: {
id,
data,
},
});
export const updateAttachmentSucceeded = (attachment) => ({
type: ActionTypes.ATTACHMENT_UPDATE_SUCCEEDED,
payload: {
attachment,
},
});
export const updateAttachmentFailed = (id, error) => ({
type: ActionTypes.ATTACHMENT_UPDATE_FAILED,
payload: { payload: {
id, id,
error, error,
}, },
}); });
export const updateAttachmentReceived = (attachment) => ({ export const handleAttachmentDelete = (attachment) => ({
type: ActionTypes.ATTACHMENT_UPDATE_RECEIVED, type: ActionTypes.ATTACHMENT_DELETE_HANDLE,
payload: {
attachment,
},
});
export const deleteAttachmentRequested = (id) => ({
type: ActionTypes.ATTACHMENT_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteAttachmentSucceeded = (attachment) => ({
type: ActionTypes.ATTACHMENT_DELETE_SUCCEEDED,
payload: {
attachment,
},
});
export const deleteAttachmentFailed = (id, error) => ({
type: ActionTypes.ATTACHMENT_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteAttachmentReceived = (attachment) => ({
type: ActionTypes.ATTACHMENT_DELETE_RECEIVED,
payload: { payload: {
attachment, attachment,
}, },

View file

@ -0,0 +1,97 @@
import ActionTypes from '../constants/ActionTypes';
export const createBoardMembership = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE,
payload: {
boardMembership,
},
});
createBoardMembership.success = (localId, boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE__SUCCESS,
payload: {
localId,
boardMembership,
},
});
createBoardMembership.failure = (localId, error) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleBoardMembershipCreate = (
boardMembership,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
payload: {
boardMembership,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
handleBoardMembershipCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH,
payload: {
id,
currentUserId,
currentBoardId,
},
});
export const deleteBoardMembership = (id) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE,
payload: {
id,
},
});
deleteBoardMembership.success = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE__SUCCESS,
payload: {
boardMembership,
},
});
deleteBoardMembership.failure = (id, error) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleBoardMembershipDelete = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,
payload: {
boardMembership,
},
});

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createBoard = (board) => ({ export const createBoard = (board) => ({
type: ActionTypes.BOARD_CREATE, type: ActionTypes.BOARD_CREATE,
payload: { payload: {
@ -9,6 +7,74 @@ export const createBoard = (board) => ({
}, },
}); });
createBoard.success = (localId, board, boardMemberships) => ({
type: ActionTypes.BOARD_CREATE__SUCCESS,
payload: {
localId,
board,
boardMemberships,
},
});
createBoard.failure = (localId, error) => ({
type: ActionTypes.BOARD_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleBoardCreate = (board) => ({
type: ActionTypes.BOARD_CREATE_HANDLE,
payload: {
board,
},
});
export const fetchBoard = (id) => ({
type: ActionTypes.BOARD_FETCH,
payload: {
id,
},
});
fetchBoard.success = (
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.BOARD_FETCH__SUCCESS,
payload: {
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
fetchBoard.failure = (id, error) => ({
type: ActionTypes.BOARD_FETCH__FAILURE,
payload: {
id,
error,
},
});
export const updateBoard = (id, data) => ({ export const updateBoard = (id, data) => ({
type: ActionTypes.BOARD_UPDATE, type: ActionTypes.BOARD_UPDATE,
payload: { payload: {
@ -17,6 +83,28 @@ export const updateBoard = (id, data) => ({
}, },
}); });
updateBoard.success = (board) => ({
type: ActionTypes.BOARD_UPDATE__SUCCESS,
payload: {
board,
},
});
updateBoard.failure = (id, error) => ({
type: ActionTypes.BOARD_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleBoardUpdate = (board) => ({
type: ActionTypes.BOARD_UPDATE_HANDLE,
payload: {
board,
},
});
export const deleteBoard = (id) => ({ export const deleteBoard = (id) => ({
type: ActionTypes.BOARD_DELETE, type: ActionTypes.BOARD_DELETE,
payload: { payload: {
@ -24,135 +112,23 @@ export const deleteBoard = (id) => ({
}, },
}); });
/* Events */ deleteBoard.success = (board) => ({
type: ActionTypes.BOARD_DELETE__SUCCESS,
export const createBoardRequested = (localId, data) => ({
type: ActionTypes.BOARD_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createBoardSucceeded = (localId, board, lists, labels) => ({
type: ActionTypes.BOARD_CREATE_SUCCEEDED,
payload: {
localId,
board,
lists,
labels,
},
});
export const createBoardFailed = (localId, error) => ({
type: ActionTypes.BOARD_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const createBoardReceived = (board, lists, labels) => ({
type: ActionTypes.BOARD_CREATE_RECEIVED,
payload: { payload: {
board, board,
lists,
labels,
}, },
}); });
export const fetchBoardRequested = (id) => ({ deleteBoard.failure = (id, error) => ({
type: ActionTypes.BOARD_FETCH_REQUESTED, type: ActionTypes.BOARD_DELETE__FAILURE,
payload: {
id,
},
});
export const fetchBoardSucceeded = (
board,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.BOARD_FETCH_SUCCEEDED,
payload: {
board,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
export const fetchBoardFailed = (id, error) => ({
type: ActionTypes.BOARD_FETCH_FAILED,
payload: { payload: {
id, id,
error, error,
}, },
}); });
export const updateBoardRequested = (id, data) => ({ export const handleBoardDelete = (board) => ({
type: ActionTypes.BOARD_UPDATE_REQUESTED, type: ActionTypes.BOARD_DELETE_HANDLE,
payload: {
id,
data,
},
});
export const updateBoardSucceeded = (board) => ({
type: ActionTypes.BOARD_UPDATE_SUCCEEDED,
payload: {
board,
},
});
export const updateBoardFailed = (id, error) => ({
type: ActionTypes.BOARD_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const updateBoardReceived = (board) => ({
type: ActionTypes.BOARD_UPDATE_RECEIVED,
payload: {
board,
},
});
export const deleteBoardRequested = (id) => ({
type: ActionTypes.BOARD_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteBoardSucceeded = (board) => ({
type: ActionTypes.BOARD_DELETE_SUCCEEDED,
payload: {
board,
},
});
export const deleteBoardFailed = (id, error) => ({
type: ActionTypes.BOARD_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteBoardReceived = (board) => ({
type: ActionTypes.BOARD_DELETE_RECEIVED,
payload: { payload: {
board, board,
}, },

View file

@ -1,62 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
/* Events */
export const createCardLabelRequested = (data) => ({
type: ActionTypes.CARD_LABEL_CREATE_REQUESTED,
payload: {
data,
},
});
export const createCardLabelSucceeded = (cardLabel) => ({
type: ActionTypes.CARD_LABEL_CREATE_SUCCEEDED,
payload: {
cardLabel,
},
});
export const createCardLabelFailed = (error) => ({
type: ActionTypes.CARD_LABEL_CREATE_FAILED,
payload: {
error,
},
});
export const createCardLabelReceived = (cardLabel) => ({
type: ActionTypes.CARD_LABEL_CREATE_RECEIVED,
payload: {
cardLabel,
},
});
export const deleteCardLabelRequested = (cardId, labelId) => ({
type: ActionTypes.CARD_LABEL_DELETE_REQUESTED,
payload: {
cardId,
labelId,
},
});
export const deleteCardLabelSucceeded = (cardLabel) => ({
type: ActionTypes.CARD_LABEL_DELETE_SUCCEEDED,
payload: {
cardLabel,
},
});
export const deleteCardLabelFailed = (cardId, labelId, error) => ({
type: ActionTypes.CARD_LABEL_DELETE_FAILED,
payload: {
cardId,
labelId,
error,
},
});
export const deleteCardLabelReceived = (cardLabel) => ({
type: ActionTypes.CARD_LABEL_DELETE_RECEIVED,
payload: {
cardLabel,
},
});

View file

@ -1,62 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
/* Events */
export const createCardMembershipRequested = (data) => ({
type: ActionTypes.CARD_MEMBERSHIP_CREATE_REQUESTED,
payload: {
data,
},
});
export const createCardMembershipSucceeded = (cardMembership) => ({
type: ActionTypes.CARD_MEMBERSHIP_CREATE_SUCCEEDED,
payload: {
cardMembership,
},
});
export const createCardMembershipFailed = (error) => ({
type: ActionTypes.CARD_MEMBERSHIP_CREATE_FAILED,
payload: {
error,
},
});
export const createCardMembershipReceived = (cardMembership) => ({
type: ActionTypes.CARD_MEMBERSHIP_CREATE_RECEIVED,
payload: {
cardMembership,
},
});
export const deleteCardMembershipRequested = (cardId, userId) => ({
type: ActionTypes.CARD_MEMBERSHIP_DELETE_REQUESTED,
payload: {
cardId,
userId,
},
});
export const deleteCardMembershipSucceeded = (cardMembership) => ({
type: ActionTypes.CARD_MEMBERSHIP_DELETE_SUCCEEDED,
payload: {
cardMembership,
},
});
export const deleteCardMembershipFailed = (cardId, userId, error) => ({
type: ActionTypes.CARD_MEMBERSHIP_DELETE_FAILED,
payload: {
cardId,
userId,
error,
},
});
export const deleteCardMembershipReceived = (cardMembership) => ({
type: ActionTypes.CARD_MEMBERSHIP_DELETE_RECEIVED,
payload: {
cardMembership,
},
});

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createCard = (card) => ({ export const createCard = (card) => ({
type: ActionTypes.CARD_CREATE, type: ActionTypes.CARD_CREATE,
payload: { payload: {
@ -9,6 +7,29 @@ export const createCard = (card) => ({
}, },
}); });
createCard.success = (localId, card) => ({
type: ActionTypes.CARD_CREATE__SUCCESS,
payload: {
localId,
card,
},
});
createCard.failure = (localId, error) => ({
type: ActionTypes.CARD_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleCardCreate = (card) => ({
type: ActionTypes.CARD_CREATE_HANDLE,
payload: {
card,
},
});
export const updateCard = (id, data) => ({ export const updateCard = (id, data) => ({
type: ActionTypes.CARD_UPDATE, type: ActionTypes.CARD_UPDATE,
payload: { payload: {
@ -17,6 +38,28 @@ export const updateCard = (id, data) => ({
}, },
}); });
updateCard.success = (card) => ({
type: ActionTypes.CARD_UPDATE__SUCCESS,
payload: {
card,
},
});
updateCard.failure = (id, error) => ({
type: ActionTypes.CARD_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleCardUpdate = (card) => ({
type: ActionTypes.CARD_UPDATE_HANDLE,
payload: {
card,
},
});
export const deleteCard = (id) => ({ export const deleteCard = (id) => ({
type: ActionTypes.CARD_DELETE, type: ActionTypes.CARD_DELETE,
payload: { payload: {
@ -24,130 +67,23 @@ export const deleteCard = (id) => ({
}, },
}); });
/* Events */ deleteCard.success = (card) => ({
type: ActionTypes.CARD_DELETE__SUCCESS,
export const createCardRequested = (localId, data) => ({
type: ActionTypes.CARD_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createCardSucceeded = (
localId,
card,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.CARD_CREATE_SUCCEEDED,
payload: {
localId,
card,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
export const createCardFailed = (localId, error) => ({
type: ActionTypes.CARD_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const createCardReceived = (card, cardMemberships, cardLabels, tasks, attachments) => ({
type: ActionTypes.CARD_CREATE_RECEIVED,
payload: {
card,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
export const fetchCardRequested = (id) => ({
type: ActionTypes.CARD_FETCH_REQUESTED,
payload: {
id,
},
});
export const fetchCardSucceeded = (card) => ({
type: ActionTypes.CARD_FETCH_SUCCEEDED,
payload: { payload: {
card, card,
}, },
}); });
export const fetchCardFailed = (id, error) => ({ deleteCard.failure = (id, error) => ({
type: ActionTypes.CARD_FETCH_FAILED, type: ActionTypes.CARD_DELETE__FAILURE,
payload: { payload: {
id, id,
error, error,
}, },
}); });
export const updateCardRequested = (id, data) => ({ export const handleCardDelete = (card) => ({
type: ActionTypes.CARD_UPDATE_REQUESTED, type: ActionTypes.CARD_DELETE_HANDLE,
payload: {
id,
data,
},
});
export const updateCardSucceeded = (card) => ({
type: ActionTypes.CARD_UPDATE_SUCCEEDED,
payload: {
card,
},
});
export const updateCardFailed = (id, error) => ({
type: ActionTypes.CARD_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const updateCardReceived = (card) => ({
type: ActionTypes.CARD_UPDATE_RECEIVED,
payload: {
card,
},
});
export const deleteCardRequested = (id) => ({
type: ActionTypes.CARD_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteCardSucceeded = (card) => ({
type: ActionTypes.CARD_DELETE_SUCCEEDED,
payload: {
card,
},
});
export const deleteCardFailed = (id, error) => ({
type: ActionTypes.CARD_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteCardReceived = (card) => ({
type: ActionTypes.CARD_DELETE_RECEIVED,
payload: { payload: {
card, card,
}, },

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createCommentAction = (action) => ({ export const createCommentAction = (action) => ({
type: ActionTypes.COMMENT_ACTION_CREATE, type: ActionTypes.COMMENT_ACTION_CREATE,
payload: { payload: {
@ -9,6 +7,22 @@ export const createCommentAction = (action) => ({
}, },
}); });
createCommentAction.success = (localId, action) => ({
type: ActionTypes.COMMENT_ACTION_CREATE__SUCCESS,
payload: {
localId,
action,
},
});
createCommentAction.failure = (localId, error) => ({
type: ActionTypes.COMMENT_ACTION_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const updateCommentAction = (id, data) => ({ export const updateCommentAction = (id, data) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE, type: ActionTypes.COMMENT_ACTION_UPDATE,
payload: { payload: {
@ -17,6 +31,21 @@ export const updateCommentAction = (id, data) => ({
}, },
}); });
updateCommentAction.success = (action) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE__SUCCESS,
payload: {
action,
},
});
updateCommentAction.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const deleteCommentAction = (id) => ({ export const deleteCommentAction = (id) => ({
type: ActionTypes.COMMENT_ACTION_DELETE, type: ActionTypes.COMMENT_ACTION_DELETE,
payload: { payload: {
@ -24,71 +53,15 @@ export const deleteCommentAction = (id) => ({
}, },
}); });
/* Events */ deleteCommentAction.success = (action) => ({
type: ActionTypes.COMMENT_ACTION_DELETE__SUCCESS,
export const createCommentActionRequested = (localId, data) => ({
type: ActionTypes.COMMENT_ACTION_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createCommentActionSucceeded = (localId, action) => ({
type: ActionTypes.COMMENT_ACTION_CREATE_SUCCEEDED,
payload: {
localId,
action,
},
});
export const createCommentActionFailed = (localId, error) => ({
type: ActionTypes.COMMENT_ACTION_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const updateCommentActionRequested = (id, data) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE_REQUESTED,
payload: {
id,
data,
},
});
export const updateCommentActionSucceeded = (action) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE_SUCCEEDED,
payload: { payload: {
action, action,
}, },
}); });
export const updateCommentActionFailed = (id, error) => ({ deleteCommentAction.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE_FAILED, type: ActionTypes.COMMENT_ACTION_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const deleteCommentActionRequested = (id) => ({
type: ActionTypes.COMMENT_ACTION_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteCommentActionSucceeded = (action) => ({
type: ActionTypes.COMMENT_ACTION_DELETE_SUCCEEDED,
payload: {
action,
},
});
export const deleteCommentActionFailed = (id, error) => ({
type: ActionTypes.COMMENT_ACTION_DELETE_FAILED,
payload: { payload: {
id, id,
error, error,

View file

@ -1,9 +1,41 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Events */
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export const coreInitialized = () => ({ export const initializeCore = (
type: ActionTypes.CORE_INITIALIZED, user,
payload: {}, board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
) => ({
type: ActionTypes.CORE_INITIALIZE,
payload: {
user,
board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
},
}); });

View file

@ -0,0 +1,22 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const handleActionCreate = (action) => ({
type: EntryActionTypes.ACTION_CREATE_HANDLE,
payload: {
action,
},
});
export const handleActionUpdate = (action) => ({
type: EntryActionTypes.ACTION_UPDATE_HANDLE,
payload: {
action,
},
});
export const handleActionDelete = (action) => ({
type: EntryActionTypes.ACTION_DELETE_HANDLE,
payload: {
action,
},
});

View file

@ -7,6 +7,14 @@ export const createAttachmentInCurrentCard = (data) => ({
}, },
}); });
export const handleAttachmentCreate = (attachment, requestId) => ({
type: EntryActionTypes.ATTACHMENT_CREATE_HANDLE,
payload: {
attachment,
requestId,
},
});
export const updateAttachment = (id, data) => ({ export const updateAttachment = (id, data) => ({
type: EntryActionTypes.ATTACHMENT_UPDATE, type: EntryActionTypes.ATTACHMENT_UPDATE,
payload: { payload: {
@ -15,9 +23,23 @@ export const updateAttachment = (id, data) => ({
}, },
}); });
export const handleAttachmentUpdate = (attachment) => ({
type: EntryActionTypes.ATTACHMENT_UPDATE_HANDLE,
payload: {
attachment,
},
});
export const deleteAttachment = (id) => ({ export const deleteAttachment = (id) => ({
type: EntryActionTypes.ATTACHMENT_DELETE, type: EntryActionTypes.ATTACHMENT_DELETE,
payload: { payload: {
id, id,
}, },
}); });
export const handleAttachmentDelete = (attachment) => ({
type: EntryActionTypes.ATTACHMENT_DELETE_HANDLE,
payload: {
attachment,
},
});

View file

@ -0,0 +1,29 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createMembershipInCurrentBoard = (data) => ({
type: EntryActionTypes.MEMBERSHIP_IN_CURRENT_BOARD_CREATE,
payload: {
data,
},
});
export const handleBoardMembershipCreate = (boardMembership) => ({
type: EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
payload: {
boardMembership,
},
});
export const deleteBoardMembership = (id) => ({
type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE,
payload: {
id,
},
});
export const handleBoardMembershipDelete = (boardMembership) => ({
type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,
payload: {
boardMembership,
},
});

View file

@ -7,6 +7,13 @@ export const createBoardInCurrentProject = (data) => ({
}, },
}); });
export const handleBoardCreate = (board) => ({
type: EntryActionTypes.BOARD_CREATE_HANDLE,
payload: {
board,
},
});
export const fetchBoard = (id) => ({ export const fetchBoard = (id) => ({
type: EntryActionTypes.BOARD_FETCH, type: EntryActionTypes.BOARD_FETCH,
payload: { payload: {
@ -22,6 +29,13 @@ export const updateBoard = (id, data) => ({
}, },
}); });
export const handleBoardUpdate = (board) => ({
type: EntryActionTypes.BOARD_UPDATE_HANDLE,
payload: {
board,
},
});
export const moveBoard = (id, index) => ({ export const moveBoard = (id, index) => ({
type: EntryActionTypes.BOARD_MOVE, type: EntryActionTypes.BOARD_MOVE,
payload: { payload: {
@ -36,3 +50,10 @@ export const deleteBoard = (id) => ({
id, id,
}, },
}); });
export const handleBoardDelete = (board) => ({
type: EntryActionTypes.BOARD_DELETE_HANDLE,
payload: {
board,
},
});

View file

@ -8,6 +8,13 @@ export const createCard = (listId, data) => ({
}, },
}); });
export const handleCardCreate = (card) => ({
type: EntryActionTypes.CARD_CREATE_HANDLE,
payload: {
card,
},
});
export const updateCard = (id, data) => ({ export const updateCard = (id, data) => ({
type: EntryActionTypes.CARD_UPDATE, type: EntryActionTypes.CARD_UPDATE,
payload: { payload: {
@ -23,6 +30,13 @@ export const updateCurrentCard = (data) => ({
}, },
}); });
export const handleCardUpdate = (card) => ({
type: EntryActionTypes.CARD_UPDATE_HANDLE,
payload: {
card,
},
});
export const moveCard = (id, listId, index = 0) => ({ export const moveCard = (id, listId, index = 0) => ({
type: EntryActionTypes.CARD_MOVE, type: EntryActionTypes.CARD_MOVE,
payload: { payload: {
@ -70,3 +84,10 @@ export const deleteCurrentCard = () => ({
type: EntryActionTypes.CURRENT_CARD_DELETE, type: EntryActionTypes.CURRENT_CARD_DELETE,
payload: {}, payload: {},
}); });
export const handleCardDelete = (card) => ({
type: EntryActionTypes.CARD_DELETE_HANDLE,
payload: {
card,
},
});

View file

@ -0,0 +1,7 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const initializeCore = () => ({
type: EntryActionTypes.CORE_INITIALIZE,
payload: {},
});

View file

@ -1,14 +1,18 @@
export * from './socket';
export * from './login'; export * from './login';
export * from './core';
export * from './modal'; export * from './modal';
export * from './user'; export * from './user';
export * from './project'; export * from './project';
export * from './project-membership'; export * from './project-manager';
export * from './board'; export * from './board';
export * from './board-membership';
export * from './label'; export * from './label';
export * from './list'; export * from './list';
export * from './card'; export * from './card';
export * from './task'; export * from './task';
export * from './attachment'; export * from './attachment';
export * from './actions'; export * from './actions';
export * from './action';
export * from './comment-action'; export * from './comment-action';
export * from './notification'; export * from './notification';

View file

@ -7,6 +7,13 @@ export const createLabelInCurrentBoard = (data) => ({
}, },
}); });
export const handleLabelCreate = (label) => ({
type: EntryActionTypes.LABEL_CREATE_HANDLE,
payload: {
label,
},
});
export const updateLabel = (id, data) => ({ export const updateLabel = (id, data) => ({
type: EntryActionTypes.LABEL_UPDATE, type: EntryActionTypes.LABEL_UPDATE,
payload: { payload: {
@ -15,6 +22,13 @@ export const updateLabel = (id, data) => ({
}, },
}); });
export const handleLabelUpdate = (label) => ({
type: EntryActionTypes.LABEL_UPDATE_HANDLE,
payload: {
label,
},
});
export const deleteLabel = (id) => ({ export const deleteLabel = (id) => ({
type: EntryActionTypes.LABEL_DELETE, type: EntryActionTypes.LABEL_DELETE,
payload: { payload: {
@ -22,6 +36,13 @@ export const deleteLabel = (id) => ({
}, },
}); });
export const handleLabelDelete = (label) => ({
type: EntryActionTypes.LABEL_DELETE_HANDLE,
payload: {
label,
},
});
export const addLabelToCard = (id, cardId) => ({ export const addLabelToCard = (id, cardId) => ({
type: EntryActionTypes.LABEL_TO_CARD_ADD, type: EntryActionTypes.LABEL_TO_CARD_ADD,
payload: { payload: {
@ -37,6 +58,13 @@ export const addLabelToCurrentCard = (id) => ({
}, },
}); });
export const handleLabelToCardAdd = (cardLabel) => ({
type: EntryActionTypes.LABEL_TO_CARD_ADD_HANDLE,
payload: {
cardLabel,
},
});
export const removeLabelFromCard = (id, cardId) => ({ export const removeLabelFromCard = (id, cardId) => ({
type: EntryActionTypes.LABEL_FROM_CARD_REMOVE, type: EntryActionTypes.LABEL_FROM_CARD_REMOVE,
payload: { payload: {
@ -52,6 +80,13 @@ export const removeLabelFromCurrentCard = (id) => ({
}, },
}); });
export const handleLabelFromCardRemove = (cardLabel) => ({
type: EntryActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE,
payload: {
cardLabel,
},
});
export const addLabelToFilterInCurrentBoard = (id) => ({ export const addLabelToFilterInCurrentBoard = (id) => ({
type: EntryActionTypes.LABEL_TO_FILTER_IN_CURRENT_BOARD_ADD, type: EntryActionTypes.LABEL_TO_FILTER_IN_CURRENT_BOARD_ADD,
payload: { payload: {

View file

@ -7,6 +7,13 @@ export const createListInCurrentBoard = (data) => ({
}, },
}); });
export const handleListCreate = (list) => ({
type: EntryActionTypes.LIST_CREATE_HANDLE,
payload: {
list,
},
});
export const updateList = (id, data) => ({ export const updateList = (id, data) => ({
type: EntryActionTypes.LIST_UPDATE, type: EntryActionTypes.LIST_UPDATE,
payload: { payload: {
@ -15,6 +22,13 @@ export const updateList = (id, data) => ({
}, },
}); });
export const handleListUpdate = (list) => ({
type: EntryActionTypes.LIST_UPDATE_HANDLE,
payload: {
list,
},
});
export const moveList = (id, index) => ({ export const moveList = (id, index) => ({
type: EntryActionTypes.LIST_MOVE, type: EntryActionTypes.LIST_MOVE,
payload: { payload: {
@ -29,3 +43,10 @@ export const deleteList = (id) => ({
id, id,
}, },
}); });
export const handleListDelete = (list) => ({
type: EntryActionTypes.LIST_DELETE_HANDLE,
payload: {
list,
},
});

View file

@ -22,6 +22,13 @@ export const openProjectAddModal = () => ({
}, },
}); });
export const openProjectSettingsModal = () => ({
type: EntryActionTypes.MODAL_OPEN,
payload: {
type: ModalTypes.PROJECT_SETTINGS,
},
});
export const closeModal = () => ({ export const closeModal = () => ({
type: EntryActionTypes.MODAL_CLOSE, type: EntryActionTypes.MODAL_CLOSE,
payload: {}, payload: {},

View file

@ -1,9 +1,22 @@
import EntryActionTypes from '../../constants/EntryActionTypes'; import EntryActionTypes from '../../constants/EntryActionTypes';
// eslint-disable-next-line import/prefer-default-export export const handleNotificationCreate = (notification) => ({
type: EntryActionTypes.NOTIFICATION_CREATE_HANDLE,
payload: {
notification,
},
});
export const deleteNotification = (id) => ({ export const deleteNotification = (id) => ({
type: EntryActionTypes.NOTIFICATION_DELETE, type: EntryActionTypes.NOTIFICATION_DELETE,
payload: { payload: {
id, id,
}, },
}); });
export const handleNotificationDelete = (notification) => ({
type: EntryActionTypes.NOTIFICATION_DELETE_HANDLE,
payload: {
notification,
},
});

View file

@ -0,0 +1,29 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createManagerInCurrentProject = (data) => ({
type: EntryActionTypes.MANAGER_IN_CURRENT_PROJECT_CREATE,
payload: {
data,
},
});
export const handleProjectManagerCreate = (projectManager) => ({
type: EntryActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
payload: {
projectManager,
},
});
export const deleteProjectManager = (id) => ({
type: EntryActionTypes.PROJECT_MANAGER_DELETE,
payload: {
id,
},
});
export const handleProjectManagerDelete = (projectManager) => ({
type: EntryActionTypes.PROJECT_MANAGER_DELETE_HANDLE,
payload: {
projectManager,
},
});

View file

@ -1,15 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createMembershipInCurrentProject = (data) => ({
type: EntryActionTypes.MEMBERSHIP_IN_CURRENT_PROJECT_CREATE,
payload: {
data,
},
});
export const deleteProjectMembership = (id) => ({
type: EntryActionTypes.PROJECT_MEMBERSHIP_DELETE,
payload: {
id,
},
});

View file

@ -7,6 +7,13 @@ export const createProject = (data) => ({
}, },
}); });
export const handleProjectCreate = (project) => ({
type: EntryActionTypes.PROJECT_CREATE_HANDLE,
payload: {
project,
},
});
export const updateCurrentProject = (data) => ({ export const updateCurrentProject = (data) => ({
type: EntryActionTypes.CURRENT_PROJECT_UPDATE, type: EntryActionTypes.CURRENT_PROJECT_UPDATE,
payload: { payload: {
@ -14,6 +21,13 @@ export const updateCurrentProject = (data) => ({
}, },
}); });
export const handleProjectUpdate = (project) => ({
type: EntryActionTypes.PROJECT_UPDATE_HANDLE,
payload: {
project,
},
});
export const updateCurrentProjectBackgroundImage = (data) => ({ export const updateCurrentProjectBackgroundImage = (data) => ({
type: EntryActionTypes.CURRENT_PROJECT_BACKGROUND_IMAGE_UPDATE, type: EntryActionTypes.CURRENT_PROJECT_BACKGROUND_IMAGE_UPDATE,
payload: { payload: {
@ -25,3 +39,10 @@ export const deleteCurrentProject = () => ({
type: EntryActionTypes.CURRENT_PROJECT_DELETE, type: EntryActionTypes.CURRENT_PROJECT_DELETE,
payload: {}, payload: {},
}); });
export const handleProjectDelete = (project) => ({
type: EntryActionTypes.PROJECT_DELETE_HANDLE,
payload: {
project,
},
});

View file

@ -0,0 +1,11 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const handleSocketDisconnect = () => ({
type: EntryActionTypes.SOCKET_DISCONNECT_HANDLE,
payload: {},
});
export const handleSocketReconnect = () => ({
type: EntryActionTypes.SOCKET_RECONNECT_HANDLE,
payload: {},
});

View file

@ -7,6 +7,13 @@ export const createTaskInCurrentCard = (data) => ({
}, },
}); });
export const handleTaskCreate = (task) => ({
type: EntryActionTypes.TASK_CREATE_HANDLE,
payload: {
task,
},
});
export const updateTask = (id, data) => ({ export const updateTask = (id, data) => ({
type: EntryActionTypes.TASK_UPDATE, type: EntryActionTypes.TASK_UPDATE,
payload: { payload: {
@ -15,9 +22,23 @@ export const updateTask = (id, data) => ({
}, },
}); });
export const handleTaskUpdate = (task) => ({
type: EntryActionTypes.TASK_UPDATE_HANDLE,
payload: {
task,
},
});
export const deleteTask = (id) => ({ export const deleteTask = (id) => ({
type: EntryActionTypes.TASK_DELETE, type: EntryActionTypes.TASK_DELETE,
payload: { payload: {
id, id,
}, },
}); });
export const handleTaskDelete = (task) => ({
type: EntryActionTypes.TASK_DELETE_HANDLE,
payload: {
task,
},
});

View file

@ -7,6 +7,13 @@ export const createUser = (data) => ({
}, },
}); });
export const handleUserCreate = (user) => ({
type: EntryActionTypes.USER_CREATE_HANDLE,
payload: {
user,
},
});
export const clearUserCreateError = () => ({ export const clearUserCreateError = () => ({
type: EntryActionTypes.USER_CREATE_ERROR_CLEAR, type: EntryActionTypes.USER_CREATE_ERROR_CLEAR,
payload: {}, payload: {},
@ -27,6 +34,13 @@ export const updateCurrentUser = (data) => ({
}, },
}); });
export const handleUserUpdate = (user) => ({
type: EntryActionTypes.USER_UPDATE_HANDLE,
payload: {
user,
},
});
export const updateCurrentUserEmail = (data) => ({ export const updateCurrentUserEmail = (data) => ({
type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE, type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE,
payload: { payload: {
@ -77,6 +91,13 @@ export const deleteUser = (id) => ({
}, },
}); });
export const handleUserDelete = (user) => ({
type: EntryActionTypes.USER_DELETE_HANDLE,
payload: {
user,
},
});
export const addUserToCard = (id, cardId) => ({ export const addUserToCard = (id, cardId) => ({
type: EntryActionTypes.USER_TO_CARD_ADD, type: EntryActionTypes.USER_TO_CARD_ADD,
payload: { payload: {
@ -92,6 +113,13 @@ export const addUserToCurrentCard = (id) => ({
}, },
}); });
export const handleUserToCardAdd = (cardMembership) => ({
type: EntryActionTypes.USER_TO_CARD_ADD_HANDLE,
payload: {
cardMembership,
},
});
export const removeUserFromCard = (id, cardId) => ({ export const removeUserFromCard = (id, cardId) => ({
type: EntryActionTypes.USER_FROM_CARD_REMOVE, type: EntryActionTypes.USER_FROM_CARD_REMOVE,
payload: { payload: {
@ -107,6 +135,13 @@ export const removeUserFromCurrentCard = (id) => ({
}, },
}); });
export const handleUserFromCardRemove = (cardMembership) => ({
type: EntryActionTypes.USER_FROM_CARD_REMOVE_HANDLE,
payload: {
cardMembership,
},
});
export const addUserToFilterInCurrentBoard = (id) => ({ export const addUserToFilterInCurrentBoard = (id) => ({
type: EntryActionTypes.USER_TO_FILTER_IN_CURRENT_BOARD_ADD, type: EntryActionTypes.USER_TO_FILTER_IN_CURRENT_BOARD_ADD,
payload: { payload: {

View file

@ -1,22 +1,19 @@
export * from './router';
export * from './socket'; export * from './socket';
export * from './login'; export * from './login';
export * from './core'; export * from './core';
export * from './modal'; export * from './modal';
export * from './users';
export * from './user'; export * from './user';
export * from './projects';
export * from './project'; export * from './project';
export * from './project-membership'; export * from './project-manager';
export * from './board'; export * from './board';
export * from './board-membership';
export * from './label'; export * from './label';
export * from './list'; export * from './list';
export * from './card'; export * from './card';
export * from './card-membership';
export * from './card-label';
export * from './task'; export * from './task';
export * from './attachment'; export * from './attachment';
export * from './actions'; export * from './actions';
export * from './action'; export * from './action';
export * from './comment-action'; export * from './comment-action';
export * from './notifications';
export * from './notification'; export * from './notification';

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createLabel = (label) => ({ export const createLabel = (label) => ({
type: ActionTypes.LABEL_CREATE, type: ActionTypes.LABEL_CREATE,
payload: { payload: {
@ -9,6 +7,29 @@ export const createLabel = (label) => ({
}, },
}); });
createLabel.success = (localId, label) => ({
type: ActionTypes.LABEL_CREATE__SUCCESS,
payload: {
localId,
label,
},
});
createLabel.failure = (localId, error) => ({
type: ActionTypes.LABEL_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleLabelCreate = (label) => ({
type: ActionTypes.LABEL_CREATE_HANDLE,
payload: {
label,
},
});
export const updateLabel = (id, data) => ({ export const updateLabel = (id, data) => ({
type: ActionTypes.LABEL_UPDATE, type: ActionTypes.LABEL_UPDATE,
payload: { payload: {
@ -17,6 +38,28 @@ export const updateLabel = (id, data) => ({
}, },
}); });
updateLabel.success = (label) => ({
type: ActionTypes.LABEL_UPDATE__SUCCESS,
payload: {
label,
},
});
updateLabel.failure = (id, error) => ({
type: ActionTypes.LABEL_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleLabelUpdate = (label) => ({
type: ActionTypes.LABEL_UPDATE_HANDLE,
payload: {
label,
},
});
export const deleteLabel = (id) => ({ export const deleteLabel = (id) => ({
type: ActionTypes.LABEL_DELETE, type: ActionTypes.LABEL_DELETE,
payload: { payload: {
@ -24,6 +67,28 @@ export const deleteLabel = (id) => ({
}, },
}); });
deleteLabel.success = (label) => ({
type: ActionTypes.LABEL_DELETE__SUCCESS,
payload: {
label,
},
});
deleteLabel.failure = (id, error) => ({
type: ActionTypes.LABEL_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleLabelDelete = (label) => ({
type: ActionTypes.LABEL_DELETE_HANDLE,
payload: {
label,
},
});
export const addLabelToCard = (id, cardId) => ({ export const addLabelToCard = (id, cardId) => ({
type: ActionTypes.LABEL_TO_CARD_ADD, type: ActionTypes.LABEL_TO_CARD_ADD,
payload: { payload: {
@ -32,6 +97,29 @@ export const addLabelToCard = (id, cardId) => ({
}, },
}); });
addLabelToCard.success = (cardLabel) => ({
type: ActionTypes.LABEL_TO_CARD_ADD__SUCCESS,
payload: {
cardLabel,
},
});
addLabelToCard.failure = (id, cardId, error) => ({
type: ActionTypes.LABEL_TO_CARD_ADD__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleLabelToCardAdd = (cardLabel) => ({
type: ActionTypes.LABEL_TO_CARD_ADD_HANDLE,
payload: {
cardLabel,
},
});
export const removeLabelFromCard = (id, cardId) => ({ export const removeLabelFromCard = (id, cardId) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE, type: ActionTypes.LABEL_FROM_CARD_REMOVE,
payload: { payload: {
@ -40,6 +128,29 @@ export const removeLabelFromCard = (id, cardId) => ({
}, },
}); });
removeLabelFromCard.success = (cardLabel) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE__SUCCESS,
payload: {
cardLabel,
},
});
removeLabelFromCard.failure = (id, cardId, error) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleLabelFromCardRemove = (cardLabel) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE,
payload: {
cardLabel,
},
});
export const addLabelToBoardFilter = (id, boardId) => ({ export const addLabelToBoardFilter = (id, boardId) => ({
type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD, type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD,
payload: { payload: {
@ -55,95 +166,3 @@ export const removeLabelFromBoardFilter = (id, boardId) => ({
boardId, boardId,
}, },
}); });
/* Events */
export const createLabelRequested = (localId, data) => ({
type: ActionTypes.LABEL_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createLabelSucceeded = (localId, label) => ({
type: ActionTypes.LABEL_CREATE_SUCCEEDED,
payload: {
localId,
label,
},
});
export const createLabelFailed = (localId, error) => ({
type: ActionTypes.LABEL_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const createLabelReceived = (label) => ({
type: ActionTypes.LABEL_CREATE_RECEIVED,
payload: {
label,
},
});
export const updateLabelRequested = (id, data) => ({
type: ActionTypes.LABEL_UPDATE_REQUESTED,
payload: {
id,
data,
},
});
export const updateLabelSucceeded = (label) => ({
type: ActionTypes.LABEL_UPDATE_SUCCEEDED,
payload: {
label,
},
});
export const updateLabelFailed = (id, error) => ({
type: ActionTypes.LABEL_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const updateLabelReceived = (label) => ({
type: ActionTypes.LABEL_UPDATE_RECEIVED,
payload: {
label,
},
});
export const deleteLabelRequested = (id) => ({
type: ActionTypes.LABEL_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteLabelSucceeded = (label) => ({
type: ActionTypes.LABEL_DELETE_SUCCEEDED,
payload: {
label,
},
});
export const deleteLabelFailed = (id, error) => ({
type: ActionTypes.LABEL_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteLabelReceived = (label) => ({
type: ActionTypes.LABEL_DELETE_RECEIVED,
payload: {
label,
},
});

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createList = (list) => ({ export const createList = (list) => ({
type: ActionTypes.LIST_CREATE, type: ActionTypes.LIST_CREATE,
payload: { payload: {
@ -9,6 +7,29 @@ export const createList = (list) => ({
}, },
}); });
createList.success = (localId, list) => ({
type: ActionTypes.LIST_CREATE__SUCCESS,
payload: {
localId,
list,
},
});
createList.failure = (localId, error) => ({
type: ActionTypes.LIST_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleListCreate = (list) => ({
type: ActionTypes.LIST_CREATE_HANDLE,
payload: {
list,
},
});
export const updateList = (id, data) => ({ export const updateList = (id, data) => ({
type: ActionTypes.LIST_UPDATE, type: ActionTypes.LIST_UPDATE,
payload: { payload: {
@ -17,6 +38,28 @@ export const updateList = (id, data) => ({
}, },
}); });
updateList.success = (list) => ({
type: ActionTypes.LIST_UPDATE__SUCCESS,
payload: {
list,
},
});
updateList.failure = (id, error) => ({
type: ActionTypes.LIST_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleListUpdate = (list) => ({
type: ActionTypes.LIST_UPDATE_HANDLE,
payload: {
list,
},
});
export const deleteList = (id) => ({ export const deleteList = (id) => ({
type: ActionTypes.LIST_DELETE, type: ActionTypes.LIST_DELETE,
payload: { payload: {
@ -24,93 +67,23 @@ export const deleteList = (id) => ({
}, },
}); });
/* Events */ deleteList.success = (list) => ({
type: ActionTypes.LIST_DELETE__SUCCESS,
export const createListRequested = (localId, data) => ({
type: ActionTypes.LIST_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createListSucceeded = (localId, list) => ({
type: ActionTypes.LIST_CREATE_SUCCEEDED,
payload: {
localId,
list,
},
});
export const createListFailed = (localId, error) => ({
type: ActionTypes.LIST_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const createListReceived = (list) => ({
type: ActionTypes.LIST_CREATE_RECEIVED,
payload: { payload: {
list, list,
}, },
}); });
export const updateListRequested = (id, data) => ({ deleteList.failure = (id, error) => ({
type: ActionTypes.LIST_UPDATE_REQUESTED, type: ActionTypes.LIST_DELETE__FAILURE,
payload: {
id,
data,
},
});
export const updateListSucceeded = (list) => ({
type: ActionTypes.LIST_UPDATE_SUCCEEDED,
payload: {
list,
},
});
export const updateListFailed = (id, error) => ({
type: ActionTypes.LIST_UPDATE_FAILED,
payload: { payload: {
id, id,
error, error,
}, },
}); });
export const updateListReceived = (list) => ({ export const handleListDelete = (list) => ({
type: ActionTypes.LIST_UPDATE_RECEIVED, type: ActionTypes.LIST_DELETE_HANDLE,
payload: {
list,
},
});
export const deleteListRequested = (id) => ({
type: ActionTypes.LIST_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteListSucceeded = (list) => ({
type: ActionTypes.LIST_DELETE_SUCCEEDED,
payload: {
list,
},
});
export const deleteListFailed = (id, error) => ({
type: ActionTypes.LIST_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteListReceived = (list) => ({
type: ActionTypes.LIST_DELETE_RECEIVED,
payload: { payload: {
list, list,
}, },

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const authenticate = (data) => ({ export const authenticate = (data) => ({
type: ActionTypes.AUTHENTICATE, type: ActionTypes.AUTHENTICATE,
payload: { payload: {
@ -9,6 +7,20 @@ export const authenticate = (data) => ({
}, },
}); });
authenticate.success = (accessToken) => ({
type: ActionTypes.AUTHENTICATE__SUCCESS,
payload: {
accessToken,
},
});
authenticate.failure = (error) => ({
type: ActionTypes.AUTHENTICATE__FAILURE,
payload: {
error,
},
});
export const clearAuthenticateError = () => ({ export const clearAuthenticateError = () => ({
type: ActionTypes.AUTHENTICATE_ERROR_CLEAR, type: ActionTypes.AUTHENTICATE_ERROR_CLEAR,
payload: {}, payload: {},
@ -18,26 +30,3 @@ export const logout = () => ({
type: ActionTypes.LOGOUT, type: ActionTypes.LOGOUT,
payload: {}, payload: {},
}); });
/* Events */
export const authenticateRequested = (data) => ({
type: ActionTypes.AUTHENTICATE_REQUESTED,
payload: {
data,
},
});
export const authenticateSucceeded = (accessToken) => ({
type: ActionTypes.AUTHENTICATE_SUCCEEDED,
payload: {
accessToken,
},
});
export const authenticateFailed = (error) => ({
type: ActionTypes.AUTHENTICATE_FAILED,
payload: {
error,
},
});

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const openModal = (type) => ({ export const openModal = (type) => ({
type: ActionTypes.MODAL_OPEN, type: ActionTypes.MODAL_OPEN,
payload: { payload: {

View file

@ -1,19 +1,39 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Events */ export const handleNotificationCreate = (notification, users, cards, actions) => ({
type: ActionTypes.NOTIFICATION_CREATE_HANDLE,
export const createNotificationReceived = (notification, user, card, action) => ({
type: ActionTypes.NOTIFICATION_CREATE_RECEIVED,
payload: { payload: {
notification, notification,
user, users,
card, cards,
action, actions,
}, },
}); });
export const deleteNotificationReceived = (notification) => ({ export const deleteNotification = (id) => ({
type: ActionTypes.NOTIFICATION_DELETE_RECEIVED, type: ActionTypes.NOTIFICATION_DELETE,
payload: {
id,
},
});
deleteNotification.success = (notification) => ({
type: ActionTypes.NOTIFICATION_DELETE__SUCCESS,
payload: {
notification,
},
});
deleteNotification.failure = (id, error) => ({
type: ActionTypes.NOTIFICATION_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleNotificationDelete = (notification) => ({
type: ActionTypes.NOTIFICATION_DELETE_HANDLE,
payload: { payload: {
notification, notification,
}, },

View file

@ -1,56 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const deleteNotifications = (ids) => ({
type: ActionTypes.NOTIFICATIONS_DELETE,
payload: {
ids,
},
});
/* Events */
export const fetchNotificationsRequested = () => ({
type: ActionTypes.NOTIFICATIONS_FETCH_REQUESTED,
payload: {},
});
export const fetchNotificationsSucceeded = (notifications, users, cards, actions) => ({
type: ActionTypes.NOTIFICATIONS_FETCH_SUCCEEDED,
payload: {
notifications,
users,
cards,
actions,
},
});
export const fetchNotificationsFailed = (error) => ({
type: ActionTypes.NOTIFICATIONS_FETCH_FAILED,
payload: {
error,
},
});
export const deleteNotificationsRequested = (ids) => ({
type: ActionTypes.NOTIFICATIONS_DELETE_REQUESTED,
payload: {
ids,
},
});
export const deleteNotificationsSucceeded = (notifications) => ({
type: ActionTypes.NOTIFICATIONS_DELETE_SUCCEEDED,
payload: {
notifications,
},
});
export const deleteNotificationsFailed = (ids, error) => ({
type: ActionTypes.NOTIFICATIONS_DELETE_FAILED,
payload: {
ids,
error,
},
});

View file

@ -0,0 +1,101 @@
import ActionTypes from '../constants/ActionTypes';
export const createProjectManager = (projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE,
payload: {
projectManager,
},
});
createProjectManager.success = (localId, projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE__SUCCESS,
payload: {
localId,
projectManager,
},
});
createProjectManager.failure = (localId, error) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleProjectManagerCreate = (
projectManager,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
payload: {
projectManager,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
handleProjectManagerCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE__PROJECT_FETCH,
payload: {
id,
currentUserId,
currentBoardId,
},
});
export const deleteProjectManager = (id, isCurrentUser, isCurrentProject) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE,
payload: {
id,
isCurrentUser,
isCurrentProject,
},
});
deleteProjectManager.success = (projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE__SUCCESS,
payload: {
projectManager,
},
});
deleteProjectManager.failure = (id, error) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleProjectManagerDelete = (projectManager, isCurrentUser, isCurrentProject) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE_HANDLE,
payload: {
projectManager,
isCurrentUser,
isCurrentProject,
},
});

View file

@ -1,80 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createProjectMembership = (projectMembership) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_CREATE,
payload: {
projectMembership,
},
});
export const deleteProjectMembership = (id) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_DELETE,
payload: {
id,
},
});
/* Events */
export const createProjectMembershipRequested = (localId, data) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createProjectMembershipSucceeded = (localId, projectMembership) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_CREATE_SUCCEEDED,
payload: {
localId,
projectMembership,
},
});
export const createProjectMembershipFailed = (localId, error) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const createProjectMembershipReceived = (projectMembership, user) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_CREATE_RECEIVED,
payload: {
projectMembership,
user,
},
});
export const deleteProjectMembershipRequested = (id) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteProjectMembershipSucceeded = (projectMembership) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_DELETE_SUCCEEDED,
payload: {
projectMembership,
},
});
export const deleteProjectMembershipFailed = (id, error) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteProjectMembershipReceived = (projectMembership) => ({
type: ActionTypes.PROJECT_MEMBERSHIP_DELETE_RECEIVED,
payload: {
projectMembership,
},
});

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createProject = (data) => ({ export const createProject = (data) => ({
type: ActionTypes.PROJECT_CREATE, type: ActionTypes.PROJECT_CREATE,
payload: { payload: {
@ -9,6 +7,32 @@ export const createProject = (data) => ({
}, },
}); });
createProject.success = (project, projectManagers) => ({
type: ActionTypes.PROJECT_CREATE__SUCCESS,
payload: {
project,
projectManagers,
},
});
createProject.failure = (error) => ({
type: ActionTypes.PROJECT_CREATE__FAILURE,
payload: {
error,
},
});
export const handleProjectCreate = (project, users, projectManagers, boards, boardMemberships) => ({
type: ActionTypes.PROJECT_CREATE_HANDLE,
payload: {
project,
users,
projectManagers,
boards,
boardMemberships,
},
});
export const updateProject = (id, data) => ({ export const updateProject = (id, data) => ({
type: ActionTypes.PROJECT_UPDATE, type: ActionTypes.PROJECT_UPDATE,
payload: { payload: {
@ -17,6 +41,50 @@ export const updateProject = (id, data) => ({
}, },
}); });
updateProject.success = (project) => ({
type: ActionTypes.PROJECT_UPDATE__SUCCESS,
payload: {
project,
},
});
updateProject.failure = (id, error) => ({
type: ActionTypes.PROJECT_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleProjectUpdate = (project) => ({
type: ActionTypes.PROJECT_UPDATE_HANDLE,
payload: {
project,
},
});
export const updateProjectBackgroundImage = (id) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE,
payload: {
id,
},
});
updateProjectBackgroundImage.success = (project) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__SUCCESS,
payload: {
project,
},
});
updateProjectBackgroundImage.failure = (id, error) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const deleteProject = (id) => ({ export const deleteProject = (id) => ({
type: ActionTypes.PROJECT_DELETE, type: ActionTypes.PROJECT_DELETE,
payload: { payload: {
@ -24,118 +92,23 @@ export const deleteProject = (id) => ({
}, },
}); });
/* Events */ deleteProject.success = (project) => ({
type: ActionTypes.PROJECT_DELETE__SUCCESS,
export const createProjectRequested = (data) => ({
type: ActionTypes.PROJECT_CREATE_REQUESTED,
payload: {
data,
},
});
export const createProjectSucceeded = (project, users, projectMemberships, boards) => ({
type: ActionTypes.PROJECT_CREATE_SUCCEEDED,
payload: {
project,
users,
projectMemberships,
boards,
},
});
export const createProjectFailed = (error) => ({
type: ActionTypes.PROJECT_CREATE_FAILED,
payload: {
error,
},
});
export const createProjectReceived = (project, users, projectMemberships, boards) => ({
type: ActionTypes.PROJECT_CREATE_RECEIVED,
payload: {
project,
users,
projectMemberships,
boards,
},
});
export const updateProjectRequested = (id, data) => ({
type: ActionTypes.PROJECT_UPDATE_REQUESTED,
payload: {
id,
data,
},
});
export const updateProjectSucceeded = (project) => ({
type: ActionTypes.PROJECT_UPDATE_SUCCEEDED,
payload: { payload: {
project, project,
}, },
}); });
export const updateProjectFailed = (id, error) => ({ deleteProject.failure = (id, error) => ({
type: ActionTypes.PROJECT_UPDATE_FAILED, type: ActionTypes.PROJECT_DELETE__FAILURE,
payload: { payload: {
id, id,
error, error,
}, },
}); });
export const updateProjectReceived = (project) => ({ export const handleProjectDelete = (project) => ({
type: ActionTypes.PROJECT_UPDATE_RECEIVED, type: ActionTypes.PROJECT_DELETE_HANDLE,
payload: {
project,
},
});
export const updateProjectBackgroundImageRequested = (id) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE_REQUESTED,
payload: {
id,
},
});
export const updateProjectBackgroundImageSucceeded = (project) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE_SUCCEEDED,
payload: {
project,
},
});
export const updateProjectBackgroundImageFailed = (id, error) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const deleteProjectRequested = (id) => ({
type: ActionTypes.PROJECT_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteProjectSucceeded = (project) => ({
type: ActionTypes.PROJECT_DELETE_SUCCEEDED,
payload: {
project,
},
});
export const deleteProjectFailed = (id, error) => ({
type: ActionTypes.PROJECT_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteProjectReceived = (project) => ({
type: ActionTypes.PROJECT_DELETE_RECEIVED,
payload: { payload: {
project, project,
}, },

View file

@ -1,25 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
/* Events */
export const fetchProjectsRequested = () => ({
type: ActionTypes.PROJECTS_FETCH_REQUESTED,
payload: {},
});
export const fetchProjectsSucceeded = (projects, users, projectMemberships, boards) => ({
type: ActionTypes.PROJECTS_FETCH_SUCCEEDED,
payload: {
projects,
users,
projectMemberships,
boards,
},
});
export const fetchProjectsFailed = (error) => ({
type: ActionTypes.PROJECTS_FETCH_FAILED,
payload: {
error,
},
});

View file

@ -0,0 +1,40 @@
import ActionTypes from '../constants/ActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const handleLocationChange = (
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
notifications,
) => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE,
payload: {
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
notifications,
},
});
handleLocationChange.fetchBoard = (id) => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE__BOARD_FETCH,
payload: {
id,
},
});

View file

@ -1,18 +1,53 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
import SocketStatuses from '../constants/SocketStatuses';
/* Events */ export const handleSocketDisconnect = () => ({
type: ActionTypes.SOCKET_DISCONNECT_HANDLE,
payload: {},
});
export const socketDisconnected = () => ({ export const handleSocketReconnect = (
type: ActionTypes.SOCKET_STATUS_CHANGED, user,
board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
) => ({
type: ActionTypes.SOCKET_RECONNECT_HANDLE,
payload: { payload: {
status: SocketStatuses.DISCONNECTED, user,
board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
}, },
}); });
export const socketReconnected = () => ({ handleSocketReconnect.fetchCore = (currentUserId, currentBoardId) => ({
type: ActionTypes.SOCKET_STATUS_CHANGED, type: ActionTypes.SOCKET_RECONNECT_HANDLE__CORE_FETCH,
payload: { payload: {
status: SocketStatuses.RECONNECTED, currentUserId,
currentBoardId,
}, },
}); });

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createTask = (task) => ({ export const createTask = (task) => ({
type: ActionTypes.TASK_CREATE, type: ActionTypes.TASK_CREATE,
payload: { payload: {
@ -9,6 +7,29 @@ export const createTask = (task) => ({
}, },
}); });
createTask.success = (localId, task) => ({
type: ActionTypes.TASK_CREATE__SUCCESS,
payload: {
localId,
task,
},
});
createTask.failure = (localId, error) => ({
type: ActionTypes.TASK_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleTaskCreate = (task) => ({
type: ActionTypes.TASK_CREATE_HANDLE,
payload: {
task,
},
});
export const updateTask = (id, data) => ({ export const updateTask = (id, data) => ({
type: ActionTypes.TASK_UPDATE, type: ActionTypes.TASK_UPDATE,
payload: { payload: {
@ -17,6 +38,28 @@ export const updateTask = (id, data) => ({
}, },
}); });
updateTask.success = (task) => ({
type: ActionTypes.TASK_UPDATE__SUCCESS,
payload: {
task,
},
});
updateTask.failure = (id, error) => ({
type: ActionTypes.TASK_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleTaskUpdate = (task) => ({
type: ActionTypes.TASK_UPDATE_HANDLE,
payload: {
task,
},
});
export const deleteTask = (id) => ({ export const deleteTask = (id) => ({
type: ActionTypes.TASK_DELETE, type: ActionTypes.TASK_DELETE,
payload: { payload: {
@ -24,93 +67,23 @@ export const deleteTask = (id) => ({
}, },
}); });
/* Events */ deleteTask.success = (task) => ({
type: ActionTypes.TASK_DELETE__SUCCESS,
export const createTaskRequested = (localId, data) => ({
type: ActionTypes.TASK_CREATE_REQUESTED,
payload: {
localId,
data,
},
});
export const createTaskSucceeded = (localId, task) => ({
type: ActionTypes.TASK_CREATE_SUCCEEDED,
payload: {
localId,
task,
},
});
export const createTaskFailed = (localId, error) => ({
type: ActionTypes.TASK_CREATE_FAILED,
payload: {
localId,
error,
},
});
export const createTaskReceived = (task) => ({
type: ActionTypes.TASK_CREATE_RECEIVED,
payload: { payload: {
task, task,
}, },
}); });
export const updateTaskRequested = (id, data) => ({ deleteTask.failure = (id, error) => ({
type: ActionTypes.TASK_UPDATE_REQUESTED, type: ActionTypes.TASK_DELETE__FAILURE,
payload: {
id,
data,
},
});
export const updateTaskSucceeded = (task) => ({
type: ActionTypes.TASK_UPDATE_SUCCEEDED,
payload: {
task,
},
});
export const updateTaskFailed = (id, error) => ({
type: ActionTypes.TASK_UPDATE_FAILED,
payload: { payload: {
id, id,
error, error,
}, },
}); });
export const updateTaskReceived = (task) => ({ export const handleTaskDelete = (task) => ({
type: ActionTypes.TASK_UPDATE_RECEIVED, type: ActionTypes.TASK_DELETE_HANDLE,
payload: {
task,
},
});
export const deleteTaskRequested = (id) => ({
type: ActionTypes.TASK_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteTaskSucceeded = (task) => ({
type: ActionTypes.TASK_DELETE_SUCCEEDED,
payload: {
task,
},
});
export const deleteTaskFailed = (id, error) => ({
type: ActionTypes.TASK_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteTaskReceived = (task) => ({
type: ActionTypes.TASK_DELETE_RECEIVED,
payload: { payload: {
task, task,
}, },

View file

@ -1,7 +1,5 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
/* Actions */
export const createUser = (data) => ({ export const createUser = (data) => ({
type: ActionTypes.USER_CREATE, type: ActionTypes.USER_CREATE,
payload: { payload: {
@ -9,6 +7,27 @@ export const createUser = (data) => ({
}, },
}); });
createUser.success = (user) => ({
type: ActionTypes.USER_CREATE__SUCCESS,
payload: {
user,
},
});
createUser.failure = (error) => ({
type: ActionTypes.USER_CREATE__FAILURE,
payload: {
error,
},
});
export const handleUserCreate = (user) => ({
type: ActionTypes.USER_CREATE_HANDLE,
payload: {
user,
},
});
export const clearUserCreateError = () => ({ export const clearUserCreateError = () => ({
type: ActionTypes.USER_CREATE_ERROR_CLEAR, type: ActionTypes.USER_CREATE_ERROR_CLEAR,
payload: {}, payload: {},
@ -22,6 +41,53 @@ export const updateUser = (id, data) => ({
}, },
}); });
updateUser.success = (user) => ({
type: ActionTypes.USER_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUser.failure = (id, error) => ({
type: ActionTypes.USER_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleUserUpdate = (user, users, isCurrent) => ({
type: ActionTypes.USER_UPDATE_HANDLE,
payload: {
user,
users,
isCurrent,
},
});
export const updateUserEmail = (id, data) => ({
type: ActionTypes.USER_EMAIL_UPDATE,
payload: {
id,
data,
},
});
updateUserEmail.success = (user) => ({
type: ActionTypes.USER_EMAIL_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserEmail.failure = (id, error) => ({
type: ActionTypes.USER_EMAIL_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const clearUserEmailUpdateError = (id) => ({ export const clearUserEmailUpdateError = (id) => ({
type: ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR, type: ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR,
payload: { payload: {
@ -29,6 +95,29 @@ export const clearUserEmailUpdateError = (id) => ({
}, },
}); });
export const updateUserPassword = (id, data) => ({
type: ActionTypes.USER_PASSWORD_UPDATE,
payload: {
id,
data,
},
});
updateUserPassword.success = (user) => ({
type: ActionTypes.USER_PASSWORD_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserPassword.failure = (id, error) => ({
type: ActionTypes.USER_PASSWORD_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const clearUserPasswordUpdateError = (id) => ({ export const clearUserPasswordUpdateError = (id) => ({
type: ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR, type: ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR,
payload: { payload: {
@ -36,6 +125,29 @@ export const clearUserPasswordUpdateError = (id) => ({
}, },
}); });
export const updateUserUsername = (id, data) => ({
type: ActionTypes.USER_USERNAME_UPDATE,
payload: {
id,
data,
},
});
updateUserUsername.success = (user) => ({
type: ActionTypes.USER_USERNAME_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserUsername.failure = (id, error) => ({
type: ActionTypes.USER_USERNAME_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const clearUserUsernameUpdateError = (id) => ({ export const clearUserUsernameUpdateError = (id) => ({
type: ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR, type: ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR,
payload: { payload: {
@ -43,6 +155,28 @@ export const clearUserUsernameUpdateError = (id) => ({
}, },
}); });
export const updateUserAvatar = (id) => ({
type: ActionTypes.USER_AVATAR_UPDATE,
payload: {
id,
},
});
updateUserAvatar.success = (user) => ({
type: ActionTypes.USER_AVATAR_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserAvatar.failure = (id, error) => ({
type: ActionTypes.USER_AVATAR_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const deleteUser = (id) => ({ export const deleteUser = (id) => ({
type: ActionTypes.USER_DELETE, type: ActionTypes.USER_DELETE,
payload: { payload: {
@ -50,6 +184,28 @@ export const deleteUser = (id) => ({
}, },
}); });
deleteUser.success = (user) => ({
type: ActionTypes.USER_DELETE__SUCCESS,
payload: {
user,
},
});
deleteUser.failure = (id, error) => ({
type: ActionTypes.USER_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleUserDelete = (user) => ({
type: ActionTypes.USER_DELETE_HANDLE,
payload: {
user,
},
});
export const addUserToCard = (id, cardId, isCurrent) => ({ export const addUserToCard = (id, cardId, isCurrent) => ({
type: ActionTypes.USER_TO_CARD_ADD, type: ActionTypes.USER_TO_CARD_ADD,
payload: { payload: {
@ -59,6 +215,29 @@ export const addUserToCard = (id, cardId, isCurrent) => ({
}, },
}); });
addUserToCard.success = (cardMembership) => ({
type: ActionTypes.USER_TO_CARD_ADD__SUCCESS,
payload: {
cardMembership,
},
});
addUserToCard.failure = (id, cardId, error) => ({
type: ActionTypes.USER_TO_CARD_ADD__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleUserToCardAdd = (cardMembership) => ({
type: ActionTypes.USER_TO_CARD_ADD_HANDLE,
payload: {
cardMembership,
},
});
export const removeUserFromCard = (id, cardId) => ({ export const removeUserFromCard = (id, cardId) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE, type: ActionTypes.USER_FROM_CARD_REMOVE,
payload: { payload: {
@ -67,6 +246,29 @@ export const removeUserFromCard = (id, cardId) => ({
}, },
}); });
removeUserFromCard.success = (cardMembership) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE__SUCCESS,
payload: {
cardMembership,
},
});
removeUserFromCard.failure = (id, cardId, error) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleUserFromCardRemove = (cardMembership) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE_HANDLE,
payload: {
cardMembership,
},
});
export const addUserToBoardFilter = (id, boardId) => ({ export const addUserToBoardFilter = (id, boardId) => ({
type: ActionTypes.USER_TO_BOARD_FILTER_ADD, type: ActionTypes.USER_TO_BOARD_FILTER_ADD,
payload: { payload: {
@ -82,202 +284,3 @@ export const removeUserFromBoardFilter = (id, boardId) => ({
boardId, boardId,
}, },
}); });
/* Events */
export const createUserRequested = (data) => ({
type: ActionTypes.USER_CREATE_REQUESTED,
payload: {
data,
},
});
export const createUserSucceeded = (user) => ({
type: ActionTypes.USER_CREATE_SUCCEEDED,
payload: {
user,
},
});
export const createUserFailed = (error) => ({
type: ActionTypes.USER_CREATE_FAILED,
payload: {
error,
},
});
export const createUserReceived = (user) => ({
type: ActionTypes.USER_CREATE_RECEIVED,
payload: {
user,
},
});
export const fetchCurrentUserRequested = () => ({
type: ActionTypes.CURRENT_USER_FETCH_REQUESTED,
payload: {},
});
export const fetchCurrentUserSucceeded = (user) => ({
type: ActionTypes.CURRENT_USER_FETCH_SUCCEEDED,
payload: {
user,
},
});
export const fetchCurrentUserFailed = (error) => ({
type: ActionTypes.CURRENT_USER_FETCH_FAILED,
payload: {
error,
},
});
export const updateUserRequested = (id, data) => ({
type: ActionTypes.USER_UPDATE_REQUESTED,
payload: {
id,
data,
},
});
export const updateUserSucceeded = (user) => ({
type: ActionTypes.USER_UPDATE_SUCCEEDED,
payload: {
user,
},
});
export const updateUserFailed = (id, error) => ({
type: ActionTypes.USER_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const updateUserReceived = (user) => ({
type: ActionTypes.USER_UPDATE_RECEIVED,
payload: {
user,
},
});
export const updateUserEmailRequested = (id, data) => ({
type: ActionTypes.USER_EMAIL_UPDATE_REQUESTED,
payload: {
id,
data,
},
});
export const updateUserEmailSucceeded = (user) => ({
type: ActionTypes.USER_EMAIL_UPDATE_SUCCEEDED,
payload: {
user,
},
});
export const updateUserEmailFailed = (id, error) => ({
type: ActionTypes.USER_EMAIL_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const updateUserPasswordRequested = (id, data) => ({
type: ActionTypes.USER_PASSWORD_UPDATE_REQUESTED,
payload: {
id,
data,
},
});
export const updateUserPasswordSucceeded = (user) => ({
type: ActionTypes.USER_PASSWORD_UPDATE_SUCCEEDED,
payload: {
user,
},
});
export const updateUserPasswordFailed = (id, error) => ({
type: ActionTypes.USER_PASSWORD_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const updateUserUsernameRequested = (id, data) => ({
type: ActionTypes.USER_USERNAME_UPDATE_REQUESTED,
payload: {
id,
data,
},
});
export const updateUserUsernameSucceeded = (user) => ({
type: ActionTypes.USER_USERNAME_UPDATE_SUCCEEDED,
payload: {
user,
},
});
export const updateUserUsernameFailed = (id, error) => ({
type: ActionTypes.USER_USERNAME_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const updateUserAvatarRequested = (id) => ({
type: ActionTypes.USER_AVATAR_UPDATE_REQUESTED,
payload: {
id,
},
});
export const updateUserAvatarSucceeded = (user) => ({
type: ActionTypes.USER_AVATAR_UPDATE_SUCCEEDED,
payload: {
user,
},
});
export const updateUserAvatarFailed = (id, error) => ({
type: ActionTypes.USER_AVATAR_UPDATE_FAILED,
payload: {
id,
error,
},
});
export const deleteUserRequested = (id) => ({
type: ActionTypes.USER_DELETE_REQUESTED,
payload: {
id,
},
});
export const deleteUserSucceeded = (user) => ({
type: ActionTypes.USER_DELETE_SUCCEEDED,
payload: {
user,
},
});
export const deleteUserFailed = (id, error) => ({
type: ActionTypes.USER_DELETE_FAILED,
payload: {
id,
error,
},
});
export const deleteUserReceived = (user) => ({
type: ActionTypes.USER_DELETE_RECEIVED,
payload: {
user,
},
});

View file

@ -1,22 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
/* Events */
export const fetchUsersRequested = () => ({
type: ActionTypes.USERS_FETCH_REQUESTED,
payload: {},
});
export const fetchUsersSucceeded = (users) => ({
type: ActionTypes.USERS_FETCH_SUCCEEDED,
payload: {
users,
},
});
export const fetchUsersFailed = (error) => ({
type: ActionTypes.USERS_FETCH_FAILED,
payload: {
error,
},
});

View file

@ -0,0 +1,14 @@
import socket from './socket';
/* Actions */
const createBoardMembership = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/memberships`, data, headers);
const deleteBoardMembership = (id, headers) =>
socket.delete(`/board-memberships/${id}`, undefined, headers);
export default {
createBoardMembership,
deleteBoardMembership,
};

View file

@ -49,10 +49,6 @@ const createCard = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/cards`, transformCardData(data), headers).then((body) => ({ socket.post(`/boards/${boardId}/cards`, transformCardData(data), headers).then((body) => ({
...body, ...body,
item: transformCard(body.item), item: transformCard(body.item),
included: {
...body.included,
attachments: body.included.attachments.map(transformAttachment),
},
})); }));
const getCard = (id, headers) => const getCard = (id, headers) =>
@ -79,10 +75,6 @@ const makeHandleCardCreate = (next) => (body) => {
next({ next({
...body, ...body,
item: transformCard(body.item), item: transformCard(body.item),
included: {
...body.included,
attachments: body.included.attachments.map(transformAttachment),
},
}); });
}; };

View file

@ -3,8 +3,9 @@ import socket from './socket';
import accessTokens from './access-tokens'; import accessTokens from './access-tokens';
import users from './users'; import users from './users';
import projects from './projects'; import projects from './projects';
import projectMemberships from './project-memberships'; import projectManagers from './project-managers';
import boards from './boards'; import boards from './boards';
import boardMemberships from './board-memberships';
import labels from './labels'; import labels from './labels';
import lists from './lists'; import lists from './lists';
import cards from './cards'; import cards from './cards';
@ -22,8 +23,9 @@ export default {
...accessTokens, ...accessTokens,
...users, ...users,
...projects, ...projects,
...projectMemberships, ...projectManagers,
...boards, ...boards,
...boardMemberships,
...labels, ...labels,
...lists, ...lists,
...cards, ...cards,

View file

@ -14,24 +14,21 @@ const getNotifications = (headers) =>
}, },
})); }));
const updateNotifications = (ids, data, headers) => const getNotification = (id, headers) =>
socket.patch(`/notifications/${ids.join(',')}`, data, headers); socket.get(`/notifications/${id}`, undefined, headers).then((body) => ({
/* Event handlers */
const makeHandleNotificationCreate = (next) => (body) => {
next({
...body, ...body,
included: { included: {
...body.included, ...body.included,
cards: body.included.cards.map(transformCard), cards: body.included.cards.map(transformCard),
actions: body.included.actions.map(transformAction), actions: body.included.actions.map(transformAction),
}, },
}); }));
};
const updateNotifications = (ids, data, headers) =>
socket.patch(`/notifications/${ids.join(',')}`, data, headers);
export default { export default {
getNotifications, getNotifications,
getNotification,
updateNotifications, updateNotifications,
makeHandleNotificationCreate,
}; };

View file

@ -0,0 +1,14 @@
import socket from './socket';
/* Actions */
const createProjectManager = (projectId, data, headers) =>
socket.post(`/projects/${projectId}/managers`, data, headers);
const deleteProjectManager = (id, headers) =>
socket.delete(`/project-managers/${id}`, undefined, headers);
export default {
createProjectManager,
deleteProjectManager,
};

View file

@ -1,14 +0,0 @@
import socket from './socket';
/* Actions */
const createProjectMembership = (projectId, data, headers) =>
socket.post(`/projects/${projectId}/memberships`, data, headers);
const deleteProjectMembership = (id, headers) =>
socket.delete(`/project-memberships/${id}`, undefined, headers);
export default {
createProjectMembership,
deleteProjectMembership,
};

View file

@ -7,6 +7,8 @@ const getProjects = (headers) => socket.get('/projects', undefined, headers);
const createProject = (data, headers) => socket.post('/projects', data, headers); const createProject = (data, headers) => socket.post('/projects', data, headers);
const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers); const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
const updateProjectBackgroundImage = (id, data, headers) => const updateProjectBackgroundImage = (id, data, headers) =>
@ -17,6 +19,7 @@ const deleteProject = (id, headers) => socket.delete(`/projects/${id}`, undefine
export default { export default {
getProjects, getProjects,
createProject, createProject,
getProject,
updateProject, updateProject,
updateProjectBackgroundImage, updateProjectBackgroundImage,
deleteProject, deleteProject,

View file

@ -7,6 +7,8 @@ const getUsers = (headers) => socket.get('/users', undefined, headers);
const createUser = (data, headers) => socket.post('/users', data, headers); const createUser = (data, headers) => socket.post('/users', data, headers);
const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers);
const getCurrentUser = (headers) => socket.get('/users/me', undefined, headers); const getCurrentUser = (headers) => socket.get('/users/me', undefined, headers);
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers); const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
@ -26,6 +28,7 @@ const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, hea
export default { export default {
getUsers, getUsers,
createUser, createUser,
getUser,
getCurrentUser, getCurrentUser,
updateUser, updateUser,
updateUserEmail, updateUserEmail,

View file

@ -0,0 +1,78 @@
import React from 'react';
import PropTypes from 'prop-types';
import Filters from './Filters';
import Memberships from '../Memberships';
import styles from './BoardActions.module.scss';
const BoardActions = React.memo(
({
memberships,
labels,
filterUsers,
filterLabels,
allUsers,
canEditMemberships,
onMembershipCreate,
onMembershipDelete,
onUserToFilterAdd,
onUserFromFilterRemove,
onLabelToFilterAdd,
onLabelFromFilterRemove,
onLabelCreate,
onLabelUpdate,
onLabelDelete,
}) => {
return (
<div className={styles.actions}>
<div className={styles.action}>
<Memberships
items={memberships}
allUsers={allUsers}
canEdit={canEditMemberships}
onCreate={onMembershipCreate}
onDelete={onMembershipDelete}
/>
</div>
<div className={styles.action}>
<Filters
users={filterUsers}
labels={filterLabels}
allBoardMemberships={memberships}
allLabels={labels}
onUserAdd={onUserToFilterAdd}
onUserRemove={onUserFromFilterRemove}
onLabelAdd={onLabelToFilterAdd}
onLabelRemove={onLabelFromFilterRemove}
onLabelCreate={onLabelCreate}
onLabelUpdate={onLabelUpdate}
onLabelDelete={onLabelDelete}
/>
</div>
</div>
);
},
);
BoardActions.propTypes = {
/* eslint-disable react/forbid-prop-types */
memberships: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired,
filterUsers: PropTypes.array.isRequired,
filterLabels: PropTypes.array.isRequired,
allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
canEditMemberships: PropTypes.bool.isRequired,
onMembershipCreate: PropTypes.func.isRequired,
onMembershipDelete: PropTypes.func.isRequired,
onUserToFilterAdd: PropTypes.func.isRequired,
onUserFromFilterRemove: PropTypes.func.isRequired,
onLabelToFilterAdd: PropTypes.func.isRequired,
onLabelFromFilterRemove: PropTypes.func.isRequired,
onLabelCreate: PropTypes.func.isRequired,
onLabelUpdate: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
};
export default BoardActions;

View file

@ -0,0 +1,11 @@
:global(#app) {
.action {
margin-right: 20px;
}
.actions {
align-items: center;
display: flex;
margin: 20px 20px;
}
}

View file

@ -4,16 +4,16 @@ import { useTranslation } from 'react-i18next';
import User from '../User'; import User from '../User';
import Label from '../Label'; import Label from '../Label';
import ProjectMembershipsPopup from '../ProjectMembershipsPopup'; import BoardMembershipsPopup from '../BoardMembershipsPopup';
import LabelsPopup from '../LabelsPopup'; import LabelsPopup from '../LabelsPopup';
import styles from './Filter.module.scss'; import styles from './Filters.module.scss';
const Filter = React.memo( const Filters = React.memo(
({ ({
users, users,
labels, labels,
allProjectMemberships, allBoardMemberships,
allLabels, allLabels,
onUserAdd, onUserAdd,
onUserRemove, onUserRemove,
@ -40,10 +40,10 @@ const Filter = React.memo(
); );
return ( return (
<div className={styles.filters}> <>
<span className={styles.filter}> <span className={styles.filter}>
<ProjectMembershipsPopup <BoardMembershipsPopup
items={allProjectMemberships} items={allBoardMemberships}
currentUserIds={users.map((user) => user.id)} currentUserIds={users.map((user) => user.id)}
title={t('common.filterByMembers', { title={t('common.filterByMembers', {
context: 'title', context: 'title',
@ -55,7 +55,7 @@ const Filter = React.memo(
<span className={styles.filterTitle}>{`${t('common.members')}:`}</span> <span className={styles.filterTitle}>{`${t('common.members')}:`}</span>
{users.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>} {users.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}
</button> </button>
</ProjectMembershipsPopup> </BoardMembershipsPopup>
{users.map((user) => ( {users.map((user) => (
<span key={user.id} className={styles.filterItem}> <span key={user.id} className={styles.filterItem}>
<User <User
@ -96,16 +96,16 @@ const Filter = React.memo(
</span> </span>
))} ))}
</span> </span>
</div> </>
); );
}, },
); );
Filter.propTypes = { Filters.propTypes = {
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
users: PropTypes.array.isRequired, users: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired, labels: PropTypes.array.isRequired,
allProjectMemberships: PropTypes.array.isRequired, allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired, allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
onUserAdd: PropTypes.func.isRequired, onUserAdd: PropTypes.func.isRequired,
@ -117,4 +117,4 @@ Filter.propTypes = {
onLabelDelete: PropTypes.func.isRequired, onLabelDelete: PropTypes.func.isRequired,
}; };
export default Filter; export default Filters;

View file

@ -1,8 +1,6 @@
:global(#app) { :global(#app) {
.filter { .filter {
display: inline-block; margin-right: 10px;
line-height: 0;
margin-right: 16px;
} }
.filterButton { .filterButton {
@ -45,9 +43,4 @@
line-height: 20px; line-height: 20px;
padding: 2px 12px; padding: 2px 12px;
} }
.filters {
line-height: 0;
margin-bottom: 12px;
}
} }

View file

@ -0,0 +1,3 @@
import BoardActions from './BoardActions';
export default BoardActions;

View file

@ -8,7 +8,6 @@ import DroppableTypes from '../../constants/DroppableTypes';
import ListContainer from '../../containers/ListContainer'; import ListContainer from '../../containers/ListContainer';
import CardModalContainer from '../../containers/CardModalContainer'; import CardModalContainer from '../../containers/CardModalContainer';
import ListAdd from './ListAdd'; import ListAdd from './ListAdd';
import Filter from './Filter';
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg'; import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
import styles from './BoardKanban.module.scss'; import styles from './BoardKanban.module.scss';
@ -16,36 +15,19 @@ import styles from './BoardKanban.module.scss';
const parseDndId = (dndId) => dndId.split(':')[1]; const parseDndId = (dndId) => dndId.split(':')[1];
const BoardKanban = React.memo( const BoardKanban = React.memo(
({ ({ listIds, isCardModalOpened, canEdit, onListCreate, onListMove, onCardMove }) => {
listIds,
filterUsers,
filterLabels,
allProjectMemberships,
allLabels,
isCardModalOpened,
onListCreate,
onListMove,
onCardMove,
onUserToFilterAdd,
onUserFromFilterRemove,
onLabelToFilterAdd,
onLabelFromFilterRemove,
onLabelCreate,
onLabelUpdate,
onLabelDelete,
}) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [isAddListOpened, setIsAddListOpened] = useState(false); const [isListAddOpened, setIsListAddOpened] = useState(false);
const wrapper = useRef(null); const wrapper = useRef(null);
const prevPosition = useRef(null); const prevPosition = useRef(null);
const handleAddListClick = useCallback(() => { const handleAddListClick = useCallback(() => {
setIsAddListOpened(true); setIsListAddOpened(true);
}, []); }, []);
const handleAddListClose = useCallback(() => { const handleAddListClose = useCallback(() => {
setIsAddListOpened(false); setIsListAddOpened(false);
}, []); }, []);
const handleDragStart = useCallback(() => { const handleDragStart = useCallback(() => {
@ -119,10 +101,10 @@ const BoardKanban = React.memo(
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isAddListOpened) { if (isListAddOpened) {
window.scroll(document.body.scrollWidth, 0); window.scroll(document.body.scrollWidth, 0);
} }
}, [listIds, isAddListOpened]); }, [listIds, isListAddOpened]);
useEffect(() => { useEffect(() => {
window.addEventListener('mouseup', handleWindowMouseUp); window.addEventListener('mouseup', handleWindowMouseUp);
@ -138,19 +120,6 @@ const BoardKanban = React.memo(
<> <>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}> <div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
<Filter
users={filterUsers}
labels={filterLabels}
allProjectMemberships={allProjectMemberships}
allLabels={allLabels}
onUserAdd={onUserToFilterAdd}
onUserRemove={onUserFromFilterRemove}
onLabelAdd={onLabelToFilterAdd}
onLabelRemove={onLabelFromFilterRemove}
onLabelCreate={onLabelCreate}
onLabelUpdate={onLabelUpdate}
onLabelDelete={onLabelDelete}
/>
<div> <div>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}> <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal"> <Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal">
@ -165,26 +134,26 @@ const BoardKanban = React.memo(
<ListContainer key={listId} id={listId} index={index} /> <ListContainer key={listId} id={listId} index={index} />
))} ))}
{placeholder} {placeholder}
<div data-drag-scroller className={styles.list}> {canEdit && (
{isAddListOpened ? ( <div data-drag-scroller className={styles.list}>
<ListAdd {isListAddOpened ? (
isOpened={isAddListOpened} <ListAdd onCreate={onListCreate} onClose={handleAddListClose} />
onCreate={onListCreate} ) : (
onClose={handleAddListClose} <button
/> type="button"
) : ( className={styles.addListButton}
<button onClick={handleAddListClick}
type="button" >
className={styles.addListButton} <PlusMathIcon className={styles.addListButtonIcon} />
onClick={handleAddListClick} <span className={styles.addListButtonText}>
> {listIds.length > 0
<PlusMathIcon className={styles.addListButtonIcon} /> ? t('action.addAnotherList')
<span className={styles.addListButtonText}> : t('action.addList')}
{listIds.length > 0 ? t('action.addAnotherList') : t('action.addList')} </span>
</span> </button>
</button> )}
)} </div>
</div> )}
</div> </div>
)} )}
</Droppable> </Droppable>
@ -198,24 +167,12 @@ const BoardKanban = React.memo(
); );
BoardKanban.propTypes = { BoardKanban.propTypes = {
/* eslint-disable react/forbid-prop-types */ listIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
listIds: PropTypes.array.isRequired,
filterUsers: PropTypes.array.isRequired,
filterLabels: PropTypes.array.isRequired,
allProjectMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
isCardModalOpened: PropTypes.bool.isRequired, isCardModalOpened: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onListCreate: PropTypes.func.isRequired, onListCreate: PropTypes.func.isRequired,
onListMove: PropTypes.func.isRequired, onListMove: PropTypes.func.isRequired,
onCardMove: PropTypes.func.isRequired, onCardMove: PropTypes.func.isRequired,
onUserToFilterAdd: PropTypes.func.isRequired,
onUserFromFilterRemove: PropTypes.func.isRequired,
onLabelToFilterAdd: PropTypes.func.isRequired,
onLabelFromFilterRemove: PropTypes.func.isRequired,
onLabelCreate: PropTypes.func.isRequired,
onLabelUpdate: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
}; };
export default BoardKanban; export default BoardKanban;

View file

@ -38,7 +38,7 @@
} }
.list { .list {
margin: 0 20px 0 4px; margin-right: 20px;
width: 272px; width: 272px;
} }
@ -48,6 +48,16 @@
min-width: 100%; min-width: 100%;
} }
.panel {
align-items: center;
display: flex;
margin-bottom: 20px;
}
.panelItem {
margin-right: 20px;
}
.wrapper { .wrapper {
margin: 0 20px; margin: 0 20px;
} }

View file

@ -0,0 +1,5 @@
import { withPopup } from '../lib/popup';
import BoardMembershipsStep from './BoardMembershipsStep';
export default withPopup(BoardMembershipsStep);

View file

@ -6,9 +6,9 @@ import { Popup } from '../../lib/custom-ui';
import Item from './Item'; import Item from './Item';
import styles from './ProjectMembershipsStep.module.scss'; import styles from './BoardMembershipsStep.module.scss';
const ProjectMembershipsStep = React.memo( const BoardMembershipsStep = React.memo(
({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => { ({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => {
const [t] = useTranslation(); const [t] = useTranslation();
@ -48,7 +48,7 @@ const ProjectMembershipsStep = React.memo(
}, },
); );
ProjectMembershipsStep.propTypes = { BoardMembershipsStep.propTypes = {
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
currentUserIds: PropTypes.array.isRequired, currentUserIds: PropTypes.array.isRequired,
@ -59,9 +59,9 @@ ProjectMembershipsStep.propTypes = {
onBack: PropTypes.func, onBack: PropTypes.func,
}; };
ProjectMembershipsStep.defaultProps = { BoardMembershipsStep.defaultProps = {
title: 'common.members', title: 'common.members',
onBack: undefined, onBack: undefined,
}; };
export default ProjectMembershipsStep; export default BoardMembershipsStep;

View file

@ -0,0 +1,3 @@
import BoardMembershipsStep from './BoardMembershipsStep';
export default BoardMembershipsStep;

View file

@ -14,145 +14,117 @@ import EditPopup from './EditPopup';
import styles from './Boards.module.scss'; import styles from './Boards.module.scss';
const Boards = React.memo( const Boards = React.memo(({ items, currentId, canEdit, onCreate, onUpdate, onMove, onDelete }) => {
({ items, currentId, isEditable, onCreate, onUpdate, onMove, onDelete }) => { const tabsWrapper = useRef(null);
const tabsWrapper = useRef(null);
const handleWheel = useCallback(({ deltaY }) => { const handleWheel = useCallback(({ deltaY }) => {
tabsWrapper.current.scrollBy({ tabsWrapper.current.scrollBy({
left: deltaY, left: deltaY,
}); });
}, []); }, []);
const handleDragStart = useCallback(() => { const handleDragStart = useCallback(() => {
closePopup(); closePopup();
}, []); }, []);
const handleDragEnd = useCallback( const handleDragEnd = useCallback(
({ draggableId, source, destination }) => { ({ draggableId, source, destination }) => {
if (!destination || source.index === destination.index) { if (!destination || source.index === destination.index) {
return; return;
} }
onMove(draggableId, destination.index); onMove(draggableId, destination.index);
}, },
[onMove], [onMove],
); );
const handleUpdate = useCallback( const handleUpdate = useCallback(
(id, data) => { (id, data) => {
onUpdate(id, data); onUpdate(id, data);
}, },
[onUpdate], [onUpdate],
); );
const handleDelete = useCallback( const handleDelete = useCallback(
(id) => { (id) => {
onDelete(id); onDelete(id);
}, },
[onDelete], [onDelete],
); );
const renderItems = useCallback( const itemsNode = items.map((item, index) => (
(safeItems) => <Draggable
safeItems.map((item) => ( key={item.id}
<div key={item.id} className={styles.tabWrapper}> draggableId={item.id}
<div className={classNames(styles.tab, item.id === currentId && styles.tabActive)}> index={index}
{item.isPersisted ? ( isDragDisabled={!item.isPersisted || !canEdit}
>
{({ innerRef, draggableProps, dragHandleProps }) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...draggableProps} ref={innerRef} className={styles.tabWrapper}>
<div className={classNames(styles.tab, item.id === currentId && styles.tabActive)}>
{item.isPersisted ? (
<>
<Link <Link
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
to={Paths.BOARDS.replace(':id', item.id)} to={Paths.BOARDS.replace(':id', item.id)}
title={item.name} title={item.name}
className={styles.link} className={styles.link}
> >
{item.name} {item.name}
</Link> </Link>
) : ( {canEdit && (
<span className={styles.link}>{item.name}</span> <EditPopup
)} defaultData={pick(item, 'name')}
</div> onUpdate={(data) => handleUpdate(item.id, data)}
</div> onDelete={() => handleDelete(item.id)}
)), >
[currentId], <Button className={classNames(styles.editButton, styles.target)}>
); <Icon fitted name="pencil" size="small" />
</Button>
const renderEditableItems = useCallback( </EditPopup>
(safeItems) => )}
safeItems.map((item, index) => ( </>
<Draggable ) : (
key={item.id}
draggableId={item.id}
index={index}
isDragDisabled={!item.isPersisted}
>
{({ innerRef, draggableProps, dragHandleProps }) => (
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
<div {...draggableProps} ref={innerRef} className={styles.tabWrapper}> <span {...dragHandleProps} className={styles.link}>
<div className={classNames(styles.tab, item.id === currentId && styles.tabActive)}> {item.name}
{item.isPersisted ? ( </span>
<Link )}
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading </div>
to={Paths.BOARDS.replace(':id', item.id)} </div>
title={item.name} )}
className={styles.link} </Draggable>
> ));
{item.name}
</Link> return (
) : ( <div className={styles.wrapper} onWheel={handleWheel}>
// eslint-disable-next-line react/jsx-props-no-spreading <div ref={tabsWrapper} className={styles.tabsWrapper}>
<span {...dragHandleProps} className={styles.link}> <DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
{item.name} <Droppable droppableId="boards" type={DroppableTypes.BOARD} direction="horizontal">
</span> {({ innerRef, droppableProps, placeholder }) => (
)} // eslint-disable-next-line react/jsx-props-no-spreading
{item.isPersisted && ( <div {...droppableProps} ref={innerRef} className={styles.tabs}>
<EditPopup {itemsNode}
defaultData={pick(item, 'name')} {placeholder}
onUpdate={(data) => handleUpdate(item.id, data)} {canEdit && (
onDelete={() => handleDelete(item.id)} <AddPopup onCreate={onCreate}>
> <Button icon="plus" className={styles.addButton} />
<Button className={classNames(styles.editButton, styles.target)}> </AddPopup>
<Icon fitted name="pencil" size="small" /> )}
</Button>
</EditPopup>
)}
</div>
</div> </div>
)} )}
</Draggable> </Droppable>
)), </DragDropContext>
[currentId, handleUpdate, handleDelete],
);
return (
<div className={styles.wrapper} onWheel={handleWheel}>
<div ref={tabsWrapper} className={styles.tabsWrapper}>
{isEditable ? (
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="boards" type={DroppableTypes.BOARD} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...droppableProps} ref={innerRef} className={styles.tabs}>
{renderEditableItems(items)}
{placeholder}
<AddPopup onCreate={onCreate}>
<Button icon="plus" className={styles.addButton} />
</AddPopup>
</div>
)}
</Droppable>
</DragDropContext>
) : (
<div className={styles.tabs}>{renderItems(items)}</div>
)}
</div>
</div> </div>
); </div>
}, );
); });
Boards.propTypes = { Boards.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
currentId: PropTypes.string, currentId: PropTypes.string,
isEditable: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
onCreate: PropTypes.func.isRequired, onCreate: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired,

View file

@ -30,6 +30,7 @@
.link { .link {
color: #fff; color: #fff;
cursor: pointer;
display: block; display: block;
line-height: 20px; line-height: 20px;
padding: 10px 34px 6px 14px; padding: 10px 34px 6px 14px;
@ -99,15 +100,11 @@
} }
.wrapper { .wrapper {
border-bottom: 2px solid rgba(0, 0, 0, 0.24); border-bottom: 1px solid rgba(0, 0, 0, 0.24);
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
height: 38px; height: 38px;
overflow: hidden; overflow: hidden;
&:hover {
border-bottom: 0;
}
} }
} }

View file

@ -7,7 +7,7 @@ import { withPopup } from '../../lib/popup';
import { Popup } from '../../lib/custom-ui'; import { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks'; import { useSteps } from '../../hooks';
import ProjectMembershipsStep from '../ProjectMembershipsStep'; import BoardMembershipsStep from '../BoardMembershipsStep';
import LabelsStep from '../LabelsStep'; import LabelsStep from '../LabelsStep';
import DueDateEditStep from '../DueDateEditStep'; import DueDateEditStep from '../DueDateEditStep';
import TimerEditStep from '../TimerEditStep'; import TimerEditStep from '../TimerEditStep';
@ -29,7 +29,7 @@ const ActionsStep = React.memo(
({ ({
card, card,
projectsToLists, projectsToLists,
projectMemberships, boardMemberships,
currentUserIds, currentUserIds,
labels, labels,
currentLabelIds, currentLabelIds,
@ -102,8 +102,8 @@ const ActionsStep = React.memo(
switch (step.type) { switch (step.type) {
case StepTypes.USERS: case StepTypes.USERS:
return ( return (
<ProjectMembershipsStep <BoardMembershipsStep
items={projectMemberships} items={boardMemberships}
currentUserIds={currentUserIds} currentUserIds={currentUserIds}
onUserSelect={onUserAdd} onUserSelect={onUserAdd}
onUserDeselect={onUserRemove} onUserDeselect={onUserRemove}
@ -224,7 +224,7 @@ ActionsStep.propTypes = {
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
card: PropTypes.object.isRequired, card: PropTypes.object.isRequired,
projectsToLists: PropTypes.array.isRequired, projectsToLists: PropTypes.array.isRequired,
projectMemberships: PropTypes.array.isRequired, boardMemberships: PropTypes.array.isRequired,
currentUserIds: PropTypes.array.isRequired, currentUserIds: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired, labels: PropTypes.array.isRequired,
currentLabelIds: PropTypes.array.isRequired, currentLabelIds: PropTypes.array.isRequired,

View file

@ -33,8 +33,9 @@ const Card = React.memo(
labels, labels,
tasks, tasks,
allProjectsToLists, allProjectsToLists,
allProjectMemberships, allBoardMemberships,
allLabels, allLabels,
canEdit,
onUpdate, onUpdate,
onMove, onMove,
onTransfer, onTransfer,
@ -129,7 +130,7 @@ const Card = React.memo(
); );
return ( return (
<Draggable draggableId={`card:${id}`} index={index} isDragDisabled={!isPersisted}> <Draggable draggableId={`card:${id}`} index={index} isDragDisabled={!isPersisted || !canEdit}>
{({ innerRef, draggableProps, dragHandleProps }) => ( {({ innerRef, draggableProps, dragHandleProps }) => (
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
<div {...draggableProps} {...dragHandleProps} ref={innerRef} className={styles.wrapper}> <div {...draggableProps} {...dragHandleProps} ref={innerRef} className={styles.wrapper}>
@ -144,40 +145,42 @@ const Card = React.memo(
> >
{contentNode} {contentNode}
</Link> </Link>
<ActionsPopup {canEdit && (
card={{ <ActionsPopup
id, card={{
name, id,
dueDate, name,
timer, dueDate,
boardId, timer,
listId, boardId,
projectId, listId,
isPersisted, projectId,
}} isPersisted,
projectsToLists={allProjectsToLists} }}
projectMemberships={allProjectMemberships} projectsToLists={allProjectsToLists}
currentUserIds={users.map((user) => user.id)} boardMemberships={allBoardMemberships}
labels={allLabels} currentUserIds={users.map((user) => user.id)}
currentLabelIds={labels.map((label) => label.id)} labels={allLabels}
onNameEdit={handleNameEdit} currentLabelIds={labels.map((label) => label.id)}
onUpdate={onUpdate} onNameEdit={handleNameEdit}
onMove={onMove} onUpdate={onUpdate}
onTransfer={onTransfer} onMove={onMove}
onDelete={onDelete} onTransfer={onTransfer}
onUserAdd={onUserAdd} onDelete={onDelete}
onUserRemove={onUserRemove} onUserAdd={onUserAdd}
onBoardFetch={onBoardFetch} onUserRemove={onUserRemove}
onLabelAdd={onLabelAdd} onBoardFetch={onBoardFetch}
onLabelRemove={onLabelRemove} onLabelAdd={onLabelAdd}
onLabelCreate={onLabelCreate} onLabelRemove={onLabelRemove}
onLabelUpdate={onLabelUpdate} onLabelCreate={onLabelCreate}
onLabelDelete={onLabelDelete} onLabelUpdate={onLabelUpdate}
> onLabelDelete={onLabelDelete}
<Button className={classNames(styles.actionsButton, styles.target)}> >
<Icon fitted name="pencil" size="small" /> <Button className={classNames(styles.actionsButton, styles.target)}>
</Button> <Icon fitted name="pencil" size="small" />
</ActionsPopup> </Button>
</ActionsPopup>
)}
</> </>
) : ( ) : (
<span className={styles.content}>{contentNode}</span> <span className={styles.content}>{contentNode}</span>
@ -208,9 +211,10 @@ Card.propTypes = {
labels: PropTypes.array.isRequired, labels: PropTypes.array.isRequired,
tasks: PropTypes.array.isRequired, tasks: PropTypes.array.isRequired,
allProjectsToLists: PropTypes.array.isRequired, allProjectsToLists: PropTypes.array.isRequired,
allProjectMemberships: PropTypes.array.isRequired, allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired, allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired,

View file

@ -54,7 +54,6 @@
border-radius: 3px; border-radius: 3px;
box-shadow: 0 1px 0 #ccc; box-shadow: 0 1px 0 #ccc;
position: relative; position: relative;
cursor: pointer;
&:hover { &:hover {
background: #f5f6f7; background: #f5f6f7;
@ -67,7 +66,6 @@
} }
.content { .content {
cursor: grab;
display: block; display: block;
&:after { &:after {

View file

@ -2,7 +2,6 @@
.button { .button {
background: transparent; background: transparent;
border: none; border: none;
cursor: pointer;
line-height: 0; line-height: 0;
margin: 0 -8px; margin: 0 -8px;
outline: none; outline: none;
@ -12,7 +11,6 @@
.count { .count {
color: #888; color: #888;
cursor: pointer;
display: inline-block; display: inline-block;
font-size: 12px; font-size: 12px;
line-height: 12px; line-height: 12px;
@ -77,7 +75,6 @@
.tasks { .tasks {
color: #333; color: #333;
cursor: grab;
list-style: none; list-style: none;
margin: -2px 0 0; margin: -2px 0 0;
padding-left: 0; padding-left: 0;

View file

@ -14,7 +14,8 @@ const Actions = React.memo(
items, items,
isFetching, isFetching,
isAllFetched, isAllFetched,
isEditable, canEdit,
canEditAllComments,
onFetch, onFetch,
onCommentCreate, onCommentCreate,
onCommentUpdate, onCommentUpdate,
@ -38,13 +39,15 @@ const Actions = React.memo(
return ( return (
<> <>
<div className={styles.contentModule}> {canEdit && (
<div className={styles.moduleWrapper}> <div className={styles.contentModule}>
<Icon name="comment outline" className={styles.moduleIcon} /> <div className={styles.moduleWrapper}>
<div className={styles.moduleHeader}>{t('common.addComment')}</div> <Icon name="comment outline" className={styles.moduleIcon} />
<CommentAdd onCreate={onCommentCreate} /> <div className={styles.moduleHeader}>{t('common.addComment')}</div>
<CommentAdd onCreate={onCommentCreate} />
</div>
</div> </div>
</div> )}
<div className={styles.contentModule}> <div className={styles.contentModule}>
<div className={styles.moduleWrapper}> <div className={styles.moduleWrapper}>
<Icon name="list ul" className={styles.moduleIcon} /> <Icon name="list ul" className={styles.moduleIcon} />
@ -59,7 +62,7 @@ const Actions = React.memo(
createdAt={item.createdAt} createdAt={item.createdAt}
isPersisted={item.isPersisted} isPersisted={item.isPersisted}
user={item.user} user={item.user}
isEditable={isEditable} canEdit={(item.user.isCurrent && canEdit) || canEditAllComments}
onUpdate={(data) => handleCommentUpdate(item.id, data)} onUpdate={(data) => handleCommentUpdate(item.id, data)}
onDelete={() => handleCommentDelete(item.id)} onDelete={() => handleCommentDelete(item.id)}
/> />
@ -91,7 +94,8 @@ Actions.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isAllFetched: PropTypes.bool.isRequired, isAllFetched: PropTypes.bool.isRequired,
isEditable: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
canEditAllComments: PropTypes.bool.isRequired,
onFetch: PropTypes.func.isRequired, onFetch: PropTypes.func.isRequired,
onCommentCreate: PropTypes.func.isRequired, onCommentCreate: PropTypes.func.isRequired,
onCommentUpdate: PropTypes.func.isRequired, onCommentUpdate: PropTypes.func.isRequired,

View file

@ -12,7 +12,7 @@ import DeletePopup from '../../DeletePopup';
import styles from './ItemComment.module.scss'; import styles from './ItemComment.module.scss';
const ItemComment = React.memo( const ItemComment = React.memo(
({ data, createdAt, isPersisted, user, isEditable, onUpdate, onDelete }) => { ({ data, createdAt, isPersisted, user, canEdit, onUpdate, onDelete }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const commentEdit = useRef(null); const commentEdit = useRef(null);
@ -38,17 +38,17 @@ const ItemComment = React.memo(
</div> </div>
<CommentEdit ref={commentEdit} defaultData={data} onUpdate={onUpdate}> <CommentEdit ref={commentEdit} defaultData={data} onUpdate={onUpdate}>
<> <>
<Markdown source={data.text} linkTarget="_blank" className={styles.text} /> <Markdown linkTarget="_blank" className={styles.text}>
<Comment.Actions> {data.text}
{user.isCurrent && ( </Markdown>
{canEdit && (
<Comment.Actions>
<Comment.Action <Comment.Action
as="button" as="button"
content={t('action.edit')} content={t('action.edit')}
disabled={!isPersisted} disabled={!isPersisted}
onClick={handleEditClick} onClick={handleEditClick}
/> />
)}
{(user.isCurrent || isEditable) && (
<DeletePopup <DeletePopup
title={t('common.deleteComment', { title={t('common.deleteComment', {
context: 'title', context: 'title',
@ -63,8 +63,8 @@ const ItemComment = React.memo(
disabled={!isPersisted} disabled={!isPersisted}
/> />
</DeletePopup> </DeletePopup>
)} </Comment.Actions>
</Comment.Actions> )}
</> </>
</CommentEdit> </CommentEdit>
</div> </div>
@ -78,7 +78,7 @@ ItemComment.propTypes = {
createdAt: PropTypes.instanceOf(Date).isRequired, createdAt: PropTypes.instanceOf(Date).isRequired,
isPersisted: PropTypes.bool.isRequired, isPersisted: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
isEditable: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
}; };

View file

@ -10,7 +10,7 @@
position: absolute; position: absolute;
text-align: center; text-align: center;
width: 100%; width: 100%;
z-index: 1; z-index: 2000;
} }
.wrapper { .wrapper {

View file

@ -16,7 +16,7 @@ import User from '../User';
import Label from '../Label'; import Label from '../Label';
import DueDate from '../DueDate'; import DueDate from '../DueDate';
import Timer from '../Timer'; import Timer from '../Timer';
import ProjectMembershipsPopup from '../ProjectMembershipsPopup'; import BoardMembershipsPopup from '../BoardMembershipsPopup';
import LabelsPopup from '../LabelsPopup'; import LabelsPopup from '../LabelsPopup';
import DueDateEditPopup from '../DueDateEditPopup'; import DueDateEditPopup from '../DueDateEditPopup';
import TimerEditPopup from '../TimerEditPopup'; import TimerEditPopup from '../TimerEditPopup';
@ -43,9 +43,10 @@ const CardModal = React.memo(
attachments, attachments,
actions, actions,
allProjectsToLists, allProjectsToLists,
allProjectMemberships, allBoardMemberships,
allLabels, allLabels,
isEditable, canEdit,
canEditAllCommentActions,
onUpdate, onUpdate,
onMove, onMove,
onTransfer, onTransfer,
@ -126,137 +127,163 @@ const CardModal = React.memo(
const userIds = users.map((user) => user.id); const userIds = users.map((user) => user.id);
const labelIds = labels.map((label) => label.id); const labelIds = labels.map((label) => label.id);
return ( const contentNode = (
<Modal open closeIcon size="small" centered={false} onClose={onClose}> <Grid className={styles.grid}>
<AttachmentAddZone onCreate={onAttachmentCreate}> <Grid.Row className={styles.headerPadding}>
<Grid className={styles.grid}> <Grid.Column width={16} className={styles.headerPadding}>
<Grid.Row className={styles.headerPadding}> <div className={styles.headerWrapper}>
<Grid.Column width={16} className={styles.headerPadding}> <Icon name="list alternate outline" className={styles.moduleIcon} />
<div className={styles.headerWrapper}> <div className={styles.headerTitleWrapper}>
<Icon name="list alternate outline" className={styles.moduleIcon} /> {canEdit ? (
<div className={styles.headerTitle}> <NameField defaultValue={name} onUpdate={handleNameUpdate} />
<NameField defaultValue={name} onUpdate={handleNameUpdate} /> ) : (
</div> <div className={styles.headerTitle}>{name}</div>
</div> )}
</Grid.Column> </div>
</Grid.Row> </div>
<Grid.Row className={styles.modalPadding}> </Grid.Column>
<Grid.Column width={12} className={styles.contentPadding}> </Grid.Row>
{(users.length > 0 || labels.length > 0 || dueDate || timer) && ( <Grid.Row className={styles.modalPadding}>
<div className={styles.moduleWrapper}> <Grid.Column width={canEdit ? 12 : 16} className={styles.contentPadding}>
{users.length > 0 && ( {(users.length > 0 || labels.length > 0 || dueDate || timer) && (
<div className={styles.attachments}> <div className={styles.moduleWrapper}>
<div className={styles.text}> {users.length > 0 && (
{t('common.members', { <div className={styles.attachments}>
context: 'title', <div className={styles.text}>
})} {t('common.members', {
</div> context: 'title',
{users.map((user) => ( })}
<span key={user.id} className={styles.attachment}> </div>
<ProjectMembershipsPopup {users.map((user) => (
items={allProjectMemberships} <span key={user.id} className={styles.attachment}>
currentUserIds={userIds} {canEdit ? (
onUserSelect={onUserAdd} <BoardMembershipsPopup
onUserDeselect={onUserRemove} items={allBoardMemberships}
> currentUserIds={userIds}
<User name={user.name} avatarUrl={user.avatarUrl} /> onUserSelect={onUserAdd}
</ProjectMembershipsPopup> onUserDeselect={onUserRemove}
</span>
))}
<ProjectMembershipsPopup
items={allProjectMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<button
type="button"
className={classNames(styles.attachment, styles.dueDate)}
> >
<Icon name="add" size="small" className={styles.addAttachment} /> <User name={user.name} avatarUrl={user.avatarUrl} />
</button> </BoardMembershipsPopup>
</ProjectMembershipsPopup> ) : (
</div> <User name={user.name} avatarUrl={user.avatarUrl} />
)} )}
{labels.length > 0 && ( </span>
<div className={styles.attachments}> ))}
<div className={styles.text}> {canEdit && (
{t('common.labels', { <BoardMembershipsPopup
context: 'title', items={allBoardMemberships}
})} currentUserIds={userIds}
</div> onUserSelect={onUserAdd}
{labels.map((label) => ( onUserDeselect={onUserRemove}
<span key={label.id} className={styles.attachment}> >
<LabelsPopup <button
key={label.id} type="button"
items={allLabels} className={classNames(styles.attachment, styles.dueDate)}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Label name={label.name} color={label.color} />
</LabelsPopup>
</span>
))}
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
> >
<button <Icon name="add" size="small" className={styles.addAttachment} />
type="button" </button>
className={classNames(styles.attachment, styles.dueDate)} </BoardMembershipsPopup>
>
<Icon name="add" size="small" className={styles.addAttachment} />
</button>
</LabelsPopup>
</div>
)}
{dueDate && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.dueDate', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate value={dueDate} />
</DueDateEditPopup>
</span>
</div>
)}
{timer && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.timer', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Timer startedAt={timer.startedAt} total={timer.total} />
</TimerEditPopup>
</span>
</div>
)} )}
</div> </div>
)} )}
<div className={styles.contentModule}> {labels.length > 0 && (
<div className={styles.moduleWrapper}> <div className={styles.attachments}>
<Icon name="align justify" className={styles.moduleIcon} /> <div className={styles.text}>
<div className={styles.moduleHeader}>{t('common.description')}</div> {t('common.labels', {
context: 'title',
})}
</div>
{labels.map((label) => (
<span key={label.id} className={styles.attachment}>
{canEdit ? (
<LabelsPopup
key={label.id}
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Label name={label.name} color={label.color} />
</LabelsPopup>
) : (
<Label name={label.name} color={label.color} />
)}
</span>
))}
{canEdit && (
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<button
type="button"
className={classNames(styles.attachment, styles.dueDate)}
>
<Icon name="add" size="small" className={styles.addAttachment} />
</button>
</LabelsPopup>
)}
</div>
)}
{dueDate && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.dueDate', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
{canEdit ? (
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate value={dueDate} />
</DueDateEditPopup>
) : (
<DueDate value={dueDate} />
)}
</span>
</div>
)}
{timer && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.timer', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
{canEdit ? (
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Timer startedAt={timer.startedAt} total={timer.total} />
</TimerEditPopup>
) : (
<Timer startedAt={timer.startedAt} total={timer.total} />
)}
</span>
</div>
)}
</div>
)}
{(description || canEdit) && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="align justify" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.description')}</div>
{canEdit ? (
<DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}> <DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}>
{description ? ( {description ? (
<button type="button" className={styles.descriptionText}> <button type="button" className={styles.descriptionText}>
<Markdown linkStopPropagation source={description} linkTarget="_blank" /> <Markdown linkStopPropagation linkTarget="_blank">
{description}
</Markdown>
</button> </button>
) : ( ) : (
<button type="button" className={styles.descriptionButton}> <button type="button" className={styles.descriptionButton}>
@ -266,142 +293,164 @@ const CardModal = React.memo(
</button> </button>
)} )}
</DescriptionEdit> </DescriptionEdit>
</div> ) : (
</div> <div className={styles.descriptionText}>
<div className={styles.contentModule}> <Markdown linkStopPropagation linkTarget="_blank">
<div className={styles.moduleWrapper}> {description}
<Icon name="check square outline" className={styles.moduleIcon} /> </Markdown>
<div className={styles.moduleHeader}>{t('common.tasks')}</div>
<Tasks
items={tasks}
onCreate={onTaskCreate}
onUpdate={onTaskUpdate}
onDelete={onTaskDelete}
/>
</div>
</div>
{attachments.length > 0 && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="attach" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.attachments')}</div>
<Attachments
items={attachments}
onUpdate={onAttachmentUpdate}
onDelete={onAttachmentDelete}
onCoverUpdate={handleCoverUpdate}
/>
</div> </div>
</div> )}
)}
<Actions
items={actions}
isFetching={isActionsFetching}
isAllFetched={isAllActionsFetched}
isEditable={isEditable}
onFetch={onActionsFetch}
onCommentCreate={onCommentActionCreate}
onCommentUpdate={onCommentActionUpdate}
onCommentDelete={onCommentActionDelete}
/>
</Grid.Column>
<Grid.Column width={4} className={styles.sidebarPadding}>
<div className={styles.actions}>
<span className={styles.actionsTitle}>{t('action.addToCard')}</span>
<ProjectMembershipsPopup
items={allProjectMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<Button fluid className={styles.actionButton}>
<Icon name="user outline" className={styles.actionIcon} />
{t('common.members')}
</Button>
</ProjectMembershipsPopup>
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Button fluid className={styles.actionButton}>
<Icon name="bookmark outline" className={styles.actionIcon} />
{t('common.labels')}
</Button>
</LabelsPopup>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="calendar check outline" className={styles.actionIcon} />
{t('common.dueDate', {
context: 'title',
})}
</Button>
</DueDateEditPopup>
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="clock outline" className={styles.actionIcon} />
{t('common.timer')}
</Button>
</TimerEditPopup>
<AttachmentAddPopup onCreate={onAttachmentCreate}>
<Button fluid className={styles.actionButton}>
<Icon name="attach" className={styles.actionIcon} />
{t('common.attachment')}
</Button>
</AttachmentAddPopup>
</div> </div>
<div className={styles.actions}> </div>
<span className={styles.actionsTitle}>{t('common.actions')}</span> )}
{(tasks.length > 0 || canEdit) && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="check square outline" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.tasks')}</div>
<Tasks
items={tasks}
canEdit={canEdit}
onCreate={onTaskCreate}
onUpdate={onTaskUpdate}
onDelete={onTaskDelete}
/>
</div>
</div>
)}
{attachments.length > 0 && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="attach" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.attachments')}</div>
<Attachments
items={attachments}
onUpdate={onAttachmentUpdate}
onDelete={onAttachmentDelete}
onCoverUpdate={handleCoverUpdate}
/>
</div>
</div>
)}
<Actions
items={actions}
isFetching={isActionsFetching}
isAllFetched={isAllActionsFetched}
canEdit={canEdit}
canEditAllComments={canEditAllCommentActions}
onFetch={onActionsFetch}
onCommentCreate={onCommentActionCreate}
onCommentUpdate={onCommentActionUpdate}
onCommentDelete={onCommentActionDelete}
/>
</Grid.Column>
{canEdit && (
<Grid.Column width={4} className={styles.sidebarPadding}>
<div className={styles.actions}>
<span className={styles.actionsTitle}>{t('action.addToCard')}</span>
<BoardMembershipsPopup
items={allBoardMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<Button fluid className={styles.actionButton}>
<Icon name="user outline" className={styles.actionIcon} />
{t('common.members')}
</Button>
</BoardMembershipsPopup>
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Button fluid className={styles.actionButton}>
<Icon name="bookmark outline" className={styles.actionIcon} />
{t('common.labels')}
</Button>
</LabelsPopup>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="calendar check outline" className={styles.actionIcon} />
{t('common.dueDate', {
context: 'title',
})}
</Button>
</DueDateEditPopup>
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="clock outline" className={styles.actionIcon} />
{t('common.timer')}
</Button>
</TimerEditPopup>
<AttachmentAddPopup onCreate={onAttachmentCreate}>
<Button fluid className={styles.actionButton}>
<Icon name="attach" className={styles.actionIcon} />
{t('common.attachment')}
</Button>
</AttachmentAddPopup>
</div>
<div className={styles.actions}>
<span className={styles.actionsTitle}>{t('common.actions')}</span>
<Button
fluid
className={styles.actionButton}
onClick={handleToggleSubscriptionClick}
>
<Icon name="paper plane outline" className={styles.actionIcon} />
{isSubscribed ? t('action.unsubscribe') : t('action.subscribe')}
</Button>
<CardMovePopup
projectsToLists={allProjectsToLists}
defaultPath={{
projectId,
boardId,
listId,
}}
onMove={onMove}
onTransfer={onTransfer}
onBoardFetch={onBoardFetch}
>
<Button <Button
fluid fluid
className={styles.actionButton} className={styles.actionButton}
onClick={handleToggleSubscriptionClick} onClick={handleToggleSubscriptionClick}
> >
<Icon name="paper plane outline" className={styles.actionIcon} /> <Icon name="share square outline" className={styles.actionIcon} />
{isSubscribed ? t('action.unsubscribe') : t('action.subscribe')} {t('action.move')}
</Button> </Button>
<CardMovePopup </CardMovePopup>
projectsToLists={allProjectsToLists} <DeletePopup
defaultPath={{ title={t('common.deleteCard', {
projectId, context: 'title',
boardId, })}
listId, content={t('common.areYouSureYouWantToDeleteThisCard')}
}} buttonContent={t('action.deleteCard')}
onMove={onMove} onConfirm={onDelete}
onTransfer={onTransfer} >
onBoardFetch={onBoardFetch} <Button fluid className={styles.actionButton}>
> <Icon name="trash alternate outline" className={styles.actionIcon} />
<Button {t('action.delete')}
fluid </Button>
className={styles.actionButton} </DeletePopup>
onClick={handleToggleSubscriptionClick} </div>
> </Grid.Column>
<Icon name="share square outline" className={styles.actionIcon} /> )}
{t('action.move')} </Grid.Row>
</Button> </Grid>
</CardMovePopup> );
<DeletePopup
title={t('common.deleteCard', { return (
context: 'title', <Modal open closeIcon size="small" centered={false} onClose={onClose}>
})} {canEdit ? (
content={t('common.areYouSureYouWantToDeleteThisCard')} <AttachmentAddZone onCreate={onAttachmentCreate}>{contentNode}</AttachmentAddZone>
buttonContent={t('action.deleteCard')} ) : (
onConfirm={onDelete} contentNode
> )}
<Button fluid className={styles.actionButton}>
<Icon name="trash alternate outline" className={styles.actionIcon} />
{t('action.delete')}
</Button>
</DeletePopup>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</AttachmentAddZone>
</Modal> </Modal>
); );
}, },
@ -425,10 +474,11 @@ CardModal.propTypes = {
attachments: PropTypes.array.isRequired, attachments: PropTypes.array.isRequired,
actions: PropTypes.array.isRequired, actions: PropTypes.array.isRequired,
allProjectsToLists: PropTypes.array.isRequired, allProjectsToLists: PropTypes.array.isRequired,
allProjectMemberships: PropTypes.array.isRequired, allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired, allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
isEditable: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
canEditAllCommentActions: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired,

View file

@ -44,7 +44,6 @@
} }
.attachment { .attachment {
cursor: pointer;
display: inline-block; display: inline-block;
margin: 0 4px 4px 0; margin: 0 4px 4px 0;
max-width: 100%; max-width: 100%;
@ -70,6 +69,7 @@
border: none; border: none;
border-radius: 3px; border-radius: 3px;
color: #6b808c; color: #6b808c;
cursor: pointer;
line-height: 20px; line-height: 20px;
outline: none; outline: none;
padding: 6px 14px; padding: 6px 14px;
@ -141,6 +141,13 @@
} }
.headerTitle { .headerTitle {
color: #17394d;
font-size: 20px;
font-weight: 700;
line-height: 24px;
}
.headerTitleWrapper {
margin: 4px 0; margin: 4px 0;
padding: 6px 0 0; padding: 6px 0 0;
} }
@ -193,8 +200,4 @@
margin: 0 8px 4px 0; margin: 0 8px 4px 0;
text-transform: uppercase; text-transform: uppercase;
} }
/* .wrapper {
min-width: 768px;
} */
} }

View file

@ -8,14 +8,14 @@ import ActionsPopup from './ActionsPopup';
import styles from './Item.module.scss'; import styles from './Item.module.scss';
const Item = React.memo(({ name, isCompleted, isPersisted, onUpdate, onDelete }) => { const Item = React.memo(({ name, isCompleted, isPersisted, canEdit, onUpdate, onDelete }) => {
const nameEdit = useRef(null); const nameEdit = useRef(null);
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
if (isPersisted) { if (isPersisted && canEdit) {
nameEdit.current.open(); nameEdit.current.open();
} }
}, [isPersisted]); }, [isPersisted, canEdit]);
const handleNameUpdate = useCallback( const handleNameUpdate = useCallback(
(newName) => { (newName) => {
@ -41,23 +41,26 @@ const Item = React.memo(({ name, isCompleted, isPersisted, onUpdate, onDelete })
<span className={styles.checkboxWrapper}> <span className={styles.checkboxWrapper}>
<Checkbox <Checkbox
checked={isCompleted} checked={isCompleted}
disabled={!isPersisted} disabled={!isPersisted || !canEdit}
className={styles.checkbox} className={styles.checkbox}
onChange={handleToggleChange} onChange={handleToggleChange}
/> />
</span> </span>
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}> <NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
<div className={styles.content}> <div className={classNames(canEdit && styles.contentHoverable)}>
{/* eslint-disable jsx-a11y/click-events-have-key-events, {/* eslint-disable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */} jsx-a11y/no-static-element-interactions */}
<span className={styles.text} onClick={handleClick}> <span
className={classNames(styles.text, canEdit && styles.textEditable)}
onClick={handleClick}
>
{/* eslint-enable jsx-a11y/click-events-have-key-events, {/* eslint-enable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */} jsx-a11y/no-static-element-interactions */}
<span className={classNames(styles.task, isCompleted && styles.taskCompleted)}> <span className={classNames(styles.task, isCompleted && styles.taskCompleted)}>
{name} {name}
</span> </span>
</span> </span>
{isPersisted && ( {isPersisted && canEdit && (
<ActionsPopup onNameEdit={handleNameEdit} onDelete={onDelete}> <ActionsPopup onNameEdit={handleNameEdit} onDelete={onDelete}>
<Button className={classNames(styles.button, styles.target)}> <Button className={classNames(styles.button, styles.target)}>
<Icon fitted name="pencil" size="small" /> <Icon fitted name="pencil" size="small" />
@ -74,6 +77,7 @@ Item.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
isCompleted: PropTypes.bool.isRequired, isCompleted: PropTypes.bool.isRequired,
isPersisted: PropTypes.bool.isRequired, isPersisted: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
}; };

View file

@ -30,7 +30,7 @@
height: 32px; height: 32px;
} }
.content:hover { .contentHoverable:hover {
background: rgba(9, 30, 66, 0.04); background: rgba(9, 30, 66, 0.04);
.target { .target {
@ -55,7 +55,6 @@
background: transparent; background: transparent;
border-radius: 3px; border-radius: 3px;
color: #17394d; color: #17394d;
cursor: pointer;
display: inline-block; display: inline-block;
font-size: 15px; font-size: 15px;
line-height: 1.5; line-height: 1.5;
@ -64,6 +63,10 @@
width: 100%; width: 100%;
} }
.textEditable {
cursor: pointer;
}
.wrapper { .wrapper {
border-radius: 3px; border-radius: 3px;
margin-left: -40px; margin-left: -40px;

View file

@ -8,7 +8,7 @@ import Add from './Add';
import styles from './Tasks.module.scss'; import styles from './Tasks.module.scss';
const Tasks = React.memo(({ items, onCreate, onUpdate, onDelete }) => { const Tasks = React.memo(({ items, canEdit, onCreate, onUpdate, onDelete }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const handleUpdate = useCallback( const handleUpdate = useCallback(
@ -45,23 +45,27 @@ const Tasks = React.memo(({ items, onCreate, onUpdate, onDelete }) => {
name={item.name} name={item.name}
isCompleted={item.isCompleted} isCompleted={item.isCompleted}
isPersisted={item.isPersisted} isPersisted={item.isPersisted}
canEdit={canEdit}
onUpdate={(data) => handleUpdate(item.id, data)} onUpdate={(data) => handleUpdate(item.id, data)}
onDelete={() => handleDelete(item.id)} onDelete={() => handleDelete(item.id)}
/> />
))} ))}
<Add onCreate={onCreate}> {canEdit && (
<button type="button" className={styles.taskButton}> <Add onCreate={onCreate}>
<span className={styles.taskButtonText}> <button type="button" className={styles.taskButton}>
{items.length > 0 ? t('action.addAnotherTask') : t('action.addTask')} <span className={styles.taskButtonText}>
</span> {items.length > 0 ? t('action.addAnotherTask') : t('action.addTask')}
</button> </span>
</Add> </button>
</Add>
)}
</> </>
); );
}); });
Tasks.propTypes = { Tasks.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired,
onCreate: PropTypes.func.isRequired, onCreate: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,

View file

@ -1,19 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Loader } from 'semantic-ui-react';
import CoreContainer from '../containers/CoreContainer';
import SocketStatusContainer from '../containers/SocketStatusContainer';
const CoreWrapper = React.memo(({ isInitializing }) => (
<>
{isInitializing ? <Loader active size="massive" /> : <CoreContainer />}
<SocketStatusContainer />
</>
));
CoreWrapper.propTypes = {
isInitializing: PropTypes.bool.isRequired,
};
export default CoreWrapper;

View file

@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation, Trans } from 'react-i18next';
import { Loader } from 'semantic-ui-react';
import CoreContainer from '../../containers/CoreContainer';
import styles from './CoreWrapper.module.scss';
const CoreWrapper = React.memo(({ isInitializing, isSocketDisconnected }) => {
const [t] = useTranslation();
return (
<>
{isInitializing ? <Loader active size="massive" /> : <CoreContainer />}
{isSocketDisconnected && (
<div className={styles.message}>
<div className={styles.messageHeader}>{t('common.noConnectionToServer')}</div>
<div className={styles.messageContent}>
<Trans i18nKey="common.allChangesWillBeAutomaticallySavedAfterConnectionRestored">
All changes will be automatically saved
<br />
after connection restored
</Trans>
</div>
</div>
)}
</>
);
});
CoreWrapper.propTypes = {
isInitializing: PropTypes.bool.isRequired,
isSocketDisconnected: PropTypes.bool.isRequired,
};
export default CoreWrapper;

View file

@ -1,29 +1,5 @@
:global(#app) { :global(#app) {
.button { .message {
background: none;
border: none;
color: #fff;
cursor: pointer;
outline: none;
padding: 0;
text-decoration: underline;
}
.content {
color: #fff;
font-size: 16px;
line-height: 1.4;
}
.header {
color: #fff;
font-size: 24px;
font-weight: bold;
line-height: 1.2;
margin-bottom: 8px;
}
.wrapper {
background: #eb5a46; background: #eb5a46;
border-radius: 4px; border-radius: 4px;
bottom: 20px; bottom: 20px;
@ -34,4 +10,18 @@
width: 390px; width: 390px;
z-index: 10001; z-index: 10001;
} }
.messageContent {
color: #fff;
font-size: 16px;
line-height: 1.4;
}
.messageHeader {
color: #fff;
font-size: 24px;
font-weight: bold;
line-height: 1.2;
margin-bottom: 8px;
}
} }

View file

@ -0,0 +1,3 @@
import CoreWrapper from './CoreWrapper';
export default CoreWrapper;

View file

@ -3,22 +3,26 @@ import PropTypes from 'prop-types';
import HeaderContainer from '../../containers/HeaderContainer'; import HeaderContainer from '../../containers/HeaderContainer';
import ProjectContainer from '../../containers/ProjectContainer'; import ProjectContainer from '../../containers/ProjectContainer';
import BoardActionsContainer from '../../containers/BoardActionsContainer';
import styles from './Fixed.module.scss'; import styles from './Fixed.module.scss';
const Fixed = ({ projectId }) => ( const Fixed = ({ projectId, board }) => (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<HeaderContainer /> <HeaderContainer />
{projectId && <ProjectContainer />} {projectId && <ProjectContainer />}
{board && !board.isFetching && <BoardActionsContainer />}
</div> </div>
); );
Fixed.propTypes = { Fixed.propTypes = {
projectId: PropTypes.string, projectId: PropTypes.string,
board: PropTypes.object, // eslint-disable-line react/forbid-prop-types
}; };
Fixed.defaultProps = { Fixed.defaultProps = {
projectId: undefined, projectId: undefined,
board: undefined,
}; };
export default Fixed; export default Fixed;

View file

@ -1,5 +1,6 @@
import React from 'react'; import React, { useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Icon, Menu } from 'semantic-ui-react'; import { Icon, Menu } from 'semantic-ui-react';
@ -11,52 +12,98 @@ import styles from './Header.module.scss';
const Header = React.memo( const Header = React.memo(
({ ({
project,
user, user,
notifications, notifications,
isEditable, canEditProject,
onUsers, canEditUsers,
onProjectSettingsClick,
onUsersClick,
onNotificationDelete, onNotificationDelete,
onUserSettings, onUserSettingsClick,
onLogout, onLogout,
}) => ( }) => {
<div className={styles.wrapper}> const handleProjectSettingsClick = useCallback(() => {
<Link to={Paths.ROOT} className={styles.logo}> if (canEditProject) {
Planka onProjectSettingsClick();
</Link> }
<Menu inverted size="large" className={styles.menu}> }, [canEditProject, onProjectSettingsClick]);
<Menu.Menu position="right">
{isEditable && ( return (
<Menu.Item className={styles.item} onClick={onUsers}> <div className={styles.wrapper}>
<Icon fitted name="users" /> {!project && (
</Menu.Item> <Link to={Paths.ROOT} className={classNames(styles.logo, styles.title)}>
Planka
</Link>
)}
<Menu inverted size="large" className={styles.menu}>
{project && (
<Menu.Menu position="left">
<Menu.Item
as={Link}
to={Paths.ROOT}
className={classNames(styles.item, styles.itemHoverable)}
>
<Icon fitted name="arrow left" />
</Menu.Item>
<Menu.Item
className={classNames(
styles.item,
canEditProject && styles.itemHoverable,
styles.title,
)}
onClick={handleProjectSettingsClick}
>
{project.name}
</Menu.Item>
</Menu.Menu>
)} )}
<NotificationsPopup items={notifications} onDelete={onNotificationDelete}> <Menu.Menu position="right">
<Menu.Item className={styles.item}> {canEditUsers && (
<Icon fitted name="bell" /> <Menu.Item
{notifications.length > 0 && ( className={classNames(styles.item, styles.itemHoverable)}
<span className={styles.notification}>{notifications.length}</span> onClick={onUsersClick}
)} >
</Menu.Item> <Icon fitted name="users" />
</NotificationsPopup> </Menu.Item>
<UserPopup onSettings={onUserSettings} onLogout={onLogout}> )}
<Menu.Item className={styles.item}>{user.name}</Menu.Item> <NotificationsPopup items={notifications} onDelete={onNotificationDelete}>
</UserPopup> <Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
</Menu.Menu> <Icon fitted name="bell" />
</Menu> {notifications.length > 0 && (
</div> <span className={styles.notification}>{notifications.length}</span>
), )}
</Menu.Item>
</NotificationsPopup>
<UserPopup onSettingsClick={onUserSettingsClick} onLogout={onLogout}>
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
{user.name}
</Menu.Item>
</UserPopup>
</Menu.Menu>
</Menu>
</div>
);
},
); );
Header.propTypes = { Header.propTypes = {
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
project: PropTypes.object,
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,
notifications: PropTypes.array.isRequired, notifications: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
isEditable: PropTypes.bool.isRequired, canEditProject: PropTypes.bool.isRequired,
onUsers: PropTypes.func.isRequired, canEditUsers: PropTypes.bool.isRequired,
onProjectSettingsClick: PropTypes.func.isRequired,
onUsersClick: PropTypes.func.isRequired,
onNotificationDelete: PropTypes.func.isRequired, onNotificationDelete: PropTypes.func.isRequired,
onUserSettings: PropTypes.func.isRequired, onUserSettingsClick: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired, onLogout: PropTypes.func.isRequired,
}; };
Header.defaultProps = {
project: undefined,
};
export default Header; export default Header;

View file

@ -1,19 +1,26 @@
:global(#app) { :global(#app) {
.item { .item {
cursor: auto;
user-select: auto;
&:before { &:before {
background: none; background: none;
} }
&:hover { &:active, &:hover {
background: rgba(0, 0, 0, 0.32); background: transparent;
color: rgba(255, 255, 255, 0.9);
} }
} }
.itemHoverable:hover {
cursor: pointer;
background: rgba(0, 0, 0, 0.32);
}
.logo { .logo {
color: #fff; color: #fff;
flex: 0 0 auto; flex: 0 0 auto;
font-size: 20px;
font-weight: bold;
letter-spacing: 3.5px; letter-spacing: 3.5px;
line-height: 50px; line-height: 50px;
padding: 0 16px; padding: 0 16px;
@ -54,6 +61,11 @@
width: 16px; width: 16px;
} }
.title {
font-size: 20px;
font-weight: bold;
}
.wrapper { .wrapper {
background: rgba(0, 0, 0, 0.24); background: rgba(0, 0, 0, 0.24);
display: flex; display: flex;

View file

@ -65,13 +65,11 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
context: 'title', context: 'title',
})} })}
</Menu.Item> </Menu.Item>
{onDelete && ( <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}> {t('action.deleteList', {
{t('action.deleteList', { context: 'title',
context: 'title', })}
})} </Menu.Item>
</Menu.Item>
)}
</Menu> </Menu>
</Popup.Content> </Popup.Content>
</> </>
@ -81,12 +79,8 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
ActionsStep.propTypes = { ActionsStep.propTypes = {
onNameEdit: PropTypes.func.isRequired, onNameEdit: PropTypes.func.isRequired,
onCardAdd: PropTypes.func.isRequired, onCardAdd: PropTypes.func.isRequired,
onDelete: PropTypes.func, onDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
}; };
ActionsStep.defaultProps = {
onDelete: undefined,
};
export default withPopup(ActionsStep); export default withPopup(ActionsStep);

View file

@ -15,7 +15,7 @@ import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-ic
import styles from './List.module.scss'; import styles from './List.module.scss';
const List = React.memo( const List = React.memo(
({ id, index, name, isPersisted, cardIds, onUpdate, onDelete, onCardCreate }) => { ({ id, index, name, isPersisted, cardIds, canEdit, onUpdate, onDelete, onCardCreate }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [isAddCardOpened, setIsAddCardOpened] = useState(false); const [isAddCardOpened, setIsAddCardOpened] = useState(false);
@ -23,10 +23,10 @@ const List = React.memo(
const listWrapper = useRef(null); const listWrapper = useRef(null);
const handleHeaderClick = useCallback(() => { const handleHeaderClick = useCallback(() => {
if (isPersisted) { if (isPersisted && canEdit) {
nameEdit.current.open(); nameEdit.current.open();
} }
}, [isPersisted]); }, [isPersisted, canEdit]);
const handleNameUpdate = useCallback( const handleNameUpdate = useCallback(
(newName) => { (newName) => {
@ -73,11 +73,13 @@ const List = React.memo(
<CardContainer key={cardId} id={cardId} index={cardIndex} /> <CardContainer key={cardId} id={cardId} index={cardIndex} />
))} ))}
{placeholder} {placeholder}
<CardAdd {canEdit && (
isOpened={isAddCardOpened} <CardAdd
onCreate={onCardCreate} isOpened={isAddCardOpened}
onClose={handleAddCardClose} onCreate={onCardCreate}
/> onClose={handleAddCardClose}
/>
)}
</div> </div>
</div> </div>
)} )}
@ -85,51 +87,64 @@ const List = React.memo(
); );
return ( return (
<Draggable draggableId={`list:${id}`} index={index} isDragDisabled={!isPersisted}> <Draggable draggableId={`list:${id}`} index={index} isDragDisabled={!isPersisted || !canEdit}>
{({ innerRef, draggableProps, dragHandleProps }) => ( {({ innerRef, draggableProps, dragHandleProps }) => (
// eslint-disable-next-line react/jsx-props-no-spreading <div
<div {...draggableProps} data-drag-scroller ref={innerRef} className={styles.wrapper}> {...draggableProps} // eslint-disable-line react/jsx-props-no-spreading
data-drag-scroller
ref={innerRef}
className={styles.innerWrapper}
>
{/* eslint-disable jsx-a11y/click-events-have-key-events, {/* eslint-disable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions, jsx-a11y/no-static-element-interactions,
react/jsx-props-no-spreading */} react/jsx-props-no-spreading */}
<div {...dragHandleProps} className={styles.header} onClick={handleHeaderClick}> <div className={styles.outerWrapper}>
{/* eslint-enable jsx-a11y/click-events-have-key-events, <div
{...dragHandleProps}
className={classNames(styles.header, canEdit && styles.headerEditable)}
onClick={handleHeaderClick}
>
{/* eslint-enable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions, jsx-a11y/no-static-element-interactions,
react/jsx-props-no-spreading */} react/jsx-props-no-spreading */}
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}> <NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
<div className={styles.headerName}>{name}</div> <div className={styles.headerName}>{name}</div>
</NameEdit> </NameEdit>
{isPersisted && ( {isPersisted && canEdit && (
<ActionsPopup <ActionsPopup
onNameEdit={handleNameEdit} onNameEdit={handleNameEdit}
onCardAdd={handleCardAdd} onCardAdd={handleCardAdd}
onDelete={onDelete} onDelete={onDelete}
>
<Button className={classNames(styles.headerButton, styles.target)}>
<Icon fitted name="pencil" size="small" />
</Button>
</ActionsPopup>
)}
</div>
<div
ref={listWrapper}
className={classNames(
styles.cardsInnerWrapper,
(isAddCardOpened || !canEdit) && styles.cardsInnerWrapperFull,
)}
>
<div className={styles.cardsOuterWrapper}>{cardsNode}</div>
</div>
{!isAddCardOpened && canEdit && (
<button
type="button"
disabled={!isPersisted}
className={classNames(styles.addCardButton)}
onClick={handleAddCardClick}
> >
<Button className={classNames(styles.headerButton, styles.target)}> <PlusMathIcon className={styles.addCardButtonIcon} />
<Icon fitted name="pencil" size="small" /> <span className={styles.addCardButtonText}>
</Button> {cardIds.length > 0 ? t('action.addAnotherCard') : t('action.addCard')}
</ActionsPopup> </span>
</button>
)} )}
</div> </div>
<div
ref={listWrapper}
className={classNames(styles.listWrapper, isAddCardOpened && styles.listWrapperFull)}
>
<div className={styles.list}>{cardsNode}</div>
</div>
{!isAddCardOpened && (
<button
type="button"
disabled={!isPersisted}
className={styles.addCardButton}
onClick={handleAddCardClick}
>
<PlusMathIcon className={styles.addCardButtonIcon} />
<span className={styles.addCardButtonText}>
{cardIds.length > 0 ? t('action.addAnotherCard') : t('action.addCard')}
</span>
</button>
)}
</div> </div>
)} )}
</Draggable> </Draggable>
@ -143,13 +158,10 @@ List.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
isPersisted: PropTypes.bool.isRequired, isPersisted: PropTypes.bool.isRequired,
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func, onDelete: PropTypes.func.isRequired,
onCardCreate: PropTypes.func.isRequired, onCardCreate: PropTypes.func.isRequired,
}; };
List.defaultProps = {
onDelete: undefined,
};
export default List; export default List;

View file

@ -2,7 +2,6 @@
.addCardButton { .addCardButton {
background: #dfe3e6; background: #dfe3e6;
border: none; border: none;
border-radius: 0 0 3px 3px;
color: #6b808c; color: #6b808c;
cursor: pointer; cursor: pointer;
display: block; display: block;
@ -37,76 +36,11 @@
} }
.cards { .cards {
flex: 1 1 auto;
min-height: 1px; min-height: 1px;
} }
.header { .cardsInnerWrapper {
background: #dfe3e6; max-height: calc(100vh - 268px);
border-radius: 3px 3px 0 0;
box-sizing: none;
flex: 0 0 auto;
margin-bottom: 0;
outline: none;
padding: 6px 36px 4px 8px;
position: relative;
&:hover .target {
opacity: 1;
}
}
.headerButton {
background: none;
box-shadow: none;
color: #798d99;
line-height: 32px;
margin: 0;
opacity: 0;
padding: 0;
position: absolute;
right: 2px;
top: 2px;
width: 32px;
&:hover {
background: rgba(9, 30, 66, 0.13);
color: #516b7a;
}
}
.headerName {
background: transparent;
border-color: transparent;
border-radius: 3px;
color: #17394d;
font-weight: bold;
line-height: 20px;
margin: 0;
max-height: 256px;
outline: none;
overflow: hidden;
overflow-wrap: break-word;
padding: 4px 8px;
resize: none;
width: 100%;
word-break: break-word;
}
.list {
background: #dfe3e6;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding: 0 8px;
position: relative;
white-space: normal;
width: 272px;
}
.listWrapper {
background: #dfe3e6;
max-height: calc(100vh - 300px);
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin; scrollbar-width: thin;
@ -129,16 +63,69 @@
} }
} }
.listWrapperFull { .cardsInnerWrapperFull {
max-height: calc(100vh - 264px); max-height: calc(100vh - 232px);
} }
.wrapper { .cardsOuterWrapper {
border-radius: 3px; padding: 0 8px;
flex: 0 0 auto; white-space: normal;
margin: 0 4px;
overflow: hidden;
vertical-align: top;
width: 272px; width: 272px;
} }
.header {
outline: none;
padding: 6px 36px 4px 8px;
position: relative;
&:hover .target {
opacity: 1;
}
}
.headerEditable {
cursor: pointer;
}
.headerButton {
background: none;
box-shadow: none;
color: #798d99;
line-height: 32px;
margin: 0;
opacity: 0;
padding: 0;
position: absolute;
right: 2px;
top: 2px;
width: 32px;
&:hover {
background: rgba(9, 30, 66, 0.13);
color: #516b7a;
}
}
.headerName {
color: #17394d;
font-weight: bold;
line-height: 20px;
max-height: 256px;
outline: none;
overflow: hidden;
overflow-wrap: break-word;
padding: 4px 8px;
word-break: break-word;
}
.innerWrapper {
margin-right: 8px;
width: 272px;
}
.outerWrapper {
background: #dfe3e6;
border-radius: 3px;
overflow: hidden;
}
} }

View file

@ -0,0 +1,110 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
import { withPopup } from '../../lib/popup';
import { useSteps } from '../../hooks';
import User from '../User';
import DeleteStep from '../DeleteStep';
import styles from './ActionsPopup.module.scss';
const StepTypes = {
DELETE: 'DELETE',
};
const ActionsStep = React.memo(
({
user,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
leaveConfirmationButtonContent,
deleteButtonContent,
deleteConfirmationTitle,
deleteConfirmationContent,
deleteConfirmationButtonContent,
canLeave,
canDelete,
onDelete,
}) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE);
}, [openStep]);
if (step && step.type === StepTypes.DELETE) {
return (
<DeleteStep
title={t(user.isCurrent ? leaveConfirmationTitle : deleteConfirmationTitle, {
context: 'title',
})}
content={t(user.isCurrent ? leaveConfirmationContent : deleteConfirmationContent)}
buttonContent={t(
user.isCurrent ? leaveConfirmationButtonContent : deleteConfirmationButtonContent,
)}
onConfirm={onDelete}
onBack={handleBack}
/>
);
}
return (
<>
<span className={styles.user}>
<User name={user.name} avatarUrl={user.avatarUrl} size="large" />
</span>
<span className={styles.content}>
<div className={styles.name}>{user.name}</div>
<div className={styles.email}>{user.email}</div>
{user.isCurrent
? canLeave && (
<Button
content={t(leaveButtonContent)}
className={styles.deleteButton}
onClick={handleDeleteClick}
/>
)
: canDelete && (
<Button
content={t(deleteButtonContent)}
className={styles.deleteButton}
onClick={handleDeleteClick}
/>
)}
</span>
</>
);
},
);
ActionsStep.propTypes = {
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
leaveConfirmationButtonContent: PropTypes.string,
deleteButtonContent: PropTypes.string,
deleteConfirmationTitle: PropTypes.string,
deleteConfirmationContent: PropTypes.string,
deleteConfirmationButtonContent: PropTypes.string,
canLeave: PropTypes.bool.isRequired,
canDelete: PropTypes.bool.isRequired,
onDelete: PropTypes.func.isRequired,
};
ActionsStep.defaultProps = {
leaveButtonContent: 'action.leaveBoard',
leaveConfirmationTitle: 'common.leaveBoard',
leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard',
leaveConfirmationButtonContent: 'action.leaveBoard',
deleteButtonContent: 'action.removeFromBoard',
deleteConfirmationTitle: 'common.removeMember',
deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard',
deleteConfirmationButtonContent: 'action.removeMember',
};
export default withPopup(ActionsStep);

View file

@ -6,9 +6,9 @@ import { Popup } from '../../../lib/custom-ui';
import UserItem from './UserItem'; import UserItem from './UserItem';
import styles from './MembershipAddPopup.module.scss'; import styles from './AddPopup.module.scss';
const MembershipAddStep = React.memo(({ users, currentUserIds, onCreate, onClose }) => { const AddStep = React.memo(({ users, currentUserIds, title, onCreate, onClose }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const handleUserSelect = useCallback( const handleUserSelect = useCallback(
@ -25,7 +25,7 @@ const MembershipAddStep = React.memo(({ users, currentUserIds, onCreate, onClose
return ( return (
<> <>
<Popup.Header> <Popup.Header>
{t('common.addMember', { {t(title, {
context: 'title', context: 'title',
})} })}
</Popup.Header> </Popup.Header>
@ -46,13 +46,18 @@ const MembershipAddStep = React.memo(({ users, currentUserIds, onCreate, onClose
); );
}); });
MembershipAddStep.propTypes = { AddStep.propTypes = {
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
users: PropTypes.array.isRequired, users: PropTypes.array.isRequired,
currentUserIds: PropTypes.array.isRequired, currentUserIds: PropTypes.array.isRequired,
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
title: PropTypes.string,
onCreate: PropTypes.func.isRequired, onCreate: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
}; };
export default withPopup(MembershipAddStep); AddStep.defaultProps = {
title: 'common.addMember',
};
export default withPopup(AddStep);

View file

@ -0,0 +1,3 @@
import AddPopup from './AddPopup';
export default AddPopup;

View file

@ -0,0 +1,107 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'semantic-ui-react';
import AddPopup from './AddPopup';
import ActionsPopup from './ActionsPopup';
import User from '../User';
import styles from './Memberships.module.scss';
const Memberships = React.memo(
({
items,
allUsers,
addTitle,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
leaveConfirmationButtonContent,
deleteButtonContent,
deleteConfirmationTitle,
deleteConfirmationContent,
deleteConfirmationButtonContent,
canEdit,
canLeaveIfLast,
onCreate,
onDelete,
}) => {
return (
<>
<span className={styles.users}>
{items.map((item) => (
<span key={item.id} className={styles.user}>
<ActionsPopup
user={item.user}
leaveButtonContent={leaveButtonContent}
leaveConfirmationTitle={leaveConfirmationTitle}
leaveConfirmationContent={leaveConfirmationContent}
leaveConfirmationButtonContent={leaveConfirmationButtonContent}
deleteButtonContent={deleteButtonContent}
deleteConfirmationTitle={deleteConfirmationTitle}
deleteConfirmationContent={deleteConfirmationContent}
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
canLeave={items.length > 1 || canLeaveIfLast}
canDelete={canEdit}
onDelete={() => onDelete(item.id)}
>
<User
name={item.user.name}
avatarUrl={item.user.avatarUrl}
size="large"
isDisabled={!item.isPersisted}
/>
</ActionsPopup>
</span>
))}
</span>
{canEdit && (
<AddPopup
users={allUsers}
currentUserIds={items.map((item) => item.user.id)}
title={addTitle}
onCreate={onCreate}
>
<Button icon="add user" className={styles.addUser} />
</AddPopup>
)}
</>
);
},
);
Memberships.propTypes = {
/* eslint-disable react/forbid-prop-types */
items: PropTypes.array.isRequired,
allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
addTitle: PropTypes.string,
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
leaveConfirmationButtonContent: PropTypes.string,
deleteButtonContent: PropTypes.string,
deleteConfirmationTitle: PropTypes.string,
deleteConfirmationContent: PropTypes.string,
deleteConfirmationButtonContent: PropTypes.string,
canEdit: PropTypes.bool,
canLeaveIfLast: PropTypes.bool,
onCreate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};
Memberships.defaultProps = {
addTitle: undefined,
leaveButtonContent: undefined,
leaveConfirmationTitle: undefined,
leaveConfirmationContent: undefined,
leaveConfirmationButtonContent: undefined,
deleteButtonContent: undefined,
deleteConfirmationTitle: undefined,
deleteConfirmationContent: undefined,
deleteConfirmationButtonContent: undefined,
canEdit: true,
canLeaveIfLast: true,
};
export default Memberships;

View file

@ -0,0 +1,30 @@
:global(#app) {
.addUser {
background: rgba(0, 0, 0, 0.24);
border-radius: 50%;
box-shadow: none;
color: #fff;
line-height: 36px;
margin: 0;
padding: 0;
transition: all 0.1s ease 0s;
vertical-align: top;
width: 36px;
&:hover {
background: rgba(0, 0, 0, 0.32);
}
}
.user {
display: inline-block;
margin: 0 -4px 0 0;
vertical-align: top;
line-height: 0;
}
.users {
display: inline-block;
vertical-align: top;
}
}

View file

@ -0,0 +1,3 @@
import Memberships from './Memberships';
export default Memberships;

Some files were not shown because too many files have changed in this diff Show more