From 7a583ec1d68b1567ead2802fc39c7d4c63c19dfe Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 27 May 2025 22:38:12 -0700 Subject: [PATCH] (FEAT): Mobile friendly and code cleanup! Cleaned the formatting with Nvim and created mobile friendly styles. --- backend/src/server.ts | 4 +- frontend/src/components/ChildrenLoading.jsx | 18 +- frontend/src/components/ContentLoading.jsx | 20 +- frontend/src/components/CreateDirectory.jsx | 83 ++++---- frontend/src/components/Directory.jsx | 93 ++++---- frontend/src/components/DirectoryList.jsx | 23 +- frontend/src/components/DownloadLoading.jsx | 18 +- frontend/src/components/Editor.jsx | 137 ++++++------ frontend/src/components/Error.jsx | 34 +-- frontend/src/components/LoginForm.jsx | 224 ++++++++++---------- frontend/src/components/Navbar.jsx | 201 +++++++++--------- frontend/src/components/PasswordInput.jsx | 98 +++++---- frontend/src/components/PathDisplay.jsx | 135 ++++++------ frontend/src/components/RememberMe.jsx | 24 ++- frontend/src/components/Uploader.jsx | 146 +++++++------ frontend/src/components/UserInput.jsx | 40 ++-- frontend/src/pages/Dashboard.jsx | 73 ++++--- frontend/src/pages/Login.jsx | 33 +-- frontend/src/pages/NotFound.jsx | 18 +- 19 files changed, 730 insertions(+), 692 deletions(-) diff --git a/backend/src/server.ts b/backend/src/server.ts index 1293dbb..6ca0415 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -20,8 +20,8 @@ import Multer from "multer"; const PORT = 5000; const APP: Express = express(); // TODO: BACK TO NORMAL PATH -const ROOT: string = "/media/vault"; -// const ROOT: string = "/home/azpect"; +// const ROOT: string = "/media/vault"; +const ROOT: string = "/home/azpect"; /** * Configure the .env file, this is for testing only, should be ignored in production. diff --git a/frontend/src/components/ChildrenLoading.jsx b/frontend/src/components/ChildrenLoading.jsx index c3d8777..4c28885 100644 --- a/frontend/src/components/ChildrenLoading.jsx +++ b/frontend/src/components/ChildrenLoading.jsx @@ -6,12 +6,12 @@ import "../index.css" * @constructor */ export default function ChildrenLoading() { - return ( -
-
-
-

Content loading...

-
- ); -}; \ No newline at end of file + return <> +
+
+
+

Content loading...

+
+ +}; diff --git a/frontend/src/components/ContentLoading.jsx b/frontend/src/components/ContentLoading.jsx index 494c9a0..62c351f 100644 --- a/frontend/src/components/ContentLoading.jsx +++ b/frontend/src/components/ContentLoading.jsx @@ -6,15 +6,15 @@ import "../index.css" * @constructor */ export default function ContentLoading() { - return ( -
-
-
-
-

Content loading...

-
-

For large files, this may take a while.

+ return <> +
+
+
- ); +

Content loading...

+
+

For large files, this may take a while.

+
+ }; diff --git a/frontend/src/components/CreateDirectory.jsx b/frontend/src/components/CreateDirectory.jsx index f552b10..cb5df53 100644 --- a/frontend/src/components/CreateDirectory.jsx +++ b/frontend/src/components/CreateDirectory.jsx @@ -5,51 +5,50 @@ import {useState} from "react"; * @constructor */ export default function CreateDirectory({close, create}) { - const [dirName, setDirName] = useState(""); + const [dirName, setDirName] = useState(""); - const createDirectory = () => { - create(dirName); - } + const createDirectory = () => create(dirName); + const updateDirName = (e) => setDirName(e.target.value); - const updateDirName = (e) => { - setDirName(e.target.value); - } + const closeWithoutSaving = () => { + setDirName(""); + close(); + } - const closeWithoutSaving = () => { - setDirName(""); - close(); - } + return <> +
+ {/* Blured backdrop */} +
- return ( -
-
-
-

Create a Directory

-

- Create a file or directory in the current directory. To create a directory, include a / at the end of the name. - Otherwise, the entry created will be a file. -

+
+

+ Create a Directory +

+

+ Create a file or directory in the current directory. To create a directory, include a / at the end of the name. + Otherwise, the entry created will be a file. +

- -
- - -
-
+ +
+ +
- ); -} \ No newline at end of file +
+
+ +} diff --git a/frontend/src/components/Directory.jsx b/frontend/src/components/Directory.jsx index 471171f..c939710 100644 --- a/frontend/src/components/Directory.jsx +++ b/frontend/src/components/Directory.jsx @@ -1,22 +1,26 @@ import "../index.css" -import {useState} from "react"; +import { useState } from "react"; function FileIcon() { - return - + return <> + + + } function DirectoryIcon() { - return - + return <> + + + } /** @@ -28,40 +32,43 @@ function DirectoryIcon() { * @returns {{}} * @constructor */ -export default function Directory({entry, showHidden, appendPath, toggleSelected, toggleEditing}) { - const [selected, setSelected] = useState(false); +export default function Directory({ entry, showHidden, appendPath, toggleSelected, toggleEditing }) { + const [selected, setSelected] = useState(false); - const handleClick = () => { - if (entry.directory) { - appendPath(entry.name); - } else { - toggleEditing(entry.path); - } - }; - - const handleCheck = () => { - toggleSelected(entry.name); - setSelected(!selected); + const handleClick = () => { + if (entry.directory) { + appendPath(entry.name); + } else { + toggleEditing(entry.path); } + }; - // This is temporary, eventually I will have a real data model that stores - // directory vs file status. - // const isDirectory = !name.endsWith(".html"); - if (entry.name.startsWith(".") && !showHidden) { - return <>; - } + const handleCheck = () => { + toggleSelected(entry.name); + setSelected(!selected); + } - return ( -
- -
+ // This is temporary, eventually I will have a real data model that stores + // directory vs file status. + // const isDirectory = !name.endsWith(".html"); + if (entry.name.startsWith(".") && !showHidden) { + return <>; + } - - ); -} \ No newline at end of file + return <> + + +} diff --git a/frontend/src/components/DirectoryList.jsx b/frontend/src/components/DirectoryList.jsx index 390426d..17bfbda 100644 --- a/frontend/src/components/DirectoryList.jsx +++ b/frontend/src/components/DirectoryList.jsx @@ -8,13 +8,16 @@ import Directory from "./Directory.jsx"; * @param toggleSelected {function(string)} Function to toggle selection status. * @constructor */ -export default function DirectoryList({dirs, showHidden, appendPath, toggleSelected, toggleEditing}) { - return ( - <> - {dirs.map((dir, idx) => )} - - ) - - -} \ No newline at end of file +export default function DirectoryList({ dirs, showHidden, appendPath, toggleSelected, toggleEditing }) { + return <> + {dirs.map((dir, idx) => + + )} + +} diff --git a/frontend/src/components/DownloadLoading.jsx b/frontend/src/components/DownloadLoading.jsx index 1dc06e0..fc554d0 100644 --- a/frontend/src/components/DownloadLoading.jsx +++ b/frontend/src/components/DownloadLoading.jsx @@ -6,13 +6,13 @@ import "../index.css" * @constructor */ export default function DownloadLoading() { - return ( -
-
-
-
-

Preparing files...

-
- ); + return <> +
+
+
+
+

Preparing files...

+
+ }; diff --git a/frontend/src/components/Editor.jsx b/frontend/src/components/Editor.jsx index 8b65634..8fb5d43 100644 --- a/frontend/src/components/Editor.jsx +++ b/frontend/src/components/Editor.jsx @@ -1,86 +1,87 @@ -import {useEffect, useRef, useState} from "react"; +import { useEffect, useRef, useState } from "react"; import ContentLoading from "./ContentLoading.jsx"; -export default function Editor({content, path, exit, saveExit, loading}) { - const [text, setText] = useState(""); - /** +export default function Editor({ content, path, exit, saveExit, loading }) { + const [text, setText] = useState(""); + /** * Store a reference to the text area object * @type {React.RefObject} */ - const textareaRef = useRef(null); + const textareaRef = useRef(null); - const updateText = (event) => { - setText(event.target.value); - }; + const updateText = (event) => setText(event.target.value); - useEffect(() => { - setText(content); - }, [content]) + useEffect(() => { + setText(content); + }, [content]) - const handleKeyPress = (event) => { - // Override tab changing focus - // Uses 2 space indents - // TODO: Allow toggle for two and four space indents - if (event.key === "Tab") { - event.preventDefault(); + const handleKeyPress = (event) => { + // Override tab changing focus + // Uses 2 space indents + // TODO: Allow toggle for two and four space indents + if (event.key === "Tab") { + event.preventDefault(); - const textarea = textareaRef.current; - if (!textarea) return; + const textarea = textareaRef.current; + if (!textarea) return; - const start = textarea.selectionStart; - const end = textarea.selectionEnd; - const value = textarea.value; + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const value = textarea.value; - // Insert tab character - // Update textarea value and cursor position - textarea.value = value.substring(0, start) + " " + value.substring(end); - textarea.selectionStart = textarea.selectionEnd = start + 2; // Move the cursor after the tab + // Insert tab character + // Update textarea value and cursor position + textarea.value = value.substring(0, start) + " " + value.substring(end); + textarea.selectionStart = textarea.selectionEnd = start + 2; // Move the cursor after the tab - // Trigger a change event so React knows the value changed. - textarea.dispatchEvent(new Event('input', {bubbles: true})); - } - }; + // Trigger a change event so React knows the value changed. + textarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }; - /** + /** * Call the parent function with the new content which is * stored in the text state. */ - const saveAndExit = () => { - saveExit(text); - }; + const saveAndExit = () => saveExit(text); - return ( -
-
-
-

Editing File: {path}

- {loading && } - {loading || - - } -
- - -
-
+ return <> +
+ {/* Blured backdrop */} +
+ +
+

+ Editing File: + {path} + +

+ {loading && } + {loading || + + } +
+ +
- ); -} \ No newline at end of file +
+
+ +} diff --git a/frontend/src/components/Error.jsx b/frontend/src/components/Error.jsx index b7f828e..bad2790 100644 --- a/frontend/src/components/Error.jsx +++ b/frontend/src/components/Error.jsx @@ -8,21 +8,21 @@ import "../index.css"; * @constructor */ export default function Error({error, clear}) { - return ( -
-
-
-

An Error Occurred!

-

{error}

-
- -
-
+ return <> +
+
+
+

An Error Occurred!

+

{error}

+
+
- ); -} \ No newline at end of file +
+
+ +} diff --git a/frontend/src/components/LoginForm.jsx b/frontend/src/components/LoginForm.jsx index dea2f12..7e1a7e5 100644 --- a/frontend/src/components/LoginForm.jsx +++ b/frontend/src/components/LoginForm.jsx @@ -1,132 +1,134 @@ -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import UserInput from "./UserInput.jsx"; import PasswordInput from "./PasswordInput.jsx"; import RememberMe from "./RememberMe.jsx"; -import {useNavigate} from "react-router-dom"; +import { useNavigate } from "react-router-dom"; export default function LoginForm() { - /** - * URL To the backend web server. - * Uses the .env var in local development, but when - * pushed to dockerhub, the .env is ignored and the real - * backend URL is used. - * @type {string} - */ - const backendUrl = import.meta.env.VITE_BACKEND_URL || "https://backend.gophernest.net"; + /** + * URL To the backend web server. + * Uses the .env var in local development, but when + * pushed to dockerhub, the .env is ignored and the real + * backend URL is used. + * @type {string} + */ + const backendUrl = import.meta.env.VITE_BACKEND_URL || "https://backend.gophernest.net"; - const [username, setUsername] = useState(""); - const [remember, setRemember] = useState(false); - const [password, setPassword] = useState(""); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); + const [username, setUsername] = useState(""); + const [remember, setRemember] = useState(false); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); - const navigate = useNavigate(); + const navigate = useNavigate(); - /** - * The name of the value stored in local storage. - * @type {string} - */ - const storage_id = "gophernest_credentials"; + /** + * The name of the value stored in local storage. + * @type {string} + */ + const storage_id = "gophernest_credentials"; - /** - * Set the email in the form state. - * @param newUsername - */ - const updateUsername = (newUsername) => { - setUsername(newUsername); + /** + * Set the email in the form state. + * @param newUsername + */ + const updateUsername = (newUsername) => { + setUsername(newUsername); + }; + + /** + * Set the 'remember me' in the form start. + * @param newRemember + */ + const updateRemember = (newRemember) => { + setRemember(newRemember); + }; + + /** + * Set the password in the form state. + * @param newPassword + */ + const updatePassword = (newPassword) => { + setPassword(newPassword); + }; + + + /** + * Handle the login submission, data is stored in the local storage. + * @param event {SubmitEvent} + */ + const handleSubmit = (event) => { + event.preventDefault(); + setLoading(true); + + const sendAuthReq = async (username, password) => { + const resp = await fetch(`${backendUrl}/v1/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ username, password }), + }); + if (!resp.ok) { + const data = await resp.json(); + console.error(data.message); + setError(data.message); + return data; + } + return await resp.json(); }; - /** - * Set the 'remember me' in the form start. - * @param newRemember - */ - const updateRemember = (newRemember) => { - setRemember(newRemember); - }; + sendAuthReq(username, password).then((data) => { + const { code, token } = data; - /** - * Set the password in the form state. - * @param newPassword - */ - const updatePassword = (newPassword) => { - setPassword(newPassword); - }; - - - /** - * Handle the login submission, data is stored in the local storage. - * @param event {SubmitEvent} - */ - const handleSubmit = (event) => { - event.preventDefault(); - setLoading(true); - - const sendAuthReq = async (username, password) => { - const resp = await fetch(`${backendUrl}/v1/login`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({username, password}), - }); - if (!resp.ok) { - const data = await resp.json(); - console.error(data.message); - setError(data.message); - return data; - } - return await resp.json(); - }; - - sendAuthReq(username, password).then((data) => { - const {code, token} = data; - - // Should always be 200, but just make sure it is - if (code === 200) { - // Store JWT in session if the user does not want to be remembered - remember ? localStorage.setItem(storage_id, token) : sessionStorage.setItem(storage_id, token); - navigate("/login"); - } else { - console.error(data); - setError(data.message); - } - }).catch((err) => { - console.error(err); - setError(err.toString()); - }).finally(() => { - // Disable loading bar - setLoading(false); - }); - }; - - // Redirect if the user is logged in - useEffect(() => { - if (localStorage.getItem(storage_id) != null || sessionStorage.getItem(storage_id) != null) { - navigate("/dashboard"); - } + // Should always be 200, but just make sure it is + if (code === 200) { + // Store JWT in session if the user does not want to be remembered + remember ? localStorage.setItem(storage_id, token) : sessionStorage.setItem(storage_id, token); + navigate("/login"); + } else { + console.error(data); + setError(data.message); + } + }).catch((err) => { + console.error(err); + setError(err.toString()); + }).finally(() => { + // Disable loading bar + setLoading(false); }); + }; - useEffect(() => { - console.log(`loading: ${loading}`); - }, [loading]); + // Redirect if the user is logged in + useEffect(() => { + if (localStorage.getItem(storage_id) != null || sessionStorage.getItem(storage_id) != null) { + navigate("/dashboard"); + } + }); + + useEffect(() => { + console.log(`loading: ${loading}`); + }, [loading]); - return
- - - + return <> + + + + - {error &&

{error}

} + {error &&

{error}

} - + -

- If you do not have an account, you're in the wrong place! -

+

+ If you do not have an account, you're in the wrong place! +

-}; \ No newline at end of file + +}; diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 352804e..68aa250 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -1,6 +1,6 @@ import "../index.css" -import {useState} from "react"; -import {useNavigate} from "react-router-dom"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; /** * Main navbar icon @@ -9,11 +9,13 @@ import {useNavigate} from "react-router-dom"; * @constructor */ function MainIcon() { - return - + return <> + + + } /** @@ -22,19 +24,19 @@ function MainIcon() { * @returns {JSX.Element} * @constructor */ -function DownloadButton({downloadFiles}) { - return ( - - ) +function DownloadButton({ downloadFiles }) { + return <> + + } /** @@ -43,116 +45,117 @@ function DownloadButton({downloadFiles}) { * @returns {JSX.Element} * @constructor */ -function UploadButton({uploadFiles}) { - return ( - - ) +function UploadButton({ uploadFiles }) { + return <> + + } /** * Information button. Not sure what this is going to do... * @returns {JSX.Element} - * @constructor - */ + * @constructor + */ function InfoButton() { - return ( - - ) + return <> + + } /** * Logout button, which clears the user from the storage's. * @returns {JSX.Element} - * @constructor - */ + * @constructor + */ function LogoutButton() { - const navigate = useNavigate(); + const navigate = useNavigate(); - /** + /** * The name of the value stored in local storage. * @type {string} - */ - const storage_id = "gophernest_credentials"; + */ + const storage_id = "gophernest_credentials"; - const handleClick = () => { - localStorage.removeItem(storage_id); - sessionStorage.removeItem(storage_id); - navigate("/login"); - }; + const handleClick = () => { + localStorage.removeItem(storage_id); + sessionStorage.removeItem(storage_id); + navigate("/login"); + }; - return ( - - ) + return <> + + } /** * Search bar input, with controlled state. * @returns {JSX.Element} - * @constructor - */ + * @constructor + */ function SearchBar() { - const [search, setSearch] = useState(""); + const [search, setSearch] = useState(""); - /** + /** * Update the controlled state. * @param event {InputEvent} - */ - const updateSearch = (event) => { - setSearch(event.target.value); - }; + */ + const updateSearch = (event) => setSearch(event.target.value); - return ( -
- -
- ) + return ( +
+ +
+ ) } -export default function Navbar({downloadFiles, uploadFiles}) { - return