diff --git a/apps/projects/app/assets/IconDoubleCheck.js b/apps/projects/app/assets/IconDoubleCheck.js index 359c0009b..610bb4f86 100644 --- a/apps/projects/app/assets/IconDoubleCheck.js +++ b/apps/projects/app/assets/IconDoubleCheck.js @@ -1,7 +1,7 @@ import React from 'react' -const IconDoubleCheck = () => ( - +const IconDoubleCheck = props => ( + diff --git a/apps/projects/app/assets/IconUserError.js b/apps/projects/app/assets/IconUserError.js new file mode 100644 index 000000000..65ceccc63 --- /dev/null +++ b/apps/projects/app/assets/IconUserError.js @@ -0,0 +1,12 @@ +import React from 'react' + +const IconUserError = props => ( + + + + + + +) + +export default IconUserError diff --git a/apps/projects/app/assets/index.js b/apps/projects/app/assets/index.js index 10fd054ef..d29425ef8 100644 --- a/apps/projects/app/assets/index.js +++ b/apps/projects/app/assets/index.js @@ -8,5 +8,18 @@ import IconCollapse from './IconCollapse' import IconDropArrow from './IconDropArrow' import IconDoubleCheck from './IconDoubleCheck' import IconUserCheck from './IconUserCheck' +import IconUserError from './IconUserError' -export { IconMore, IconSort, IconGrid, IconCoins, IconFilter, IconOpen, IconCollapse, IconDropArrow, IconDoubleCheck, IconUserCheck } +export { + IconDoubleCheck, + IconDropArrow, + IconCoins, + IconCollapse, + IconFilter, + IconGrid, + IconMore, + IconOpen, + IconSort, + IconUserCheck, + IconUserError, +} diff --git a/apps/projects/app/components/Card/Issue.js b/apps/projects/app/components/Card/Issue.js index 1449b541a..a5767ec36 100644 --- a/apps/projects/app/components/Card/Issue.js +++ b/apps/projects/app/components/Card/Issue.js @@ -7,6 +7,7 @@ import { ContextMenu, IconAddUser, IconClock, + IconCoin, IconGraph2, IconInfo, Tag, @@ -19,11 +20,12 @@ import { formatDistance } from 'date-fns' import { BountyContextMenu } from '../Shared' import { BOUNTY_STATUS, BOUNTY_BADGE_COLOR } from '../../utils/bounty-status' import { issueShape } from '../../utils/shapes' +import { IconUserError, IconDoubleCheck } from '../../assets' -const DeadlineDistance = date => +const deadlineDistance = date => formatDistance(new Date(date), new Date(), { addSuffix: true }) -const dot = · +const Dot = () => · const labelsTags = (labels, theme) => labels.edges.map(label => ( @@ -45,7 +47,7 @@ const FlexibleDiv = ({ compact, children }) => { ) : ( - {dot} + {children} ) @@ -56,46 +58,66 @@ FlexibleDiv.propTypes = { children: PropTypes.node.isRequired, } -const Bounty = ({ issue }) => { +const BountyDetail = ({ Icon, iconColor, text }) => { const theme = useTheme() + return ( + + + {text} + + ) +} +BountyDetail.propTypes = { + Icon: PropTypes.func.isRequired, + iconColor: PropTypes.string, + text: PropTypes.string.isRequired, +} + +const Bounty = ({ issue }) => { const { layoutName } = useLayout() const { workStatus, deadline, expLevel, openSubmission } = issue + const theme = useTheme() let status = BOUNTY_STATUS[workStatus] if (openSubmission && workStatus === 'funded') status = BOUNTY_STATUS['open-submission-funded'] + const pastDueDate = new Date() > new Date(issue.deadline) + + if (issue.workStatus === 'fulfilled') return ( + + + + + + ) + + if (pastDueDate) return ( + + + + + + ) + return ( - + <> - - - {status} - - {dot} - - - Due {DeadlineDistance(deadline)} - + + + - - - {expLevel} - + + + - + ) } - Bounty.propTypes = issueShape const Issue = ({ isSelected, onClick, onSelect, ...issue }) => { @@ -161,15 +183,19 @@ const Issue = ({ isSelected, onClick, onSelect, ...issue }) => { {repo} #{number} + - - - opened {DeadlineDistance(createdAt)} - + + {issue.hasBounty && ( )} + diff --git a/apps/projects/app/components/Content/IssueDetail/BountyCard.js b/apps/projects/app/components/Content/IssueDetail/BountyCard.js index 4385bc3f9..8a80ebb3e 100644 --- a/apps/projects/app/components/Content/IssueDetail/BountyCard.js +++ b/apps/projects/app/components/Content/IssueDetail/BountyCard.js @@ -7,6 +7,7 @@ import { GU, IconAddUser, IconClock, + IconCoin, IconFile, IconGraph2, IdentityBadge, @@ -18,7 +19,7 @@ import { issueShape } from '../../../utils/shapes.js' import { usePanelManagement } from '../../Panel' import { useAragonApi } from '../../../api-react' import { formatDistance } from 'date-fns' -import { IconDoubleCheck, IconUserCheck } from '../../../assets' +import { IconDoubleCheck, IconUserCheck, IconUserError } from '../../../assets' const ActionButton = ({ panel, caption, issue }) => ( const pluralize = (word, number) => `${number} ${word}${number > 1 ? 's' : ''}` +const pastDueDate = deadline => new Date() > new Date(deadline) + const Status = ({ issue }) => { const theme = useTheme() const workStatus = (issue.openSubmission && issue.workStatus !== 'fulfilled') ? @@ -63,6 +66,15 @@ const Status = ({ issue }) => { : issue.workStatus + if (pastDueDate(issue.deadline) && workStatus !== 'fulfilled') return ( + <> + + + Not completed + + + ) + switch(workStatus) { case 'openSubmission': return ( <> @@ -113,7 +125,7 @@ const Submissions = ({ issue }) => { 'No applications' ) case 'review-applicants': return ( - reviewApplication({ issue, requestIndex: 0 })}> + reviewApplication({ issue, requestIndex: 0, readOnly: pastDueDate(issue.deadline) })}> {pluralize('application', issue.requestsData.length)} @@ -121,7 +133,7 @@ const Submissions = ({ issue }) => { case 'openSubmission': case 'in-progress': if ('workSubmissions' in issue) return ( - reviewWork({ issue, index: 0 })}> + reviewWork({ issue, index: 0, readOnly: pastDueDate(issue.deadline) })}> {pluralize('work submission', issue.workSubmissions.length)} ) @@ -130,7 +142,7 @@ const Submissions = ({ issue }) => { ) case 'review-work': case 'fulfilled': return ( - reviewWork({ issue, index: 0 })}> + reviewWork({ issue, index: 0, readOnly: pastDueDate(issue.deadline) })}> {pluralize('work submission', issue.workSubmissions.length)} ) default: return null @@ -150,10 +162,12 @@ const Dot = ({ color }) => ( ) Dot.propTypes = PropTypes.string.isRequired -const BountyDot = ({ workStatus }) => { +const BountyDot = ({ issue }) => { const theme = useTheme() - switch(workStatus) { + if (pastDueDate(issue.deadline)) return null + + switch(issueShape.workStatus) { case 'funded': case 'review-applicants': return ( @@ -167,7 +181,7 @@ const BountyDot = ({ workStatus }) => { default: return null } } -BountyDot.propTypes = PropTypes.string.isRequired +BountyDot.propTypes = issueShape const Action = ({ issue }) => { const { requestAssignment, submitWork } = usePanelManagement() @@ -177,6 +191,8 @@ const Action = ({ issue }) => { : issue.workStatus + if (pastDueDate(issue.deadline)) return null + switch(workStatus) { case 'funded': case 'review-applicants': return ( @@ -196,12 +212,12 @@ Action.propTypes = issueShape const BountyCard = ({ issue }) => { const theme = useTheme() - const { appState: { bountySettings } } = useAragonApi() + const { appState: { bountySettings }, connectedAccount } = useAragonApi() const expLevels = bountySettings.expLvls return ( { padding: 0; `} > -
- -
- {issue.balance} - {issue.symbol} -
-
+
:not(:last-child) { + margin-bottom: ${GU}px; + } + `}> + + {!(pastDueDate(issue.deadline) && issue.workStatus !== 'fulfilled') && ( +
+ +
+ {issue.balance} + {issue.symbol} +
+
+ )} - - - + {connectedAccount !== issue.assignee && ( + + + 1 bounty + + )} - {issue.workStatus !== 'fulfilled' && ( - - Due + - )} - - - {expLevels[issue.exp].name} - + {issue.workStatus !== 'fulfilled' && ( + + + Due + + )} - - - - - - + {!(pastDueDate(issue.deadline) && issue.workStatus !== 'fulfilled') && ( + + + {expLevels[issue.exp].name} + + )} - + + + + + + + + +
) } @@ -258,7 +292,6 @@ const EventButton = styled(Button)` ` const Row = styled.div` display: flex; - margin-bottom: ${GU}px; ` const BountyText = styled.div` margin-left: ${GU}px; diff --git a/apps/projects/app/components/Shared/BountyContextMenu.js b/apps/projects/app/components/Shared/BountyContextMenu.js index 6c9c9b3d8..c32214567 100644 --- a/apps/projects/app/components/Shared/BountyContextMenu.js +++ b/apps/projects/app/components/Shared/BountyContextMenu.js @@ -32,9 +32,10 @@ MenuItem.propTypes = { const pluralize = (word, number) => `${word}${number > 1 ? 's (' + number + ')' : ''}` +const pastDueDate = deadline => new Date() > new Date(deadline) + const BountyContextMenu = ({ issue }) => { - const pastDeadline = (new Date()) > (new Date(issue.deadline)) - const { openSubmission, workStatus, assignee } = issue + const { openSubmission, assignee } = issue const { connectedAccount } = useAragonApi() const { allocateBounty, @@ -44,8 +45,11 @@ const BountyContextMenu = ({ issue }) => { submitWork, } = usePanelManagement() + const workStatus = (pastDueDate(issue.deadline)) ? 'not-funded' : issue.workStatus + switch(workStatus) { - case undefined: return ( + case undefined: + case 'not-funded': return ( ) case 'funded': return openSubmission ? ( @@ -55,9 +59,7 @@ const BountyContextMenu = ({ issue }) => { ) case 'review-applicants': return ( <> - {!pastDeadline && ( - - )} +