initial commit
This commit is contained in:
91
src/components/auth/AuthForm.tsx
Normal file
91
src/components/auth/AuthForm.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
Anchor,
|
||||
Button,
|
||||
Container,
|
||||
Paper,
|
||||
PasswordInput,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import * as yup from "yup";
|
||||
import aw from "../../utils/appwrite.util";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
||||
const AuthForm = ({ mode }: { mode: "signUp" | "signIn" }) => {
|
||||
const validationSchema = yup.object().shape({
|
||||
email: yup.string().email().required(),
|
||||
password: yup.string().min(8).required(),
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
},
|
||||
schema: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const signIn = (email: string, password: string) => {
|
||||
aw.account
|
||||
.createSession(email, password)
|
||||
.then(() => window.location.replace("/upload"))
|
||||
.catch((e) => toast.error(e.message));
|
||||
};
|
||||
const signUp = (email: string, password: string) => {
|
||||
aw.account
|
||||
.create("unique()", email, password)
|
||||
.then(() => signIn(email, password))
|
||||
.catch((e) => toast.error(e.message));
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size={420} my={40}>
|
||||
<Title
|
||||
align="center"
|
||||
sx={(theme) => ({
|
||||
fontFamily: `Greycliff CF, ${theme.fontFamily}`,
|
||||
fontWeight: 900,
|
||||
})}
|
||||
>
|
||||
{mode == "signUp" ? "Sign up" : "Welcome back"}
|
||||
</Title>
|
||||
<Text color="dimmed" size="sm" align="center" mt={5}>
|
||||
{mode == "signUp"
|
||||
? "You have an account already?"
|
||||
: "You don't have an account yet?"}{" "}
|
||||
<Anchor href={mode == "signUp" ? "signIn" : "signUp"} size="sm">
|
||||
{mode == "signUp" ? "Sign in" : "Sign up"}
|
||||
</Anchor>
|
||||
</Text>
|
||||
|
||||
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) =>
|
||||
mode == "signIn"
|
||||
? signIn(values.email, values.password)
|
||||
: signUp(values.email, values.password)
|
||||
)}
|
||||
>
|
||||
<TextInput
|
||||
label="Email"
|
||||
placeholder="you@email.com"
|
||||
{...form.getInputProps("email")}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Your password"
|
||||
mt="md"
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<Button fullWidth mt="xl" type="submit">
|
||||
{mode == "signUp" ? "Let's get started" : "Sign in"}
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthForm;
|
||||
41
src/components/mantine/ThemeProvider.tsx
Normal file
41
src/components/mantine/ThemeProvider.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
ColorScheme,
|
||||
ColorSchemeProvider,
|
||||
MantineProvider,
|
||||
} from "@mantine/core";
|
||||
import { ModalsProvider } from "@mantine/modals";
|
||||
import { setCookies } from "cookies-next";
|
||||
import { Dispatch, ReactNode, SetStateAction } from "react";
|
||||
import mantineTheme from "../../styles/global.style";
|
||||
|
||||
const ThemeProvider = ({
|
||||
children,
|
||||
colorScheme,
|
||||
setColorScheme,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
colorScheme: ColorScheme;
|
||||
setColorScheme: Dispatch<SetStateAction<ColorScheme>>;
|
||||
}) => {
|
||||
const toggleColorScheme = (value?: ColorScheme) => {
|
||||
const nextColorScheme =
|
||||
value || (colorScheme === "dark" ? "light" : "dark");
|
||||
setColorScheme(nextColorScheme);
|
||||
setCookies("mantine-color-scheme", nextColorScheme, {
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<MantineProvider theme={{ colorScheme, ...mantineTheme }} withGlobalStyles>
|
||||
<ColorSchemeProvider
|
||||
colorScheme={colorScheme}
|
||||
toggleColorScheme={toggleColorScheme}
|
||||
>
|
||||
<ModalsProvider>{children}</ModalsProvider>
|
||||
</ColorSchemeProvider>
|
||||
</MantineProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
||||
122
src/components/navBar/NavBar.tsx
Normal file
122
src/components/navBar/NavBar.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
Burger,
|
||||
Container,
|
||||
Group,
|
||||
Header as MantineHeader,
|
||||
Paper,
|
||||
Space,
|
||||
Text,
|
||||
Transition,
|
||||
} from "@mantine/core";
|
||||
import { useBooleanToggle } from "@mantine/hooks";
|
||||
import { NextLink } from "@mantine/next";
|
||||
import Image from "next/image";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import headerStyle from "../../styles/header.style";
|
||||
import aw from "../../utils/appwrite.util";
|
||||
import { IsSignedInContext } from "../../utils/auth.util";
|
||||
import ToggleThemeButton from "./ToggleThemeButton";
|
||||
|
||||
type Link = {
|
||||
link?: string;
|
||||
label: string;
|
||||
action?: () => Promise<void>;
|
||||
};
|
||||
|
||||
const authenticatedLinks: Link[] = [
|
||||
{
|
||||
link: "/upload",
|
||||
label: "Upload",
|
||||
},
|
||||
{
|
||||
label: "Sign out",
|
||||
action: async () => {
|
||||
await aw.account.deleteSession("current");
|
||||
window.location.reload();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const unauthenticatedLinks: Link[] = [
|
||||
{
|
||||
link: "/",
|
||||
label: "Home",
|
||||
},
|
||||
{
|
||||
link: "/auth/signUp",
|
||||
label: "Sign up",
|
||||
},
|
||||
{
|
||||
link: "/auth/signIn",
|
||||
label: "Sign in",
|
||||
},
|
||||
];
|
||||
|
||||
const Header = () => {
|
||||
const [opened, toggleOpened] = useBooleanToggle(false);
|
||||
const [active, setActive] = useState<string>();
|
||||
const isSignedIn = useContext(IsSignedInContext);
|
||||
const { classes, cx } = headerStyle();
|
||||
|
||||
const links = isSignedIn ? authenticatedLinks : unauthenticatedLinks;
|
||||
|
||||
const items = links.map((link) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
if (window.location.pathname == link.link) {
|
||||
setActive(link.link);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<NextLink
|
||||
key={link.label}
|
||||
href={link.link ?? ""}
|
||||
onClick={link.action}
|
||||
className={cx(classes.link, {
|
||||
[classes.linkActive]: link.link && active === link.link,
|
||||
})}
|
||||
>
|
||||
{link.label}
|
||||
</NextLink>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<MantineHeader height={60} mb={20} className={classes.root}>
|
||||
<Container className={classes.header}>
|
||||
<NextLink href="/">
|
||||
<Group>
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="Pinvgin Share Logo"
|
||||
height={40}
|
||||
width={40}
|
||||
/>
|
||||
<Text weight={600}>Pingvin Share</Text>
|
||||
</Group>
|
||||
</NextLink>
|
||||
<Group spacing={5} className={classes.links}>
|
||||
{items}
|
||||
<Space w={5} />
|
||||
<ToggleThemeButton />
|
||||
</Group>
|
||||
|
||||
<Burger
|
||||
opened={opened}
|
||||
onClick={() => toggleOpened()}
|
||||
className={classes.burger}
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
<Transition transition="pop-top-right" duration={200} mounted={opened}>
|
||||
{(styles) => (
|
||||
<Paper className={classes.dropdown} withBorder style={styles}>
|
||||
{items}
|
||||
</Paper>
|
||||
)}
|
||||
</Transition>
|
||||
</Container>
|
||||
</MantineHeader>
|
||||
);
|
||||
};
|
||||
export default Header;
|
||||
26
src/components/navBar/ToggleThemeButton.tsx
Normal file
26
src/components/navBar/ToggleThemeButton.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ActionIcon, useMantineColorScheme } from "@mantine/core";
|
||||
import { Sun, MoonStars } from "tabler-icons-react";
|
||||
|
||||
const ToggleThemeButton = () => {
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
onClick={() => toggleColorScheme()}
|
||||
sx={(theme) => ({
|
||||
backgroundColor:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[6]
|
||||
: theme.colors.gray[0],
|
||||
color:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.yellow[4]
|
||||
: theme.colors.violet,
|
||||
})}
|
||||
>
|
||||
{colorScheme === "dark" ? <Sun size={18} /> : <MoonStars size={18} />}
|
||||
</ActionIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleThemeButton;
|
||||
80
src/components/share/FileList.tsx
Normal file
80
src/components/share/FileList.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ActionIcon, Skeleton, Table } from "@mantine/core";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { Download } from "tabler-icons-react";
|
||||
import { AppwriteFileWithPreview } from "../../types/File.type";
|
||||
import aw from "../../utils/appwrite.util";
|
||||
import { bytesToSize } from "../../utils/math/byteToSize.util";
|
||||
|
||||
const FileList = ({
|
||||
files,
|
||||
shareId,
|
||||
isLoading,
|
||||
}: {
|
||||
files: AppwriteFileWithPreview[];
|
||||
shareId: string;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const skeletonRows = [...Array(5)].map((c, i) => (
|
||||
<tr key={i}>
|
||||
<td>
|
||||
<Skeleton height={30} width={30} />
|
||||
</td>
|
||||
<td>
|
||||
<Skeleton height={14} />
|
||||
</td>
|
||||
<td>
|
||||
<Skeleton height={14} />
|
||||
</td>
|
||||
<td>
|
||||
<Skeleton height={25} width={25} />
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
const rows = files.map((file) => (
|
||||
<tr key={file.name}>
|
||||
<td>
|
||||
<Image
|
||||
width={30}
|
||||
height={30}
|
||||
alt={file.name}
|
||||
objectFit="cover"
|
||||
src={`data:image/png;base64,${new Buffer(file.preview).toString(
|
||||
"base64"
|
||||
)}`}
|
||||
></Image>
|
||||
</td>
|
||||
<td>{file.name}</td>
|
||||
<td>{bytesToSize(file.sizeOriginal)}</td>
|
||||
<td>
|
||||
<ActionIcon
|
||||
size={25}
|
||||
onClick={() =>
|
||||
router.push(aw.storage.getFileDownload(shareId, file.$id))
|
||||
}
|
||||
>
|
||||
<Download />
|
||||
</ActionIcon>
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{isLoading ? skeletonRows : rows}</tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileList;
|
||||
58
src/components/share/showEnterPasswordModal.tsx
Normal file
58
src/components/share/showEnterPasswordModal.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Button, Group, PasswordInput, Text, Title } from "@mantine/core";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useState } from "react";
|
||||
|
||||
const showEnterPasswordModal = (
|
||||
modals: ModalsContextProps,
|
||||
submitCallback: any
|
||||
) => {
|
||||
return modals.openModal({
|
||||
closeOnClickOutside: false,
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
title: (
|
||||
<>
|
||||
<Title order={4}>Password required</Title>
|
||||
<Text size="sm">
|
||||
This access this share please enter the password for the share.
|
||||
</Text>
|
||||
</>
|
||||
),
|
||||
children: <Body submitCallback={submitCallback} />,
|
||||
});
|
||||
};
|
||||
|
||||
const Body = ({ submitCallback }: { submitCallback: any }) => {
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordWrong, setPasswordWrong] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Group grow direction="column">
|
||||
<PasswordInput
|
||||
variant="filled"
|
||||
placeholder="Password"
|
||||
error={passwordWrong && "Wrong password"}
|
||||
onFocus={() => setPasswordWrong(false)}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
value={password}
|
||||
/>
|
||||
<Button
|
||||
onClick={() =>
|
||||
submitCallback(password)
|
||||
.then((res: any) => res)
|
||||
.catch((e: any) => {
|
||||
const error = e.response.data.message;
|
||||
if (error == "wrong_password") {
|
||||
setPasswordWrong(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default showEnterPasswordModal;
|
||||
39
src/components/share/showShareNotFoundModal.tsx
Normal file
39
src/components/share/showShareNotFoundModal.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Button, Group, Text, Title } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const showShareNotFoundModal = (modals: ModalsContextProps) => {
|
||||
return modals.openModal({
|
||||
closeOnClickOutside: false,
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
title: <Title order={4}>Not found</Title>,
|
||||
|
||||
children: <Body />,
|
||||
});
|
||||
};
|
||||
|
||||
const Body = () => {
|
||||
const modals = useModals();
|
||||
const router = useRouter();
|
||||
return (
|
||||
<>
|
||||
<Group grow direction="column">
|
||||
<Text size="sm">
|
||||
This share can't be found. Please check your link.
|
||||
</Text>
|
||||
<Button
|
||||
onClick={() => {
|
||||
modals.closeAll();
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default showShareNotFoundModal;
|
||||
39
src/components/share/showVisitorLimitExceededModal.tsx
Normal file
39
src/components/share/showVisitorLimitExceededModal.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Button, Group, Text, Title } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const showVisitorLimitExceededModal = (modals: ModalsContextProps) => {
|
||||
return modals.openModal({
|
||||
closeOnClickOutside: false,
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
title: <Title order={4}>Visitor limit exceeded</Title>,
|
||||
|
||||
children: <Body />,
|
||||
});
|
||||
};
|
||||
|
||||
const Body = () => {
|
||||
const modals = useModals();
|
||||
const router = useRouter();
|
||||
return (
|
||||
<>
|
||||
<Group grow direction="column">
|
||||
<Text size="sm">
|
||||
The visitor count limit from this share has been exceeded.
|
||||
</Text>
|
||||
<Button
|
||||
onClick={() => {
|
||||
modals.closeAll();
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default showVisitorLimitExceededModal;
|
||||
104
src/components/upload/Dropzone.tsx
Normal file
104
src/components/upload/Dropzone.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
createStyles,
|
||||
Group,
|
||||
MantineTheme,
|
||||
Text,
|
||||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import { Dropzone as MantineDropzone, DropzoneStatus } from "@mantine/dropzone";
|
||||
import React, { Dispatch, ForwardedRef, SetStateAction, useRef } from "react";
|
||||
import { CloudUpload, Upload } from "tabler-icons-react";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
wrapper: {
|
||||
position: "relative",
|
||||
marginBottom: 30,
|
||||
},
|
||||
|
||||
dropzone: {
|
||||
borderWidth: 1,
|
||||
paddingBottom: 50,
|
||||
},
|
||||
|
||||
icon: {
|
||||
color:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[3]
|
||||
: theme.colors.gray[4],
|
||||
},
|
||||
|
||||
control: {
|
||||
position: "absolute",
|
||||
bottom: -20,
|
||||
},
|
||||
}));
|
||||
|
||||
function getActiveColor(status: DropzoneStatus, theme: MantineTheme) {
|
||||
return status.accepted
|
||||
? theme.colors[theme.primaryColor][6]
|
||||
: theme.colorScheme === "dark"
|
||||
? theme.colors.dark[2]
|
||||
: theme.black;
|
||||
}
|
||||
|
||||
const Dropzone = ({
|
||||
isUploading,
|
||||
setFiles,
|
||||
}: {
|
||||
isUploading: boolean;
|
||||
setFiles: Dispatch<SetStateAction<File[]>>;
|
||||
}) => {
|
||||
const theme = useMantineTheme();
|
||||
const { classes } = useStyles();
|
||||
const openRef = useRef<() => void>();
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<MantineDropzone
|
||||
disabled={isUploading}
|
||||
openRef={openRef as ForwardedRef<() => void>}
|
||||
onDrop={(files) => {
|
||||
setFiles(files);
|
||||
}}
|
||||
className={classes.dropzone}
|
||||
radius="md"
|
||||
>
|
||||
{(status) => (
|
||||
<div style={{ pointerEvents: "none" }}>
|
||||
<Group position="center">
|
||||
<CloudUpload size={50} color={getActiveColor(status, theme)} />
|
||||
</Group>
|
||||
<Text
|
||||
align="center"
|
||||
weight={700}
|
||||
size="lg"
|
||||
mt="xl"
|
||||
sx={{ color: getActiveColor(status, theme) }}
|
||||
>
|
||||
{status.accepted ? "Drop files here" : "Upload files"}
|
||||
</Text>
|
||||
<Text align="center" size="sm" mt="xs" color="dimmed">
|
||||
Drag and drop your files or use the upload button to start your
|
||||
share.
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</MantineDropzone>
|
||||
<Center>
|
||||
<Button
|
||||
className={classes.control}
|
||||
variant="light"
|
||||
size="sm"
|
||||
radius="xl"
|
||||
disabled={isUploading}
|
||||
onClick={() => openRef.current && openRef.current()}
|
||||
>
|
||||
{<Upload />}
|
||||
</Button>
|
||||
</Center>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Dropzone;
|
||||
60
src/components/upload/FileList.tsx
Normal file
60
src/components/upload/FileList.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { ActionIcon, Loader, Table } from "@mantine/core";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { CircleCheck, Trash } from "tabler-icons-react";
|
||||
import { FileUpload } from "../../types/File.type";
|
||||
import { bytesToSize } from "../../utils/math/byteToSize.util";
|
||||
|
||||
const FileList = ({
|
||||
files,
|
||||
setFiles,
|
||||
}: {
|
||||
files: FileUpload[];
|
||||
setFiles: Dispatch<SetStateAction<FileUpload[]>>;
|
||||
}) => {
|
||||
const remove = (index: number) => {
|
||||
files.splice(index, 1);
|
||||
setFiles([...files]);
|
||||
};
|
||||
|
||||
const rows = files.map((file, i) => (
|
||||
<tr key={file.name}>
|
||||
<td>{file.name}</td>
|
||||
<td>{file.type}</td>
|
||||
<td>{bytesToSize(file.size)}</td>
|
||||
<td>
|
||||
{file.uploadingState ? (
|
||||
file.uploadingState != "finished" ? (
|
||||
<Loader size={22} />
|
||||
) : (
|
||||
<CircleCheck color="green" size={22} />
|
||||
)
|
||||
) : (
|
||||
<ActionIcon
|
||||
color="red"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={() => remove(i)}
|
||||
>
|
||||
<Trash />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Size</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileList;
|
||||
65
src/components/upload/showCompletedUploadModal.tsx
Normal file
65
src/components/upload/showCompletedUploadModal.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Group,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useClipboard } from "@mantine/hooks";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useRouter } from "next/router";
|
||||
import { Copy } from "tabler-icons-react";
|
||||
|
||||
const showCompletedUploadModal = (
|
||||
modals: ModalsContextProps,
|
||||
link: string,
|
||||
expiresAt: string
|
||||
) => {
|
||||
return modals.openModal({
|
||||
closeOnClickOutside: false,
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
title: <Title order={4}>Share ready</Title>,
|
||||
children: <Body link={link} expiresAt={expiresAt} />,
|
||||
});
|
||||
};
|
||||
|
||||
const Body = ({ link, expiresAt }: { link: string; expiresAt: string }) => {
|
||||
const clipboard = useClipboard({ timeout: 500 });
|
||||
const modals = useModals();
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Group grow direction="column">
|
||||
<TextInput
|
||||
variant="filled"
|
||||
value={link}
|
||||
rightSection={
|
||||
<ActionIcon onClick={() => clipboard.copy(link)}>
|
||||
<Copy />
|
||||
</ActionIcon>
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
size="xs"
|
||||
sx={(theme) => ({
|
||||
color: theme.colors.gray[6],
|
||||
})}
|
||||
>
|
||||
Your share expires at {expiresAt}{" "}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
modals.closeAll();
|
||||
router.push("/upload");
|
||||
}}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default showCompletedUploadModal;
|
||||
140
src/components/upload/showCreateUploadModal.tsx
Normal file
140
src/components/upload/showCreateUploadModal.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
Col,
|
||||
Grid,
|
||||
Group,
|
||||
NumberInput,
|
||||
PasswordInput,
|
||||
Select,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import * as yup from "yup";
|
||||
|
||||
const showCreateUploadModal = (
|
||||
modals: ModalsContextProps,
|
||||
uploadCallback: (
|
||||
id: string,
|
||||
expiration: number,
|
||||
security: { password?: string; maxVisitors?: number }
|
||||
) => void
|
||||
) => {
|
||||
return modals.openModal({
|
||||
title: <Title order={4}>Share</Title>,
|
||||
children: <Body uploadCallback={uploadCallback} />,
|
||||
});
|
||||
};
|
||||
|
||||
const Body = ({
|
||||
uploadCallback,
|
||||
}: {
|
||||
uploadCallback: (
|
||||
id: string,
|
||||
expiration: number,
|
||||
security: { password?: string; maxVisitors?: number }
|
||||
) => void;
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const validationSchema = yup.object().shape({
|
||||
link: yup.string().required().min(2).max(50),
|
||||
password: yup.string().min(3).max(100),
|
||||
maxVisitors: yup.number().min(1),
|
||||
});
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
link: "",
|
||||
password: undefined,
|
||||
maxVisitors: undefined,
|
||||
expiration: "1440",
|
||||
},
|
||||
schema: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
modals.closeAll();
|
||||
uploadCallback(values.link, parseInt(values.expiration), {
|
||||
password: values.password,
|
||||
maxVisitors: values.maxVisitors,
|
||||
});
|
||||
})}
|
||||
>
|
||||
<Group direction="column" grow>
|
||||
<Grid align="flex-end">
|
||||
<Col xs={9}>
|
||||
<TextInput
|
||||
variant="filled"
|
||||
label="Link"
|
||||
placeholder="myAwesomeShare"
|
||||
{...form.getInputProps("link")}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
form.setFieldValue(
|
||||
"link",
|
||||
Buffer.from(Math.random().toString(), "utf8")
|
||||
.toString("base64")
|
||||
.substr(10, 7)
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
</Col>
|
||||
</Grid>
|
||||
|
||||
<Text
|
||||
size="xs"
|
||||
sx={(theme) => ({
|
||||
color: theme.colors.gray[6],
|
||||
})}
|
||||
>
|
||||
{window.location.origin}/share/
|
||||
{form.values.link == "" ? "myAwesomeShare" : form.values.link}
|
||||
</Text>
|
||||
<Select
|
||||
label="Expiration"
|
||||
{...form.getInputProps("expiration")}
|
||||
data={[
|
||||
{ value: "10", label: "10 Minutes" },
|
||||
{ value: "60", label: "1 Hour" },
|
||||
{ value: "1440", label: "1 Day" },
|
||||
{ value: "1080", label: "1 Week" },
|
||||
{ value: "43000", label: "1 Month" },
|
||||
]}
|
||||
/>
|
||||
<Accordion>
|
||||
<Accordion.Item label="Security" sx={{ borderBottom: "none" }}>
|
||||
<Group direction="column" grow>
|
||||
<PasswordInput
|
||||
variant="filled"
|
||||
placeholder="No password"
|
||||
label="Password protection"
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<NumberInput
|
||||
type="number"
|
||||
variant="filled"
|
||||
placeholder="No limit"
|
||||
label="Maximal views"
|
||||
{...form.getInputProps("maxVisitors")}
|
||||
/>
|
||||
</Group>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
<Button type="submit">Share</Button>
|
||||
</Group>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default showCreateUploadModal;
|
||||
Reference in New Issue
Block a user