Merge remote-tracking branch 'stonith404/main' into main

This commit is contained in:
Elias Schneider
2022-10-16 00:08:37 +02:00
47 changed files with 6692 additions and 881 deletions

View File

@@ -8,9 +8,6 @@ import {
import { useColorScheme } from "@mantine/hooks";
import { ModalsProvider } from "@mantine/modals";
import { NotificationsProvider } from "@mantine/notifications";
import { setCookies } from "cookies-next";
import { GetServerSidePropsContext } from "next";
import cookies from "next-cookies";
import type { AppProps } from "next/app";
import { useEffect, useState } from "react";
import Footer from "../components/Footer";
@@ -23,14 +20,10 @@ import globalStyle from "../styles/mantine.style";
import { CurrentUser } from "../types/user.type";
import { GlobalLoadingContext } from "../utils/loading.util";
function App(props: AppProps & { colorScheme: ColorScheme }) {
const { Component, pageProps } = props;
function App({ Component, pageProps }: AppProps) {
const systemTheme = useColorScheme();
const [colorScheme, setColorScheme] = useState<ColorScheme>(
props.colorScheme
);
const [colorScheme, setColorScheme] = useState<ColorScheme>();
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState<CurrentUser | null>(null);
@@ -47,9 +40,6 @@ function App(props: AppProps & { colorScheme: ColorScheme }) {
}, []);
useEffect(() => {
setCookies("color-schema", systemTheme, {
maxAge: 60 * 60 * 24 * 30,
});
setColorScheme(systemTheme);
}, [systemTheme]);
@@ -87,9 +77,3 @@ function App(props: AppProps & { colorScheme: ColorScheme }) {
}
export default App;
App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => {
return {
colorScheme: cookies(ctx)["color-schema"] || "light",
};
};

View File

@@ -1,131 +1,140 @@
import {
ActionIcon,
Button,
Center,
Group,
LoadingOverlay,
Space,
Stack,
Table,
Text,
Title,
ActionIcon,
Button,
Center,
Group,
LoadingOverlay,
Space,
Stack,
Table,
Text,
Title,
} from "@mantine/core";
import {useClipboard} from "@mantine/hooks";
import {useModals} from "@mantine/modals";
import {NextLink} from "@mantine/next";
import { useClipboard } from "@mantine/hooks";
import { useModals } from "@mantine/modals";
import { NextLink } from "@mantine/next";
import moment from "moment";
import {useEffect, useState} from "react";
import {Link, Trash} from "tabler-icons-react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { TbLink, TbTrash } from "react-icons/tb";
import Meta from "../../components/Meta";
import useUser from "../../hooks/user.hook";
import shareService from "../../services/share.service";
import {MyShare} from "../../types/share.type";
import { MyShare } from "../../types/share.type";
import toast from "../../utils/toast.util";
import getConfig from "next/config";
const {publicRuntimeConfig} = getConfig();
const { publicRuntimeConfig } = getConfig();
const MyShares = () => {
const modals = useModals();
const clipboard = useClipboard();
const modals = useModals();
const clipboard = useClipboard();
const router = useRouter();
const user = useUser();
const [shares, setShares] = useState<MyShare[]>();
const [shares, setShares] = useState<MyShare[]>();
useEffect(() => {
shareService.getMyShares().then((shares) => setShares(shares));
}, []);
useEffect(() => {
shareService.getMyShares().then((shares) => setShares(shares));
}, []);
if (!shares) return <LoadingOverlay visible/>;
if (!user) {
router.replace("/");
} else {
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"}}>
<Stack 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>
</Stack>
</Center>
) : (
<Table>
<thead>
<tr>
<th>Name</th>
<th>Visitors</th>
<th>Expires at</th>
<th></th>
</tr>
</thead>
<tbody>
{shares.map((share) => (
<tr key={share.id}>
<td>{share.id}</td>
<td>{share.views}</td>
<td>
{moment(share.expiration).unix() === 0
? "Never"
: (publicRuntimeConfig.TWELVE_HOUR_TIME === "true")
? moment(share.expiration).format("MMMM Do YYYY, h:mm a")
: moment(share.expiration).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>
)}
</>
<>
<Meta title="My shares" />
<Title mb={30} order={3}>
My shares
</Title>
{shares.length == 0 ? (
<Center style={{ height: "70vh" }}>
<Stack 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>
</Stack>
</Center>
) : (
<Table>
<thead>
<tr>
<th>Name</th>
<th>Visitors</th>
<th>Expires at</th>
<th></th>
</tr>
</thead>
<tbody>
{shares.map((share) => (
<tr key={share.id}>
<td>{share.id}</td>
<td>{share.views}</td>
<td>
{moment(share.expiration).unix() === 0
? "Never"
: publicRuntimeConfig.TWELVE_HOUR_TIME === "true"
? moment(share.expiration).format("MMMM Do YYYY, h:mm a")
: moment(share.expiration).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."
);
}}
>
<TbLink />
</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)
);
},
});
}}
>
<TbTrash />
</ActionIcon>
</Group>
</td>
</tr>
))}
</tbody>
</Table>
)}
</>
);
}
};
export default MyShares;

View File

@@ -12,10 +12,9 @@ import { NextLink } from "@mantine/next";
import getConfig from "next/config";
import Image from "next/image";
import { useRouter } from "next/router";
import { Check } from "tabler-icons-react";
import { TbCheck } from "react-icons/tb";
import Meta from "../components/Meta";
import useUser from "../hooks/user.hook";
const { publicRuntimeConfig } = getConfig();
const useStyles = createStyles((theme) => ({
@@ -101,7 +100,7 @@ export default function Home() {
size="sm"
icon={
<ThemeIcon size={20} radius="xl">
<Check size={12} />
<TbCheck size={12} />
</ThemeIcon>
}
>

View File

@@ -1,6 +1,6 @@
import { Group } from "@mantine/core";
import { useModals } from "@mantine/modals";
import { useRouter } from "next/router";
import { GetServerSidePropsContext } from "next";
import { useEffect, useState } from "react";
import Meta from "../../components/Meta";
import DownloadAllButton from "../../components/share/DownloadAllButton";
@@ -9,18 +9,31 @@ import showEnterPasswordModal from "../../components/share/showEnterPasswordModa
import showErrorModal from "../../components/share/showErrorModal";
import shareService from "../../services/share.service";
const Share = () => {
const router = useRouter();
export function getServerSideProps(context: GetServerSidePropsContext) {
return {
props: { shareId: context.params!.shareId },
};
}
const Share = ({ shareId }: { shareId: string }) => {
const modals = useModals();
const shareId = router.query.shareId as string;
const [fileList, setFileList] = useState<any[]>([]);
const submitPassword = async (password: string) => {
const getShareToken = async (password?: string) => {
await shareService
.exchangeSharePasswordWithToken(shareId, password)
.getShareToken(shareId, password)
.then(() => {
modals.closeAll();
getFiles();
})
.catch((e) => {
if (e.response.data.error == "share_max_views_exceeded") {
showErrorModal(
modals,
"Visitor limit exceeded",
"The visitor limit from this share has been exceeded."
);
}
});
};
@@ -38,14 +51,10 @@ const Share = () => {
"Not found",
"This share can't be found. Please check your link."
);
} else if (error == "share_password_required") {
showEnterPasswordModal(modals, getShareToken);
} else if (error == "share_token_required") {
showEnterPasswordModal(modals, submitPassword);
} else if (error == "share_max_views_exceeded") {
showErrorModal(
modals,
"Visitor limit exceeded",
"The visitor limit from this share has been exceeded."
);
getShareToken();
} else if (error == "forbidden") {
showErrorModal(
modals,
@@ -69,9 +78,7 @@ const Share = () => {
description="Look what I've shared with you."
/>
<Group position="right" mb="lg">
<DownloadAllButton
shareId={shareId}
/>
<DownloadAllButton shareId={shareId} />
</Group>
<FileList
files={fileList}

View File

@@ -28,39 +28,61 @@ const Upload = () => {
security: ShareSecurity
) => {
setisUploading(true);
try {
files.forEach((file) => {
file.uploadingState = "inProgress";
});
setFiles([...files]);
setFiles((files) =>
files.map((file) => {
file.uploadingProgress = 1;
return file;
})
);
const share = await shareService.create(id, expiration, security);
for (let i = 0; i < files.length; i++) {
await shareService.uploadFile(share.id, files[i]);
const progressCallBack = (bytesProgress: number) => {
setFiles((files) => {
return files.map((file, callbackIndex) => {
if (i == callbackIndex) {
file.uploadingProgress = Math.round(
(100 * bytesProgress) / files[i].size
);
}
return file;
});
});
};
files[i].uploadingState = "finished";
setFiles([...files]);
if (!files.some((f) => f.uploadingState == "inProgress")) {
await shareService.completeShare(share.id);
try {
await shareService.uploadFile(share.id, files[i], progressCallBack);
} catch {
files[i].uploadingProgress = -1;
}
if (
files.every(
(file) =>
file.uploadingProgress >= 100 || file.uploadingProgress == -1
)
) {
const fileErrorCount = files.filter(
(file) => file.uploadingProgress == -1
).length;
setisUploading(false);
showCompletedUploadModal(
modals,
share
);
setFiles([]);
if (fileErrorCount > 0) {
toast.error(
`${fileErrorCount} file(s) failed to upload. Try again.`
);
} else {
await shareService.completeShare(share.id);
showCompletedUploadModal(modals, share);
setFiles([]);
}
}
}
} catch (e) {
files.forEach((file) => {
file.uploadingState = undefined;
});
if (axios.isAxiosError(e)) {
toast.error(e.response?.data?.message ?? "An unkown error occured.");
} else {
toast.error("An unkown error occured.");
}
setFiles([...files]);
setisUploading(false);
}
};