Compare commits

..

3 Commits

Author SHA1 Message Date
Henry
5fcd0e8688 fix: delete attachment from s3 2025-12-05 22:03:39 +00:00
Henry
d208952d15 feat: card due dates (#271)
* feat: add card due dates to schema

* feat: update repo funcs

* feat: update card router to support due dates

* chore: update migration journal

* feat: add date selector

* feat: display date icon and label on cards

* feat: add due date filters

* feat: add due date to new card form

* feat: light mode tweaks

* feat: improve text eligibility on light mode

* feat: reduce selector font size

* feat: display date updates in card activity

* chore: gen translations
2025-12-05 21:49:45 +00:00
Eliott Herbert-Byrnes
88f1aa0ec4 feat: implemented addOrRemoveLabel mutation on CardPage #266 (#270)
* feat: implemented addOrRemoveLabel mutation on CardPage to automatically add new labels #266

* Remove .env.backup
2025-12-05 19:39:18 +00:00
3 changed files with 58 additions and 1 deletions

View File

@@ -156,6 +156,7 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
getModalState,
clearModalState,
isOpen,
modalStates,
} = useModal();
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
@@ -192,6 +193,21 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
},
});
const addOrRemoveLabel = api.card.addOrRemoveLabel.useMutation({
onError: () => {
showPopup({
header: t`Unable to add label`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
onSettled: async () => {
if (cardId) {
await utils.card.byId.invalidate({ cardPublicId: cardId });
}
},
});
const { register, handleSubmit, setValue, watch } = useForm<FormValues>({
values: {
cardId: cardId ?? "",
@@ -208,6 +224,24 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
});
};
// this adds the new created label to selected labels
useEffect(() => {
const newLabelId = modalStates.NEW_LABEL_CREATED;
if (newLabelId && cardId) {
const isAlreadyAdded = card?.labels.some(
(label) => label.publicId === newLabelId,
);
if (!isAlreadyAdded) {
addOrRemoveLabel.mutate({
cardPublicId: cardId,
labelPublicId: newLabelId,
});
}
clearModalState("NEW_LABEL_CREATED");
}
}, [modalStates.NEW_LABEL_CREATED, card, cardId]);
// Open the new item form after creating a new checklist
useEffect(() => {
if (!card) return;

View File

@@ -9,7 +9,7 @@ import { generateUID } from "@kan/shared/utils";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { assertUserInWorkspace } from "../utils/auth";
import { generateUploadUrl } from "../utils/s3";
import { deleteObject, generateUploadUrl } from "../utils/s3";
export const attachmentRouter = createTRPCRouter({
generateUploadUrl: protectedProcedure
@@ -189,6 +189,18 @@ export const attachmentRouter = createTRPCRouter({
await assertUserInWorkspace(ctx.db, userId, workspaceId);
const bucket = process.env.NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME;
if (bucket) {
try {
await deleteObject(bucket, attachment.s3Key);
} catch (error) {
console.error(
`Failed to delete attachment from S3: ${attachment.s3Key}`,
error,
);
}
}
await cardAttachmentRepo.softDelete(ctx.db, {
attachmentId: attachment.id,
deletedAt: new Date(),

View File

@@ -1,4 +1,5 @@
import {
DeleteObjectCommand,
GetObjectCommand,
PutObjectCommand,
S3Client,
@@ -56,3 +57,13 @@ export async function generateDownloadUrl(
{ expiresIn },
);
}
export async function deleteObject(bucket: string, key: string) {
const client = createS3Client();
await client.send(
new DeleteObjectCommand({
Bucket: bucket,
Key: key,
}),
);
}