diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 238d2e4..96e0c8c 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,38 +1,39 @@ -import js from '@eslint/js' -import globals from 'globals' -import react from 'eslint-plugin-react' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' +import js from "@eslint/js"; +import globals from "globals"; +import react from "eslint-plugin-react"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; export default [ - { ignores: ['dist'] }, + { ignores: ["dist"] }, { - files: ['**/*.{js,jsx}'], + files: ["**/*.{js,jsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, parserOptions: { - ecmaVersion: 'latest', + ecmaVersion: "latest", ecmaFeatures: { jsx: true }, - sourceType: 'module', + sourceType: "module", }, }, - settings: { react: { version: '18.3' } }, + settings: { react: { version: "18.3" } }, plugins: { react, - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, }, rules: { ...js.configs.recommended.rules, ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, + ...react.configs["jsx-runtime"].rules, ...reactHooks.configs.recommended.rules, - 'react/jsx-no-target-blank': 'off', - 'react-refresh/only-export-components': [ - 'warn', + "react/jsx-no-target-blank": "off", + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], + "react/prop-types": "off", }, }, -] +]; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3ae9d37..d51271d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "react-icons": "^5.4.0" + "react-hot-toast": "^2.5.1", + "react-icons": "^5.4.0", + "react-router-dom": "^7.1.1" }, "devDependencies": { "@eslint/js": "^9.17.0", @@ -1400,6 +1402,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -2023,6 +2031,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2066,7 +2083,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/culori": { @@ -3108,6 +3124,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4580,6 +4605,23 @@ "react": "^18.3.1" } }, + "node_modules/react-hot-toast": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.1.tgz", + "integrity": "sha512-54Gq1ZD1JbmAb4psp9bvFHjS7lje+8ubboUmvKZkCsQBLH6AOpZ9JemfRvIdHcfb9AZXRaFLrb3qUobGYDJhFQ==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-icons": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", @@ -4606,6 +4648,46 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", + "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.1.tgz", + "integrity": "sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==", + "license": "MIT", + "dependencies": { + "react-router": "7.1.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4849,6 +4931,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5386,6 +5474,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index b358d0c..72d5abc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,9 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "react-icons": "^5.4.0" + "react-hot-toast": "^2.5.1", + "react-icons": "^5.4.0", + "react-router-dom": "^7.1.1" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 57c2529..c4e4363 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,13 +1,19 @@ +import { Routes, Route } from "react-router-dom"; import "./App.css"; import Home from "./pages/home/Home"; import Login from "./pages/login/login"; import SignUp from "./pages/signup/SignUp"; +import { Toaster } from "react-hot-toast"; function App() { return (
- - {/* */} + + } /> + } /> + } /> + +
); } diff --git a/frontend/src/hooks/useSignup.js b/frontend/src/hooks/useSignup.js new file mode 100644 index 0000000..4c610f7 --- /dev/null +++ b/frontend/src/hooks/useSignup.js @@ -0,0 +1,72 @@ +import { useState } from "react"; +import toast from "react-hot-toast"; + +const useSignup = () => { + const [loading, setLoading] = useState(false); + + const signup = async ({ + fullName, + username, + password, + confirmPassword, + gender, + }) => { + const success = handleInputErrors({ + fullName, + username, + password, + confirmPassword, + gender, + }); + if (!success) return; + + setLoading(true); + + try { + const res = await fetch("/api/auth/signup", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + fullName, + username, + password, + confirmPassword, + gender, + }), + }); + const data = await res.json(); + console.log(data); + } catch (error) { + toast.error(error.message); + } finally { + setLoading(false); + } + }; + return { loading, signup }; +}; + +export default useSignup; + +function handleInputErrors({ + fullName, + username, + password, + confirmPassword, + gender, +}) { + if (!fullName || !username || !password || !confirmPassword || !gender) { + toast.error("All fields are required"); + return false; + } + if (password !== confirmPassword) { + toast.error("Passwords do not match"); + return false; + } + if (password.length < 6) { + toast.error("Password must be at least 6 characters"); + return false; + } + return true; +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index b9a1a6d..02a4c65 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,10 +1,13 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.jsx' +import React from "react"; +import ReactDom from "react-dom/client"; +import "./index.css"; +import App from "./App.jsx"; +import { BrowserRouter } from "react-router-dom"; -createRoot(document.getElementById('root')).render( - - - , -) +ReactDom.createRoot(document.getElementById("root")).render( + + + + + +); diff --git a/frontend/src/pages/login/login.jsx b/frontend/src/pages/login/login.jsx index 9361f7f..204c48e 100644 --- a/frontend/src/pages/login/login.jsx +++ b/frontend/src/pages/login/login.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import { Link } from "react-router-dom"; const login = () => { return ( @@ -29,12 +29,12 @@ const login = () => { className="w-full input input-bordered h-10" /> - Don't have an account? Signup - +
diff --git a/frontend/src/pages/signup/gendercheckbox.jsx b/frontend/src/pages/signup/gendercheckbox.jsx index 8f42203..31971ad 100644 --- a/frontend/src/pages/signup/gendercheckbox.jsx +++ b/frontend/src/pages/signup/gendercheckbox.jsx @@ -1,18 +1,36 @@ import React from "react"; -const GenderCheckbox = () => { +const GenderCheckbox = ({ onCheckboxChange, selectedGender }) => { return (
-
-
diff --git a/frontend/src/pages/signup/signup.jsx b/frontend/src/pages/signup/signup.jsx index 23b7ddb..c426430 100644 --- a/frontend/src/pages/signup/signup.jsx +++ b/frontend/src/pages/signup/signup.jsx @@ -1,7 +1,28 @@ -import React from "react"; -import GenderCheckbox from "./gendercheckbox"; +import { useState } from "react"; +import GenderCheckBox from "./GenderCheckBox"; +import { Link } from "react-router-dom"; +import userSignup from "../../hooks/useSignup"; const SignUp = () => { + const [inputs, setInputs] = useState({ + fullName: "", + username: "", + password: "", + confirmPassword: "", + gender: "", + }); + + const { loading, signup } = userSignup(); + + const handleCheckboxChange = (gender) => { + setInputs({ ...inputs, gender }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); // Prevents the page from reloading + await signup(inputs); + }; + return (
@@ -9,7 +30,7 @@ const SignUp = () => { Sign Up ChatApp -
+
@@ -28,6 +53,10 @@ const SignUp = () => { type="text" placeholder="u0509421" className="w-full input input-bordered h-10" + value={inputs.username} + onChange={(e) => + setInputs({ ...inputs, username: e.target.value }) + } />
@@ -38,6 +67,10 @@ const SignUp = () => { type="password" placeholder="Enter Password" className="w-full input input-bordered h-10" + value={inputs.password} + onChange={(e) => + setInputs({ ...inputs, password: e.target.value }) + } />
@@ -48,20 +81,30 @@ const SignUp = () => { type="password" placeholder="Confirm Password" className="w-full input input-bordered h-10" + value={inputs.confirmPassword} + onChange={(e) => + setInputs({ ...inputs, confirmPassword: e.target.value }) + } />
- + - Alread have an account? - -
+ +
+
diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 8b0f57b..2698544 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,7 +1,15 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], -}) + server: { + port: 3000, + proxy: { + "/api": { + target: "http://localhost:3000", + }, + }, + }, +});