diff --git a/.dockerignore b/.dockerignore index 8568a5bc..1f9167d0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,8 @@ backend/dist/ backend/node_modules/ +backend/data frontend/node_modules/ frontend/.next/ -frontend/dist/ **/.git/ diff --git a/README.md b/README.md index e0f91863..f3d48d1d 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The website is now listening available on `http://localhost:3000`, have fun with ### Upgrade to a new version -Just updated the docker container by running `docker-compose pull && docker-compose up -d` +Just update the docker container by running `docker compose pull && docker compose up -d` > Note: If you installed Pingvin Share before it used Sqlite, you unfortunately have to set up the project from scratch again, sorry for that. @@ -65,3 +65,7 @@ Contact me, create an issue or directly create a pull request. 5. Start the frontend with `npm run dev` You're all set! + +### Testing + +At the moment we only have system tests for the backend. To run these tests, run `npm run test:system` in the backend folder. diff --git a/backend/src/share/share.service.ts b/backend/src/share/share.service.ts index e417fd93..602e82e0 100644 --- a/backend/src/share/share.service.ts +++ b/backend/src/share/share.service.ts @@ -99,12 +99,19 @@ export class ShareService { async getSharesByUser(userId: string) { return await this.prisma.share.findMany({ - where: { creator: { id: userId }, expiration: { gt: new Date() } }, + where: { + creator: { id: userId }, + expiration: { gt: new Date() }, + uploadLocked: true, + }, + orderBy: { + expiration: "desc", + }, }); } async get(id: string) { - const share : any = await this.prisma.share.findUnique({ + const share: any = await this.prisma.share.findUnique({ where: { id }, include: { files: true, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 33b29589..96737ac9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,7 +26,7 @@ "next-pwa": "^5.6.0", "react": "18.0.0", "react-dom": "18.0.0", - "tabler-icons-react": "^1.44.0", + "react-icons": "^4.4.0", "yup": "^0.32.11" }, "devDependencies": { @@ -6381,6 +6381,14 @@ "react": ">= 16.8 || 18.0.0" } }, + "node_modules/react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6971,14 +6979,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tabler-icons-react": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/tabler-icons-react/-/tabler-icons-react-1.44.0.tgz", - "integrity": "sha512-AYQQGl55yVe1KHZl+zyDAAwDOcPknKZNC7vgwmjyvpmz4P5Gjb7DtpsOPa1nB0qMYW5Orsrt+1e4qnRoCKgo6A==", - "peerDependencies": { - "react": ">= 16.8.0" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -12479,6 +12479,12 @@ "prop-types": "^15.8.1" } }, + "react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -12910,12 +12916,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, - "tabler-icons-react": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/tabler-icons-react/-/tabler-icons-react-1.44.0.tgz", - "integrity": "sha512-AYQQGl55yVe1KHZl+zyDAAwDOcPknKZNC7vgwmjyvpmz4P5Gjb7DtpsOPa1nB0qMYW5Orsrt+1e4qnRoCKgo6A==", - "requires": {} - }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1e4c45b9..75cf76c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,7 @@ "next-pwa": "^5.6.0", "react": "18.0.0", "react-dom": "18.0.0", - "tabler-icons-react": "^1.44.0", + "react-icons": "^4.4.0", "yup": "^0.32.11" }, "devDependencies": { diff --git a/frontend/src/components/Logo.tsx b/frontend/src/components/Logo.tsx new file mode 100644 index 00000000..7fea40e3 --- /dev/null +++ b/frontend/src/components/Logo.tsx @@ -0,0 +1,34 @@ +const Logo = ({ height, width }: { height: number; width: number }) => { + return ( + + + + + + + + + + + ); +}; +export default Logo; diff --git a/frontend/src/components/navBar/ActionAvatar.tsx b/frontend/src/components/navBar/ActionAvatar.tsx index 121fc957..867029c9 100644 --- a/frontend/src/components/navBar/ActionAvatar.tsx +++ b/frontend/src/components/navBar/ActionAvatar.tsx @@ -1,6 +1,6 @@ import { ActionIcon, Avatar, Menu } from "@mantine/core"; import { NextLink } from "@mantine/next"; -import { DoorExit, Link } from "tabler-icons-react"; +import { TbDoorExit, TbLink } from "react-icons/tb";; import authService from "../../services/auth.service"; const ActionAvatar = () => { @@ -15,7 +15,7 @@ const ActionAvatar = () => { } + icon={} > My shares @@ -23,7 +23,7 @@ const ActionAvatar = () => { onClick={async () => { authService.signOut(); }} - icon={} + icon={} > Sign out diff --git a/frontend/src/components/navBar/NavBar.tsx b/frontend/src/components/navBar/NavBar.tsx index a732e952..044f2a3d 100644 --- a/frontend/src/components/navBar/NavBar.tsx +++ b/frontend/src/components/navBar/NavBar.tsx @@ -5,7 +5,6 @@ import { createStyles, Group, Header, - Image, Paper, Stack, Text, @@ -16,6 +15,7 @@ import { NextLink } from "@mantine/next"; import getConfig from "next/config"; import { ReactNode, useEffect, useState } from "react"; import useUser from "../../hooks/user.hook"; +import Logo from "../Logo"; import ActionAvatar from "./ActionAvatar"; const { publicRuntimeConfig } = getConfig(); @@ -180,12 +180,7 @@ const NavBar = () => { - Pinvgin Share Logo + Pingvin Share diff --git a/frontend/src/components/share/DownloadAllButton.tsx b/frontend/src/components/share/DownloadAllButton.tsx index 44497976..1bb0a140 100644 --- a/frontend/src/components/share/DownloadAllButton.tsx +++ b/frontend/src/components/share/DownloadAllButton.tsx @@ -1,6 +1,7 @@ -import { Button, Tooltip } from "@mantine/core"; +import { Button } from "@mantine/core"; import { useEffect, useState } from "react"; import shareService from "../../services/share.service"; +import toast from "../../utils/toast.util"; const DownloadAllButton = ({ shareId }: { shareId: string }) => { const [isZipReady, setIsZipReady] = useState(false); @@ -32,21 +33,18 @@ const DownloadAllButton = ({ shareId }: { shareId: string }) => { }; }, []); - if (!isZipReady) - return ( - - - - ); return ( - ); diff --git a/frontend/src/components/share/FileList.tsx b/frontend/src/components/share/FileList.tsx index e032171d..9cce2449 100644 --- a/frontend/src/components/share/FileList.tsx +++ b/frontend/src/components/share/FileList.tsx @@ -1,5 +1,5 @@ import { ActionIcon, Loader, Skeleton, Table } from "@mantine/core"; -import { CircleCheck, Download } from "tabler-icons-react"; +import { TbCircleCheck, TbDownload } from "react-icons/tb";; import shareService from "../../services/share.service"; import { byteStringToHumanSizeString } from "../../utils/math/byteStringToHumanSizeString.util"; @@ -39,7 +39,7 @@ const FileList = ({ file.uploadingState != "finished" ? ( ) : ( - + ) ) : ( - + )} diff --git a/frontend/src/components/upload/Dropzone.tsx b/frontend/src/components/upload/Dropzone.tsx index 4c55d3ae..9ad55bf9 100644 --- a/frontend/src/components/upload/Dropzone.tsx +++ b/frontend/src/components/upload/Dropzone.tsx @@ -2,7 +2,7 @@ import { Button, Center, createStyles, Group, Text } from "@mantine/core"; import { Dropzone as MantineDropzone } from "@mantine/dropzone"; import getConfig from "next/config"; import { Dispatch, ForwardedRef, SetStateAction, useRef } from "react"; -import { CloudUpload, Upload } from "tabler-icons-react"; +import { TbCloudUpload, TbUpload } from "react-icons/tb";;; import { FileUpload } from "../../types/File.type"; import { byteStringToHumanSizeString } from "../../utils/math/byteStringToHumanSizeString.util"; import toast from "../../utils/toast.util"; @@ -67,7 +67,7 @@ const Dropzone = ({ >
- + Upload files @@ -89,7 +89,7 @@ const Dropzone = ({ disabled={isUploading} onClick={() => openRef.current && openRef.current()} > - {} + {}
diff --git a/frontend/src/components/upload/FileList.tsx b/frontend/src/components/upload/FileList.tsx index c9a3260e..fb4c2a73 100644 --- a/frontend/src/components/upload/FileList.tsx +++ b/frontend/src/components/upload/FileList.tsx @@ -1,6 +1,6 @@ import { ActionIcon, Table } from "@mantine/core"; import { Dispatch, SetStateAction } from "react"; -import { Trash } from "tabler-icons-react"; +import { TbTrash } from "react-icons/tb";; import { FileUpload } from "../../types/File.type"; import { byteStringToHumanSizeString } from "../../utils/math/byteStringToHumanSizeString.util"; import UploadProgressIndicator from "./UploadProgressIndicator"; @@ -28,7 +28,7 @@ const FileList = ({ size={25} onClick={() => remove(i)} > - + ) : ( diff --git a/frontend/src/components/upload/UploadProgressIndicator.tsx b/frontend/src/components/upload/UploadProgressIndicator.tsx index 95800bbf..11b9f0c3 100644 --- a/frontend/src/components/upload/UploadProgressIndicator.tsx +++ b/frontend/src/components/upload/UploadProgressIndicator.tsx @@ -1,6 +1,5 @@ import { RingProgress } from "@mantine/core"; -import { CircleCheck, CircleX } from "tabler-icons-react"; - +import { TbCircleCheck, TbCircleX } from "react-icons/tb"; const UploadProgressIndicator = ({ progress }: { progress: number }) => { if (progress > 0 && progress < 100) { return ( @@ -10,10 +9,10 @@ const UploadProgressIndicator = ({ progress }: { progress: number }) => { size={25} /> ); - } else if (progress == 100) { - return ; + } else if (progress >= 100) { + return ; } else { - return ; + return ; } }; diff --git a/frontend/src/components/upload/showCompletedUploadModal.tsx b/frontend/src/components/upload/showCompletedUploadModal.tsx index 09c2b73a..372b9a3b 100644 --- a/frontend/src/components/upload/showCompletedUploadModal.tsx +++ b/frontend/src/components/upload/showCompletedUploadModal.tsx @@ -1,25 +1,20 @@ import { ActionIcon, Button, - Group, Stack, Text, TextInput, - Title + Title, } from "@mantine/core"; import { useClipboard } from "@mantine/hooks"; import { useModals } from "@mantine/modals"; import { ModalsContextProps } from "@mantine/modals/lib/context"; import moment from "moment"; import { useRouter } from "next/router"; -import { Copy } from "tabler-icons-react"; +import { TbCopy } from "react-icons/tb"; import { Share } from "../../types/share.type"; import toast from "../../utils/toast.util"; - -const showCompletedUploadModal = ( - modals: ModalsContextProps, - share: Share, -) => { +const showCompletedUploadModal = (modals: ModalsContextProps, share: Share) => { return modals.openModal({ closeOnClickOutside: false, withCloseButton: false, @@ -39,7 +34,7 @@ const Body = ({ share }: { share: Share }) => { const router = useRouter(); const link = `${window.location.origin}/share/${share.id}`; return ( - + { toast.success("Your link was copied to the keyboard."); }} > - + } /> diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index adf86132..78bd0e89 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -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( - props.colorScheme - ); + const [colorScheme, setColorScheme] = useState(); const [isLoading, setIsLoading] = useState(true); const [user, setUser] = useState(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", - }; -}; diff --git a/frontend/src/pages/account/shares.tsx b/frontend/src/pages/account/shares.tsx index f633f119..d4ed3ea7 100644 --- a/frontend/src/pages/account/shares.tsx +++ b/frontend/src/pages/account/shares.tsx @@ -15,8 +15,8 @@ import { useModals } from "@mantine/modals"; import { NextLink } from "@mantine/next"; import moment from "moment"; import { useRouter } from "next/router"; -import { useState } from "react"; -import { Link, Trash } from "tabler-icons-react"; +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"; @@ -31,9 +31,9 @@ const MyShares = () => { const [shares, setShares] = useState(); - // useEffect(() => { - // shareService.getMyShares().then((shares) => setShares(shares)); - // }, []); + useEffect(() => { + shareService.getMyShares().then((shares) => setShares(shares)); + }, []); if (!user) { router.replace("/"); @@ -89,7 +89,7 @@ const MyShares = () => { ); }} > - + { }); }} > - + diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 5c046ead..cfe7c55e 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -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={ - + } > diff --git a/frontend/src/pages/share/[shareId].tsx b/frontend/src/pages/share/[shareId].tsx index 21b23fde..2555f792 100644 --- a/frontend/src/pages/share/[shareId].tsx +++ b/frontend/src/pages/share/[shareId].tsx @@ -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,10 +9,14 @@ 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([]); const getShareToken = async (password?: string) => { diff --git a/frontend/src/pages/upload.tsx b/frontend/src/pages/upload.tsx index 825f0174..fdec6579 100644 --- a/frontend/src/pages/upload.tsx +++ b/frontend/src/pages/upload.tsx @@ -56,11 +56,25 @@ const Upload = () => { files[i].uploadingProgress = -1; } - if (!files.some((f) => f.uploadingProgress != 100)) { - await shareService.completeShare(share.id); + 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) { diff --git a/frontend/src/services/share.service.ts b/frontend/src/services/share.service.ts index 2913803e..5a4f51f8 100644 --- a/frontend/src/services/share.service.ts +++ b/frontend/src/services/share.service.ts @@ -72,7 +72,7 @@ const uploadFile = async ( file: File, progressCallBack: (uploadingProgress: number) => void ) => { - var formData = new FormData(); + let formData = new FormData(); formData.append("file", file); return ( diff --git a/frontend/src/utils/toast.util.tsx b/frontend/src/utils/toast.util.tsx index 28c293a6..3e1f56e5 100644 --- a/frontend/src/utils/toast.util.tsx +++ b/frontend/src/utils/toast.util.tsx @@ -1,19 +1,18 @@ import { showNotification } from "@mantine/notifications"; -import { Check, X } from "tabler-icons-react"; - +import { TbCheck, TbX } from "react-icons/tb"; const error = (message: string) => showNotification({ - icon: , + icon: , color: "red", radius: "md", title: "Error", - + message: message, }); const success = (message: string) => showNotification({ - icon: , + icon: , color: "green", radius: "md", title: "Success",