From 64e6d551562f8d5a980511e3d871d19ce26731f9 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:38:26 +0000 Subject: [PATCH 1/5] Work in progress --- bun.lockb | Bin 364957 -> 364957 bytes src/routeTree.gen.ts | 9 +++++++++ 2 files changed, 9 insertions(+) diff --git a/bun.lockb b/bun.lockb index 75dfddd2d3335b0b5709d90f3e60f6e863151dbc..328f2486a5ae6644bba24c75b506664b2860c529 100755 GIT binary patch delta 2424 zcmZ8jdrXyO7$5X}0|E((IY*<#3IS1sbCfuO5*|`P?P9YvXXPa`*NF8;H??BK*s4u% z0S}<8l7HCZA_DtFZi1lPEKo!Q$@#+9SOele6BKUa+_+m*3_zThB0bb}`3cfWS6Ckf_L9ZZB{muqJ1*HtTaorPl}<68yW1m{#iyx?cf!A`+> z=TzdJ)etTG(Q0XFqELHkU=v~0$e|hzngTW9wpuBy35#oCKM7Kjhio6B>cBCX&(*7% zCfCCjvHw>+x0Y+L?>tYh^3{7DmWyCmgW8f~4G={^Cg{jyUI3iYNTDTV+XJRWSM|dp`w{o2s?`netn{zYvw(-n!1Z&z=j|$t>%F_HB zo5eCT`o=skrnxlsg)tB9*2`)Fc_ix9Gn9oH(>visF*)9; z<};y7wW6?#=Yz}Xe}&7*A$)%YT1l7rw5!}MvKfJouJXD|NnG%7H(eJ=-5dcov8S6O zU>tFc8%h>Um~)M-vnDm&9hdZgpM9f3hK%!65Vqj)alY*dTk*B`$+pV`=QuJC zahNF|@T*B#ax2bFav52ju!b2^B=Pl> zvav9wt~a{lUGSqj5Mm42ihXyK?VWce6)TS31&heMcaK}gE=KRk1!ctz{ZyR9X)YNb zPE2!M7$2RX!=xE?I61@XLx*v=XBJ+zSvOt-(MG``j<@d#nY^tOd(b z(j3wp)1Ih+j4X!;^r{0BHfF&p!VT3BhRJA7n$Ib5HI|x zlMqgr)1k24(Ax@YiTGs}!qM{~Xqak+phZpnrWN8m=*RJ9%KnWa&X`?Q6v4NPLU^bc zR*KBUVz$OIo*m$ei6yXH1V8TpA1o;WKOy=%z!SYn!AFR9N+CpWZ7H+~t~w1H1+OlH z2*E#=L8xHT^+uO7${1OH8V{aP{>kEUY+WKjW&?43xl;MA96lp^Tpd#d+ve1>D_|Y* zj2$bvf}G;PN_$uIhDu0vkYGO5!9+N2g%*zex@yI)vv5pge5zoZ;M^)m5d6$J*eN*w zoJ!oI8e)V$S}iS25^7HkY$B{0IaI?zQ=lf?Rx5=yVR0?&CqYW`knKZM9oQ%Hxq4O8 zlzP}A_W!Eq)^ZK@o#*LQzIxBY3K0x#P+M}W0iua#rN0{>fUw^Mis!9HSVLGDsB46M zwnHPfPcG3kp1r_h&dmsFf|rQj+XTt<>1u)l^x+nK*le^wCl4<6>UUg}%)>D15-$Z= zw5vF`FcsGVN5slZ3-^NKOlsxi2(QKAR<1MSU2TwPa~_VpZ9MZFK}5UiQDM7US(<-i zvsi{k-4eo}hO!99j81q_OpbS| z`AqCmttjl``QUQ;UEy+a2;X0UR?=lY{VKPMY)0avtGw<~5+^*|P1i+oH%Gut?CItR z7)M^?hLS}S=3Zm#tVvCGMfV=?mGE}=uplXgV||YrHNWc+Co);rIaChMtrvQTSCjJW z4KAf3(r<7`ig;ir%=+Fwh;+2+oAs^%@Gxr0k^OXwBJ&+oL(N3yAa8<_a34}}r3|qr zb@;~+w_6b(4D)CyVqh374__Sv z7mOWy%uxFnJc%`9{|m+q!)~eNBW2@)p(1U>;BlS`!e%@^&bK{b3%>R~*>;-X97pCM z9#bZy)D~Qr;4oRRXg>)azAa_9V8{TC#y_`}#Be_Vel;meZo#=pE+eZG)-ZF5B)*2G8?s4nb#ppe`pe(qdpNf+-%_ZZ* ziD|A2&C0V)b2UHln9%#dk(6^`Rn(=T_3y;FBtR1 zlA9htjt%p}`X*s6YT-k+KsW(I=Q%CHTk!NeF9YFpn`i=-E!eZuL;i#)<87oTB?b%p zHLDF9HyAJAEIlB=eB`gW;hG>tLLIi??8*qo{L{s(KTiqQZ7 diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 95b590e..1983a7d 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -1077,3 +1077,12 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} From 2d9d3fbaea334eb17abd7df2ae91fee222f6b791 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:39:23 +0000 Subject: [PATCH 2/5] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- .../cases/share-document-dialog.tsx | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/components/cases/share-document-dialog.tsx diff --git a/src/components/cases/share-document-dialog.tsx b/src/components/cases/share-document-dialog.tsx new file mode 100644 index 0000000..71bab34 --- /dev/null +++ b/src/components/cases/share-document-dialog.tsx @@ -0,0 +1,192 @@ +import { useEffect, useState } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; +import { supabase } from "@/integrations/supabase/client"; +import { toast } from "sonner"; +import { Loader2, Mail } from "lucide-react"; + +type Contact = { id: string; name: string; email: string | null; role: string | null }; + +export function ShareDocumentDialog({ + open, + onOpenChange, + caseId, + doc, +}: { + open: boolean; + onOpenChange: (o: boolean) => void; + caseId: string; + doc: { id: string; name: string; storage_path: string } | null; +}) { + const [contacts, setContacts] = useState([]); + const [selected, setSelected] = useState>({}); + const [extraEmail, setExtraEmail] = useState(""); + const [subject, setSubject] = useState(""); + const [message, setMessage] = useState(""); + const [loading, setLoading] = useState(false); + const [sending, setSending] = useState(false); + + useEffect(() => { + if (!open || !doc) return; + setSelected({}); + setExtraEmail(""); + setSubject(`Shared document: ${doc.name}`); + setMessage(`Please find the attached document: ${doc.name}.\n\nClick the link in the email to download. The link expires in 7 days.`); + (async () => { + setLoading(true); + const { data, error } = await supabase + .from("case_contacts") + .select("role, contact:contact_id(id, name, email)") + .eq("case_id", caseId); + setLoading(false); + if (error) { toast.error(error.message); return; } + const list: Contact[] = (data ?? []) + .map((row: any) => row.contact ? { ...row.contact, role: row.role } : null) + .filter(Boolean); + setContacts(list); + })(); + }, [open, caseId, doc]); + + const toggle = (id: string) => setSelected((s) => ({ ...s, [id]: !s[id] })); + + const send = async () => { + if (!doc) return; + const recipients = contacts.filter((c) => selected[c.id] && c.email).map((c) => c.email!); + const extras = extraEmail.split(/[,;\s]+/).map((s) => s.trim()).filter((s) => s.includes("@")); + const allRecipients = Array.from(new Set([...recipients, ...extras])); + if (allRecipients.length === 0) { toast.error("Pick at least one contact with an email, or enter an address"); return; } + + setSending(true); + // 7 days = 604800 seconds (max for signed URLs) + const { data: signed, error: signErr } = await supabase.storage + .from("case-documents") + .createSignedUrl(doc.storage_path, 60 * 60 * 24 * 7); + if (signErr || !signed) { + setSending(false); + toast.error(signErr?.message || "Could not create download link"); + return; + } + + const safeMsg = (message || "").replace(/&/g, "&").replace(//g, ">").replace(/\n/g, "
"); + const html = ` +
+

${safeMsg}

+

+ + Download ${doc.name} + +

+

+ Or copy this link into your browser:
+ ${signed.signedUrl} +

+

This download link expires in 7 days.

+
+ `; + + const { error } = await supabase.functions.invoke("send-smtp-email", { + body: { + to: allRecipients, + subject: subject || `Shared document: ${doc.name}`, + html, + context: "document_share", + case_id: caseId, + }, + }); + setSending(false); + if (error) { toast.error(error.message); return; } + toast.success(`Document shared with ${allRecipients.length} recipient${allRecipients.length > 1 ? "s" : ""}`); + onOpenChange(false); + }; + + return ( + + + + Share document + +
+
+ {doc?.name} +
+ +
+ +
+ {loading ? ( +
+ Loading contacts… +
+ ) : contacts.length === 0 ? ( +
No contacts linked to this case.
+ ) : ( + contacts.map((c) => { + const hasEmail = !!c.email; + return ( + + ); + }) + )} +
+
+ +
+ + setExtraEmail(e.target.value)} + placeholder="comma-separated, optional" + className="mt-1" + /> +
+ +
+ + setSubject(e.target.value)} + className="mt-1" + /> +
+ +
+ +