diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index faa9100..aeb54eb 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -16,7 +16,7 @@ "locomotive-scroll": "^4.1.4", "lucide-react": "^0.553.0", "motion": "^12.23.24", - "react": "^19.1.1", + "react": "^19.2.0", "react-dom": "^19.1.1", "react-parallax-tilt": "^1.7.313", "react-router-dom": "^7.9.5", diff --git a/Frontend/package.json b/Frontend/package.json index f56db63..0848f23 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -18,7 +18,7 @@ "locomotive-scroll": "^4.1.4", "lucide-react": "^0.553.0", "motion": "^12.23.24", - "react": "^19.1.1", + "react": "^19.2.0", "react-dom": "^19.1.1", "react-parallax-tilt": "^1.7.313", "react-router-dom": "^7.9.5", diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index 16de99e..f96a38f 100644 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -1,5 +1,6 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { useEffect } from "react"; +import { ThemeProvider } from "./context/ThemeContext"; import Home from "./pages/Home"; import Signup from "./pages/SignupLoginPage"; @@ -14,11 +15,13 @@ export default function App() { }, []); return ( - - - } /> - } /> - - + + + + } /> + } /> + + + ); } diff --git a/Frontend/src/components/LoginForm.jsx b/Frontend/src/components/LoginForm.jsx index 9bd989d..7e7793b 100644 --- a/Frontend/src/components/LoginForm.jsx +++ b/Frontend/src/components/LoginForm.jsx @@ -1,8 +1,11 @@ import React, { useState } from "react"; import axios from "axios"; +import { Chrome } from "lucide-react"; +import { useTheme } from "../context/ThemeContext"; export default function LoginForm() { const API_URL = import.meta.env.VITE_Backend_API_URL; + const { isDark } = useTheme(); const [data, setData] = useState({ email: "", @@ -29,7 +32,11 @@ export default function LoginForm() { name="email" onChange={handle} placeholder="Enter your email" - className="w-full px-4 py-3 rounded-lg bg-black/30 border border-white/10 text-white" + className={`w-full px-4 py-3 rounded-lg border transition ${ + isDark + ? 'bg-black/30 border-white/10 text-white placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500' + : 'bg-white/60 border-blue-200/50 text-black placeholder-gray-600 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500' + }`} required /> @@ -38,37 +45,41 @@ export default function LoginForm() { name="password" onChange={handle} placeholder="Password" - className="w-full px-4 py-3 rounded-lg bg-black/30 border border-white/10 text-white" + className={`w-full px-4 py-3 rounded-lg border transition ${ + isDark + ? 'bg-black/30 border-white/10 text-white placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500' + : 'bg-white/60 border-blue-200/50 text-black placeholder-gray-600 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500' + }`} required /> - -
-
- OR SIGN IN WITH -
+
+
+ OR CONTINUE WITH +
-
- - - -
+ ); } diff --git a/Frontend/src/components/Navbar.jsx b/Frontend/src/components/Navbar.jsx index 636f5ce..602eaa4 100644 --- a/Frontend/src/components/Navbar.jsx +++ b/Frontend/src/components/Navbar.jsx @@ -1,16 +1,14 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { Link } from "react-router-dom"; import { Menu, X, ShieldCheck } from "lucide-react"; import { Classic } from "@theme-toggles/react"; import "@theme-toggles/react/css/Classic.css"; import { LiquidButton } from "@/components/ui/shadcn-io/liquid-button"; +import { useTheme } from "@/context/ThemeContext"; export default function Navbar({ locoScrollRef }) { const [isOpen, setIsOpen] = useState(false); - const [isDark, setIsDark] = useState(() => { - const savedTheme = localStorage.getItem("theme"); - return savedTheme === "dark"; - }); + const { isDark, toggleTheme } = useTheme(); const navLinks = [ { name: "Features", target: "#features" }, @@ -25,18 +23,8 @@ export default function Navbar({ locoScrollRef }) { } }; - useEffect(() => { - if (isDark) { - document.body.setAttribute("data-theme", "dark"); - localStorage.setItem("theme", "dark"); - } else { - document.body.setAttribute("data-theme", "light"); - localStorage.setItem("theme", "light"); - } - }, [isDark]); - function handleTheme() { - setIsDark((prev) => !prev); + toggleTheme(); } return ( diff --git a/Frontend/src/components/RoleSelector.jsx b/Frontend/src/components/RoleSelector.jsx index 1a96d7f..20f1d33 100644 --- a/Frontend/src/components/RoleSelector.jsx +++ b/Frontend/src/components/RoleSelector.jsx @@ -1,7 +1,9 @@ import React from "react"; import { User, BriefcaseMedical, Hospital } from "lucide-react"; +import { useTheme } from "../context/ThemeContext"; export default function RoleSelector({ role, setRole }) { + const { isDark } = useTheme(); const items = [ { key: "patient", label: "Patient", icon: }, { key: "doctor", label: "Doctor", icon: }, @@ -14,16 +16,17 @@ export default function RoleSelector({ role, setRole }) {
setRole(r.key)} - className={`cursor-pointer px-4 py-5 rounded-xl flex flex-col items-center gap-2 - backdrop-blur-lg border transition-all - ${ - role === r.key + className={`cursor-pointer px-4 py-5 rounded-xl flex flex-col items-center gap-2 backdrop-blur-lg border transition-all ${ + role === r.key + ? isDark ? "bg-white/10 border-white/30 text-white shadow-lg scale-[1.02]" - : "bg-black/40 border-white/10 text-neutral-300 hover:bg-black/50" - } - `} + : "bg-blue-600/20 border-blue-400/50 text-blue-700 shadow-lg scale-[1.02]" + : isDark + ? "bg-black/40 border-white/10 text-neutral-300 hover:bg-black/50" + : "bg-white/40 border-blue-200/30 text-gray-700 hover:bg-white/60" + }`} > -
{r.icon}
+
{r.icon}
{r.label}
))} diff --git a/Frontend/src/components/SignupForm.jsx b/Frontend/src/components/SignupForm.jsx index a1600cc..87d8a68 100644 --- a/Frontend/src/components/SignupForm.jsx +++ b/Frontend/src/components/SignupForm.jsx @@ -1,11 +1,14 @@ import React, { useState } from "react"; import axios from "axios"; import RoleSelector from "./RoleSelector"; +import { Loader2, Chrome } from "lucide-react"; // Icons +import { useTheme } from "../context/ThemeContext"; export default function SignupForm() { const API_URL = import.meta.env.VITE_Backend_API_URL; - + const { isDark } = useTheme(); const [role, setRole] = useState("patient"); + const [loading, setLoading] = useState(false); // Add loading state const [data, setData] = useState({ firstName: "", @@ -24,6 +27,7 @@ export default function SignupForm() { const submit = async (e) => { e.preventDefault(); + setLoading(true); // Start loading const payload = { name: `${data.firstName} ${data.lastName}`.trim(), @@ -33,139 +37,165 @@ export default function SignupForm() { gender: data.gender, role, ...(role === "doctor" - ? { - specialization: data.specialization, - licenseNumber: data.licenseNumber, - } + ? { specialization: data.specialization, licenseNumber: data.licenseNumber } : {}), }; try { await axios.post(`${API_URL}/auth/signup`, payload); alert("Account created!"); + // You might want to redirect here: window.location.href = '/dashboard'; } catch (err) { alert(err?.response?.data?.message || "Signup failed"); + } finally { + setLoading(false); // Stop loading } }; return ( <> - {/* Role Selector (only once!) */} -
- {/* Name */} +
- {/* Email */} - {/* DOB + Gender */}
- {/* Password */} - {/* Doctor-only fields */} {role === "doctor" && ( -
+
)} - {/* Submit button */} - - {/* Divider */} -
-
- OR SIGN IN WITH -
+
+
+ Or continue with +
- {/* Social */} -
- - - -
+ ); -} - +} \ No newline at end of file diff --git a/Frontend/src/context/ThemeContext.jsx b/Frontend/src/context/ThemeContext.jsx new file mode 100644 index 0000000..c6e2eba --- /dev/null +++ b/Frontend/src/context/ThemeContext.jsx @@ -0,0 +1,40 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; + +const ThemeContext = createContext(); + +export const ThemeProvider = ({ children }) => { + const [isDark, setIsDark] = useState(() => { + const savedTheme = localStorage.getItem("theme"); + return savedTheme === "dark"; + }); + + useEffect(() => { + if (isDark) { + document.documentElement.setAttribute("data-theme", "dark"); + document.documentElement.classList.add("dark"); + localStorage.setItem("theme", "dark"); + } else { + document.documentElement.setAttribute("data-theme", "light"); + document.documentElement.classList.remove("dark"); + localStorage.setItem("theme", "light"); + } + }, [isDark]); + + const toggleTheme = () => { + setIsDark((prev) => !prev); + }; + + return ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useTheme must be used within ThemeProvider"); + } + return context; +}; diff --git a/Frontend/src/index.css b/Frontend/src/index.css index 36db0da..a7f5c74 100644 --- a/Frontend/src/index.css +++ b/Frontend/src/index.css @@ -9,6 +9,37 @@ --color-greenish: #00c6ad; } +/* Slide animations for form switching */ +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(100px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(-100px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.slide-in-left { + animation: slideInLeft 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.slide-in-right { + animation: slideInRight 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + .reveal { opacity: 0; visibility: hidden; diff --git a/Frontend/src/pages/SignUp/RoleSelector.jsx b/Frontend/src/pages/SignUp/RoleSelector.jsx deleted file mode 100644 index dd4f1cf..0000000 --- a/Frontend/src/pages/SignUp/RoleSelector.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import { User, Hospital, BriefcaseMedical } from "lucide-react"; - -export default function RoleSelector({ role, setRole }) { - const roles = [ - { key: "patient", icon: , label: "Patient" }, - { key: "doctor", icon: , label: "Doctor" }, - { key: "hospital", icon: , label: "Hospital" }, - ]; - - return ( -
- {roles.map((r) => ( -
setRole(r.key)} - className={`cursor-pointer text-center p-6 rounded-xl border transition-all duration-300 transform ${ - role === r.key - ? "bg-blue-100 border-blue-500 scale-110 shadow-md" - : "bg-white/10 border-transparent hover:bg-white/20 hover:scale-105" - }`} - > -
-
{r.icon}
-

{r.label}

-
-
- ))} -
- ); -} diff --git a/Frontend/src/pages/SignUp/SignupForm.jsx b/Frontend/src/pages/SignUp/SignupForm.jsx deleted file mode 100644 index d167c6b..0000000 --- a/Frontend/src/pages/SignUp/SignupForm.jsx +++ /dev/null @@ -1,100 +0,0 @@ -export default function SignupForm({ - role, - formData, - setFormData, - handleSubmit, -}) { - const handleChange = (e) => { - setFormData({ ...formData, [e.target.name]: e.target.value }); - }; - - return ( -
-

- {role - ? `Sign up as a ${role.charAt(0).toUpperCase() + role.slice(1)}` - : "Create Account"} -

- -
- - - -
- - {/* Patient and Doctor Fields*/} - {(role === "patient" || role === "doctor") && ( -
- - -
- )} - - {/* Doctor Fields */} - {role === "doctor" && ( -
- - -
- )} - - -
- ); -} diff --git a/Frontend/src/pages/SignUp/SignupPage.jsx b/Frontend/src/pages/SignUp/SignupPage.jsx deleted file mode 100644 index f6c1cd7..0000000 --- a/Frontend/src/pages/SignUp/SignupPage.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useState, useEffect } from "react"; -import axios from "axios"; -import RoleSelector from "./RoleSelector"; -import SignupForm from "./SignupForm"; -import Typewriter from "typewriter-effect"; - -export default function SignupPage() { - const [role, setRole] = useState(""); - const [formData, setFormData] = useState({}); - const API_URL = import.meta.env.VITE_Backend_API_URL; - - const handleSubmit = async (e) => { - e.preventDefault(); - try { - await axios.post(`${API_URL}/auth/signup`, { ...formData, role }); - alert("Signup successful!"); - } catch (err) { - alert(err.response?.data?.message || "Signup failed"); - } - }; - - return ( -
- {/* Header */} -
- - Logo - MediChain - -
- - {/* Card */} -
-

Create Account

-
- -
- - - - {role && ( - - )} -
-
- ); -} diff --git a/Frontend/src/pages/SignupLoginPage.jsx b/Frontend/src/pages/SignupLoginPage.jsx index 490e947..375cfba 100644 --- a/Frontend/src/pages/SignupLoginPage.jsx +++ b/Frontend/src/pages/SignupLoginPage.jsx @@ -3,65 +3,126 @@ import SignupForm from "../components/SignupForm"; import LoginForm from "../components/LoginForm"; import BackgroundDNA from "../components/BackgroundDNA"; import Navbar from "../components/Navbar"; +import { useTheme } from "../context/ThemeContext"; -/** - * SignupLoginPage.jsx - * - Uses BackgroundDNA for extreme DNA lab animation - * - Modal card sits on top (z-10) - */ export default function SignupLoginPage() { const [mode, setMode] = useState("signup"); - const [open, setOpen] = useState(true); - - if (!open) return null; + const { isDark } = useTheme(); return ( -
- - - {/* Animated background */} - + // Change 1: Allow scrolling. We use min-h-screen instead of fixed inset-0 for the container content +
+ + - {/* subtle dim overlay to increase contrast on modal */} -
+ {/* Background stays fixed so it doesn't scroll with the form */} +
+ {isDark && } + {/* Overlay */} +
+
- {/* modal container */} -
-
- {/* Header row: tabs + optional vector logo */} -
+ {/* Modal Container - Centered but allows scrolling if height is too big */} +
+ + {/* The Card */} +
+ + {/* Header row */} +
-
MediChain
-
- - - +
+ {/* Toggle Buttons */} +
+ + +
-

+ {/* Dynamic Title */} +

{mode === "signup" ? "Create an account" : "Welcome back"}

+

+ {mode === "signup" ? "Enter your details to get started." : "Please enter your details to sign in."} +

- {mode === "signup" ? : } -
+ {/* Render Form */} +
+ {mode === "signup" ? : } +
-
- {mode === "signup" ? "By creating an account, you agree to our Terms & Service" : "Don't have an account? Switch to Sign up"} + {/* Footer Toggle Text */} +
+ {mode === "signup" ? ( + <> + Already have an account?{" "} + + + ) : ( + <> + Don't have an account?{" "} + + + )} +
); -} +} \ No newline at end of file