Add setup page

This commit is contained in:
mbaharip
2023-04-24 14:33:39 +07:00
parent 475e5f985e
commit b3efeda91c
17 changed files with 416 additions and 8 deletions
+112
View File
@@ -0,0 +1,112 @@
import { GetServerSideProps } from "next";
import Link from "next/link";
import useSWR from "swr";
import fetcher from "@utils/swrFetch";
import LoadingFeedback from "@components/APIFeedback/Loading";
import MarkdownRender from "@components/utility/MarkdownRender";
import useLocalStorage from "@hooks/useLocalStorage";
import { useEffect, useRef, useState } from "react";
import { MdContentCopy } from "react-icons/md";
import useCopyText from "@hooks/useCopyText";
type ConfigProps = {
client_id: string;
client_secret: string;
refresh_token: string;
};
const defaultConfig: ConfigProps = {
client_id: "",
client_secret: "",
refresh_token: "",
};
export default function SetupFinal() {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [tempKey] = useLocalStorage("tempEncryption", "");
const [tempConfig] = useLocalStorage<ConfigProps>(
"tempGoogleCloud",
defaultConfig,
);
const [dataConfig, setDataConfig] = useState<ConfigProps>(defaultConfig);
const [dataKey, setDataKey] = useState<string>("");
const [mdContent, setMdContent] = useState<string>("");
const dataRef = useRef<HTMLElement>(null);
const copyText = useCopyText();
useEffect(() => {
setIsLoading(true);
if (tempConfig) {
setDataConfig(tempConfig);
}
if (tempKey) {
setDataKey(tempKey);
}
setIsLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setIsLoading(true);
const mdContent = `To finishing the setup, you need to do the following steps:
## API Config
The API Config file are located at \`src/config/api.ts\`. You need to fill in the following information:
\`\`\`js
module.exports = {
client_id: "${dataConfig.client_id}",
client_secret: "${dataConfig.client_secret}",
refresh_token: "${dataConfig.refresh_token}",
}
\`\`\`
## Environment Config
The environment variables are differ for each hosting service. If you are using Vercel, you can add the environment variables on the project settings page.
\`\`\`
ENCRYPTION_KEY="${dataKey}"
\`\`\`
**NOTE:** Make sure to redeploy the site after adding the environment variables.
## Customizing the Site
The site can be customized by editing the \`src/config/site.ts\` file. You can change the site title, description, and other information.
All the explanation also included in the file.`;
setMdContent(mdContent);
setIsLoading(false);
// Remove the temp data
localStorage.removeItem("tempEncryption");
localStorage.removeItem("tempGoogleCloud");
}, [dataConfig, dataKey]);
return (
<div className={"mx-auto flex max-w-screen-xl flex-col gap-4"}>
<div className={"card"}>
<div className='flex w-full items-center justify-between rounded-lg px-4'>
<span className='font-bold'>Finishing setup</span>
</div>
<div className={"divider-horizontal"} />
{isLoading ? (
<LoadingFeedback message={"Loading data..."} />
) : (
<MarkdownRender content={mdContent} />
)}
<div className={"divider-horizontal"} />
<div
className={
"mx-auto flex w-full flex-row items-center justify-end gap-2 tablet:gap-4"
}
>
<Link href={"/"}>
<button className={"primary"}>Finish</button>
</Link>
</div>
</div>
</div>
);
}
+151
View File
@@ -0,0 +1,151 @@
import { MdContentCopy, MdWarning } from "react-icons/md";
import { ReactNode, useEffect, useState } from "react";
import { encrypt, generateRandomEncryptionKey } from "@utils/encryptionHelper";
import useCopyText from "@hooks/useCopyText";
import { toast } from "react-toastify";
import Link from "next/link";
import useLocalStorage from "@hooks/useLocalStorage";
import MarkdownRender from "@components/utility/MarkdownRender";
import useSWR from "swr";
import fetcher from "@utils/swrFetch";
import LoadingFeedback from "@components/APIFeedback/Loading";
type StepProps = {
stepName: string;
imgSrc?: string;
children: ReactNode;
};
export default function SetupGoogleCloud() {
const [encryptionKey] = useLocalStorage("tempEncryption", "");
const [settingJson, setSettingJson] = useLocalStorage("tempGoogleCloud", {
client_id: "",
client_secret: "",
refresh_token: "",
});
const [client_id, setClientID] = useState<string>("");
const [client_secret, setClientSecret] = useState<string>("");
const [refresh_token, setRefreshToken] = useState<string>("");
const [allowNext, setAllowNext] = useState<boolean>(false);
const { data, isLoading } = useSWR("/setup/GoogleCloudStep.md", fetcher);
useEffect(() => {
if (client_id && client_secret && refresh_token) {
setAllowNext(true);
} else {
setAllowNext(false);
}
}, [client_id, client_secret, refresh_token]);
return (
<div className='mx-auto flex max-w-screen-xl flex-col gap-4'>
<div className={"card"}>
<div className='flex w-full items-center justify-between rounded-lg px-4'>
<span className='font-bold'>Setting up Google Cloud</span>
</div>
<div className={"divider-horizontal"} />
<div className={"banner"}>
<span>
If you already have Client ID, Client Secret, and Refresh Token.
Click <a href={"#skip"}>here</a> to skip this step.
</span>
</div>
<div
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
>
<div className={"flex flex-col gap-2"}>
{isLoading ? (
<LoadingFeedback message={"Loading file..."} />
) : (
<MarkdownRender content={data as string} />
)}
</div>
</div>
</div>
<div
className={"card"}
id={"skip"}
>
<div className='flex w-full items-center justify-between rounded-lg px-4'>
<span className='font-bold'>Store Google Cloud credentials</span>
</div>
<div className={"divider-horizontal"} />
<div
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
>
<div className={"flex flex-col gap-2"}>
<span className='font-bold'>Client ID</span>
<input
type={"text"}
className={"pr-4"}
value={client_id}
onChange={(e) => setClientID(e.target.value)}
placeholder={"Enter your client id here..."}
/>
</div>
<div className={"flex flex-col gap-2"}>
<span className='font-bold'>Client Secret</span>
<input
type={"text"}
className={"pr-4"}
value={client_secret}
onChange={(e) => setClientSecret(e.target.value)}
placeholder={"Enter your client secret here..."}
/>
<span className={"text-xs tablet:text-sm"}>
* Client secret will be encrypted using your encryption key
</span>
</div>
<div className={"flex flex-col gap-2"}>
<span className='font-bold'>Refresh Token</span>
<input
type={"text"}
className={"pr-4"}
value={refresh_token}
onChange={(e) => setRefreshToken(e.target.value)}
placeholder={"Enter your refresh token here..."}
/>
<span className={"text-xs tablet:text-sm"}>
* Refresh token will be encrypted using your encryption key
</span>
</div>
</div>
<div className={"divider-horizontal"} />
<div
className={
"mx-auto flex w-full items-center justify-end gap-2 tablet:gap-4"
}
>
<Link href={"/setup/encryption"}>
<button className={"secondary"}>Previous Page</button>
</Link>
<Link
href={allowNext ? "/setup/final" : ""}
onClick={() => {
if (!allowNext) return;
setSettingJson({
client_id,
client_secret: encrypt(client_secret, encryptionKey),
refresh_token: encrypt(refresh_token, encryptionKey),
});
}}
>
<button
className={"primary"}
disabled={!allowNext}
>
Next Page
</button>
</Link>
</div>
</div>
</div>
);
}
+38
View File
@@ -0,0 +1,38 @@
import { GetServerSideProps } from "next";
import Link from "next/link";
export default function Setup() {
return (
<div className={"mx-auto flex max-w-screen-xl flex-col gap-4"}>
<div className={"card"}>
<div className='flex w-full items-center justify-between rounded-lg px-4'>
<span className='font-bold'>Starting configuration</span>
</div>
<div className={"divider-horizontal"} />
<p className={"mx-auto max-w-screen-md px-4 py-4 text-center"}>
This page will guide you through the initial configuration for
deploying guDora-index. It start from setting up your encryption key,
Google cloud, and setting up the project.
<br />
<br />
This step will take you couple of minutes. So please read the
instructions and follow them carefully.
</p>
<div className={"divider-horizontal"} />
<div
className={
"mx-auto flex w-full flex-row items-center justify-end gap-2 tablet:gap-4"
}
>
<Link href={"/setup/encryption"}>
<button className={"primary"}>Start</button>
</Link>
</div>
</div>
</div>
);
}
+40 -6
View File
@@ -71,11 +71,16 @@ html, body {
}
a {
@apply hover:opacity-75 transition duration-150;
@apply text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 underline;
}
a:has(div.items), a:has(span) {
@apply text-zinc-900 dark:text-zinc-100 hover:text-zinc-800 dark:hover:text-zinc-200;
@apply no-underline;
}
input, textarea, button, select {
@apply rounded-full px-4 py-2 tablet:py-1;
@apply rounded-lg px-4 py-2 tablet:py-1;
@apply text-sm tablet:text-base;
@apply outline-none border;
@apply border-zinc-400 hover:border-blue-300 focus:border-blue-400;
@@ -91,20 +96,33 @@ html, body {
button {
@apply cursor-pointer disabled:cursor-default;
}
button[disabled] {
@apply cursor-not-allowed !important;
@apply border-zinc-300 dark:border-zinc-700 !important;
@apply bg-zinc-400 dark:bg-zinc-800 !important;
@apply text-zinc-500 !important;
}
button.primary {
@apply text-zinc-100 dark:text-zinc-300;
@apply text-zinc-100 dark:text-zinc-100;
@apply border-blue-400 hover:border-blue-500 active:border-blue-600;
@apply bg-blue-500 hover:bg-blue-600 active:bg-blue-400;
@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-400 dark:active:bg-blue-600;
@apply dark:bg-blue-500 dark:hover:bg-blue-600 dark:active:bg-blue-600;
}
button.secondary{
@apply text-zinc-100 dark:text-zinc-300;
@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 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;
}
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;
}
table {
@apply w-full border-collapse;
@@ -118,6 +136,10 @@ html, body {
td {
@apply border-b border-zinc-400 dark:border-zinc-700 py-1.5;
}
img.thumbnail {
@apply rounded-lg w-full max-w-screen-tablet mt-2 mb-4;
}
}
@layer components {
@@ -128,6 +150,18 @@ html, body {
@apply bg-zinc-100 dark:bg-zinc-900;
}
div.banner {
@apply rounded-lg p-2 tablet:p-4 my-2 tablet:my-4;
@apply border border-zinc-400 dark:border-zinc-700;
@apply drop-shadow;
@apply bg-zinc-100 dark:bg-zinc-900;
@apply flex gap-4 items-center;
}
div.banner.warning {
@apply border-yellow-400 dark:border-yellow-600;
@apply bg-yellow-100 dark:bg-yellow-900;
}
div.layoutDropdown {
@apply rounded-lg pr-2 pl-4 py-1 cursor-pointer;
@apply flex items-center justify-between gap-8;
@@ -187,7 +221,7 @@ html, body {
}
.navbar a {
@apply text-inherit font-normal not-italic;
@apply text-inherit font-normal not-italic no-underline;
}
.interactive {
+2 -2
View File
@@ -54,7 +54,7 @@ html.dark {
}
code[class*="language-"],
pre[class*="language-"] {
pre[class*="language-"], pre {
background: var(--syntax-bg);
color: var(--syntax-fg);
text-shadow: 0 1px rgba(0, 0, 0, 0);
@@ -92,7 +92,7 @@ pre[class*="language-"] *::selection {
}
/* Code blocks */
pre[class*="language-"] {
pre[class*="language-"], pre {
padding: 1em;
margin: 0.5em 0;
overflow: auto;