Skip to content

Commit c4451f8

Browse files
authored
Merge pull request #377 from captableinc/feat/updates-feature-extension
feat: extend investor update feature and some refactoring for push modal usage
2 parents 9acd0f8 + 14a9cff commit c4451f8

File tree

23 files changed

+776
-250
lines changed

23 files changed

+776
-250
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@prisma/client": "^5.13.0",
3939
"@prisma/generator-helper": "^5.9.1",
4040
"@radix-ui/react-accordion": "^1.1.2",
41+
"@radix-ui/react-alert-dialog": "^1.0.5",
4142
"@radix-ui/react-avatar": "^1.0.4",
4243
"@radix-ui/react-checkbox": "^1.0.4",
4344
"@radix-ui/react-dialog": "^1.0.5",

pnpm-lock.yaml

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "Update" ADD COLUMN "public" BOOLEAN NOT NULL DEFAULT false;

prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@ model Update {
881881
title String
882882
content Json
883883
html String
884+
public Boolean @default(false)
884885
status UpdateStatusEnum @default(DRAFT)
885886
886887
authorId String

src/app/(authenticated)/(dashboard)/[publicId]/documents/data-rooms/[dataRoomPublicId]/page.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"use server";
22

3-
import { type ExtendedRecipientType } from "@/components/common/share-modal";
43
import { api } from "@/trpc/server";
54
import type { Bucket, DataRoom } from "@prisma/client";
65
import { notFound } from "next/navigation";
@@ -11,15 +10,14 @@ const DataRoomSettinsPage = async ({
1110
}: {
1211
params: { publicId: string; dataRoomPublicId: string };
1312
}) => {
14-
const { dataRoom, documents, recipients } =
15-
await api.dataRoom.getDataRoom.query({
16-
dataRoomPublicId,
17-
include: {
18-
company: false,
19-
recipients: true,
20-
documents: true,
21-
},
22-
});
13+
const { dataRoom, documents } = await api.dataRoom.getDataRoom.query({
14+
dataRoomPublicId,
15+
include: {
16+
company: false,
17+
recipients: true,
18+
documents: true,
19+
},
20+
});
2321
const contacts = await api.common.getContacts.query();
2422

2523
if (!dataRoom) {
@@ -30,7 +28,6 @@ const DataRoomSettinsPage = async ({
3028
<DataRoomFiles
3129
contacts={contacts}
3230
dataRoom={dataRoom as DataRoom}
33-
recipients={recipients as ExtendedRecipientType[]}
3431
documents={documents as Bucket[]}
3532
companyPublicId={publicId}
3633
/>

src/app/(authenticated)/(dashboard)/[publicId]/documents/data-rooms/components/data-room-files.tsx

Lines changed: 43 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
"use client";
22

33
import EmptyState from "@/components/common/empty-state";
4-
import ShareModal, {
5-
type ExtendedRecipientType,
6-
} from "@/components/common/share-modal";
4+
import type { ExtendedDataRoomRecipientType } from "@/components/common/share-modal";
75
import DataRoomFileExplorer from "@/components/documents/data-room/explorer";
86
import { Button } from "@/components/ui/button";
97
import { Card } from "@/components/ui/card";
10-
import type { ShareContactType, ShareRecipientType } from "@/schema/contacts";
8+
import type { ShareContactType } from "@/schema/contacts";
119
import { api } from "@/trpc/react";
12-
import { toast } from "sonner";
1310

1411
import type { Bucket, DataRoom } from "@prisma/client";
1512
import { RiShareLine } from "@remixicon/react";
16-
import { useRouter } from "next/navigation";
1713
import { useDebounceCallback } from "usehooks-ts";
1814

19-
import { env } from "@/env";
15+
import { pushModal } from "@/components/modals";
2016
import {
2117
RiFolder3Fill as FolderIcon,
2218
RiAddFill,
@@ -28,7 +24,6 @@ import DataRoomUploader from "./data-room-uploader";
2824
type DataRoomFilesProps = {
2925
dataRoom: DataRoom;
3026
documents: Bucket[];
31-
recipients: ExtendedRecipientType[];
3227
companyPublicId: string;
3328
contacts: ShareContactType[];
3429
};
@@ -37,36 +32,9 @@ const DataRoomFiles = ({
3732
dataRoom,
3833
documents,
3934
contacts,
40-
recipients,
4135
companyPublicId,
4236
}: DataRoomFilesProps) => {
43-
const router = useRouter();
44-
const baseUrl = env.NEXT_PUBLIC_BASE_URL;
4537
const { mutateAsync: saveDataRoomMutation } = api.dataRoom.save.useMutation();
46-
const { mutateAsync: shareDataRoomMutation } = api.dataRoom.share.useMutation(
47-
{
48-
onSuccess: () => {
49-
router.refresh();
50-
toast.success("Data room successfully shared.");
51-
},
52-
53-
onError: (error) => {
54-
toast.error(error.message);
55-
},
56-
},
57-
);
58-
59-
const { mutateAsync: unShareDataRoomMutation } =
60-
api.dataRoom.unShare.useMutation({
61-
onSuccess: () => {
62-
router.refresh();
63-
toast.success("Successfully removed access to data room.");
64-
},
65-
66-
onError: (error) => {
67-
toast.error(error.message);
68-
},
69-
});
7038

7139
const debounced = useDebounceCallback(async (name) => {
7240
await saveDataRoomMutation({
@@ -78,63 +46,51 @@ const DataRoomFiles = ({
7846
return (
7947
<div className="mt-2">
8048
<div className="flex flex-col gap-y-8">
81-
<form className="container mx-auto flex items-center justify-between gap-y-2 px-4">
82-
<div className="gap-y-3">
83-
<div className="flex w-full font-medium">
84-
<FolderIcon
85-
className="mr-3 h-6 w-6 text-primary/60"
86-
aria-hidden="true"
87-
/>
88-
<Link
89-
href={`/${companyPublicId}/documents/data-rooms`}
90-
className="h4 text-primary/70 hover:underline"
91-
>
92-
Data room
93-
</Link>
94-
<span className="h4 ml-2 text-primary/70">/</span>
95-
<input
96-
name="title"
97-
required
98-
type="text"
99-
className="h4 min-w-[300px] bg-transparent px-2 text-gray-800 outline-none focus:ring-0 focus:ring-offset-0"
100-
placeholder={`Data room's folder name`}
101-
defaultValue={dataRoom.name}
102-
onChange={async (e) => {
103-
const name = e.target.value;
104-
await debounced(name);
105-
}}
106-
/>
49+
<div className="container mx-auto flex items-center justify-between gap-y-2 px-4">
50+
<form>
51+
<div className="gap-y-3">
52+
<div className="flex w-full font-medium">
53+
<FolderIcon
54+
className="mr-3 h-6 w-6 text-primary/60"
55+
aria-hidden="true"
56+
/>
57+
<Link
58+
href={`/${companyPublicId}/documents/data-rooms`}
59+
className="h4 text-primary/70 hover:underline"
60+
>
61+
Data room
62+
</Link>
63+
<span className="h4 ml-2 text-primary/70">/</span>
64+
<input
65+
name="title"
66+
required
67+
type="text"
68+
className="h4 min-w-[300px] bg-transparent px-2 text-gray-800 outline-none focus:ring-0 focus:ring-offset-0"
69+
placeholder={`Data room's folder name`}
70+
defaultValue={dataRoom.name}
71+
onChange={async (e) => {
72+
const name = e.target.value;
73+
await debounced(name);
74+
}}
75+
/>
76+
</div>
10777
</div>
108-
</div>
78+
</form>
10979

11080
{documents.length > 0 && (
11181
<div className="flex gap-3">
112-
<ShareModal
113-
recipients={recipients}
114-
contacts={contacts}
115-
baseLink={`${baseUrl}/data-rooms/${dataRoom.publicId}`}
116-
title={`Share data room - "${dataRoom.name}"`}
117-
subtitle="Share this data room with others."
118-
onShare={async ({ selectedContacts, others }) => {
119-
await shareDataRoomMutation({
120-
dataRoomId: dataRoom.id,
121-
selectedContacts: selectedContacts as ShareRecipientType[],
122-
others: others as ShareRecipientType[],
82+
<Button
83+
variant={"outline"}
84+
onClick={() => {
85+
pushModal("ShareDataRoomModal", {
86+
contacts: contacts,
87+
dataRoom: dataRoom,
12388
});
12489
}}
125-
removeAccess={async ({ recipientId }) => {
126-
await unShareDataRoomMutation({
127-
dataRoomId: dataRoom.id,
128-
recipientId,
129-
});
130-
}}
131-
trigger={
132-
<Button variant={"outline"}>
133-
<RiShareLine className="mr-2 h-5 w-5" />
134-
Share
135-
</Button>
136-
}
137-
/>
90+
>
91+
<RiShareLine className="mr-2 h-5 w-5" />
92+
Share
93+
</Button>
13894

13995
<DataRoomUploader
14096
dataRoom={dataRoom}
@@ -148,7 +104,7 @@ const DataRoomFiles = ({
148104
/>
149105
</div>
150106
)}
151-
</form>
107+
</div>
152108

153109
<div>
154110
{documents.length > 0 ? (
Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
"use server";
2-
import type { ExtendedRecipientType } from "@/components/common/share-modal";
32
import { db } from "@/server/db";
4-
import { api } from "@/trpc/server";
5-
63
import dynamic from "next/dynamic";
74

85
const Editor = dynamic(
@@ -16,35 +13,17 @@ const getUpdate = async (publicId: string) => {
1613
});
1714
};
1815

19-
const getContacts = async () => {
20-
return await api.common.getContacts.query();
21-
};
22-
2316
const UpdatePage = async ({
2417
params: { publicId, updatePublicId },
2518
}: {
2619
params: { publicId: string; updatePublicId: string };
2720
}) => {
28-
const contacts = await getContacts();
29-
3021
if (updatePublicId === "new") {
31-
return <Editor companyPublicId={publicId} contacts={contacts} mode="new" />;
22+
return <Editor companyPublicId={publicId} mode="new" />;
3223
}
3324
const update = await getUpdate(updatePublicId);
34-
const recipients = await api.update.getRecipiants.query({
35-
updateId: update?.id,
36-
publicUpdateId: update.publicId,
37-
});
3825

39-
return (
40-
<Editor
41-
companyPublicId={publicId}
42-
update={update}
43-
contacts={contacts}
44-
recipients={recipients as object[] as ExtendedRecipientType[]}
45-
mode="edit"
46-
/>
47-
);
26+
return <Editor companyPublicId={publicId} update={update} mode="edit" />;
4827
};
4928

5029
export default UpdatePage;

src/app/updates/[publicId]/page.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { SharePageLayout } from "@/components/share/page-layout";
55
import { Avatar, AvatarImage } from "@/components/ui/avatar";
66
import UpdateRenderer from "@/components/update/renderer";
77
import { type JWTVerifyResult, decode } from "@/lib/jwt";
8+
import { UpdateStatusEnum } from "@/prisma/enums";
89
import { db } from "@/server/db";
10+
import { RiLock2Line } from "@remixicon/react";
911
import { render } from "jsx-email";
1012
import { notFound } from "next/navigation";
1113
import { Fragment } from "react";
@@ -22,6 +24,7 @@ const PublicUpdatePage = async ({
2224
try {
2325
decodedToken = await decode(token);
2426
} catch (error) {
27+
console.error(error);
2528
return notFound();
2629
}
2730

@@ -68,6 +71,22 @@ const PublicUpdatePage = async ({
6871
return notFound();
6972
}
7073

74+
const canRenderInPublic =
75+
update.status === UpdateStatusEnum.PUBLIC && update.public;
76+
77+
if (!canRenderInPublic) {
78+
return (
79+
<div className="h-screen w-full flex justify-center items-center">
80+
<div className="flex items-center space-x-5">
81+
<RiLock2Line className="h-10 w-10" />
82+
<p className="text-lg font-semibold text-gray-600">
83+
Public access denied
84+
</p>
85+
</div>
86+
</div>
87+
);
88+
}
89+
7190
const recipients = await db.updateRecipient.findFirst({
7291
where: {
7392
id: payload.recipientId,
@@ -116,6 +135,7 @@ const PublicUpdatePage = async ({
116135
<div className="mt-5">
117136
<article
118137
className="prose"
138+
//biome-ignore lint/security/noDangerouslySetInnerHtml: allow dangerouslySetInnerHtml
119139
dangerouslySetInnerHTML={{ __html: html }}
120140
/>
121141
</div>

0 commit comments

Comments
 (0)