mirror of
https://github.com/stonith404/pingvin-share.git
synced 2024-11-05 15:30:14 +01:00
Add User Info Page
This commit is contained in:
parent
80f055899c
commit
3cb7285e8f
@ -65,6 +65,12 @@ export default [
|
|||||||
attributes: ["expiresAt"],
|
attributes: ["expiresAt"],
|
||||||
orders: ["ASC"],
|
orders: ["ASC"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "enabled",
|
||||||
|
type: "key",
|
||||||
|
attributes: ["enabled"],
|
||||||
|
orders: ["ASC"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -63,7 +63,7 @@ module.exports = async function (req, res) {
|
|||||||
users: userIds,
|
users: userIds,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
expiresAt: expiration,
|
expiresAt: expiration,
|
||||||
});
|
}, [`user:${userId}`]);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
id: payload.id,
|
id: payload.id,
|
||||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"jose": "^4.8.1",
|
"jose": "^4.8.1",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"jszip": "^3.9.1",
|
"jszip": "^3.9.1",
|
||||||
|
"moment": "^2.29.3",
|
||||||
"next": "12.1.5",
|
"next": "12.1.5",
|
||||||
"next-pwa": "^5.5.2",
|
"next-pwa": "^5.5.2",
|
||||||
"node-appwrite": "^5.1.0",
|
"node-appwrite": "^5.1.0",
|
||||||
@ -5690,6 +5691,14 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.29.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
||||||
|
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
@ -11991,6 +12000,11 @@
|
|||||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.29.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
||||||
|
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"jose": "^4.8.1",
|
"jose": "^4.8.1",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"jszip": "^3.9.1",
|
"jszip": "^3.9.1",
|
||||||
|
"moment": "^2.29.3",
|
||||||
"next": "12.1.5",
|
"next": "12.1.5",
|
||||||
"next-pwa": "^5.5.2",
|
"next-pwa": "^5.5.2",
|
||||||
"node-appwrite": "^5.1.0",
|
"node-appwrite": "^5.1.0",
|
||||||
|
43
src/components/navBar/ActionAvatar.tsx
Normal file
43
src/components/navBar/ActionAvatar.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { ActionIcon, Avatar, Menu } from "@mantine/core";
|
||||||
|
import { NextLink } from "@mantine/next";
|
||||||
|
import { DoorExit, Link } from "tabler-icons-react";
|
||||||
|
import aw from "../../utils/appwrite.util";
|
||||||
|
|
||||||
|
const ActionAvatar = () => {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
control={
|
||||||
|
<ActionIcon>
|
||||||
|
<Avatar size={28} radius="xl" />
|
||||||
|
</ActionIcon>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu.Label>My account</Menu.Label>
|
||||||
|
<Menu.Item
|
||||||
|
component={NextLink}
|
||||||
|
href="/account/shares"
|
||||||
|
icon={<Link size={14} />}
|
||||||
|
>
|
||||||
|
Shares
|
||||||
|
</Menu.Item>
|
||||||
|
{/* <Menu.Item
|
||||||
|
component={NextLink}
|
||||||
|
href="/account/shares"
|
||||||
|
icon={<Settings size={14} />}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</Menu.Item> */}
|
||||||
|
<Menu.Item
|
||||||
|
onClick={async () => {
|
||||||
|
await aw.account.deleteSession("current");
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
icon={<DoorExit size={14} />}
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionAvatar;
|
@ -13,10 +13,9 @@ import { NextLink } from "@mantine/next";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import headerStyle from "../../styles/header.style";
|
import headerStyle from "../../styles/header.style";
|
||||||
import aw from "../../utils/appwrite.util";
|
|
||||||
import { IsSignedInContext } from "../../utils/auth.util";
|
import { IsSignedInContext } from "../../utils/auth.util";
|
||||||
import { useConfig } from "../../utils/config.util";
|
import { useConfig } from "../../utils/config.util";
|
||||||
import ToggleThemeButton from "./ToggleThemeButton";
|
import ActionAvatar from "./ActionAvatar";
|
||||||
|
|
||||||
type Link = {
|
type Link = {
|
||||||
link?: string;
|
link?: string;
|
||||||
@ -36,26 +35,21 @@ const Header = () => {
|
|||||||
link: "/upload",
|
link: "/upload",
|
||||||
label: "Upload",
|
label: "Upload",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Sign out",
|
|
||||||
action: async () => {
|
|
||||||
await aw.account.deleteSession("current");
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const unauthenticatedLinks: Link[] | undefined = [
|
const unauthenticatedLinks: Link[] | undefined = [
|
||||||
{
|
|
||||||
link: "/",
|
|
||||||
label: "Home",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
link: "/auth/signIn",
|
link: "/auth/signIn",
|
||||||
label: "Sign in",
|
label: "Sign in",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (!config.DISABLE_HOME_PAGE)
|
||||||
|
unauthenticatedLinks.unshift({
|
||||||
|
link: "/",
|
||||||
|
label: "Home",
|
||||||
|
});
|
||||||
|
|
||||||
if (!config.DISABLE_REGISTRATION)
|
if (!config.DISABLE_REGISTRATION)
|
||||||
unauthenticatedLinks.push({
|
unauthenticatedLinks.push({
|
||||||
link: "/auth/signUp",
|
link: "/auth/signUp",
|
||||||
@ -67,11 +61,11 @@ const Header = () => {
|
|||||||
const items = links.map((link) => {
|
const items = links.map((link) => {
|
||||||
if (link) {
|
if (link) {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// if (window.location.pathname == link.link) {
|
if (window.location.pathname == link.link) {
|
||||||
// setActive(link.link);
|
setActive(link.link);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
return (
|
return (
|
||||||
<NextLink
|
<NextLink
|
||||||
key={link.label}
|
key={link.label}
|
||||||
@ -104,9 +98,8 @@ const Header = () => {
|
|||||||
<Group spacing={5} className={classes.links}>
|
<Group spacing={5} className={classes.links}>
|
||||||
{items}
|
{items}
|
||||||
<Space w={5} />
|
<Space w={5} />
|
||||||
<ToggleThemeButton />
|
{isSignedIn && <ActionAvatar />}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Burger
|
<Burger
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClick={() => toggleOpened()}
|
onClick={() => toggleOpened()}
|
||||||
|
@ -32,7 +32,10 @@ const CreateUploadModalBody = ({
|
|||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
const validationSchema = yup.object().shape({
|
const validationSchema = yup.object().shape({
|
||||||
link: yup.string().required().min(2).max(50),
|
link: yup.string().required().min(2).max(50),
|
||||||
emails: mode == "email" ? yup.array().of(yup.string().email()).min(1) : yup.array(),
|
emails:
|
||||||
|
mode == "email"
|
||||||
|
? yup.array().of(yup.string().email()).min(1)
|
||||||
|
: yup.array(),
|
||||||
password: yup.string().min(3).max(100),
|
password: yup.string().min(3).max(100),
|
||||||
maxVisitors: yup.number().min(1),
|
maxVisitors: yup.number().min(1),
|
||||||
});
|
});
|
||||||
|
@ -42,6 +42,7 @@ const FileList = ({
|
|||||||
height={30}
|
height={30}
|
||||||
alt={file.name}
|
alt={file.name}
|
||||||
objectFit="cover"
|
objectFit="cover"
|
||||||
|
style={{ borderRadius: 3 }}
|
||||||
src={`data:image/png;base64,${new Buffer(file.preview).toString(
|
src={`data:image/png;base64,${new Buffer(file.preview).toString(
|
||||||
"base64"
|
"base64"
|
||||||
)}`}
|
)}`}
|
||||||
|
132
src/pages/account/shares.tsx
Normal file
132
src/pages/account/shares.tsx
Normal 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;
|
@ -7,6 +7,8 @@ import * as jose from "jose";
|
|||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const shareId = req.query.shareId as string;
|
const shareId = req.query.shareId as string;
|
||||||
|
|
||||||
|
if (req.method == "POST") {
|
||||||
const fileList: AppwriteFileWithPreview[] = [];
|
const fileList: AppwriteFileWithPreview[] = [];
|
||||||
const hashedPassword = req.cookies[`${shareId}-password`];
|
const hashedPassword = req.cookies[`${shareId}-password`];
|
||||||
|
|
||||||
@ -54,8 +56,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
`${shareId}-password=${hashedPassword}; Path=/share/${shareId}; max-age=3600; HttpOnly`
|
`${shareId}-password=${hashedPassword}; Path=/share/${shareId}; max-age=3600; HttpOnly`
|
||||||
);
|
);
|
||||||
res.status(200).json(fileList);
|
res.status(200).json(fileList);
|
||||||
|
} else if (req.method == "DELETE") {
|
||||||
|
awServer.database.updateDocument("shares", shareId, {
|
||||||
|
enabled: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Util functions
|
||||||
const hasUserAccess = (jwt: string, shareDocument: ShareDocument) => {
|
const hasUserAccess = (jwt: string, shareDocument: ShareDocument) => {
|
||||||
if (shareDocument.users?.length == 0) return true;
|
if (shareDocument.users?.length == 0) return true;
|
||||||
try {
|
try {
|
||||||
@ -79,5 +87,4 @@ const addVisitorCount = async (shareId: string) => {
|
|||||||
|
|
||||||
awServer.database.updateDocument("shares", shareId, currentDocument);
|
awServer.database.updateDocument("shares", shareId, currentDocument);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handler;
|
export default handler;
|
||||||
|
@ -5,6 +5,11 @@ const get = async (shareId: string, password?: string) => {
|
|||||||
return (await axios.post(`/api/share/${shareId}`, { password }))
|
return (await axios.post(`/api/share/${shareId}`, { password }))
|
||||||
.data as AppwriteFileWithPreview[];
|
.data as AppwriteFileWithPreview[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const remove = async (shareId: string) => {
|
||||||
|
await axios.delete(`/api/share/${shareId}`);
|
||||||
|
};
|
||||||
|
|
||||||
const isIdAlreadyInUse = async (shareId: string) => {
|
const isIdAlreadyInUse = async (shareId: string) => {
|
||||||
return (await axios.get(`/api/share/${shareId}/exists`)).data
|
return (await axios.get(`/api/share/${shareId}/exists`)).data
|
||||||
.exists as boolean;
|
.exists as boolean;
|
||||||
@ -21,6 +26,7 @@ const authenticateWithPassword = async (shareId: string, password?: string) => {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
get,
|
get,
|
||||||
|
remove,
|
||||||
authenticateWithPassword,
|
authenticateWithPassword,
|
||||||
isIdAlreadyInUse,
|
isIdAlreadyInUse,
|
||||||
doesUserExist,
|
doesUserExist,
|
||||||
|
Loading…
Reference in New Issue
Block a user