diff --git a/cypress/e2e/62_BountyCard.cy.ts b/cypress/e2e/62_BountyCard.cy.ts new file mode 100644 index 00000000..67542dd9 --- /dev/null +++ b/cypress/e2e/62_BountyCard.cy.ts @@ -0,0 +1,40 @@ +// cypress/e2e/bountyCard.spec.js +describe('BountyCardComponent', () => { + beforeEach(() => { + cy.viewport(500, 800); + }); + + it('renders correctly with given props', () => { + const props = { + id: 'bounty123', + title: 'Test Bounty Card', + features: { name: 'Feature A' }, + phase: { name: 'Phase 1' }, + assignee_img: 'https://via.placeholder.com/40', + workspace: { name: 'Workspace X' }, + onTitleClick: cy.stub() + }; + + cy.get('h3').should('contain', props.title); + cy.get('span').should('contain', props.features.name); + cy.get('span').should('contain', props.phase.name); + cy.get('span').should('contain', props.workspace.name); + cy.get('img').should('have.attr', 'src', props.assignee_img); + }); + + it('triggers onTitleClick when title is clicked', () => { + const onTitleClick = cy.stub(); + const props = { + id: 'bounty123', + title: 'Clickable Title', + features: {}, + phase: {}, + assignee_img: '', + workspace: {}, + onTitleClick + }; + + cy.get('h3').click(); + cy.wrap(onTitleClick).should('be.calledWith', 'bounty123'); + }); +}); diff --git a/src/people/WorkSpacePlanner/BountyCard/index.tsx b/src/people/WorkSpacePlanner/BountyCard/index.tsx new file mode 100644 index 00000000..41880826 --- /dev/null +++ b/src/people/WorkSpacePlanner/BountyCard/index.tsx @@ -0,0 +1,145 @@ +// Senior-level React implementation of BountyCardComponent +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { BountyCard } from 'store/interface'; +import { colors } from '../../../config'; + +const Card = styled.div` + width: 384px; + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; + display: flex; + flex-direction: column; + background-color: ${colors.light.grayish.G950}; + transition: + box-shadow 0.3s ease, + border 0.3s ease; + + &:hover { + border: 1px solid ${colors.light.light_blue100}; + box-shadow: 0 0 5px 1px ${colors.light.light_blue200}; + } +`; + +const Row = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 14px; + font-weight: 400; + margin-bottom: 8px; + color: ${colors.light.text2}; + + span { + margin-right: 20%; + white-space: nowrap; + } + + .span { + margin-left: auto; + justify-content: flex-end; + } +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +`; + +const CardTitle = styled.h3` + font-size: 20px; + font-weight: 700; + padding-right: 16px; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + text-align: left; + color: ${colors.light.text1}; + + &:hover { + color: ${colors.light.primaryColor}; + } +`; + +const AssignerPic = styled.div` + width: 40px; + height: 40px; + border-radius: 50%; + overflow: hidden; + background-color: ${colors.light.red1}; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + color: white; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +`; + +interface BountyCardProps extends BountyCard { + onclick: (bountyId: string) => void; +} + +const BountyCardComp = ({ + id, + title, + features, + phase, + assigneePic, + workspace, + onclick +}: BountyCardProps) => { + const handleTitleClick = ( + event: React.MouseEvent | React.KeyboardEvent + ) => { + event.preventDefault(); + onclick(id); + }; + + return ( + +
+ {title} + {assigneePic ? Assigner : 'Pic'} +
+ + + {features?.name ?? 'No Feature'} + {phase?.name ?? 'No Phase'} + + + {id} + {workspace?.name ?? 'No Workspace'} + Paid? + +
+ ); +}; + +BountyCardComp.propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + features: PropTypes.shape({ + name: PropTypes.string + }), + phase: PropTypes.shape({ + name: PropTypes.string + }), + assignee_img: PropTypes.string, + workspace: PropTypes.shape({ + name: PropTypes.string + }), + onclick: PropTypes.func.isRequired +}; + +export default BountyCardComp; diff --git a/src/people/WorkSpacePlanner/index.tsx b/src/people/WorkSpacePlanner/index.tsx index 5f91ecf2..5bc5712f 100644 --- a/src/people/WorkSpacePlanner/index.tsx +++ b/src/people/WorkSpacePlanner/index.tsx @@ -5,9 +5,11 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import styled from 'styled-components'; import { useBountyCardStore } from 'store/bountyCard'; import { BountyCard } from 'store/interface'; +import history from 'config/history'; import { useStores } from '../../store'; import { colors } from '../../config'; import { WorkspacePlannerHeader } from './WorkspacePlannerHeader'; +import BountyCardComp from './BountyCard'; const PlannerContainer = styled.div` padding: 0; @@ -67,6 +69,10 @@ const WorkspacePlanner = () => { ); } + const onclick = (bountyId: string) => { + history.push(`/bounty/${bountyId}`); + }; + return ( @@ -80,9 +86,7 @@ const WorkspacePlanner = () => { ) : ( {bountyCardStore.bountyCards.map((card: BountyCard) => ( -
  • - {card.title} -
  • + ))}
    )} diff --git a/src/store/interface.ts b/src/store/interface.ts index 3702f13a..6b3f563b 100644 --- a/src/store/interface.ts +++ b/src/store/interface.ts @@ -511,4 +511,5 @@ export interface BountyCard { features: Feature; phase: Phase; workspace: Workspace; + assigneePic: string; }