diff --git a/src/clients/Governance.ts b/src/clients/Governance.ts index 0d4b51e..636a797 100644 --- a/src/clients/Governance.ts +++ b/src/clients/Governance.ts @@ -33,7 +33,7 @@ import { NewProposalTender, PendingProposalsQuery, PriorityProposal, - ProjectAttributes, + Project, ProposalAttributes, ProposalCommentsInDiscourse, ProposalListFilter, @@ -188,7 +188,7 @@ export class Governance extends API { return proposals } async getProject(projectId: string) { - return await this.fetchApiResponse(`/projects/${projectId}`) + return await this.fetchApiResponse(`/projects/${projectId}`) } async getOpenPitchesTotal() { diff --git a/src/components/Common/Typography/Text.css b/src/components/Common/Typography/Text.css index be853d2..ea98b47 100644 --- a/src/components/Common/Typography/Text.css +++ b/src/components/Common/Typography/Text.css @@ -2,6 +2,22 @@ font-stretch: normal; } +.Text.Text--transform-none { + text-transform: none; +} + +.Text.Text--transform-uppercase { + text-transform: uppercase; +} + +.Text.Text--transform-lowercase { + text-transform: lowercase; +} + +.Text.Text--transform-capitalize { + text-transform: capitalize; +} + .Text.Text--size-xs { font-size: 11px; line-height: 16px; diff --git a/src/components/Common/Typography/Text.tsx b/src/components/Common/Typography/Text.tsx index dd88a80..b6da580 100644 --- a/src/components/Common/Typography/Text.tsx +++ b/src/components/Common/Typography/Text.tsx @@ -12,6 +12,7 @@ export type FontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' export type FontWeight = 'bold' | 'semi-bold' | 'normal' | 'medium' | 'light' type TextColor = 'default' | 'primary' | 'secondary' | 'error' | 'white-900' type FontStyle = 'normal' | 'italic' +type TransformOptions = 'uppercase' | 'lowercase' | 'capitalize' | 'none' interface Props { children?: React.ReactNode @@ -22,6 +23,7 @@ interface Props { style?: FontStyle as?: 'span' title?: string + transform?: TransformOptions } const Text = React.forwardRef( @@ -35,6 +37,7 @@ const Text = React.forwardRef( className, as, title, + transform = 'none', }, ref ) => { @@ -44,6 +47,7 @@ const Text = React.forwardRef( `Text--weight-${weight}`, `Text--color-${color}`, `Text--style-${style}`, + `Text--transform-${transform}`, className ) const Component = as ?? 'p' diff --git a/src/components/GrantRequest/BreakdownAccordion.tsx b/src/components/GrantRequest/BreakdownAccordion.tsx index f998a29..5b7e757 100644 --- a/src/components/GrantRequest/BreakdownAccordion.tsx +++ b/src/components/GrantRequest/BreakdownAccordion.tsx @@ -10,7 +10,7 @@ import ChevronRightCircleOutline from '../Icon/ChevronRightCircleOutline' import './BreakdownAccordion.css' export interface BreakdownItem { - title: string + title: React.ReactNode subtitle: string value?: string content: React.ReactNode diff --git a/src/components/GrantRequest/GrantRequestTeamSection.tsx b/src/components/GrantRequest/GrantRequestTeamSection.tsx index 4328d1e..1c70afa 100644 --- a/src/components/GrantRequest/GrantRequestTeamSection.tsx +++ b/src/components/GrantRequest/GrantRequestTeamSection.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'react' import isEmpty from 'lodash/isEmpty' import useFormatMessage from '../../hooks/useFormatMessage' -import { GrantRequestTeam, TeamMember } from '../../types/grants' +import { ProposalRequestTeam, TeamMember } from '../../types/grants' import { userModifiedForm } from '../../utils/proposal' import SubLabel from '../Common/SubLabel' import Label from '../Common/Typography/Label' @@ -13,13 +13,13 @@ import AddBox from './AddBox' import AddTeamMemberModal from './AddTeamMemberModal' import BreakdownItem from './BreakdownItem' -export const INITIAL_GRANT_REQUEST_TEAM_STATE: GrantRequestTeam = { +export const INITIAL_GRANT_REQUEST_TEAM_STATE: ProposalRequestTeam = { members: [], } interface Props { sectionNumber: number - onValidation: (data: GrantRequestTeam, sectionValid: boolean) => void + onValidation: (data: ProposalRequestTeam, sectionValid: boolean) => void isDisabled?: boolean } diff --git a/src/components/Icon/Open.tsx b/src/components/Icon/Open.tsx index 48ef539..06d05ab 100644 --- a/src/components/Icon/Open.tsx +++ b/src/components/Icon/Open.tsx @@ -1,8 +1,8 @@ -function Open({ className }: { className?: string }) { +function Open({ className, size = 12 }: { className?: string; size?: number }) { return ( void +} + +function EditableBreakdownContent({ about, relevantLink, onClick }: Props) { + const t = useFormatMessage() + + return ( +
+
{about}
+
+ {relevantLink ? ( + +
{t('component.expandable_breakdown_item.relevant_link_label')}
+ + + ) : ( +
+ )} + +
+
+ ) +} + +export default EditableBreakdownContent diff --git a/src/components/Projects/EditablePersonnelView.tsx b/src/components/Projects/EditablePersonnelView.tsx new file mode 100644 index 0000000..a54c837 --- /dev/null +++ b/src/components/Projects/EditablePersonnelView.tsx @@ -0,0 +1,43 @@ +import { useMemo } from 'react' + +import useFormatMessage from '../../hooks/useFormatMessage.ts' +import { TeamMember } from '../../types/grants.ts' +import Username from '../Common/Username.tsx' +import { BreakdownItem } from '../GrantRequest/BreakdownAccordion.tsx' + +import EditableBreakdownContent from './EditableBreakdownContent.tsx' +import ExpandableBreakdownItem from './ExpandableBreakdownItem.tsx' +import ProjectSidebarSectionTitle from './ProjectSidebarSectionTitle.tsx' + +interface Props { + members: TeamMember[] +} + +function EditablePersonnelView({ members }: Props) { + const t = useFormatMessage() + + function getTitle(name: string, address?: string) { + return address && address.length > 0 ? : name + } + + const items = useMemo( + () => + members.map(({ name, role, about, relevantLink, address }) => ({ + title: getTitle(name, address), + subtitle: role, + content: {}} relevantLink={relevantLink} />, + })), + [members] + ) + + return ( + <> + + {items.map((item, key) => { + return + })} + + ) +} + +export default EditablePersonnelView diff --git a/src/components/Projects/ExpandableBreakdownItem.css b/src/components/Projects/ExpandableBreakdownItem.css new file mode 100644 index 0000000..f47bcc4 --- /dev/null +++ b/src/components/Projects/ExpandableBreakdownItem.css @@ -0,0 +1,36 @@ +.ExpandableBreakdownItem { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + background: var(--white-900); + margin-bottom: 16px; + padding: 16px; + min-height: 64px; + border: 1px solid var(--black-300); + box-shadow: 0 1px 4px var(--alpha-black-300); + border-radius: 8px; +} + +.ExpandableBreakdownItem__Header { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + width: 100%; +} + +.ExpandableBreakdownItem__Content { + display: none; + min-height: 64px; +} + +.ExpandableBreakdownItem__Content--expanded { + display: block; + align-items: center; + justify-content: space-between; + margin-top: 16px; + background: var(--white-900); + width: 100%; + border-top: 1px solid var(--black-300); +} diff --git a/src/components/Projects/ExpandableBreakdownItem.tsx b/src/components/Projects/ExpandableBreakdownItem.tsx new file mode 100644 index 0000000..f5fb9e0 --- /dev/null +++ b/src/components/Projects/ExpandableBreakdownItem.tsx @@ -0,0 +1,51 @@ +import { useState } from 'react' + +import classNames from 'classnames' + +import ChevronRightCircleOutline from '../Icon/ChevronRightCircleOutline.tsx' + +import './ExpandableBreakdownItem.css' + +interface ExpandableBreakdownItemProps { + title: React.ReactNode + subtitle: string + content: React.ReactNode +} + +interface Props { + item: ExpandableBreakdownItemProps + initiallyExpanded?: boolean +} + +function ExpandableBreakdownItem({ item, initiallyExpanded = false }: Props) { + const { title, subtitle, content } = item + const [isActive, setIsActive] = useState(initiallyExpanded) + + return ( +
+
setIsActive((prev) => !prev)}> +
+
{title}
+
{subtitle}
+
+
+ + + +
+
+
+ {content} +
+
+ ) +} + +export default ExpandableBreakdownItem diff --git a/src/components/Projects/ProjectGeneralInfo.tsx b/src/components/Projects/ProjectGeneralInfo.tsx new file mode 100644 index 0000000..aaa041d --- /dev/null +++ b/src/components/Projects/ProjectGeneralInfo.tsx @@ -0,0 +1,18 @@ +import { Project } from '../../types/proposals.ts' + +import EditablePersonnelView from './EditablePersonnelView.tsx' +import './ProjectSidebar.css' + +interface Props { + project: Project +} + +function ProjectGeneralInfo({ project }: Props) { + return ( +
+ +
+ ) +} + +export default ProjectGeneralInfo diff --git a/src/components/Projects/ProjectSidebar.tsx b/src/components/Projects/ProjectSidebar.tsx index ce63c56..2fb5ea1 100644 --- a/src/components/Projects/ProjectSidebar.tsx +++ b/src/components/Projects/ProjectSidebar.tsx @@ -2,6 +2,7 @@ import useProject from '../../hooks/useProject.ts' import BoxTabs from '../Common/BoxTabs' import GovernanceSidebar from '../Sidebar/GovernanceSidebar' +import ProjectGeneralInfo from './ProjectGeneralInfo.tsx' import ProjectSheetTitle from './ProjectSheetTitle.tsx' import './ProjectSidebar.css' @@ -33,11 +34,7 @@ function ProjectSidebar({ projectId, isSidebarVisible, onClose }: Props) { Activity -
- {/*{hasUpdates && (*/} - {/* */} - {/*)}*/} -
+ {project && } ) } diff --git a/src/components/Projects/ProjectSidebarSectionTitle.tsx b/src/components/Projects/ProjectSidebarSectionTitle.tsx new file mode 100644 index 0000000..04f455f --- /dev/null +++ b/src/components/Projects/ProjectSidebarSectionTitle.tsx @@ -0,0 +1,15 @@ +import Text from '../Common/Typography/Text.tsx' + +interface Props { + text: string +} + +function ProjectSidebarSectionTitle({ text }: Props) { + return ( + + {text} + + ) +} + +export default ProjectSidebarSectionTitle diff --git a/src/intl/en.json b/src/intl/en.json index 3cfead4..59d675c 100644 --- a/src/intl/en.json +++ b/src/intl/en.json @@ -865,6 +865,10 @@ "markdown_field": { "preview_button": "Preview", "edit_button": "Edit" + }, + "expandable_breakdown_item": { + "relevant_link_label": "Relevant Link", + "edit_action_label": "Edit" } }, "page": { diff --git a/src/types/bids.ts b/src/types/bids.ts index 2e7350e..99b99c7 100644 --- a/src/types/bids.ts +++ b/src/types/bids.ts @@ -6,6 +6,7 @@ import { GrantRequestTeamSchema, Milestone, MilestoneItemSchema, + ProposalRequestTeam, } from './grants' export enum UnpublishedBidStatus { @@ -46,25 +47,13 @@ export type BidRequestGeneralInfo = { coAuthors?: string[] } -export type TeamMember = { - name: string - role: string - about: string - address?: string - relevantLink?: string -} - -export type BidRequestTeam = { - members: TeamMember[] -} - export type BidRequestDueDiligence = { budgetBreakdown: BudgetBreakdownConcept[] } export type BidRequest = BidRequestFunding & BidRequestGeneralInfo & - BidRequestTeam & + ProposalRequestTeam & BidRequestDueDiligence & { linked_proposal_id: string coAuthors?: string[] diff --git a/src/types/grants.ts b/src/types/grants.ts index 06c130a..5565a4b 100644 --- a/src/types/grants.ts +++ b/src/types/grants.ts @@ -386,7 +386,7 @@ export type GrantRequest = { category: NewGrantCategory | null } & GrantRequestFunding & GrantRequestGeneralInfo & - GrantRequestTeam & + ProposalRequestTeam & GrantRequestCategoryAssessment & GrantRequestDueDiligence @@ -424,9 +424,9 @@ export type GrantRequestDueDiligence = { export type TeamMember = { name: string + address?: string role: string about: string - address?: string relevantLink?: string } @@ -436,7 +436,7 @@ export type Milestone = { tasks: string } -export type GrantRequestTeam = { +export type ProposalRequestTeam = { members: TeamMember[] } diff --git a/src/types/proposals.ts b/src/types/proposals.ts index 9e219ad..31fad13 100644 --- a/src/types/proposals.ts +++ b/src/types/proposals.ts @@ -6,12 +6,13 @@ import { CategoryAssessmentQuestions, GrantRequestDueDiligence, GrantRequestGeneralInfo, - GrantRequestTeam, GrantTierType, PaymentToken, ProjectStatus, ProposalGrantCategory, + ProposalRequestTeam, SubtypeOptions, + TeamMember, VestingStartDate, } from './grants' import { IndexedUpdate } from './updates' @@ -598,7 +599,7 @@ export const newProposalTenderScheme = { export type GrantProposalConfiguration = GrantRequestGeneralInfo & GrantRequestDueDiligence & - GrantRequestTeam & { + ProposalRequestTeam & { category: ProposalGrantCategory | null size: number paymentToken?: PaymentToken @@ -769,6 +770,8 @@ export type ProjectAttributes = { created_at: Date } +export type Project = ProjectAttributes & { personnel: PersonnelAttributes[] } + export type ProposalProjectWithUpdate = ProposalProject & { update?: IndexedUpdate | null update_timestamp?: number @@ -796,3 +799,12 @@ export type PriorityProposal = Pick< linked_proposals_data?: LinkedProposal[] unpublished_bids_data?: UnpublishedBidInfo[] } + +export type PersonnelAttributes = TeamMember & { + id: string + project_id: string + deleted: boolean + updated_by?: string + updated_at?: Date + created_at: Date +}