diff --git a/apps/web/src/pages/api/download/attatchment.ts b/apps/web/src/pages/api/download/attatchment.ts new file mode 100644 index 00000000..2c82e54f --- /dev/null +++ b/apps/web/src/pages/api/download/attatchment.ts @@ -0,0 +1,48 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method !== "GET") { + return res.status(405).json({ message: "Method not allowed" }); + } + + const { url, filename } = req.query; + + if (!url || typeof url !== "string") { + return res.status(400).json({ + message: "url parameter is required", + }); + } + + try { + const downloadUrl = decodeURIComponent(url); + const downloadFilename = + (typeof filename === "string" ? decodeURIComponent(filename) : null) ?? + "attachment"; + + const upstream = await fetch(downloadUrl); + + if (!upstream.ok) { + return res.status(upstream.status).json({ + message: "Failed to fetch attachment", + }); + } + + const contentType = + upstream.headers.get("Content-Type") ?? "application/octet-stream"; + + res.setHeader("Content-Type", contentType); + res.setHeader( + "Content-Disposition", + `attachment; filename="${downloadFilename}"`, + ); + + const buffer = await upstream.arrayBuffer(); + return res.send(Buffer.from(buffer)); + } catch (error) { + console.error("Error downloading attachment:", error); + return res.status(500).json({ message: "Failed to download attachment" }); + } +} diff --git a/apps/web/src/views/board/components/Card.tsx b/apps/web/src/views/board/components/Card.tsx index f09886d8..3577288b 100644 --- a/apps/web/src/views/board/components/Card.tsx +++ b/apps/web/src/views/board/components/Card.tsx @@ -1,3 +1,4 @@ +import { HiOutlinePaperClip } from "react-icons/hi"; import { HiBars3BottomLeft, HiChatBubbleLeft } from "react-icons/hi2"; import Avatar from "~/components/Avatar"; @@ -13,6 +14,7 @@ const Card = ({ checklists, description, comments, + attachments, }: { title: string; labels: { name: string; colourCode: string | null }[]; @@ -33,6 +35,7 @@ const Card = ({ }[]; description: string | null; comments: { publicId: string }[]; + attachments?: { publicId: string }[]; }) => { const completedItems = checklists.reduce((acc, checklist) => { return acc + checklist.items.filter((item) => item.completed).length; @@ -47,6 +50,7 @@ const Card = ({ const hasDescription = description && description.replace(/<[^>]*>/g, "").trim().length > 0; + const hasAttachments = attachments && attachments.length > 0; return (