From 7279b72b0877f34d3bd5cd5f6d93a39b8da40e88 Mon Sep 17 00:00:00 2001 From: mattcasey Date: Mon, 3 Feb 2025 15:09:51 -0700 Subject: [PATCH 1/7] hide deleted properties --- .../components/cardDetail/cardDetailProperties.tsx | 6 ++++-- lib/databases/board.ts | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx b/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx index 94b9ef791e..eea213e9ee 100644 --- a/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx +++ b/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx @@ -270,11 +270,13 @@ function CardDetailProperties(props: Props) { [mutator, props.boardType, board, activeView, isSmallScreen] ); - let boardProperties = board.fields.cardProperties || []; + let boardProperties = (board.fields.cardProperties || []) + // hide deleted properties unless they were deleted before the card was created + .filter((p) => !p.deletedAt || new Date(p.deletedAt) > new Date(card.createdAt)); if (board.fields.sourceType === 'proposals') { // remove properties that belong to a different template - boardProperties = board.fields?.cardProperties.filter( + boardProperties = boardProperties.filter( (property) => !property.templateId || card.fields.properties[property.id] !== undefined ); } diff --git a/lib/databases/board.ts b/lib/databases/board.ts index 2b747f8741..953b15ebff 100644 --- a/lib/databases/board.ts +++ b/lib/databases/board.ts @@ -72,6 +72,7 @@ export type IPropertyTemplate = { id: string; name: string; type: T; + deletedAt?: string; // ISODate. If set, the property will be visible based on when the page was created options: IPropertyOption[]; description?: string; tooltip?: string; From 794a5d5f9d08d85d1556f5e6b223c470c571292a Mon Sep 17 00:00:00 2001 From: mattcasey Date: Mon, 3 Feb 2025 15:40:37 -0700 Subject: [PATCH 2/7] update --- .../cardDetail/cardDetailProperties.tsx | 8 ++++--- .../components/CustomPropertiesAdapter.tsx | 1 + .../CustomPropertiesAdapter.tsx | 1 + components/rewards/hooks/useRewardsBoard.ts | 9 +++++++- hooks/useProposalBlocks.tsx | 9 +++++++- lib/proposals/createDraftProposal.ts | 22 +++++++++++++++++-- lib/rewards/createReward.ts | 21 ++++++++++++++++++ 7 files changed, 64 insertions(+), 7 deletions(-) diff --git a/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx b/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx index eea213e9ee..285ac5f355 100644 --- a/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx +++ b/components/common/DatabaseEditor/components/cardDetail/cardDetailProperties.tsx @@ -39,6 +39,7 @@ type Props = { disableEditPropertyOption?: boolean; boardType?: 'proposals' | 'rewards'; showCard?: (cardId: string | null) => void; + isTemplate?: boolean; // whether or not to always hide deleted properties }; function CardDetailProperties(props: Props) { @@ -52,7 +53,8 @@ function CardDetailProperties(props: Props) { pageUpdatedBy, syncWithPageId, mutator = defaultMutator, - disableEditPropertyOption + disableEditPropertyOption, + isTemplate } = props; const [newTemplateId, setNewTemplateId] = useState(''); @@ -271,8 +273,8 @@ function CardDetailProperties(props: Props) { ); let boardProperties = (board.fields.cardProperties || []) - // hide deleted properties unless they were deleted before the card was created - .filter((p) => !p.deletedAt || new Date(p.deletedAt) > new Date(card.createdAt)); + // hide deleted properties unless they were deleted before the card was created and it's not a template + .filter((p) => !p.deletedAt || (!isTemplate && new Date(p.deletedAt) > new Date(card.createdAt))); if (board.fields.sourceType === 'proposals') { // remove properties that belong to a different template diff --git a/components/proposals/ProposalPage/components/ProposalProperties/components/CustomPropertiesAdapter.tsx b/components/proposals/ProposalPage/components/ProposalProperties/components/CustomPropertiesAdapter.tsx index 57a5387d20..3cc0f3663b 100644 --- a/components/proposals/ProposalPage/components/ProposalProperties/components/CustomPropertiesAdapter.tsx +++ b/components/proposals/ProposalPage/components/ProposalProperties/components/CustomPropertiesAdapter.tsx @@ -78,6 +78,7 @@ export function CustomPropertiesAdapter({ onChange, readOnly, readOnlyProperties mutator={mutator ?? undefined} readOnlyProperties={readOnlyProperties} disableEditPropertyOption={!isAdmin} + isTemplate={!proposalFromDb} // templates are not loaded into the proposalsmap boardType='proposals' /> ); diff --git a/components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx b/components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx index 9108edde19..d76ef13a72 100644 --- a/components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx +++ b/components/rewards/components/RewardProperties/CustomPropertiesAdapter.tsx @@ -87,6 +87,7 @@ export function CustomPropertiesAdapter({ reward, onChange, readOnly }: Props) { mutator={mutator ?? undefined} disableEditPropertyOption={!isAdmin} boardType='rewards' + isTemplate={rewardPage?.type === 'bounty_template'} /> ); } diff --git a/components/rewards/hooks/useRewardsBoard.ts b/components/rewards/hooks/useRewardsBoard.ts index 0ec08692e2..f3a8fcde8f 100644 --- a/components/rewards/hooks/useRewardsBoard.ts +++ b/components/rewards/hooks/useRewardsBoard.ts @@ -126,7 +126,14 @@ export function useRewardsBoard() { return; } - const updatedProperties = dbBlock.fields.cardProperties.filter((p) => p.id !== propertyTemplateId); + const updatedProperties = dbBlock.fields.cardProperties.map((p) => + p.id === propertyTemplateId + ? { + ...p, + deletedAt: new Date().toISOString() + } + : p + ); const updatedBlock = { ...dbBlock, fields: { ...(dbBlock.fields as BoardFields), cardProperties: updatedProperties } diff --git a/hooks/useProposalBlocks.tsx b/hooks/useProposalBlocks.tsx index 6b4209fb91..898fe6215a 100644 --- a/hooks/useProposalBlocks.tsx +++ b/hooks/useProposalBlocks.tsx @@ -175,7 +175,14 @@ export function ProposalBlocksProvider({ children }: { children: ReactNode }) { return; } - const updatedProperties = proposalBoardBlock.fields.cardProperties.filter((p) => p.id !== propertyTemplateId); + const updatedProperties = proposalBoardBlock.fields.cardProperties.map((p) => + p.id === propertyTemplateId + ? { + ...p, + deletedAt: new Date().toISOString() + } + : p + ); const updatedBlock = { ...proposalBoardBlock, fields: { cardProperties: updatedProperties } }; try { const res = await updateProposalBlocks([updatedBlock]); diff --git a/lib/proposals/createDraftProposal.ts b/lib/proposals/createDraftProposal.ts index c457ef0dcd..a367f6516a 100644 --- a/lib/proposals/createDraftProposal.ts +++ b/lib/proposals/createDraftProposal.ts @@ -1,6 +1,7 @@ import type { PageType } from '@charmverse/core/prisma'; import { prisma } from '@charmverse/core/prisma-client'; import type { WorkflowEvaluationJson } from '@charmverse/core/proposals'; +import type { BoardFields } from '@root/lib/databases/board'; import { generatePagePathFromPathAndTitle } from '@root/lib/pages/utils'; import { createDefaultProjectAndMembersFieldConfig } from '@root/lib/projects/formField'; import type { FormFieldInput } from '@root/lib/proposals/forms/interfaces'; @@ -31,7 +32,7 @@ export type CreateDraftProposalInput = { export async function createDraftProposal(input: CreateDraftProposalInput) { // get source data - const [template, sourcePage, sourcePost] = await Promise.all([ + const [template, sourcePage, sourcePost, proposalBoardBlock] = await Promise.all([ input.templateId ? await prisma.page.findUniqueOrThrow({ where: { @@ -76,7 +77,15 @@ export async function createDraftProposal(input: CreateDraftProposalInput) { id: input.sourcePostId } }) - : null + : null, + prisma.proposalBlock.findUnique({ + where: { + id_spaceId: { + id: '__defaultBoard', + spaceId: input.spaceId + } + } + }) ]); const workflow = @@ -130,6 +139,15 @@ export async function createDraftProposal(input: CreateDraftProposalInput) { pendingRewards: [] }; + // dont create properties that were deleted + if (proposalBoardBlock && fields.properties) { + (proposalBoardBlock.fields as unknown as BoardFields).cardProperties.forEach((property) => { + if (property.deletedAt) { + delete fields.properties![property.id]; + } + }); + } + let formFields: FormFieldInput[] = []; if (input.pageType === 'proposal_template' && template?.proposal?.form) { formFields = template.proposal.form.formFields.map( diff --git a/lib/rewards/createReward.ts b/lib/rewards/createReward.ts index f3b74de626..808b897a28 100644 --- a/lib/rewards/createReward.ts +++ b/lib/rewards/createReward.ts @@ -1,11 +1,14 @@ import type { Page, PagePermission, Prisma } from '@charmverse/core/prisma'; import { prisma } from '@charmverse/core/prisma-client'; +import type { BoardFields } from '@root/lib/databases/board'; import { NotFoundError } from '@root/lib/middleware'; import { generatePagePathFromPathAndTitle } from '@root/lib/pages/utils'; import { extractPreviewImage } from '@root/lib/prosemirror/extractPreviewImage'; import { InvalidInputError } from '@root/lib/utils/errors'; import { v4 } from 'uuid'; +import type { RewardFields } from 'lib/rewards/blocks/interfaces'; + import { trackOpUserAction } from '../metrics/mixpanel/trackOpUserAction'; import { getRewardOrThrow } from './getReward'; @@ -82,6 +85,24 @@ export async function createReward({ } }); + const rewardBoardBlock = await prisma.rewardBlock.findUnique({ + where: { + id_spaceId: { + id: '__defaultBoard', + spaceId + } + } + }); + + // dont create properties that were deleted + if (rewardBoardBlock && (fields as unknown as RewardFields | undefined)?.properties) { + (rewardBoardBlock.fields as unknown as BoardFields).cardProperties.forEach((property) => { + if (property.deletedAt) { + delete (fields as unknown as RewardFields).properties![property.id]; + } + }); + } + const rewardId = v4(); const isAssignedReward = Array.isArray(assignedSubmitters) && assignedSubmitters.length > 0; From 43ad0da7ef8f469c6988ae45b6aa1f33045f89f2 Mon Sep 17 00:00:00 2001 From: mattcasey Date: Mon, 3 Feb 2025 16:37:08 -0700 Subject: [PATCH 3/7] disable editing deleted fields --- .../components/cardProperties/CardDetailProperty.tsx | 6 +++++- .../components/properties/PropertyLabel.tsx | 9 ++++++--- .../common/DatabaseEditor/widgets/buttons/button.tsx | 2 ++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/components/common/DatabaseEditor/components/cardProperties/CardDetailProperty.tsx b/components/common/DatabaseEditor/components/cardProperties/CardDetailProperty.tsx index bb1949314a..87a75aca40 100644 --- a/components/common/DatabaseEditor/components/cardProperties/CardDetailProperty.tsx +++ b/components/common/DatabaseEditor/components/cardProperties/CardDetailProperty.tsx @@ -67,6 +67,10 @@ export function CardDetailProperty({ const [isDragging, isOver, columnRef] = useSortable('column', property, !readOnly, onDrop); const changePropertyPopupState = usePopupState({ variant: 'popover', popupId: 'card-property' }); const propertyTooltip = property.name.length > 20 ? property.name : ''; + + // do not allow editing deleted properties (simpler UX) + readOnly = readOnly || !!property.deletedAt; + return ( {(readOnly || disableEditPropertyOption) && ( - + {property.name} )} diff --git a/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx b/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx index 1aaaaf86af..791df09bda 100644 --- a/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx +++ b/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx @@ -11,6 +11,7 @@ type PropertyLabelProps = { highlighted?: boolean; fullWidth?: boolean; tooltip?: string; + deleted?: boolean; // when showing deleted card properties }; const Wrapper = styled(({ highlighted, fullWidth, ...props }: any) => )<{ @@ -31,18 +32,20 @@ export function PropertyLabel({ fullWidth, tooltip, readOnly = true, - highlighted + highlighted, + deleted }: PropertyLabelProps) { if (readOnly) { + const deletedTooltip = deleted ? 'This property was deleted' : tooltip; return ( - + - diff --git a/components/common/DatabaseEditor/widgets/buttons/button.tsx b/components/common/DatabaseEditor/widgets/buttons/button.tsx index 6643110a65..b38e4c4aaf 100644 --- a/components/common/DatabaseEditor/widgets/buttons/button.tsx +++ b/components/common/DatabaseEditor/widgets/buttons/button.tsx @@ -13,6 +13,7 @@ type Props = { rightIcon?: boolean; disabled?: boolean; 'data-test'?: string; + deleted?: boolean; }; function Button({ size = 'small', ...props }: Props): JSX.Element { @@ -31,6 +32,7 @@ function Button({ size = 'small', ...props }: Props): JSX.Element { title={props.title} onBlur={props.onBlur} disabled={props.disabled} + style={{ textDecoration: props.deleted ? 'line-through' : 'none' }} > {!props.rightIcon && props.icon} {props.children} From 9095eebbc40de66200b7e5f0abe547084c9ad8fc Mon Sep 17 00:00:00 2001 From: mattcasey Date: Mon, 3 Feb 2025 18:33:00 -0700 Subject: [PATCH 4/7] fix tests --- .../__snapshots__/centerPanel.spec.tsx.snap | 8 ++++++++ .../kanban/__snapshots__/kanban.spec.tsx.snap | 6 ++++++ .../kanbanColumnHeader.spec.tsx.snap | 3 +++ .../kanbanHiddenColumnItem.spec.tsx.snap | 2 ++ .../__snapshots__/calculation.spec.tsx.snap | 3 +++ .../table/__snapshots__/table.spec.tsx.snap | 15 +++++++++++++++ .../tableGroupHeaderRow.spec.tsx.snap | 5 +++++ .../__snapshots__/tableHeaders.spec.tsx.snap | 1 + .../table/__snapshots__/tableRow.spec.tsx.snap | 6 ++++++ package-lock.json | 15 +++++++++++++++ 10 files changed, 64 insertions(+) diff --git a/components/common/DatabaseEditor/components/__snapshots__/centerPanel.spec.tsx.snap b/components/common/DatabaseEditor/components/__snapshots__/centerPanel.spec.tsx.snap index 9096b2ac08..d4648c5ae8 100644 --- a/components/common/DatabaseEditor/components/__snapshots__/centerPanel.spec.tsx.snap +++ b/components/common/DatabaseEditor/components/__snapshots__/centerPanel.spec.tsx.snap @@ -278,6 +278,7 @@ exports[`components/centerPanel return centerPanel and click on card to show car class="KanbanCalculation" > + @@ -113,6 +116,10 @@ export function CardDetailProperty({ { + onRestore(); + changePropertyPopupState.close(); + }} deleteDisabled={deleteDisabledMessage?.length !== 0} property={property} onTypeAndNameChanged={(newType, newName, relationData) => { diff --git a/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx b/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx index 791df09bda..db8bd154b5 100644 --- a/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx +++ b/components/common/DatabaseEditor/components/properties/PropertyLabel.tsx @@ -36,14 +36,13 @@ export function PropertyLabel({ deleted }: PropertyLabelProps) { if (readOnly) { - const deletedTooltip = deleted ? 'This property was deleted' : tooltip; return ( - +