diff --git a/client/src/App/AppStyles.js b/client/src/App/AppStyles.js deleted file mode 100644 index 4bdbdfd..0000000 --- a/client/src/App/AppStyles.js +++ /dev/null @@ -1,8 +0,0 @@ -import styled from 'styled-components'; -import { sizes } from 'shared/utils/styles'; - -export const Main = styled.main` - position: relative; - width: 100%; - padding-left: ${sizes.appNavBarLeftWidth}px; -`; diff --git a/client/src/App/Routes.jsx b/client/src/App/Routes.jsx index a94f506..33539fa 100644 --- a/client/src/App/Routes.jsx +++ b/client/src/App/Routes.jsx @@ -1,10 +1,10 @@ import React from 'react'; import { Router, Switch, Route, Redirect } from 'react-router-dom'; -import history from 'browserHistory'; -import PageError from 'shared/components/PageError'; +import history from 'browserHistory'; import Project from 'Project'; -import Authenticate from './Authenticate'; +import Authenticate from 'Auth/Authenticate'; +import PageError from 'shared/components/PageError'; const Routes = () => ( diff --git a/client/src/App/Toast/Styles.js b/client/src/App/Toast/Styles.js index dd9b966..554fbc6 100644 --- a/client/src/App/Toast/Styles.js +++ b/client/src/App/Toast/Styles.js @@ -1,6 +1,7 @@ import styled from 'styled-components'; import { color, font, mixin, zIndexValues } from 'shared/utils/styles'; +import { Icon } from 'shared/components'; export const Container = styled.div` z-index: ${zIndexValues.modal + 1}; @@ -33,15 +34,15 @@ export const StyledToast = styled.div` opacity: 1; right: 0; } +`; - i { - position: absolute; - top: 13px; - right: 14px; - font-size: 22px; - cursor: pointer; - color: #fff; - } +export const CloseIcon = styled(Icon)` + position: absolute; + top: 13px; + right: 14px; + font-size: 22px; + cursor: pointer; + color: #fff; `; export const Title = styled.div` diff --git a/client/src/App/Toast/index.jsx b/client/src/App/Toast/index.jsx index e61b161..f344502 100644 --- a/client/src/App/Toast/index.jsx +++ b/client/src/App/Toast/index.jsx @@ -3,15 +3,14 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group'; import pubsub from 'sweet-pubsub'; import { uniqueId } from 'lodash'; -import { Icon } from 'shared/components'; -import { Container, StyledToast, Title, Message } from './Styles'; +import { Container, StyledToast, CloseIcon, Title, Message } from './Styles'; const Toast = () => { const [toasts, setToasts] = useState([]); useEffect(() => { const addToast = ({ type = 'success', title, message, duration = 5 }) => { - const id = uniqueId(); + const id = uniqueId('toast-'); setToasts(currentToasts => [...currentToasts, { id, type, title, message }]); @@ -19,7 +18,9 @@ const Toast = () => { setTimeout(() => removeToast(id), duration * 1000); } }; + pubsub.on('toast', addToast); + return () => { pubsub.off('toast', addToast); }; @@ -35,7 +36,7 @@ const Toast = () => { {toasts.map(toast => ( removeToast(toast.id)}> - + {toast.title && {toast.title}} {toast.message && {toast.message}} diff --git a/client/src/App/assets/fonts/jira.svg b/client/src/App/assets/fonts/jira.svg index 8a9a873..085a0ec 100755 --- a/client/src/App/assets/fonts/jira.svg +++ b/client/src/App/assets/fonts/jira.svg @@ -36,4 +36,7 @@ + + + \ No newline at end of file diff --git a/client/src/App/assets/fonts/jira.ttf b/client/src/App/assets/fonts/jira.ttf index a42e6da..50ea2eb 100755 Binary files a/client/src/App/assets/fonts/jira.ttf and b/client/src/App/assets/fonts/jira.ttf differ diff --git a/client/src/App/assets/fonts/jira.woff b/client/src/App/assets/fonts/jira.woff index 5bcf189..c2a3981 100755 Binary files a/client/src/App/assets/fonts/jira.woff and b/client/src/App/assets/fonts/jira.woff differ diff --git a/client/src/App/index.jsx b/client/src/App/index.jsx index 7734795..ca099ca 100644 --- a/client/src/App/index.jsx +++ b/client/src/App/index.jsx @@ -5,7 +5,7 @@ import BaseStyles from './BaseStyles'; import Toast from './Toast'; import Routes from './Routes'; -// We're importing css because @font-face in styled-components causes font files +// We're importing .css because @font-face in styled-components causes font files // to be constantly re-requested from the server (which causes screen flicker) // https://github.com/styled-components/styled-components/issues/1593 import './fontStyles.css'; diff --git a/client/src/App/Authenticate.jsx b/client/src/Auth/Authenticate.jsx similarity index 64% rename from client/src/App/Authenticate.jsx rename to client/src/Auth/Authenticate.jsx index a8fef30..cf22672 100644 --- a/client/src/App/Authenticate.jsx +++ b/client/src/Auth/Authenticate.jsx @@ -1,8 +1,8 @@ import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; -import toast from 'shared/utils/toast'; import api from 'shared/utils/api'; +import toast from 'shared/utils/toast'; import { getStoredAuthToken, storeAuthToken } from 'shared/utils/authToken'; import { PageLoader } from 'shared/components'; @@ -11,17 +11,18 @@ const Authenticate = () => { useEffect(() => { const createGuestAccount = async () => { - if (!getStoredAuthToken()) { - try { - const { authToken } = await api.post('/authentication/guest'); - storeAuthToken(authToken); - history.push('/'); - } catch (error) { - toast.error(error); - } + try { + const { authToken } = await api.post('/authentication/guest'); + storeAuthToken(authToken); + history.push('/'); + } catch (error) { + toast.error(error); } }; - createGuestAccount(); + + if (!getStoredAuthToken()) { + createGuestAccount(); + } }, [history]); return ; diff --git a/client/src/Project/Board/Filters/Styles.js b/client/src/Project/Board/Filters/Styles.js index 65de5f4..b5162c6 100644 --- a/client/src/Project/Board/Filters/Styles.js +++ b/client/src/Project/Board/Filters/Styles.js @@ -1,7 +1,7 @@ import styled from 'styled-components'; -import { InputDebounced, Avatar, Button } from 'shared/components'; import { color, font, mixin } from 'shared/utils/styles'; +import { InputDebounced, Avatar, Button } from 'shared/components'; export const Filters = styled.div` display: flex; diff --git a/client/src/Project/Board/Filters/index.jsx b/client/src/Project/Board/Filters/index.jsx index db76dc4..16a7a4d 100644 --- a/client/src/Project/Board/Filters/index.jsx +++ b/client/src/Project/Board/Filters/index.jsx @@ -16,14 +16,12 @@ const propTypes = { projectUsers: PropTypes.array.isRequired, defaultFilters: PropTypes.object.isRequired, filters: PropTypes.object.isRequired, - setFilters: PropTypes.func.isRequired, + mergeFilters: PropTypes.func.isRequired, }; -const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters }) => { +const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, mergeFilters }) => { const { searchQuery, userIds, myOnly, recent } = filters; - const setFiltersMerge = newFilters => setFilters({ ...filters, ...newFilters }); - const areFiltersCleared = !searchQuery && userIds.length === 0 && !myOnly && !recent; return ( @@ -31,7 +29,7 @@ const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters setFiltersMerge({ searchQuery: value })} + onChange={value => mergeFilters({ searchQuery: value })} /> {projectUsers.map(user => ( @@ -39,27 +37,27 @@ const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters setFiltersMerge({ userIds: xor(userIds, [user.id]) })} + onClick={() => mergeFilters({ userIds: xor(userIds, [user.id]) })} /> ))} setFiltersMerge({ myOnly: !myOnly })} + onClick={() => mergeFilters({ myOnly: !myOnly })} > Only My Issues setFiltersMerge({ recent: !recent })} + onClick={() => mergeFilters({ recent: !recent })} > Recently Updated {!areFiltersCleared && ( - setFilters(defaultFilters)}>Clear all + mergeFilters(defaultFilters)}>Clear all )} ); diff --git a/client/src/Project/Board/Header/index.jsx b/client/src/Project/Board/Header/index.jsx index bf22666..7416dc5 100644 --- a/client/src/Project/Board/Header/index.jsx +++ b/client/src/Project/Board/Header/index.jsx @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { CopyLinkButton } from 'shared/components'; + import { Breadcrumbs, Divider, Header, BoardName } from './Styles'; const propTypes = { @@ -17,6 +18,7 @@ const ProjectBoardHeader = ({ projectName }) => ( / Kanban Board +
Kanban board diff --git a/client/src/Project/Board/Lists/Issue/Styles.js b/client/src/Project/Board/Lists/Issue/Styles.js index b1a6c99..33ac15d 100644 --- a/client/src/Project/Board/Lists/Issue/Styles.js +++ b/client/src/Project/Board/Lists/Issue/Styles.js @@ -1,8 +1,8 @@ import styled, { css } from 'styled-components'; import { Link } from 'react-router-dom'; -import { Avatar } from 'shared/components'; import { color, font, mixin } from 'shared/utils/styles'; +import { Avatar } from 'shared/components'; export const IssueLink = styled(Link)` display: block; diff --git a/client/src/Project/Board/Lists/Issue/index.jsx b/client/src/Project/Board/Lists/Issue/index.jsx index 6d5493f..122aad6 100644 --- a/client/src/Project/Board/Lists/Issue/index.jsx +++ b/client/src/Project/Board/Lists/Issue/index.jsx @@ -4,6 +4,7 @@ import { useRouteMatch } from 'react-router-dom'; import { Draggable } from 'react-beautiful-dnd'; import { IssueTypeIcon, IssuePriorityIcon } from 'shared/components'; + import { IssueLink, Issue, Title, Bottom, Assignees, AssigneeAvatar } from './Styles'; const propTypes = { diff --git a/client/src/Project/Board/Lists/index.jsx b/client/src/Project/Board/Lists/index.jsx index f338a15..c3f7a5c 100644 --- a/client/src/Project/Board/Lists/index.jsx +++ b/client/src/Project/Board/Lists/index.jsx @@ -8,6 +8,7 @@ import api from 'shared/utils/api'; import useApi from 'shared/hooks/api'; import { moveItemWithinArray, insertItemIntoArray } from 'shared/utils/javascript'; import { IssueStatus, IssueStatusCopy } from 'shared/constants/issues'; + import Issue from './Issue'; import { Lists, List, Title, IssuesCount, Issues } from './Styles'; @@ -21,13 +22,8 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => { const [{ data: currentUserData }] = useApi.get('/currentUser'); const currentUserId = get(currentUserData, 'currentUser.id'); - const filteredIssues = filterIssues(project.issues, filters, currentUserId); - const handleIssueDrop = async ({ draggableId, destination, source }) => { - if (!destination) return; - const isSameList = destination.droppableId === source.droppableId; - const isSamePosition = destination.index === source.index; - if (isSameList && isSamePosition) return; + if (!isPositionChanged(source, destination)) return; const issueId = Number(draggableId); @@ -35,7 +31,7 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => { url: `/issues/${issueId}`, updatedFields: { status: destination.droppableId, - listPosition: calculateListPosition(project.issues, destination, isSameList, issueId), + listPosition: calculateListPosition(project.issues, destination, source, issueId), }, currentFields: project.issues.find(({ id }) => id === issueId), setLocalData: fields => updateLocalIssuesArray(issueId, fields), @@ -43,21 +39,17 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => { }; const renderList = status => { + const filteredIssues = filterIssues(project.issues, filters, currentUserId); const filteredListIssues = getSortedListIssues(filteredIssues, status); const allListIssues = getSortedListIssues(project.issues, status); - const issuesCount = - allListIssues.length !== filteredListIssues.length - ? `${filteredListIssues.length} of ${allListIssues.length}` - : allListIssues.length; - return ( {provided => ( {`${IssueStatusCopy[status]} `} - <IssuesCount>{issuesCount}</IssuesCount> + <IssuesCount>{formatIssuesCount(allListIssues, filteredListIssues)}</IssuesCount> {filteredListIssues.map((issue, index) => ( @@ -102,6 +94,20 @@ const filterIssues = (projectIssues, filters, currentUserId) => { const getSortedListIssues = (issues, status) => issues.filter(issue => issue.status === status).sort((a, b) => a.listPosition - b.listPosition); +const formatIssuesCount = (allListIssues, filteredListIssues) => { + if (allListIssues.length !== filteredListIssues.length) { + return `${filteredListIssues.length} of ${allListIssues.length}`; + } + return allListIssues.length; +}; + +const isPositionChanged = (destination, source) => { + if (!destination) return false; + const isSameList = destination.droppableId === source.droppableId; + const isSamePosition = destination.index === source.index; + return !isSameList || !isSamePosition; +}; + const calculateListPosition = (...args) => { const { prevIssue, nextIssue } = getAfterDropPrevNextIssue(...args); let position; @@ -118,9 +124,10 @@ const calculateListPosition = (...args) => { return position; }; -const getAfterDropPrevNextIssue = (allIssues, destination, isSameList, droppedIssueId) => { +const getAfterDropPrevNextIssue = (allIssues, destination, source, droppedIssueId) => { const destinationIssues = getSortedListIssues(allIssues, destination.droppableId); const droppedIssue = allIssues.find(issue => issue.id === droppedIssueId); + const isSameList = destination.droppableId === source.droppableId; const afterDropDestinationIssues = isSameList ? moveItemWithinArray(destinationIssues, droppedIssue, destination.index) diff --git a/client/src/Project/Board/index.jsx b/client/src/Project/Board/index.jsx index 67aeca3..a786813 100644 --- a/client/src/Project/Board/index.jsx +++ b/client/src/Project/Board/index.jsx @@ -1,6 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; +import useMergeState from 'shared/hooks/mergeState'; + import Header from './Header'; import Filters from './Filters'; import Lists from './Lists'; @@ -18,7 +20,7 @@ const defaultFilters = { }; const ProjectBoard = ({ project, updateLocalIssuesArray }) => { - const [filters, setFilters] = useState(defaultFilters); + const [filters, mergeFilters] = useMergeState(defaultFilters); return ( <>
@@ -26,7 +28,7 @@ const ProjectBoard = ({ project, updateLocalIssuesArray }) => { projectUsers={project.users} defaultFilters={defaultFilters} filters={filters} - setFilters={setFilters} + mergeFilters={mergeFilters} /> diff --git a/client/src/Project/IssueCreateForm/index.jsx b/client/src/Project/IssueCreateForm/index.jsx index eb2c352..0bdd79f 100644 --- a/client/src/Project/IssueCreateForm/index.jsx +++ b/client/src/Project/IssueCreateForm/index.jsx @@ -11,6 +11,7 @@ import { import toast from 'shared/utils/toast'; import useApi from 'shared/hooks/api'; import { Form, IssueTypeIcon, Icon, Avatar, IssuePriorityIcon } from 'shared/components'; + import { FormHeading, FormElement, @@ -152,10 +153,10 @@ const ProjectIssueCreateForm = ({ project, fetchProject, modalClose }) => { renderValue={renderPriority} /> - + Create Issue - + Cancel diff --git a/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx b/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx index d96a728..fea70f7 100644 --- a/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx +++ b/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx @@ -2,6 +2,7 @@ import React, { useRef } from 'react'; import PropTypes from 'prop-types'; import { Textarea } from 'shared/components'; + import { Actions, FormButton } from './Styles'; const propTypes = { @@ -20,6 +21,7 @@ const ProjectBoardIssueDetailsCommentsBodyForm = ({ onCancel, }) => { const $textareaRef = useRef(); + return ( <>