diff --git a/package-lock.json b/package-lock.json index 05bb5f6..563218f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "marked": "^15.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hot-toast": "^2.6.0", "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "react-social-icons": "^6.16.0", @@ -31,6 +32,7 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "tailwindcss": "^3.4.17" } }, @@ -660,9 +662,18 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -1905,6 +1916,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -6238,9 +6261,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001596", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", - "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", "funding": [ { "type": "opencollective", @@ -6254,7 +6277,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -9447,6 +9471,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -15593,6 +15626,23 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -19575,10 +19625,16 @@ } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "requires": {} + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -20373,6 +20429,12 @@ "semver": "^6.3.1" }, "dependencies": { + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "requires": {} + }, "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -23525,9 +23587,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001596", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", - "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==" + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -25806,6 +25868,12 @@ "slash": "^3.0.0" } }, + "goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "requires": {} + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -30017,6 +30085,15 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "requires": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index 35cfced..4d76044 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "marked": "^15.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hot-toast": "^2.6.0", "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "react-social-icons": "^6.16.0", @@ -29,7 +30,8 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "lint": "eslint src --ext .js,.jsx" }, "eslintConfig": { "extends": [ @@ -50,6 +52,7 @@ ] }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "tailwindcss": "^3.4.17" } } diff --git a/src/App.js b/src/App.js index 9af0573..7f9999f 100644 --- a/src/App.js +++ b/src/App.js @@ -15,11 +15,13 @@ import {Dashboard} from "./screens/Dashboard"; import {DashboardView} from "./screens/DashboardView"; import {GraphService} from "./screens/GraphService"; import Tunnels from "./screens/Tunnels"; +import { Toaster } from "react-hot-toast"; function App() { return (
+ } /> } /> diff --git a/src/components/AvatarMenu.js b/src/components/AvatarMenu.js index 8819c65..173828c 100644 --- a/src/components/AvatarMenu.js +++ b/src/components/AvatarMenu.js @@ -1,3 +1,5 @@ +import { AuthHandler } from "../utils/authHandler"; + function AvatarMenu({ account, initials, togglePopup, activePopup }) { return (
@@ -28,8 +30,7 @@ function AvatarMenu({ account, initials, togglePopup, activePopup }) {
); } -export default Footer; +export default Footer; \ No newline at end of file diff --git a/src/components/FormControls.js b/src/components/FormControls.js index efd930b..c71d427 100644 --- a/src/components/FormControls.js +++ b/src/components/FormControls.js @@ -91,4 +91,10 @@ export const DropdownSelect = ({ ) } -export default { TextInput, NumberInput, DropdownSelect } +const FormControls = { + TextInput, + NumberInput, + DropdownSelect, +} + +export default FormControls \ No newline at end of file diff --git a/src/components/HomeComponent.js b/src/components/HomeComponent.js index 504433b..76e6ad6 100644 --- a/src/components/HomeComponent.js +++ b/src/components/HomeComponent.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import { useState } from "react"; export default function LockBoxLanding() { const [faqOpen, setFaqOpen] = useState(null); @@ -40,15 +40,6 @@ export default function LockBoxLanding() { }, ]; - const [copied, setCopied] = useState(false); - const bashScript = `/bin/bash -c "$(curl -fsSL https://github.com/avinash84319/providerServer/install.sh)"`; - - const handleCopy = () => { - navigator.clipboard.writeText(bashScript); - setCopied(true); - setTimeout(() => setCopied(false), 2000); // reset after 2 seconds - }; - return (
{/* Header Section */} diff --git a/src/components/Navbar.js b/src/components/Navbar.js index e92502b..cf45c78 100644 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -7,7 +7,6 @@ import useNavbarScroll from "../hooks/useNavbarScroll"; import { getInitials } from "../utils/userUtils"; import useClickOutside from "../hooks/useClickOutside"; - const Navbar = () => { const [activePopup, setActivePopup] = useState(null); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); diff --git a/src/docs/components/docsMainPage.js b/src/docs/components/docsMainPage.js index 7e5d6fc..a46e7c5 100644 --- a/src/docs/components/docsMainPage.js +++ b/src/docs/components/docsMainPage.js @@ -1,10 +1,8 @@ import React, { useState } from 'react'; -import Navbar from '../../components/Navbar'; import Sidebar from './sidebar'; import ShowDocumentation from './showDocumentation'; import Mainheadings from './mainheadings'; import "../css/docsMainPage.css"; -import { file } from 'jszip'; const DocsMainPage = () => { diff --git a/src/screens/Buckets.js b/src/screens/Buckets.js index f513831..090c6f4 100644 --- a/src/screens/Buckets.js +++ b/src/screens/Buckets.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import axios from 'axios'; -import JSZip from 'jszip'; // Import JSZip for zipping folders +import JSZip from 'jszip'; import { apiCall } from '../Api'; const Buckets = () => { @@ -57,7 +57,7 @@ const Buckets = () => { const zipFolder = async (files) => { const zip = new JSZip(); - const folderName = path.split('/').pop(); // Folder name + // const folderName = path.split('/').pop(); // Folder name const fileArray = Array.from(files); // ✅ Convert FileList to array fileArray.forEach(file => { // Add each file to the zip diff --git a/src/screens/DashboardView.js b/src/screens/DashboardView.js index e68dd72..9c5d0e8 100644 --- a/src/screens/DashboardView.js +++ b/src/screens/DashboardView.js @@ -1,7 +1,7 @@ -import React, { useEffect, useState, useRef } from 'react' +import React, { useEffect, useState } from 'react' import { GraphView } from '../components/GraphView' import DashboardCreateModal from '../components/DashboardCreateModal' -import { useNavigate, useSearchParams } from 'react-router-dom' +import { useSearchParams } from 'react-router-dom' import { Trash2 } from 'lucide-react' import Toast from '../components/ToastService' import ActionConfirmModal from '../components/actionConfirmModal' @@ -11,7 +11,6 @@ import { epochToReadable } from '../helper' import { listGraphsForDashboard, deleteGraph, getGraphPoints } from '../apiServices' export const DashboardView = () => { - const navigate = useNavigate() const [searchParams] = useSearchParams() const dashboardId = searchParams.get('id') const [graphs, setGraphs] = useState([]) diff --git a/src/screens/Login.js b/src/screens/Login.js index 354a034..a81a40d 100644 --- a/src/screens/Login.js +++ b/src/screens/Login.js @@ -1,7 +1,9 @@ -import { apiCall } from '../Api'; -import { Link } from 'react-router-dom'; -import { useState } from 'react'; - +import { apiCall } from "../Api"; +import { Link } from "react-router-dom"; +import { useState } from "react"; +import { validators } from "../utils/validators"; +import { logger } from "../utils/logger"; +import {AuthHandler} from "../utils/authHandler"; function Login() { const [error, setError] = useState(null); @@ -9,66 +11,107 @@ function Login() { const handlesubmit = async (e) => { e.preventDefault(); - const usernameOrEmail = e.target[0].value; - const password = e.target[1].value; + + const usernameOrEmail = e.target.username.value; + const password = e.target.password.value; + + // validation + let validationError = null; + + if (usernameOrEmail.includes("@")) { + validationError = validators.email(usernameOrEmail); + } else { + validationError = validators.username(usernameOrEmail); + } + + if (!validationError) { + validationError = validators.password(password); + } + + if (validationError) { + setError(validationError); + logger.error(validationError); + return; + } + try { - setIsLoading(true) - const res = await apiCall("post", `/login`, { username_or_email: usernameOrEmail, password: password }); - setIsLoading(false); - console.log("Login successful:", res.token); - console.log(res) - sessionStorage.setItem('token', res.token); - window.location.href = "/home"; - } catch (error) { + setIsLoading(true); + setError(null); + + const res = await apiCall("post", `/login`, { + username_or_email: usernameOrEmail, + password: password, + }); + + AuthHandler.login(res.token); + + logger.success("Login successful"); + } catch (err) { + const message = + err || + "Login failed"; + + setError(message); + logger.error(message); + + + } finally { setIsLoading(false); - // alert(error); - setError(error); } }; - - return ( <> -
- {/* Left - Login Form */}
- {isLoading && ( -
-
-
- )} -

Login

-
+ {isLoading && ( +
+
+
+ )} +

+ Login +

+
-
{error}
+
+ {error} +

Terms & Conditions

- Don’t have an account?{' '} - + Don’t have an account?{" "} + Register

@@ -86,8 +129,7 @@ function Login() {
- ) + ); } - -export default Login; \ No newline at end of file +export default Login; diff --git a/src/screens/Register.js b/src/screens/Register.js index cfbe480..f818b1e 100644 --- a/src/screens/Register.js +++ b/src/screens/Register.js @@ -1,103 +1,147 @@ import { Link } from "react-router-dom"; import { apiCall } from "../Api"; import { useState } from "react"; +import { logger } from "../utils/logger"; +import { validators } from "../utils/validators"; +import {AuthHandler} from "../utils/authHandler"; + function Register() { - const [error, setError] = useState(null); - const [isLoading, setIsLoading] = useState(false) - - const handlesubmit = async(e) => { - e.preventDefault(); - if (e.target[3].value === e.target[2].value) { - const email = e.target[0].value; - const username = e.target[1].value; - const password = e.target[2].value; - try { - setIsLoading(true) - await apiCall("post", "/register", { email: email, username: username, password: password }).then((res) => { - console.log(res); - window.location.href = '/login'; - }) - } catch (error) { - // alert(error); - setError(error); - } - finally{ - setIsLoading(false) - } - } - else { - alert('Passwords do not match'); - } - } - - return ( -
-
- - {/* Left - Registration Form */} -
+const [error, setError] = useState(null); +const [isLoading, setIsLoading] = useState(false); + +const handleError = (message) => { + setError(message); + logger.error(message); +}; + +const handleSubmit = async (event) => { + event.preventDefault(); + setError(null); + + const { email, username, password, confirmPassword } = event.target; + + const emailValue = email.value.trim(); + const usernameValue = username.value.trim(); + const passwordValue = password.value; + const confirmPasswordValue = confirmPassword.value; + + // VALIDATION + const usernameError = validators.username(usernameValue); + if (usernameError) return handleError(usernameError); + + const emailError = validators.email(emailValue); + if (emailError) return handleError(emailError); + + const passwordError = validators.password(passwordValue); + if (passwordError) return handleError(passwordError); + + if (passwordValue !== confirmPasswordValue) { + return handleError("Passwords do not match"); + } + + try { + setIsLoading(true); + + const res = await apiCall("post", "/register", { + email: emailValue, + username: usernameValue, + password: passwordValue, + }); + + logger.success("Registered successfully"); + + AuthHandler.login(res.token); + } catch (err) { + const message = + err || + "Registration failed"; + + handleError(message); + } finally { + setIsLoading(false); + } +}; + return ( +
+
+ {/* Left - Registration Form */} +
{isLoading && ( -
-
-
- )} -

Register

- -
- - - - - - -
- -
{error}
- -
-

- Already registered?{' '} - - Login here - -

+
+
-
- - {/* Right - Image (hidden on mobile) */} -
- Register + Register + + +
+ + + + + + +
+ +
+ {error} +
+ +
+

+ Already registered?{" "} + + Login here + +

+ + {/* Right - Image (hidden on mobile) */} +
+ Register +
- - ); +
+ ); } -export default Register; \ No newline at end of file +export default Register; diff --git a/src/utils/authHandler.js b/src/utils/authHandler.js new file mode 100644 index 0000000..3c1bb08 --- /dev/null +++ b/src/utils/authHandler.js @@ -0,0 +1,28 @@ +import Cookies from "js-cookie"; + +export const AuthHandler = { + login: (token) => { + sessionStorage.setItem("token", token); + + Cookies.set("token", token, { + expires: 1, + path: "/", + secure: true, + sameSite: "Strict", + }); + + window.location.replace("/home"); + }, + + logout: () => { + sessionStorage.removeItem("token"); + + Cookies.remove("token"); + + window.location.replace("/login"); + }, + + getToken: () => { + return Cookies.get("token"); + }, +}; \ No newline at end of file diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..0bd641b --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,7 @@ +import toast from "react-hot-toast"; + +export const logger = { + success: (message) => toast.success(message), + error: (message) => toast.error(message), + info: (message) => toast(message), +}; \ No newline at end of file diff --git a/src/utils/validators.js b/src/utils/validators.js new file mode 100644 index 0000000..520b27f --- /dev/null +++ b/src/utils/validators.js @@ -0,0 +1,28 @@ +export const validators = { + username: (value) => { + if (!value || value.trim().length < 3) { + return "Username must be at least 3 characters"; + } + return null; + }, + + email: (value) => { + const emailRegex = + /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + + if (!value) return "Email is required"; + + if (!emailRegex.test(value)) { + return "Invalid email format"; + } + + return null; + }, + + password: (value) => { + if (!value || value.length < 6) { + return "Password must be at least 6 characters"; + } + return null; + }, +}; \ No newline at end of file