diff --git a/package.json b/package.json index 729e7e8..a4b7199 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "jsonwebtoken": "^9.0.0", "mime-types": "^2.1.35", "next": "13.3.0", + "next-seo": "^6.0.0", "nextjs-progressbar": "^0.0.16", "postcss": "8.4.22", "react": "18.2.0", diff --git a/src/components/Breadcrumb/index.tsx b/src/components/Breadcrumb/index.tsx index 1a00be3..7d67b8a 100644 --- a/src/components/Breadcrumb/index.tsx +++ b/src/components/Breadcrumb/index.tsx @@ -2,7 +2,6 @@ import { TFileParent } from "@/types/googleapis"; import Link from "next/link"; import { Fragment, useEffect, useState } from "react"; import { MdHome } from "react-icons/md"; -import useLocalStorage from "@hooks/useLocalStorage"; import ReactLoading from "react-loading"; import config from "@config/site.config"; @@ -21,6 +20,10 @@ export default function Breadcrumb({ data, isLoading }: Props) { useEffect(() => { // setIsLoading(true); if (data.length > 0) { + const findRoot = data.find((item) => item.id === config.files.rootFolder); + if (findRoot) { + data = data.filter((item) => item.id !== config.files.rootFolder); + } setLimitedPath(data.slice(0, limitItem).reverse()); setSlicedPath(data.slice(limitItem)[0]); setIsLimited(data.length > limitItem); @@ -30,51 +33,52 @@ export default function Breadcrumb({ data, isLoading }: Props) { return (
- - - Root - - {isLoading && ( + {isLoading ? ( - )} - {isLimited && !isLoading && ( - - / - ... - - )} - {!isLoading && ( + ) : ( <> - {limitedPath.map((parent, idx) => ( - - / - - {idx === limitedPath.length - 1 ? ( - - {parent.name} - - ) : ( - - {parent.name} - - )} + + + Root + + {isLimited && ( + + / + ... - ))} + )} + <> + {limitedPath.map((parent, idx) => ( + + / + + {idx === limitedPath.length - 1 ? ( + + {parent.name} + + ) : ( + + {parent.name} + + )} + + ))} + )}
diff --git a/src/components/FilePreview/ImagePreview/index.tsx b/src/components/FilePreview/ImagePreview/index.tsx index e296120..631215c 100644 --- a/src/components/FilePreview/ImagePreview/index.tsx +++ b/src/components/FilePreview/ImagePreview/index.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import LoadingFeedback from "@components/APIFeedback/Loading"; import ErrorFeedback from "@components/APIFeedback/Error"; import config from "@config/site.config"; +import { reverseString } from "@utils/hashHelper"; type Props = { data: TFile | drive_v3.Schema$File; @@ -22,7 +23,9 @@ export default function ImagePreview({ data, hash }: Props) { try { let loaded = false; const _image = new Image(); - _image.src = `/api/files/${data.id}/view${hash ? `?hash=${hash}` : ""}`; + _image.src = `/api/files/${data.id}/view${ + hash ? `?hash=${reverseString(hash)}` : "" + }`; _image.onload = () => { setImage(_image.src); setIsImageLoaded(true); diff --git a/src/components/layout/FileDetails/DetailsButtons.tsx b/src/components/layout/FileDetails/DetailsButtons.tsx index 09ccc15..40e0477 100644 --- a/src/components/layout/FileDetails/DetailsButtons.tsx +++ b/src/components/layout/FileDetails/DetailsButtons.tsx @@ -3,43 +3,55 @@ import { drive_v3 } from "googleapis"; import { MdCopyAll, MdDownload, MdOpenInBrowser } from "react-icons/md"; import Link from "next/link"; import { toast } from "react-toastify"; +import config from "@config/site.config"; +import { reverseString } from "@utils/hashHelper"; type Props = { data: TFile | drive_v3.Schema$File; + hash?: string; }; -export default function DetailsButtons({ data }: Props) { +export default function DetailsButtons({ data, hash }: Props) { return (
- - Download file + - - Open in new tab + - { e.preventDefault(); if (!window.navigator) { @@ -48,14 +60,28 @@ export default function DetailsButtons({ data }: Props) { } const host = window.location.host; - const url = `${host}/api/files/${data.id}/view`; + const url = `${host}/api/files/${data.id}/view${ + hash ? `?hash=${reverseString(hash)}` : "" + }`; await window.navigator.clipboard.writeText(url); toast.success("Copied to clipboard"); }} > Copy direct link - + + {!config.files.allowDownloadProtectedWithoutAccess && ( +
+
+
+ Copying the direct link will also copy the access token. +
+

+ Only share the direct link with people you trust. +

+
+
+ )}
); } diff --git a/src/components/layout/FileDetails/index.tsx b/src/components/layout/FileDetails/index.tsx index 222d878..3dcd48a 100644 --- a/src/components/layout/FileDetails/index.tsx +++ b/src/components/layout/FileDetails/index.tsx @@ -123,7 +123,10 @@ export default function FileDetails({ data, hash }: Props) {
- +
diff --git a/src/components/layout/Navbar/index.tsx b/src/components/layout/Navbar/index.tsx index ad15c11..2b2d5bf 100644 --- a/src/components/layout/Navbar/index.tsx +++ b/src/components/layout/Navbar/index.tsx @@ -107,7 +107,11 @@ export default function Navbar() {
{/* Search */} -
+
setIsSearching(true)} @@ -117,8 +121,10 @@ export default function Navbar() {
{/* Dark mode */}
void; }; -export default function Password({ folderId }: Props) { +export default function Password({ folderId, inputCallback }: Props) { const router = useRouter(); + const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [passwordStorage, setPasswordStorage] = useLocalStorage<{ @@ -17,12 +20,16 @@ export default function Password({ folderId }: Props) { }>("passwordStorage", {}); const handleSubmit = () => { - setPasswordStorage({ + inputCallback({ ...passwordStorage, [folderId]: hashToken(password), }); - - router.reload(); + // setPasswordStorage({ + // ...passwordStorage, + // [folderId]: hashToken(password), + // }); + // + // callback(); }; return ( @@ -89,7 +96,16 @@ export default function Password({ folderId }: Props) { className={"primary w-full whitespace-nowrap tablet:w-fit"} onClick={handleSubmit} > + {/*{isLoading ? (*/} + {/* */} + {/*) : (*/} Submit + {/*)}*/}
diff --git a/src/config/site.config.js b/src/config/site.config.js index ad32f7f..4d9fd25 100644 --- a/src/config/site.config.js +++ b/src/config/site.config.js @@ -52,7 +52,7 @@ const config = { // If this set to true, any user can download or view protected files. // If this set to false, only authorized users can download or view protected files. // The authorized users URL will have a token in it that valid for 1 hour. - allowDownloadProtectedFiles: false, // If this set to true, any user can download protected files, but can't see the details of the file or folder. + allowDownloadProtectedWithoutAccess: false, // If this set to true, any user can download protected files, but can't see the details of the file or folder. }, /* Config for readme file render */ readme: { diff --git a/src/pages/api/files/[id]/download.ts b/src/pages/api/files/[id]/download.ts index 87df4dc..546878e 100644 --- a/src/pages/api/files/[id]/download.ts +++ b/src/pages/api/files/[id]/download.ts @@ -4,6 +4,7 @@ import { NextApiRequest, NextApiResponse } from "next"; import config from "@config/site.config"; import { validateProtected } from "@utils/driveHelper"; import { ExtendedError } from "@/types/default"; +import { reverseString } from "@utils/hashHelper"; export default async function handler( request: NextApiRequest, @@ -19,9 +20,13 @@ export default async function handler( fields: "id, name, mimeType, size, exportLinks, parents", }); - if (!config.files.allowDownloadProtectedFiles) { + if (!config.files.allowDownloadProtectedWithoutAccess) { const parentsArray: TFileParent[] = []; + let validHash = headerHash as string; + if (hash) { + validHash = reverseString(hash as string); + } // Fetch parents if ( fetchFileMetadata.data.mimeType === "application/vnd.google-apps.folder" @@ -54,10 +59,7 @@ export default async function handler( } // Check for password file - const validatePassword = await validateProtected( - parentsArray, - (headerHash as string) || (hash as string), - ); + const validatePassword = await validateProtected(parentsArray, validHash); if (validatePassword.isProtected && !validatePassword.valid) { return response.status(200).json({ success: true, diff --git a/src/pages/api/files/[id]/view.ts b/src/pages/api/files/[id]/view.ts index bce72a7..7114d80 100644 --- a/src/pages/api/files/[id]/view.ts +++ b/src/pages/api/files/[id]/view.ts @@ -4,6 +4,9 @@ import { NextApiRequest, NextApiResponse } from "next"; import config from "@config/site.config"; import { validateProtected } from "@utils/driveHelper"; import { ExtendedError } from "@/types/default"; +import { decrypt } from "@utils/encryptionHelper"; +import { verify } from "jsonwebtoken"; +import { reverseString } from "@utils/hashHelper"; export default async function handler( request: NextApiRequest, @@ -11,6 +14,7 @@ export default async function handler( ) { try { const { id, hash } = request.query; + const { vector, data } = request.query; const { authorization } = request.headers; const headerHash = authorization?.split(" ")[1] || null; @@ -19,9 +23,14 @@ export default async function handler( fields: "id, name, mimeType, size, exportLinks, parents", }); - if (!config.files.allowDownloadProtectedFiles) { + if (!config.files.allowDownloadProtectedWithoutAccess) { const parentsArray: TFileParent[] = []; + let validHash = headerHash as string; + if (hash) { + validHash = reverseString(hash as string); + } + // Fetch parents if ( fetchFileMetadata.data.mimeType === "application/vnd.google-apps.folder" @@ -54,10 +63,7 @@ export default async function handler( } // Check for password file - const validatePassword = await validateProtected( - parentsArray, - (headerHash as string) || (hash as string), - ); + const validatePassword = await validateProtected(parentsArray, validHash); if (validatePassword.isProtected && !validatePassword.valid) { return response.status(200).json({ success: true, diff --git a/src/pages/file/[id].tsx b/src/pages/file/[id].tsx index 3fa172c..d484452 100644 --- a/src/pages/file/[id].tsx +++ b/src/pages/file/[id].tsx @@ -1,13 +1,7 @@ import useSWR from "swr"; -import fetcher from "@utils/swrFetch"; -import { - ErrorResponse, - FileResponse, - FilesResponse, - TFileParent, -} from "@/types/googleapis"; +import { ErrorResponse, FileResponse, TFileParent } from "@/types/googleapis"; import Breadcrumb from "@/components/Breadcrumb"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import LoadingFeedback from "@components/APIFeedback/Loading"; import ErrorFeedback from "@components/APIFeedback/Error"; import { useRouter } from "next/router"; @@ -15,6 +9,7 @@ import FileDetails from "@components/layout/FileDetails"; import useLocalStorage from "@hooks/useLocalStorage"; import axios from "axios"; import { GetServerSidePropsContext } from "next"; +import Password from "@components/layout/Password"; type Props = { passwordParent?: string; @@ -24,33 +19,51 @@ export default function File({ passwordParent }: Props) { const { id } = router.query; const [data, setData] = useState(); - const [dataLoading, setDataLoading] = useState(true); + const [globalLoading, setGlobalLoading] = useState(true); - const [passwordStorage] = useLocalStorage<{ + const [passwordStorage, setPasswordStorage] = useLocalStorage<{ [key: string]: string; }>("passwordStorage", {}); + const [password, setPassword] = useState<{ [p: string]: string }>( + passwordStorage, + ); const { data: swrData, error, isLoading, - } = useSWR(`/api/files/${id}`, (url, headers) => - axios - .get(url, { - headers: { - Authorization: `Bearer ${ - passwordStorage?.[passwordParent as string] || - passwordStorage?.[id as string] || - "" - }`, - ...headers, - }, - }) - .then((res) => res.data), + isValidating, + mutate, + } = useSWR( + `/api/files/${id}`, + (url, headers) => + axios + .get(url, { + headers: { + Authorization: `Bearer ${ + password?.[passwordParent as string] || + password?.[id as string] || + passwordStorage?.[passwordParent as string] || + passwordStorage?.[id as string] || + "" + }`, + ...headers, + }, + }) + .then((res) => res.data), + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + refreshWhenHidden: false, + refreshInterval: 0, + shouldRetryOnError: false, + revalidateIfStale: true, + }, ); useEffect(() => { - setDataLoading(true); + setGlobalLoading(true); if (swrData) { const parentsArray: TFileParent[] | undefined = swrData.parents; parentsArray?.unshift({ @@ -62,28 +75,67 @@ export default function File({ passwordParent }: Props) { ...swrData, }; setData(payload); - setDataLoading(false); + setGlobalLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [swrData, error, isLoading]); + }, [swrData, error, isLoading, isValidating, password]); + + useEffect(() => { + if (!isLoading && !isValidating) { + setGlobalLoading(false); + } else { + setGlobalLoading(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading, isValidating]); + + useEffect(() => { + mutate(swrData, { + revalidate: true, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [password]); + + const inputPassCallback = useCallback( + (data: { [p: string]: string }) => { + setGlobalLoading(true); + setPasswordStorage(data); + setPassword(data); + }, + [setPasswordStorage], + ); return (
-
- -
+ {globalLoading && } + {!globalLoading && error && ( + + )} + {!globalLoading && !error && data && ( + <> + {data.passwordRequired && !data.passwordValidated && ( + + )} + {(data.passwordValidated || !data.passwordRequired) && ( + <> +
+ +
- {isLoading && } - {!isLoading && error && } - {!isLoading && !error && data && ( - + + + )} + )}
); @@ -95,6 +147,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { `http://localhost:5000/api/files/${id}`, ); + context.res.setHeader( + "Cache-Control", + "public, s-maxage=10, stale-while-revalidate=59", + ); + if (passwordParent) { return { props: { diff --git a/src/pages/folder/[id].tsx b/src/pages/folder/[id].tsx index 2bc52aa..3345737 100644 --- a/src/pages/folder/[id].tsx +++ b/src/pages/folder/[id].tsx @@ -1,10 +1,16 @@ -import useSWR from "swr"; +import useSWR, { mutate } from "swr"; import useSWRInfinite from "swr/infinite"; import fetcher, { buildNextKey } from "@utils/swrFetch"; import { ErrorResponse, FilesResponse, TFile } from "@/types/googleapis"; import Breadcrumb from "@/components/Breadcrumb"; import { drive_v3 } from "googleapis"; -import { useEffect, useState } from "react"; +import { + Dispatch, + SetStateAction, + useCallback, + useEffect, + useState, +} from "react"; import MarkdownRender from "@/components/utility/MarkdownRender"; import config from "@config/site.config"; import GridLayout from "@components/layout/Files/GridLayout"; @@ -26,15 +32,19 @@ export default function Folder({ passwordParent }: Props) { const { id } = router.query; const [data, setData] = useState(); - const [dataLoading, setDataLoading] = useState(true); const [isReadmeExists, setIsReadmeExists] = useState(false); const [renderStyle] = useLocalStorage<"grid" | "list">("renderStyle", "grid"); const [layoutStyle, setLayoutStyle] = useState<"grid" | "list">(renderStyle); + const [globalLoading, setGlobalLoading] = useState(true); - const [passwordStorage] = useLocalStorage<{ + const [passwordStorage, setPasswordStorage] = useLocalStorage<{ [key: string]: string; }>("passwordStorage", {}); + const [password, setPassword] = useState<{ [p: string]: string }>( + passwordStorage, + ); + const getNextKey = buildNextKey(`/api/files/${id}`); const { data: swrData, @@ -42,25 +52,47 @@ export default function Folder({ passwordParent }: Props) { isLoading, size, setSize, - } = useSWRInfinite(getNextKey, (url, headers) => - axios - .get(url, { - headers: { - Authorization: `Bearer ${ - passwordStorage?.[passwordParent as string] || - passwordStorage?.[id as string] || - "" - }`, - ...headers, - }, - }) - .then((res) => res.data), + isValidating, + mutate, + } = useSWRInfinite( + getNextKey, + (url, headers) => + axios + .get(url, { + headers: { + Authorization: `Bearer ${ + password?.[passwordParent as string] || + password?.[id as string] || + passwordStorage?.[passwordParent as string] || + passwordStorage?.[id as string] || + "" + }`, + ...headers, + }, + }) + .then((res) => res.data), + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + refreshWhenHidden: false, + refreshInterval: 0, + shouldRetryOnError: false, + revalidateIfStale: true, + }, ); const { data: readmeData, error: readmeError, isLoading: readmeLoading, - } = useSWR(`/api/readme/${id}`, fetcher); + } = useSWR(`/api/readme/${id}`, fetcher, { + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + refreshWhenHidden: false, + refreshInterval: 0, + shouldRetryOnError: false, + }); const isLoadingInitialData = !swrData && !error; const isLoadingMore = @@ -74,7 +106,7 @@ export default function Folder({ passwordParent }: Props) { typeof swrData[swrData.length - 1]?.nextPageToken === "undefined"); useEffect(() => { - setDataLoading(true); + setGlobalLoading(true); const files: (TFile | drive_v3.Schema$File)[] | undefined = swrData?.flatMap((item: FilesResponse) => item.files); const folders: (TFile | drive_v3.Schema$File)[] | undefined = @@ -85,28 +117,58 @@ export default function Folder({ passwordParent }: Props) { folders: folders || [], }; setData(newData); - setDataLoading(false); if (newData.readmeExists) setIsReadmeExists(true); + setGlobalLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [swrData, error, isLoading, size]); + }, [swrData, error, isLoading, size, isValidating, password]); + + useEffect(() => { + if (!isLoading && !isValidating) { + setGlobalLoading(false); + } else { + setGlobalLoading(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading, isValidating]); + + useEffect(() => { + mutate(swrData, { + revalidate: true, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [password]); + + const inputPassCallback = useCallback( + (data: { [p: string]: string }) => { + setGlobalLoading(true); + setPasswordStorage(data); + setPassword(data); + }, + [setPasswordStorage], + ); return (
- {isLoading && } - {!isLoading && error && } - {!isLoading && !error && data && ( + {globalLoading && } + {!globalLoading && error && ( + + )} + {!globalLoading && !error && data && ( <> {data.passwordRequired && !data.passwordValidated && ( - + )} {(data.passwordValidated || !data.passwordRequired) && ( <> @@ -178,6 +240,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { `http://localhost:5000/api/files/${id}`, ); + context.res.setHeader( + "Cache-Control", + "public, s-maxage=10, stale-while-revalidate=59", + ); + if (passwordParent) { return { props: { diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 7dc6c11..db4cb3b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,7 +4,7 @@ import fetcher, { buildNextKey } from "@utils/swrFetch"; import { ErrorResponse, FilesResponse, TFile } from "@/types/googleapis"; import Breadcrumb from "@/components/Breadcrumb"; import { drive_v3 } from "googleapis"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import MarkdownRender from "@/components/utility/MarkdownRender"; import config from "@config/site.config"; import GridLayout from "@components/layout/Files/GridLayout"; @@ -13,20 +13,22 @@ import SwitchLayout from "@components/utility/SwitchLayout"; import ListLayout from "@components/layout/Files/ListLayout"; import LoadingFeedback from "@components/APIFeedback/Loading"; import ErrorFeedback from "@components/APIFeedback/Error"; -import { hashToken } from "@utils/hashHelper"; import axios from "axios"; import Password from "@components/layout/Password"; export default function Home() { const [data, setData] = useState(); - const [dataLoading, setDataLoading] = useState(true); const [isReadmeExists, setIsReadmeExists] = useState(false); const [renderStyle] = useLocalStorage<"grid" | "list">("renderStyle", "grid"); const [layoutStyle, setLayoutStyle] = useState<"grid" | "list">(renderStyle); + const [globalLoading, setGlobalLoading] = useState(true); const [passwordStorage, setPasswordStorage] = useLocalStorage<{ [key: string]: string; }>("passwordStorage", {}); + const [password, setPassword] = useState<{ [p: string]: string }>( + passwordStorage, + ); const getNextKey = buildNextKey("/api/files/"); const { @@ -35,24 +37,41 @@ export default function Home() { isLoading, size, setSize, + isValidating, mutate, - } = useSWRInfinite(getNextKey, (url, headers) => - axios - .get(url, { - headers: { - Authorization: `Bearer ${ - passwordStorage?.[config.files.rootFolder] || "" - }`, - ...headers, - }, - }) - .then((res) => res.data), + } = useSWRInfinite( + getNextKey, + (url, headers) => + axios + .get(url, { + headers: { + Authorization: `Bearer ${ + passwordStorage?.[config.files.rootFolder] || "" + }`, + ...headers, + }, + }) + .then((res) => res.data), + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + refreshWhenHidden: false, + refreshInterval: 0, + shouldRetryOnError: false, + revalidateIfStale: true, + }, ); const { data: readmeData, error: readmeError, isLoading: readmeLoading, } = useSWR("/api/readme/", fetcher, { + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + refreshWhenHidden: false, + refreshInterval: 0, shouldRetryOnError: false, }); @@ -68,7 +87,7 @@ export default function Home() { typeof swrData[swrData.length - 1]?.nextPageToken === "undefined"); useEffect(() => { - setDataLoading(true); + setGlobalLoading(true); const files: (TFile | drive_v3.Schema$File)[] | undefined = swrData?.flatMap((item: FilesResponse) => item.files); const folders: (TFile | drive_v3.Schema$File)[] | undefined = @@ -79,31 +98,61 @@ export default function Home() { folders: folders || [], }; setData(newData); - setDataLoading(false); if (newData.readmeExists) setIsReadmeExists(true); + setGlobalLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [swrData, error, isLoading, size]); + }, [swrData, error, isLoading, size, isValidating, password]); + + useEffect(() => { + if (!isLoading && !isValidating) { + setGlobalLoading(false); + } else { + setGlobalLoading(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading, isValidating]); + + useEffect(() => { + mutate(swrData, { + revalidate: true, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [password]); + + const inputPassCallback = useCallback( + (data: { [p: string]: string }) => { + setGlobalLoading(true); + setPasswordStorage(data); + setPassword(data); + }, + [setPasswordStorage], + ); return (
- {isLoading && } - {!isLoading && error && ( + {globalLoading && } + {!globalLoading && error && ( )} - {!isLoading && !error && data && ( + {!globalLoading && !error && data && ( <> + {/* If the root folder have password and the password isn't validated, show password input */} {data.passwordRequired && !data.passwordValidated && ( - + )} + {/* If password is validated or the root folder doesn't require password, show the files */} {(data.passwordValidated || !data.passwordRequired) && ( <> {isReadmeExists && config.readme.position === "start" && ( diff --git a/src/styles/globals.css b/src/styles/globals.css index 65d4210..7d8e520 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -106,21 +106,21 @@ html, body { @apply border-blue-400 hover:border-blue-500 active:border-blue-600; @apply bg-blue-500 hover:bg-blue-700 active:bg-blue-400; @apply dark:border-blue-500 dark:hover:border-blue-600 dark:active:border-blue-700; - @apply dark:bg-blue-500 dark:hover:bg-blue-600 dark:active:bg-blue-600; + @apply dark:bg-blue-500 dark:hover:bg-blue-600 dark:active:bg-blue-700; } button.secondary{ @apply text-zinc-100 dark:text-zinc-100; @apply border-zinc-400 hover:border-zinc-500 active:border-zinc-600; - @apply bg-zinc-500 hover:bg-zinc-600 active:bg-zinc-400; + @apply bg-zinc-500 hover:bg-zinc-700 active:bg-zinc-400; @apply dark:border-zinc-500 dark:hover:border-zinc-600 dark:active:border-zinc-700; - @apply dark:bg-zinc-500 dark:hover:bg-zinc-400 dark:active:bg-zinc-600; + @apply dark:bg-zinc-500 dark:hover:bg-zinc-600 dark:active:bg-zinc-700; } button.danger { @apply text-zinc-100 dark:text-zinc-100; @apply border-red-400 hover:border-red-500 active:border-red-600; @apply bg-red-500 hover:bg-red-700 active:bg-red-400; @apply dark:border-red-500 dark:hover:border-red-600 dark:active:border-red-700; - @apply dark:bg-red-500 dark:hover:bg-red-600 dark:active:bg-red-600; + @apply dark:bg-red-500 dark:hover:bg-red-600 dark:active:bg-red-700; } table { @@ -150,7 +150,7 @@ html, body { } div.banner { - @apply rounded-lg p-2 tablet:p-4 my-2 tablet:my-4; + @apply rounded-lg p-2 tablet:p-4 my-2; @apply border border-zinc-400 dark:border-zinc-700; @apply drop-shadow; @apply bg-zinc-100 dark:bg-zinc-900; diff --git a/src/utils/driveHelper.ts b/src/utils/driveHelper.ts index a31bcc0..07ba168 100644 --- a/src/utils/driveHelper.ts +++ b/src/utils/driveHelper.ts @@ -73,7 +73,7 @@ export async function _validateFolderPassword( export async function validateProtected( fileId: string | TFileParent[], - passwordHash: string, + passwordHash?: string, ): Promise<{ isProtected: boolean; valid?: boolean; protectedId?: string }> { const fetchPassword = await drive.files.list({ q: `name = '.password' and 'me' in owners and trashed = false`, diff --git a/src/utils/hashHelper.ts b/src/utils/hashHelper.ts index 0e804a9..06495fc 100644 --- a/src/utils/hashHelper.ts +++ b/src/utils/hashHelper.ts @@ -7,3 +7,9 @@ export function hashToken(text: string): string { export function verifyHash(text: string, hash: string): boolean { return hashToken(text) === hash; } + +// It's not a good idea to use this. +// Change this to jwt or something else. +export function reverseString(str: string): string { + return str.split("").reverse().join(""); +} diff --git a/yarn.lock b/yarn.lock index 8fc0770..ae72404 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3239,6 +3239,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +next-seo@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-6.0.0.tgz#4568dc61a44dbdf5fe5ff44156cd0ff8804889a2" + integrity sha512-jKKt1p1z4otMA28AyeoAONixVjdYmgFCWwpEFtu+DwRHQDllVX3RjtyXbuCQiUZEfQ9rFPBpAI90vDeLZlMBdg== + next@13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/next/-/next-13.3.0.tgz#40632d303d74fc8521faa0a5bf4a033a392749b1"