Skip to content
This repository was archived by the owner on Jun 29, 2025. It is now read-only.

Commit 8728fa5

Browse files
committed
feat: add description field to share
1 parent 78dd4a7 commit 8728fa5

File tree

10 files changed

+108
-73
lines changed

10 files changed

+108
-73
lines changed

backend/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ model Share {
3939
isZipReady Boolean @default(false)
4040
views Int @default(0)
4141
expiration DateTime
42+
description String?
4243
4344
creatorId String?
4445
creator User? @relation(fields: [creatorId], references: [id], onDelete: Cascade)

backend/src/share/dto/createShare.dto.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Type } from "class-transformer";
22
import {
33
IsEmail,
4+
IsOptional,
45
IsString,
56
Length,
67
Matches,
8+
MaxLength,
79
ValidateNested,
810
} from "class-validator";
911
import { ShareSecurityDTO } from "./shareSecurity.dto";
@@ -19,6 +21,10 @@ export class CreateShareDTO {
1921
@IsString()
2022
expiration: string;
2123

24+
@MaxLength(512)
25+
@IsOptional()
26+
description: string;
27+
2228
@IsEmail({}, { each: true })
2329
recipients: string[];
2430

backend/src/share/dto/share.dto.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export class ShareDTO {
1717
@Type(() => PublicUserDTO)
1818
creator: PublicUserDTO;
1919

20+
@Expose()
21+
description: string;
22+
2023
from(partial: Partial<ShareDTO>) {
2124
return plainToClass(ShareDTO, partial, { excludeExtraneousValues: true });
2225
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { PickType } from "@nestjs/mapped-types";
22
import { UserDTO } from "./user.dto";
33

4-
export class PublicUserDTO extends PickType(UserDTO, ["email"] as const) {}
4+
export class PublicUserDTO extends PickType(UserDTO, ["username"] as const) {}

frontend/src/components/share/FileList.tsx

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,10 @@ const FileList = ({
99
shareId,
1010
isLoading,
1111
}: {
12-
files: any[];
12+
files?: any[];
1313
shareId: string;
1414
isLoading: boolean;
1515
}) => {
16-
const rows = files.map((file) => (
17-
<tr key={file.name}>
18-
<td>{file.name}</td>
19-
<td>{byteStringToHumanSizeString(file.size)}</td>
20-
<td>
21-
{file.uploadingState ? (
22-
file.uploadingState != "finished" ? (
23-
<Loader size={22} />
24-
) : (
25-
<TbCircleCheck color="green" size={22} />
26-
)
27-
) : (
28-
<ActionIcon
29-
size={25}
30-
onClick={async () => {
31-
await shareService.downloadFile(shareId, file.id);
32-
}}
33-
>
34-
<TbDownload />
35-
</ActionIcon>
36-
)}
37-
</td>
38-
</tr>
39-
));
40-
4116
return (
4217
<Table>
4318
<thead>
@@ -47,7 +22,34 @@ const FileList = ({
4722
<th></th>
4823
</tr>
4924
</thead>
50-
<tbody>{isLoading ? skeletonRows : rows}</tbody>
25+
<tbody>
26+
{isLoading
27+
? skeletonRows
28+
: files!.map((file) => (
29+
<tr key={file.name}>
30+
<td>{file.name}</td>
31+
<td>{byteStringToHumanSizeString(file.size)}</td>
32+
<td>
33+
{file.uploadingState ? (
34+
file.uploadingState != "finished" ? (
35+
<Loader size={22} />
36+
) : (
37+
<TbCircleCheck color="green" size={22} />
38+
)
39+
) : (
40+
<ActionIcon
41+
size={25}
42+
onClick={async () => {
43+
await shareService.downloadFile(shareId, file.id);
44+
}}
45+
>
46+
<TbDownload />
47+
</ActionIcon>
48+
)}
49+
</td>
50+
</tr>
51+
))}
52+
</tbody>
5153
</Table>
5254
);
5355
};

frontend/src/components/upload/modals/showCreateUploadModal.tsx

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Select,
1313
Stack,
1414
Text,
15+
Textarea,
1516
TextInput,
1617
Title,
1718
} from "@mantine/core";
@@ -22,7 +23,7 @@ import { useState } from "react";
2223
import { TbAlertCircle } from "react-icons/tb";
2324
import * as yup from "yup";
2425
import shareService from "../../../services/share.service";
25-
import { ShareSecurity } from "../../../types/share.type";
26+
import { CreateShare } from "../../../types/share.type";
2627
import ExpirationPreview from "../ExpirationPreview";
2728

2829
const showCreateUploadModal = (
@@ -32,12 +33,7 @@ const showCreateUploadModal = (
3233
allowUnauthenticatedShares: boolean;
3334
enableEmailRecepients: boolean;
3435
},
35-
uploadCallback: (
36-
id: string,
37-
expiration: string,
38-
recipients: string[],
39-
security: ShareSecurity
40-
) => void
36+
uploadCallback: (createShare: CreateShare) => void
4137
) => {
4238
return modals.openModal({
4339
title: <Title order={4}>Share</Title>,
@@ -54,12 +50,7 @@ const CreateUploadModalBody = ({
5450
uploadCallback,
5551
options,
5652
}: {
57-
uploadCallback: (
58-
id: string,
59-
expiration: string,
60-
recipients: string[],
61-
security: ShareSecurity
62-
) => void;
53+
uploadCallback: (createShare: CreateShare) => void;
6354
options: {
6455
isUserSignedIn: boolean;
6556
allowUnauthenticatedShares: boolean;
@@ -88,6 +79,7 @@ const CreateUploadModalBody = ({
8879
recipients: [] as string[],
8980
password: undefined,
9081
maxViews: undefined,
82+
description: undefined,
9183
expiration_num: 1,
9284
expiration_unit: "-days",
9385
never_expires: false,
@@ -116,9 +108,15 @@ const CreateUploadModalBody = ({
116108
const expiration = form.values.never_expires
117109
? "never"
118110
: form.values.expiration_num + form.values.expiration_unit;
119-
uploadCallback(values.link, expiration, values.recipients, {
120-
password: values.password,
121-
maxViews: values.maxViews,
111+
uploadCallback({
112+
id: values.link,
113+
expiration: expiration,
114+
recipients: values.recipients,
115+
description: values.description,
116+
security: {
117+
password: values.password,
118+
maxViews: values.maxViews,
119+
},
122120
});
123121
modals.closeAll();
124122
}
@@ -258,6 +256,18 @@ const CreateUploadModalBody = ({
258256
</Accordion.Panel>
259257
</Accordion.Item>
260258
)}
259+
<Accordion.Item value="description" sx={{ borderBottom: "none" }}>
260+
<Accordion.Control>Description</Accordion.Control>
261+
<Accordion.Panel>
262+
<Stack align="stretch">
263+
<Textarea
264+
variant="filled"
265+
placeholder="Note for the recepients"
266+
{...form.getInputProps("description")}
267+
/>
268+
</Stack>
269+
</Accordion.Panel>
270+
</Accordion.Item>
261271
<Accordion.Item value="security" sx={{ borderBottom: "none" }}>
262272
<Accordion.Control>Security options</Accordion.Control>
263273
<Accordion.Panel>

frontend/src/pages/share/[shareId].tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Group } from "@mantine/core";
1+
import { Box, Group, Text, Title } from "@mantine/core";
22
import { useModals } from "@mantine/modals";
33
import { GetServerSidePropsContext } from "next";
44
import { useEffect, useState } from "react";
@@ -8,6 +8,7 @@ import FileList from "../../components/share/FileList";
88
import showEnterPasswordModal from "../../components/share/showEnterPasswordModal";
99
import showErrorModal from "../../components/share/showErrorModal";
1010
import shareService from "../../services/share.service";
11+
import { Share as ShareType } from "../../types/share.type";
1112

1213
export function getServerSideProps(context: GetServerSidePropsContext) {
1314
return {
@@ -17,7 +18,7 @@ export function getServerSideProps(context: GetServerSidePropsContext) {
1718

1819
const Share = ({ shareId }: { shareId: string }) => {
1920
const modals = useModals();
20-
const [files, setFiles] = useState<any[]>([]);
21+
const [share, setShare] = useState<ShareType>();
2122

2223
const getShareToken = async (password?: string) => {
2324
await shareService
@@ -41,7 +42,7 @@ const Share = ({ shareId }: { shareId: string }) => {
4142
shareService
4243
.get(shareId)
4344
.then((share) => {
44-
setFiles(share.files);
45+
setShare(share);
4546
})
4647
.catch((e) => {
4748
const { error } = e.response.data;
@@ -77,12 +78,16 @@ const Share = ({ shareId }: { shareId: string }) => {
7778
title={`Share ${shareId}`}
7879
description="Look what I've shared with you."
7980
/>
80-
{files.length > 1 && (
81-
<Group position="right" mb="lg">
82-
<DownloadAllButton shareId={shareId} />
83-
</Group>
84-
)}
85-
<FileList files={files} shareId={shareId} isLoading={files.length == 0} />
81+
82+
<Group position="apart" mb="lg">
83+
<Box style={{ maxWidth: "70%" }}>
84+
<Title order={3}>{share?.id}</Title>
85+
<Text size="sm">{share?.description}</Text>
86+
</Box>
87+
{share?.files.length > 1 && <DownloadAllButton shareId={shareId} />}
88+
</Group>
89+
90+
<FileList files={share?.files} shareId={shareId} isLoading={!share} />
8691
</>
8792
);
8893
};

frontend/src/pages/upload.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import useConfig from "../hooks/config.hook";
1313
import useUser from "../hooks/user.hook";
1414
import shareService from "../services/share.service";
1515
import { FileUpload } from "../types/File.type";
16-
import { Share, ShareSecurity } from "../types/share.type";
16+
import { CreateShare, Share } from "../types/share.type";
1717
import toast from "../utils/toast.util";
1818

19-
let share: Share;
19+
let createdShare: Share;
2020
const promiseLimit = pLimit(3);
2121

2222
const Upload = () => {
@@ -28,12 +28,7 @@ const Upload = () => {
2828
const [files, setFiles] = useState<FileUpload[]>([]);
2929
const [isUploading, setisUploading] = useState(false);
3030

31-
const uploadFiles = async (
32-
id: string,
33-
expiration: string,
34-
recipients: string[],
35-
security: ShareSecurity
36-
) => {
31+
const uploadFiles = async (share: CreateShare) => {
3732
setisUploading(true);
3833
try {
3934
setFiles((files) =>
@@ -42,7 +37,8 @@ const Upload = () => {
4237
return file;
4338
})
4439
);
45-
share = await shareService.create(id, expiration, recipients, security);
40+
createdShare = await shareService.create(share);
41+
4642
const uploadPromises = files.map((file, i) => {
4743
// Callback to indicate current upload progress
4844
const progressCallBack = (progress: number) => {
@@ -91,9 +87,9 @@ const Upload = () => {
9187
toast.error(`${fileErrorCount} file(s) failed to upload. Try again.`);
9288
} else {
9389
shareService
94-
.completeShare(share.id)
90+
.completeShare(createdShare.id)
9591
.then(() => {
96-
showCompletedUploadModal(modals, share);
92+
showCompletedUploadModal(modals, createdShare);
9793
setFiles([]);
9894
})
9995
.catch(() =>

frontend/src/services/share.service.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import {
2+
CreateShare,
23
MyShare,
34
Share,
45
ShareMetaData,
5-
ShareSecurity,
66
} from "../types/share.type";
77
import api from "./api.service";
88

9-
const create = async (
10-
id: string,
11-
expiration: string,
12-
recipients: string[],
13-
security?: ShareSecurity
14-
) => {
15-
return (await api.post("shares", { id, expiration, recipients, security }))
16-
.data;
9+
const create = async (share: CreateShare) => {
10+
const { id, expiration, recipients, security, description } = share;
11+
return (
12+
await api.post("shares", {
13+
id,
14+
expiration,
15+
recipients,
16+
security,
17+
description,
18+
})
19+
).data;
1720
};
1821

1922
const completeShare = async (id: string) => {

frontend/src/types/share.type.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@ export type Share = {
44
id: string;
55
files: any;
66
creator: User;
7+
description?: string;
78
expiration: Date;
89
};
910

11+
export type CreateShare = {
12+
id: string;
13+
description?: string;
14+
recipients: string[];
15+
expiration: string;
16+
security: ShareSecurity;
17+
};
18+
1019
export type ShareMetaData = {
1120
id: string;
1221
isZipReady: boolean;

0 commit comments

Comments
 (0)