Add User Info Page

This commit is contained in:
Elias Schneider
2022-05-11 13:50:28 +02:00
parent 80f055899c
commit 3cb7285e8f
11 changed files with 274 additions and 68 deletions

View File

@@ -0,0 +1,132 @@
import {
ActionIcon,
Button,
Center,
Group,
LoadingOverlay,
Space,
Table,
Text,
Title,
} from "@mantine/core";
import { useClipboard } from "@mantine/hooks";
import { useModals } from "@mantine/modals";
import { NextLink } from "@mantine/next";
import { Query } from "appwrite";
import moment from "moment";
import { useEffect, useState } from "react";
import { Link, Trash } from "tabler-icons-react";
import Meta from "../../components/Meta";
import shareService from "../../services/share.service";
import { ShareDocument } from "../../types/Appwrite.type";
import aw from "../../utils/appwrite.util";
import toast from "../../utils/toast.util";
const MyShares = () => {
const modals = useModals();
const clipboard = useClipboard();
const [shares, setShares] = useState<ShareDocument[]>();
useEffect(() => {
aw.database
.listDocuments<ShareDocument>(
"shares",
[Query.equal("enabled", true)],
100
)
.then((res) => setShares(res.documents));
}, []);
if (!shares) return <LoadingOverlay visible />;
return (
<>
<Meta title="My shares" />
<Title mb={30} order={3}>
My shares
</Title>
{shares.length == 0 ? (
<Center style={{ height: "70vh" }}>
<Group direction="column" align="center" spacing={10}>
<Title order={3}>It's empty here 👀</Title>
<Text>You don't have any shares.</Text>
<Space h={5} />
<Button component={NextLink} href="/upload" variant="light">
Create one
</Button>
</Group>
</Center>
) : (
<Table>
<thead>
<tr>
<th>Name</th>
<th>Visitors</th>
<th>Security enabled</th>
<th>Email</th>
<th>Expires at</th>
<th></th>
</tr>
</thead>
<tbody>
{shares.map((share) => (
<tr key={share.$id}>
<td>{share.$id}</td>
<td>{share.visitorCount}</td>
<td>{share.securityID ? "Yes" : "No"}</td>
<td>{share.users!.length > 0 ? "Yes" : "No"}</td>
<td>{moment(share.expiresAt).format("MMMM DD YYYY, HH:mm")}</td>
<td>
<Group position="right">
<ActionIcon
color="victoria"
variant="light"
size={25}
onClick={() => {
clipboard.copy(
`${window.location.origin}/share/${share.$id}`
);
toast.success("Your link was copied to the keyboard.");
}}
>
<Link />
</ActionIcon>
<ActionIcon
color="red"
variant="light"
size={25}
onClick={() => {
modals.openConfirmModal({
title: `Delete share ${share.$id}`,
children: (
<Text size="sm">
Do you really want to delete this share?
</Text>
),
confirmProps: {
color: "red",
},
labels: { confirm: "Confirm", cancel: "Cancel" },
onConfirm: () => {
shareService.remove(share.$id);
setShares(
shares.filter((item) => item.$id !== share.$id)
);
},
});
}}
>
<Trash />
</ActionIcon>
</Group>
</td>
</tr>
))}
</tbody>
</Table>
)}
</>
);
};
export default MyShares;

View File

@@ -7,55 +7,63 @@ import * as jose from "jose";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const shareId = req.query.shareId as string;
const fileList: AppwriteFileWithPreview[] = [];
const hashedPassword = req.cookies[`${shareId}-password`];
let shareDocument;
try {
shareDocument = await awServer.database.getDocument<ShareDocument>(
"shares",
shareId
);
} catch {
return res.status(404).json({ message: "not_found" });
if (req.method == "POST") {
const fileList: AppwriteFileWithPreview[] = [];
const hashedPassword = req.cookies[`${shareId}-password`];
let shareDocument;
try {
shareDocument = await awServer.database.getDocument<ShareDocument>(
"shares",
shareId
);
} catch {
return res.status(404).json({ message: "not_found" });
}
if (!shareExists(shareDocument)) {
return res.status(404).json({ message: "not_found" });
}
if (!hasUserAccess(req.cookies.aw_token, shareDocument)) {
return res.status(403).json({ message: "forbidden" });
}
try {
await checkSecurity(shareId, hashedPassword);
} catch (e) {
return res.status(403).json({ message: e });
}
addVisitorCount(shareId);
const fileListWithoutPreview = (
await awServer.storage.listFiles(shareId, undefined, 100)
).files;
for (const file of fileListWithoutPreview) {
const filePreview = await awServer.storage.getFilePreview(
shareId,
file.$id
);
fileList.push({ ...file, preview: filePreview });
}
if (hashedPassword)
res.setHeader(
"Set-Cookie",
`${shareId}-password=${hashedPassword}; Path=/share/${shareId}; max-age=3600; HttpOnly`
);
res.status(200).json(fileList);
} else if (req.method == "DELETE") {
awServer.database.updateDocument("shares", shareId, {
enabled: false,
});
}
if (!shareExists(shareDocument)) {
return res.status(404).json({ message: "not_found" });
}
if (!hasUserAccess(req.cookies.aw_token, shareDocument)) {
return res.status(403).json({ message: "forbidden" });
}
try {
await checkSecurity(shareId, hashedPassword);
} catch (e) {
return res.status(403).json({ message: e });
}
addVisitorCount(shareId);
const fileListWithoutPreview = (
await awServer.storage.listFiles(shareId, undefined, 100)
).files;
for (const file of fileListWithoutPreview) {
const filePreview = await awServer.storage.getFilePreview(
shareId,
file.$id
);
fileList.push({ ...file, preview: filePreview });
}
if (hashedPassword)
res.setHeader(
"Set-Cookie",
`${shareId}-password=${hashedPassword}; Path=/share/${shareId}; max-age=3600; HttpOnly`
);
res.status(200).json(fileList);
};
// Util functions
const hasUserAccess = (jwt: string, shareDocument: ShareDocument) => {
if (shareDocument.users?.length == 0) return true;
try {
@@ -79,5 +87,4 @@ const addVisitorCount = async (shareId: string) => {
awServer.database.updateDocument("shares", shareId, currentDocument);
};
export default handler;