xinnni's picture
Upload 146 files
f909d7c verified
import React from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import {
Modal,
Text,
Divider,
ScrollArea,
ModalProps,
Table,
ActionIcon,
Badge,
Paper,
Flex,
DefaultMantineColor,
Input,
Button,
Group,
Stack,
RingProgress,
UnstyledButton,
Drawer,
LoadingOverlay,
} from "@mantine/core";
import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import toast from "react-hot-toast";
import { AiOutlineLink } from "react-icons/ai";
import { FaTrash } from "react-icons/fa";
import { MdFileOpen } from "react-icons/md";
import { VscAdd, VscEdit, VscLock, VscUnlock } from "react-icons/vsc";
import { FileFormat } from "src/enums/file.enum";
import { documentSvc } from "src/services/document.service";
import useFile, { File } from "src/store/useFile";
import useUser from "src/store/useUser";
dayjs.extend(relativeTime);
const colorByFormat: Record<FileFormat, DefaultMantineColor> = {
json: "orange",
yaml: "blue",
xml: "red",
toml: "dark",
csv: "grape",
};
const UpdateNameModal: React.FC<{
file: File | null;
onClose: () => void;
refetch: () => void;
}> = ({ file, onClose, refetch }) => {
const [name, setName] = React.useState("");
const onSubmit = () => {
if (!file) return;
toast
.promise(documentSvc.update(file.id, { name }), {
loading: "Updating document...",
error: "Error occurred while updating document!",
success: `Renamed document to ${name}`,
})
.then(() => {
refetch();
setName("");
});
onClose();
};
return (
<Modal title="Update Document name" opened={!!file} onClose={onClose} centered zIndex={202}>
<Stack>
<Input
value={name}
placeholder={file?.name}
onChange={e => setName(e.currentTarget.value)}
/>
<Group justify="right">
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button onClick={onSubmit}>Update</Button>
</Group>
</Stack>
</Modal>
);
};
export const CloudModal: React.FC<ModalProps> = ({ opened, onClose }) => {
const totalQuota = useUser(state => (state.premium ? 200 : 25));
const isPremium = useUser(state => state.premium);
const getContents = useFile(state => state.getContents);
const setHasChanges = useFile(state => state.setHasChanges);
const getFormat = useFile(state => state.getFormat);
const [currentFile, setCurrentFile] = React.useState<File | null>(null);
const { isReady, query, replace } = useRouter();
const { data, isLoading, refetch } = useQuery(["allJson", query], () => documentSvc.getAll(), {
enabled: isReady && opened,
});
const isCreateDisabled = React.useMemo(() => {
if (!data?.length) return false;
return isPremium ? data.length >= 200 : data.length >= 25;
}, [isPremium, data?.length]);
const onCreate = async () => {
try {
toast.loading("Saving document...", { id: "fileSave" });
const { data, error } = await documentSvc.upsert({
contents: getContents(),
format: getFormat(),
});
if (error) throw error;
toast.success("Document saved to cloud", { id: "fileSave" });
setHasChanges(false);
replace({ query: { json: data } });
onClose();
} catch (error: any) {
toast.error("Failed to save document!", { id: "fileSave" });
console.error(error);
}
};
const onDeleteClick = React.useCallback(
(file: File) => {
toast
.promise(documentSvc.delete(file.id), {
loading: "Deleting file...",
error: "An error occurred while deleting the file!",
success: `Deleted ${file.name}!`,
})
.then(() => refetch());
},
[refetch]
);
const copyShareLink = React.useCallback((fileId: string) => {
const shareLink = `${window.location.origin}/editor?json=${fileId}`;
navigator.clipboard.writeText(shareLink);
toast.success("Copied share link to clipboard!");
}, []);
const rows = React.useMemo(
() =>
data?.map(element => (
<Table.Tr key={element.id}>
<Table.Td>
<Flex align="center" gap="xs">
{element.private ? <VscLock /> : <VscUnlock />}
{element.id}
</Flex>
</Table.Td>
<Table.Td>
<Flex align="center" justify="space-between">
{element.name}
<ActionIcon
variant="transparent"
color="cyan"
onClick={() => setCurrentFile(element)}
>
<VscEdit />
</ActionIcon>
</Flex>
</Table.Td>
<Table.Td>{dayjs(element.created_at).format("DD.MM.YYYY")}</Table.Td>
<Table.Td>
<Badge variant="light" color={colorByFormat[element.format]} size="sm">
{element.format.toUpperCase()}
</Badge>
</Table.Td>
<Table.Td>{element.views.toLocaleString("en-US")}</Table.Td>
<Table.Td>
<Flex gap="xs">
<ActionIcon.Group>
<ActionIcon
variant="transparent"
component={Link}
href={`?json=${element.id}`}
prefetch={false}
color="blue"
onClick={onClose}
>
<MdFileOpen size="18" />
</ActionIcon>
<ActionIcon
variant="transparent"
color="red"
onClick={() => onDeleteClick(element)}
>
<FaTrash size="18" />
</ActionIcon>
<ActionIcon
variant="transparent"
color="green"
onClick={() => copyShareLink(element.id)}
>
<AiOutlineLink />
</ActionIcon>
</ActionIcon.Group>
</Flex>
</Table.Td>
</Table.Tr>
)),
[data, copyShareLink, onClose, onDeleteClick]
);
return (
<Drawer
title="Saved On The Cloud"
opened={opened}
size="xl"
onClose={onClose}
transitionProps={{ duration: 300, timingFunction: "ease", transition: "slide-right" }}
pos="relative"
>
<LoadingOverlay visible={isLoading} />
{data && (
<Flex gap="md">
<Paper my="lg" withBorder radius="md" p="xs" w="100%">
<Group>
<RingProgress
size={40}
roundCaps
thickness={6}
sections={[
{
value: (data.length * 100) / totalQuota,
color: data.length > totalQuota / 1.5 ? "red" : "blue",
},
]}
/>
<div>
<Text c="dimmed" fz="xs" fw={700} style={{ textTransform: "uppercase" }}>
Total Quota
</Text>
<Text fw={700} size="lg">
{data.length} / {totalQuota}
</Text>
</div>
</Group>
</Paper>
<Paper my="lg" withBorder radius="md" p="xs" w={250}>
<UnstyledButton
fw="bold"
w="100%"
h="100%"
onClick={onCreate}
disabled={isCreateDisabled}
>
<Text
fz="sm"
fw="bold"
c="blue"
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
textAlign: "center",
}}
>
<VscAdd size="18" strokeWidth={1} />
Create New Document
</Text>
</UnstyledButton>
</Paper>
</Flex>
)}
<Text fz="xs" pb="lg">
The Cloud Save feature is primarily designed for convenient access and is not advisable for
storing sensitive data.
</Text>
<Divider py="xs" />
<Paper>
<ScrollArea h="100%" offsetScrollbars>
<Table fz="xs" verticalSpacing="xs" striped withTableBorder>
<Table.Thead>
<Table.Tr>
<Table.Th>ID</Table.Th>
<Table.Th>Name</Table.Th>
<Table.Th>Create Date</Table.Th>
<Table.Th>Format</Table.Th>
<Table.Th>Views</Table.Th>
<Table.Th>Actions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
</ScrollArea>
</Paper>
<UpdateNameModal file={currentFile} onClose={() => setCurrentFile(null)} refetch={refetch} />
</Drawer>
);
};