Remove Pages

This commit is contained in:
mbaharip
2024-04-06 22:10:14 +07:00
parent 4c0ba8288a
commit 1bd5f75f48
6 changed files with 1 additions and 793 deletions
+1 -1
View File
@@ -38,6 +38,6 @@ next-env.d.ts
/data
/.*/
/src/_app
/src/_pages
/src/_page
/src/utils/_legacy
/src/components/_legacy
-42
View File
@@ -1,42 +0,0 @@
import Button from "components/Button";
import LoaderLayout from "components/Layout/Loader";
import Link from "next/link";
export default function NotFoundPage() {
return (
<LoaderLayout
seo={{
title: "Not Found",
}}
>
<div className='relative flex h-full w-full flex-col items-center justify-center gap-4'>
<figure>
<img
src='/images/404.png'
alt='Page not found'
className='h-64 w-64 object-contain tablet:h-96 tablet:w-96'
/>
</figure>
<div className='my-8 flex w-full flex-col items-center justify-center'>
<span className='text-lg font-medium'>
The page you are trying to access is not found.
</span>
<span>Please check the URL or go back to the homepage.</span>
<div className='my-4 flex flex-col items-center justify-between gap-2 tablet:my-12 tablet:flex-row tablet:gap-4'>
<Link href='/'>
<Button
variant='accent'
rounded='large'
className='w-full tablet:w-auto'
>
Go back to homepage
</Button>
</Link>
</div>
</div>
</div>
</LoaderLayout>
);
}
-303
View File
@@ -1,303 +0,0 @@
import axios from "axios";
import ExplorerLayout from "components/Layout/Explorer";
import LoaderLayout from "components/Layout/Loader";
import PasswordLayout from "components/Layout/Password";
import PreviewLayout from "components/Layout/Preview";
import gIndexConfig from "config";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { IGDriveFiles } from "types/api/files";
import {
APIGetFileResponse,
APIGetPasswordResponse,
APIGetReadmeResponse,
} from "types/api/response";
import { Constant } from "types/constant";
import { decryptData, encryptData } from "utils/encryptionHelper/hash";
import { gdriveFilesList } from "utils/gdrive";
import gdrive from "utils/gdriveInstance";
import { addNewPassword, checkPathPassword } from "utils/passwordHelper";
interface FilePathPageProps {
mappedEncryptedPath: Record<"name" | "id" | "mimeType", string>[];
}
interface StateDataProps {
file: IGDriveFiles | null;
files: IGDriveFiles[];
folders: IGDriveFiles[];
pageToken: string | null;
}
export default function FilePathPage(props: FilePathPageProps) {
const router = useRouter();
const [data, setData] = useState<StateDataProps>({
file: null,
files: [],
folders: [],
pageToken: null,
});
const [readmeFile, setReadmeFile] = useState<string | null>(null);
const [isProtected, setIsProtected] = useState<boolean>(false);
const [nearestProtected, setNearestProtected] = useState<string>("");
const [isFileProtected, setIsFileProtected] = useState<boolean>(false);
const [isFile, setIsFile] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(true);
useEffect(() => {
setIsLoadingData(true);
const lastPathMimeType =
props.mappedEncryptedPath[(props.mappedEncryptedPath.length ?? 1) - 1]
.mimeType;
let isLastPathFile = false;
if (lastPathMimeType === "application/vnd.google-apps.folder") {
isLastPathFile = false;
setIsFile(false);
} else {
isLastPathFile = true;
setIsFile(true);
}
const _getPassword = axios.get<APIGetPasswordResponse>("/api/getPassword", {
params: {
path: encryptData(JSON.stringify(props.mappedEncryptedPath)),
},
});
const _getData = axios.get<APIGetFileResponse>("/api/getData", {
params: {
encryptedId:
props.mappedEncryptedPath[props.mappedEncryptedPath.length - 1].id,
isFile: isLastPathFile ? "1" : undefined,
},
});
const _getReadme = axios.get<APIGetReadmeResponse>("/api/getReadme", {
params: {
encryptedId:
props.mappedEncryptedPath[props.mappedEncryptedPath.length - 1].id,
},
});
Promise.all([_getPassword, _getData, _getReadme])
.then(async ([passwordData, fileData, readmeData]) => {
// Check password
if (passwordData.data.data && passwordData.data.data.length) {
let isPathProtected = true;
const savedPasswordCookie =
document.cookie
.split(";")
.find((cookie) =>
cookie.startsWith(`${Constant.cookies_SitePassword}=`),
) ?? undefined;
const savedPasswordValue =
savedPasswordCookie?.split("=")[1] ?? undefined;
if (savedPasswordValue) {
// Check only nearest path
const nearestPassword =
passwordData.data.data[passwordData.data.data.length - 1];
setNearestProtected(nearestPassword.relativePath);
const isPasswordValid = checkPathPassword(
nearestPassword.relativePath,
savedPasswordValue,
decryptData(nearestPassword.password),
);
if (isPasswordValid) {
isPathProtected = false;
}
}
setIsProtected(isPathProtected);
setIsFileProtected(true);
}
// Assign files
setData(fileData.data.data);
// Fetch readme
setReadmeFile(readmeData.data.data);
})
.catch((err) => {
console.error(err);
throw new Error(err);
})
.finally(() => {
setIsLoadingData(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.mappedEncryptedPath]);
return (
<LoaderLayout
seo={{
title:
props.mappedEncryptedPath[(props.mappedEncryptedPath.length ?? 1) - 1]
.name,
openGraph: {
type: "website",
title:
props.mappedEncryptedPath[
(props.mappedEncryptedPath.length ?? 1) - 1
].name,
description: gIndexConfig.siteConfig.siteDescription,
url: process.env.NEXT_PUBLIC_VERCEL_URL,
siteName: gIndexConfig.siteConfig.siteName,
images: [
{
url: `/og.png`,
width: 1200,
height: 630,
},
],
},
}}
>
{isLoadingData ? (
<div className='flex h-full w-full items-center justify-center'>
<div className='flex animate-pulse flex-col items-center justify-center gap-4'>
<div className='relative grid h-fit w-fit place-items-center'>
<img
src={gIndexConfig.siteConfig.siteIcon}
alt={gIndexConfig.siteConfig.siteName}
className='relative -top-1 w-12'
/>
<div className='border-primary-50 absolute h-12 w-12 animate-ping rounded-full border bg-transparent' />
</div>
<span>Fetching {isFile ? "file info" : "folder contents"}...</span>
</div>
</div>
) : (
<>
{isProtected ? (
<PasswordLayout
path={nearestProtected}
onSubmitted={(password) => {
const cookie =
document.cookie
.split(";")
.find((cookie) =>
cookie.startsWith(`${Constant.cookies_SitePassword}=`),
) ?? undefined;
addNewPassword(
decodeURIComponent(nearestProtected),
password,
cookie?.split("=")[1] ?? undefined,
);
router.reload();
}}
/>
) : (
<>
{isFile ? (
<PreviewLayout
key={router.asPath}
data={data}
isProtected={false}
isFileProtected={isFileProtected}
encryptedId={
props.mappedEncryptedPath[
(props.mappedEncryptedPath.length ?? 1) - 1
].id
}
/>
) : (
<ExplorerLayout
key={router.asPath}
data={data}
readmeFile={readmeFile}
isProtected={false}
isFileProtected={isFileProtected}
encryptedId={
props.mappedEncryptedPath[
(props.mappedEncryptedPath.length ?? 1) - 1
].id
}
/>
)}
</>
)}
</>
)}
</LoaderLayout>
);
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { path } = context.params as { path: string[] };
const fetchRootId = gdrive.files.get({
fileId: gIndexConfig.apiConfig.rootFolder,
fields: "id",
supportsAllDrives: gIndexConfig.apiConfig.isTeamDrive,
});
const fetchPathId = path.map(async (path) => {
const query = ["trashed = false", `name = '${path}'`];
const fetchFolderContents = await gdriveFilesList({
q: `${query.join(" and ")}`,
fields: "files(id, name, mimeType, parents)",
});
if (!fetchFolderContents.data.files?.length) {
return {
path,
data: [],
};
}
return {
path,
data: fetchFolderContents.data.files.map((file) => ({
id: file.id,
parents: file.parents?.[0],
mimeType: file.mimeType,
})),
};
});
const [rootId, pathId] = await Promise.all([
fetchRootId,
Promise.all(fetchPathId),
]);
if (pathId.some((path) => !path.data.length)) {
return {
notFound: true,
};
}
const selectedPath: string[] = [];
const mappedPath: Record<"name" | "id" | "mimeType", string>[] = [];
const rejectedPath: string[] = [];
// Check path validity
pathId.forEach((path, index) => {
let checkPath =
index === 0
? path.data.find((file) => file.parents === rootId.data.id)
: path.data.find((file) => file.parents === selectedPath[index - 1]);
if (!checkPath) {
rejectedPath.push(path.path);
return;
}
selectedPath.push(checkPath.id as string);
mappedPath.push({
name: path.path,
id: checkPath.id as string,
mimeType: checkPath.mimeType as string,
});
});
if (rejectedPath.length) {
return {
notFound: true,
};
}
return {
props: {
mappedEncryptedPath: mappedPath.map((path) => ({
name: path.name,
id: encryptData(path.id),
mimeType: path.mimeType,
})),
},
};
}
-352
View File
@@ -1,352 +0,0 @@
import { Icon } from "@iconify/react";
import Button from "components/Button";
import ButtonGroup from "components/ButtonGroup";
import ButtonIcon from "components/ButtonIcon";
import ClickAway from "components/ClickAway";
import Modal from "components/Modal";
import Tooltip from "components/Tooltip";
import gIndexConfig from "config";
import { DefaultSeo } from "next-seo";
import { AppProps } from "next/app";
import { JetBrains_Mono, Kanit, Poppins } from "next/font/google";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import "styles/globals.css";
import { twMerge } from "tailwind-merge";
import twColor from "tailwindcss/colors";
import hexToRgb from "utils/hexToRGB";
import { removeAllPassword } from "utils/passwordHelper";
const kanit = Kanit({
display: "auto",
weight: ["300", "400", "500", "600", "700", "800", "900"],
subsets: ["latin-ext", "latin"],
preload: true,
});
export const poppins = Poppins({
display: "auto",
weight: ["300", "400", "500", "600", "700", "800", "900"],
subsets: ["latin-ext", "latin"],
preload: true,
});
export const jetbrainsMono = JetBrains_Mono({
display: "auto",
weight: ["300", "400", "500", "600", "700", "800"],
subsets: ["latin-ext", "latin"],
style: ["normal", "italic"],
preload: true,
});
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
const [accentColor] = useState<keyof typeof twColor>(
gIndexConfig.siteConfig.defaultAccentColor as keyof typeof twColor,
);
const [showToTopButton, setShowToTopButton] = useState<boolean>(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState<boolean>(false);
const [clearPasswordModalOpen, setClearPasswordModalOpen] =
useState<boolean>(false);
useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 150) {
setShowToTopButton(true);
} else {
setShowToTopButton(false);
}
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const handleClearPassword = () => {
removeAllPassword();
setClearPasswordModalOpen(false);
router.reload();
};
useEffect(() => {
const colorObject = twColor[accentColor];
Object.keys(colorObject).forEach((key) => {
document.documentElement.style.setProperty(
`--accent-${key}`,
hexToRgb(colorObject[key as keyof typeof colorObject]),
);
});
}, [accentColor]);
return (
<div
id='next-gdrive-index'
className={`${kanit.className} flex w-full flex-col tablet:flex-row`}
>
<DefaultSeo
defaultTitle={gIndexConfig.siteConfig.siteName}
titleTemplate={`%s | ${gIndexConfig.siteConfig.siteName}`}
description={gIndexConfig.siteConfig.siteDescription}
canonical={process.env.NEXT_PUBLIC_VERCEL_URL}
openGraph={{
type: "website",
title: gIndexConfig.siteConfig.siteName,
description: gIndexConfig.siteConfig.siteDescription,
url: process.env.NEXT_PUBLIC_VERCEL_URL,
siteName: gIndexConfig.siteConfig.siteName,
images: [
{
url: `/og.png`,
width: 1200,
height: 630,
},
],
}}
twitter={{
cardType: "summary_large_image",
handle: gIndexConfig.siteConfig.twitterHandle,
}}
additionalMetaTags={[
{
name: "twitter:title",
content: gIndexConfig.siteConfig.siteName,
},
{
name: "twitter:description",
content: gIndexConfig.siteConfig.siteDescription,
},
{
name: "twitter:image",
content: `/og.png`,
},
]}
/>
<nav className='bg-primary-950 text-primary-50 fixed top-0 z-50 flex h-12 w-full flex-row items-center justify-between tablet:min-h-screen tablet:w-20 tablet:flex-col'>
<div className='bg-primary-950 relative z-50 flex h-12 w-full flex-row items-center justify-between px-4 tablet:min-h-screen tablet:w-20 tablet:flex-col tablet:py-4'>
<Link
href='/'
onClick={() => setIsMobileMenuOpen(false)}
>
<Tooltip
content={"Home"}
placement={"right"}
>
<img
src={gIndexConfig.siteConfig.siteIcon}
alt={gIndexConfig.siteConfig.siteName}
className='h-8 w-8 tablet:h-10 tablet:w-10'
loading='eager'
/>
</Tooltip>
</Link>
<div className='hidden flex-col gap-4 tablet:flex'>
{gIndexConfig.siteConfig.navbarItems.map((link) => (
<Link
href={link.href}
key={`navbar-${link.name}`}
target={link.external ? "_blank" : undefined}
rel={link.external ? "noopener noreferrer" : undefined}
className={`transition-smooth flex flex-col items-center justify-center opacity-80 hover:opacity-100 ${
router.pathname === link.href
? "cursor-default opacity-100"
: "cursor-pointer"
}`}
>
<Tooltip
content={link.name}
placement='right'
>
<ButtonIcon
icon={link.icon as string}
variant='transparent'
/>
</Tooltip>
</Link>
))}
<div className='bg-primary-500 h-px w-full' />
<Tooltip
content='Clear password'
placement='right'
>
<ButtonIcon
icon={"ion:log-out"}
variant='transparent'
className='transition-smooth opacity-80 hover:opacity-100'
onClick={() => setClearPasswordModalOpen(true)}
/>
</Tooltip>
</div>
<div className='flex h-full items-center tablet:hidden'>
<Button
variant='transparent'
pill
square
onClick={() => setIsMobileMenuOpen((prev) => !prev)}
>
<Icon
icon='ion:menu'
width={24}
height={24}
color='white'
className={`transition-smooth absolute ${
isMobileMenuOpen
? "pointer-events-none rotate-90 opacity-0"
: "pointer-events-auto rotate-0 opacity-100"
}`}
/>
<Icon
icon='ion:close'
width={24}
height={24}
color='white'
className={`transition-smooth absolute ${
isMobileMenuOpen
? "pointer-events-auto rotate-0 opacity-100"
: "pointer-events-none rotate-90 opacity-0"
}`}
/>
</Button>
</div>
</div>
<ClickAway
open={isMobileMenuOpen}
onClickAway={() => setIsMobileMenuOpen(false)}
className={`transition-smooth absolute top-0 z-40 block h-screen w-full tablet:hidden ${
isMobileMenuOpen
? "pointer-events-auto opacity-100"
: "pointer-events-none opacity-0"
}`}
>
<div
className={`transition-smooth ${
isMobileMenuOpen ? "translate-y-0" : "-translate-y-full"
}`}
>
<div className='bg-primary-950 z-40 flex h-fit w-full flex-col gap-4 px-4 py-16 pb-4 drop-shadow-md'>
{gIndexConfig.siteConfig.navbarItems.map((link) => (
<Link
href={link.href}
key={`navbar-${link.name}`}
target={link.external ? "_blank" : undefined}
rel={link.external ? "noopener noreferrer" : undefined}
className={`transition-smooth flex w-full items-center justify-start gap-4 opacity-80 hover:opacity-100 ${
router.pathname === link.href
? "cursor-default opacity-100"
: "cursor-pointer"
}`}
onClick={(e) => {
// If href === current page, prevent default
if (router.pathname === link.href) {
e.preventDefault();
}
e.stopPropagation();
setIsMobileMenuOpen(false);
}}
>
<Icon
icon={link.icon as string}
width={24}
height={24}
color='white'
/>
<span>{link.name}</span>
</Link>
))}
<Button
variant='danger'
fullWidth
onClick={() => setClearPasswordModalOpen(true)}
rounded='large'
startIcon={"ion:log-out"}
className='mt-4'
>
Clear password
</Button>
</div>
</div>
</ClickAway>
</nav>
<main
className={`bg-primary-950 text-primary-50 flex min-h-screen w-full flex-col pl-0 pt-12 tablet:pl-20 tablet:pt-0`}
>
<div className='flex h-full w-full flex-grow'>
<Component {...pageProps} />
</div>
<footer className='flex h-8 w-full items-center justify-center py-8 text-center text-sm'>
<span>
Powered by{" "}
<Link
href={"https://www.github.com/mbaharip/next-gdrive-index"}
target='_blank'
rel='noopener noreferrer'
className='hover:decoration-accent-400 text-accent-400 transition-smooth underline decoration-transparent decoration-wavy underline-offset-8 hover:underline-offset-1'
>
next-gdrive-index
</Link>
</span>
</footer>
</main>
<ToastContainer
autoClose={1500}
limit={3}
position='bottom-center'
theme='dark'
/>
{/* To the top */}
<ButtonIcon
icon={"ion:chevron-up"}
variant='accent'
size='large'
className={twMerge(
"fixed right-4 z-50",
showToTopButton
? "pointer-events-auto bottom-4 opacity-100"
: "pointer-events-none bottom-0 opacity-0",
)}
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
/>
{/* Clear password */}
<Modal
key={`clear-password-modal`}
open={clearPasswordModalOpen}
onClose={() => setClearPasswordModalOpen(false)}
title='Clear password'
>
<div className='flex flex-col gap-4'>
<div className='flex flex-col'>
<span className='text-lg'>
Are you sure you want to clear your password?
</span>
<span className='text-primary-400 text-sm'>
You will need to re-enter your password to access protected
folders / files.
</span>
</div>
<ButtonGroup>
<Button
variant='primary'
startIcon={"ion:close"}
onClick={() => setClearPasswordModalOpen(false)}
>
Cancel
</Button>
<Button
variant='danger'
startIcon={"ion:log-out"}
onClick={handleClearPassword}
>
Clear password
</Button>
</ButtonGroup>
</div>
</Modal>
</div>
);
}
-19
View File
@@ -1,19 +0,0 @@
import gIndexConfig from "config";
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang='en'>
<Head>
<link
rel='icon'
href={gIndexConfig.siteConfig.favIcon}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
-76
View File
@@ -1,76 +0,0 @@
import axios from "axios";
import ExplorerLayout from "components/Layout/Explorer";
import LoaderLayout from "components/Layout/Loader";
import gIndexConfig from "config";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { IGDriveFiles } from "types/api/files";
import { APIGetFileResponse, APIGetReadmeResponse } from "types/api/response";
interface StateDataProps {
file: IGDriveFiles | null;
files: IGDriveFiles[];
folders: IGDriveFiles[];
pageToken: string | null;
}
export default function RootPage() {
const router = useRouter();
const [data, setData] = useState<StateDataProps>({
file: null,
files: [],
folders: [],
pageToken: null,
});
const [readmeFile, setReadmeFile] = useState<string | null>(null);
const [isLoadingData, setIsLoadingData] = useState<boolean>(true);
useEffect(() => {
setIsLoadingData(true);
const _getData = axios.get<APIGetFileResponse>("/api/getData");
const _getReadme = axios.get<APIGetReadmeResponse>("/api/getReadme");
Promise.all([_getData, _getReadme])
.then(([fileData, readmeData]) => {
// Assign files
setData(fileData.data.data);
// Fetch readme
setReadmeFile(readmeData.data.data);
})
.catch((err) => {
console.error(err);
throw new Error(err);
})
.finally(() => {
setIsLoadingData(false);
});
}, []);
return (
<LoaderLayout>
{isLoadingData ? (
<div className='flex h-full w-full items-center justify-center'>
<div className='flex animate-pulse flex-col items-center justify-center gap-4'>
<div className='relative grid h-fit w-fit place-items-center'>
<img
src={gIndexConfig.siteConfig.siteIcon}
alt={gIndexConfig.siteConfig.siteName}
className='relative -top-1 w-12'
/>
<div className='border-primary-50 absolute h-12 w-12 animate-ping rounded-full border bg-transparent' />
</div>
<span>Fetching folder contents...</span>
</div>
</div>
) : (
<ExplorerLayout
key={router.asPath}
data={data}
readmeFile={readmeFile}
// If you want the root to be protected, use Private index instead
isProtected={false}
/>
)}
</LoaderLayout>
);
}