From 14c1639d64053e5dece46e74cf21f6ce1abbb49c Mon Sep 17 00:00:00 2001 From: BHUVESH ROHANKAR Date: Mon, 21 Sep 2026 01:33:28 +0530 Subject: [PATCH 1/2] Login page UI --- src/components/auth/AuthCard.jsx | 73 ++++++++++++ src/components/auth/LoginForm.jsx | 175 +++++++++++++++++++++++++++++ src/components/auth/SignUpForm.jsx | 156 +++++++++++++++++++++++++ src/pages/Login.jsx | 110 +++++------------- 4 files changed, 432 insertions(+), 82 deletions(-) create mode 100644 src/components/auth/AuthCard.jsx create mode 100644 src/components/auth/SignUpForm.jsx diff --git a/src/components/auth/AuthCard.jsx b/src/components/auth/AuthCard.jsx new file mode 100644 index 0000000..29a15f4 --- /dev/null +++ b/src/components/auth/AuthCard.jsx @@ -0,0 +1,73 @@ +import { useState } from 'react'; +import { X } from 'lucide-react'; +import HoloCard from '../ui/HoloCard'; +import LoginForm from './LoginForm'; +import SignUpForm from './SignUpForm'; + +/** + * AuthCard — single container that toggles between the "Log in" and + * "Create Account" views, matching the reference screenshots structurally + * while using the site's existing HoloCard / NeoButton / design tokens. + * + * Usage: + * { ... }} + * onSignUp={async ({ email, password }) => { ... }} + * onGoogleAuth={() => { ... }} + * onForgotPassword={() => { ... }} + * /> + * + * Pass `onClose` if this is rendered inside a modal/dialog — it renders + * the top-right "X" seen in the reference screenshots. Omit it (e.g. when + * used as a full page like pages/Login.jsx) and the close button is hidden. + * + * @param {object} props + * @param {'login'|'signup'} [props.initialView='login'] + * @param {() => void} [props.onClose] + * @param {(credentials: { identifier: string, password: string }) => void|Promise} [props.onLogin] + * @param {(data: { email: string, password: string }) => void|Promise} [props.onSignUp] + * @param {() => void} [props.onGoogleAuth] + * @param {() => void} [props.onForgotPassword] + * @param {string} [props.className] + */ +export default function AuthCard({ + initialView = 'login', + onClose, + onLogin, + onSignUp, + onGoogleAuth, + onForgotPassword, + className = '', +}) { + const [view, setView] = useState(initialView); + + return ( + + {onClose && ( + + )} + + {view === 'login' ? ( + setView('signup')} + onSubmit={onLogin} + onGoogleAuth={onGoogleAuth} + onForgotPassword={onForgotPassword} + /> + ) : ( + setView('login')} + onSubmit={onSignUp} + onGoogleAuth={onGoogleAuth} + /> + )} + + ); +} \ No newline at end of file diff --git a/src/components/auth/LoginForm.jsx b/src/components/auth/LoginForm.jsx index e69de29..c3dacbb 100644 --- a/src/components/auth/LoginForm.jsx +++ b/src/components/auth/LoginForm.jsx @@ -0,0 +1,175 @@ +import { useState } from 'react'; +import { Lock, Mail, Eye, EyeOff } from 'lucide-react'; +import { FcGoogle } from 'react-icons/fc'; +import NeoButton from '../ui/NeoButton'; + +/** + * LoginForm — "Log in" view. + * + * Structure (content only — styling pulled entirely from the existing + * RTF design system: HoloCard-compatible spacing, `input` field classes + * from Login.jsx, NeoButton, cyan accents): + * 1. Header — title + "New user? Register Now" toggle + * 2. Full-width "Continue with Google" button + * 3. "or" divider + * 4. Email field + * 5. Password field w/ show/hide toggle + * 6. "Forgot password" link (left-aligned) + * 7. Full-width "Sign In" submit button + * + * @param {object} props + * @param {() => void} props.onSwitchToSignup - called when "Register Now" is clicked + * @param {(credentials: { email: string, password: string }) => void|Promise} [props.onSubmit] + * @param {() => void} [props.onGoogleAuth] + * @param {() => void} [props.onForgotPassword] + */ +export default function LoginForm({ + onSwitchToSignup, + onSubmit, + onGoogleAuth, + onForgotPassword, +}) { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + + if (!email.trim() || !password) { + setError('Please fill in both fields.'); + return; + } + + if (!onSubmit) { + // No handler wired up yet — mirrors the existing placeholder + // behaviour in pages/Login.jsx until auth is connected. + setError('Member portal coming soon. Contact RTF leader for access.'); + return; + } + + try { + setIsSubmitting(true); + await onSubmit({ email: email.trim(), password }); + } catch (err) { + setError(err?.message || 'Unable to sign in. Please try again.'); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+ {/* Header */} +
+

Log in

+

+ New user?{' '} + +

+
+ + {/* Continue with Google */} + + + {/* Divider */} +
+
+ or +
+
+ +
+ {/* Email */} +
+ +
+ + setEmail(e.target.value)} + placeholder="you@gcoea.ac.in" + autoComplete="email" + className="w-full pl-10 pr-4 py-3 bg-elevated border border-border rounded-button text-sm text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 transition-all" + /> +
+
+ + {/* Password */} +
+ +
+ + setPassword(e.target.value)} + placeholder="Enter password" + autoComplete="current-password" + className="w-full pl-10 pr-11 py-3 bg-elevated border border-border rounded-button text-sm text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 transition-all" + /> + +
+
+ + {/* Forgot password */} +
+ {onForgotPassword ? ( + + ) : ( + Forgot password + )} +
+ + {/* Error */} + {error && ( +
+

{error}

+
+ )} + + + {isSubmitting ? 'Signing in…' : 'Sign In'} + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/auth/SignUpForm.jsx b/src/components/auth/SignUpForm.jsx new file mode 100644 index 0000000..abb5319 --- /dev/null +++ b/src/components/auth/SignUpForm.jsx @@ -0,0 +1,156 @@ +import { useState } from 'react'; +import { Lock, Mail, Eye, EyeOff } from 'lucide-react'; +import { FcGoogle } from 'react-icons/fc'; +import NeoButton from '../ui/NeoButton'; + +/** + * SignUpForm — "Create Account" view. + * + * NOTE: this is intentionally a separate, lightweight component from + * `RegisterForm.jsx` (which drives the full member-application flow with + * enrollment number / domain / etc. via `authService.registerUser`). This + * one matches the simple email+password "Create Account" screenshot the + * user asked for. If both are meant to submit to the same backend route, + * wire `onSubmit` here to whichever service function is appropriate. + * + * Structure: + * 1. Header — title + "Already have an account? Log in" toggle + * 2. Email field + * 3. Password field w/ show/hide toggle + * 4. Full-width "Sign Up" submit button + * 5. "or" divider + * 6. Full-width "Continue with Google" button + * + * @param {object} props + * @param {() => void} props.onSwitchToLogin - called when "Log in" is clicked + * @param {(data: { email: string, password: string }) => void|Promise} [props.onSubmit] + * @param {() => void} [props.onGoogleAuth] + */ +export default function SignUpForm({ onSwitchToLogin, onSubmit, onGoogleAuth }) { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + + if (!email.trim() || !password) { + setError('Please fill in both fields.'); + return; + } + + if (!onSubmit) { + setError('Member portal coming soon. Contact RTF leader for access.'); + return; + } + + try { + setIsSubmitting(true); + await onSubmit({ email: email.trim(), password }); + } catch (err) { + setError(err?.message || 'Unable to create account. Please try again.'); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+ {/* Header */} +
+

Create Account

+

+ Already have an account?{' '} + +

+
+ +
+ {/* Email */} +
+ +
+ + setEmail(e.target.value)} + placeholder="you@gcoea.ac.in" + autoComplete="email" + className="w-full pl-10 pr-4 py-3 bg-elevated border border-border rounded-button text-sm text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 transition-all" + /> +
+
+ + {/* Password */} +
+ +
+ + setPassword(e.target.value)} + placeholder="Enter password" + autoComplete="new-password" + className="w-full pl-10 pr-11 py-3 bg-elevated border border-border rounded-button text-sm text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 transition-all" + /> + +
+
+ + {/* Error */} + {error && ( +
+

{error}

+
+ )} + + + {isSubmitting ? 'Creating account…' : 'Sign Up'} + +
+ + {/* Divider */} +
+
+ or +
+
+ + {/* Continue with Google */} + +
+ ); +} \ No newline at end of file diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index a0df498..f82c927 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,21 +1,22 @@ -import { useState } from 'react'; import { motion } from 'framer-motion'; import { pageTransition } from '../lib/animations'; -import HoloCard from '../components/ui/HoloCard'; -import NeoButton from '../components/ui/NeoButton'; -import { Lock, Mail, Eye, EyeOff } from 'lucide-react'; +import AuthCard from '../components/auth/AuthCard'; import rtfLogo from '../assets/images/rtf-logo-img.jpg'; export default function Login() { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [error, setError] = useState(''); + // TODO: wire these up to real auth once the backend route is ready. + // AuthCard already handles its own loading/error state — just make + // these throw (or reject) on failure and AuthCard will surface it. + const handleLogin = async ({ identifier, password }) => { + throw new Error('Member portal coming soon. Contact RTF leader for access.'); + }; + + const handleSignUp = async ({ email, password }) => { + throw new Error('Member portal coming soon. Contact RTF leader for access.'); + }; - const handleSubmit = (e) => { - e.preventDefault(); - // Placeholder — wire to Firebase / Supabase / custom backend - setError('Member portal coming soon. Contact RTF leader for access.'); + const handleGoogleAuth = () => { + // TODO: hook up Google OAuth (Firebase/Supabase/custom). }; return ( @@ -33,81 +34,26 @@ export default function Login() {
RTF Logo
-

Member Login

+

Member Access

Access the RTF member portal for team resources, schedules, and internal tools.

- -
- {/* Email */} -
- -
- - setEmail(e.target.value)} - placeholder="you@gcoea.ac.in" - className="w-full pl-10 pr-4 py-3 bg-elevated border border-border rounded-button text-sm text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 transition-all" - /> -
-
- - {/* Password */} -
- -
- - setPassword(e.target.value)} - placeholder="••••••••" - className="w-full pl-10 pr-11 py-3 bg-elevated border border-border rounded-button text-sm text-text-primary placeholder:text-text-muted/50 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 transition-all" - /> - -
-
- - {/* Error */} - {error && ( -
-

{error}

-
- )} - - - SIGN IN - -
- -
-

- Not a member?{' '} - - Contact us to join RTF - -

-
-
+ + +
+

+ Not a member?{' '} + + Contact us to join RTF + +

+
); From 32c7490bfa94940c297265ca0ad93b45ac503bcb Mon Sep 17 00:00:00 2001 From: BHUVESH ROHANKAR Date: Sat, 26 Sep 2026 03:54:21 +0530 Subject: [PATCH 2/2] updated login page --- package-lock.json | 495 +++++++++++++++++++++++++-- package.json | 7 +- src/components/auth/AuthCard.jsx | 87 +++-- src/components/auth/LoginForm.jsx | 76 ++-- src/components/auth/RegisterForm.jsx | 309 ++++++++++++----- src/components/auth/SignUpForm.jsx | 156 --------- src/pages/Login.jsx | 24 +- src/utils/validators.js | 55 ++- 8 files changed, 826 insertions(+), 383 deletions(-) delete mode 100644 src/components/auth/SignUpForm.jsx diff --git a/package-lock.json b/package-lock.json index fa0d704..d496d70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "name": "rtf-website", "version": "0.0.0", "dependencies": { + "@hookform/resolvers": "^5.9.1", "@use-gesture/react": "^10.3.1", "autoprefixer": "^10.4.27", + "axios": "^1.20.0", "face-api.js": "^0.22.2", "framer-motion": "^12.40.0", "gsap": "^3.15.0", @@ -20,11 +22,12 @@ "postprocessing": "^6.39.1", "react": "^19.2.0", "react-dom": "^19.2.0", - "react-hook-form": "^7.71.2", + "react-hook-form": "^7.88.0", "react-icons": "^5.6.0", "react-router-dom": "^7.13.1", "tailwindcss": "^3.4.19", - "three": "^0.184.0" + "three": "^0.184.0", + "zod": "^4.6.5" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -881,6 +884,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -894,6 +914,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { "version": "9.39.4", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", @@ -931,6 +958,116 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.9.1.tgz", + "integrity": "sha512-7b7vsbraJxKgjVSA1Nur9tLwj539WGJUBLA7QNvXnFoT2pM5Z7G+6rlukk4B2/QrTZy6huRtH6wKeESPKuIr6w==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "@sinclair/typebox": ">=0.25.24", + "@standard-schema/spec": "^1.0.0", + "@typeschema/main": ">=0.13.7", + "@vinejs/vine": "^2.0.0 || ^3.0.0 || ^4.0.0", + "ajv": "^8.12.0", + "ajv-errors": "^3.0.0", + "ajv-formats": "^2.1.1", + "arktype": "^2.0.0", + "ata-validator": "^1.2.0", + "class-transformer": ">=0.4.0", + "class-validator": ">=0.12.0", + "computed-types": "^1.0.0", + "effect": "^3.10.3", + "fluentvalidation-ts": "^3.0.0", + "fp-ts": "^2.7.0", + "io-ts": "^2.0.0", + "joi": "^17.0.0 || ^18.0.0", + "nope-validator": ">=0.12.0", + "react-hook-form": "^7.55.0", + "superstruct": ">=0.12.0", + "typanion": "^3.3.2", + "valibot": ">=0.31.0 || ^1.0.0-beta.4 || ^1.0.0-rc", + "vest": ">=6.0.0", + "yup": "^1.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@sinclair/typebox": { + "optional": true + }, + "@standard-schema/spec": { + "optional": true + }, + "@typeschema/main": { + "optional": true + }, + "@vinejs/vine": { + "optional": true + }, + "ajv": { + "optional": true + }, + "ajv-errors": { + "optional": true + }, + "ajv-formats": { + "optional": true + }, + "arktype": { + "optional": true + }, + "ata-validator": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + }, + "computed-types": { + "optional": true + }, + "effect": { + "optional": true + }, + "fluentvalidation-ts": { + "optional": true + }, + "fp-ts": { + "optional": true + }, + "io-ts": { + "optional": true + }, + "joi": { + "optional": true + }, + "nope-validator": { + "optional": true + }, + "superstruct": { + "optional": true + }, + "typanion": { + "optional": true + }, + "valibot": { + "optional": true + }, + "vest": { + "optional": true + }, + "yup": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1421,6 +1558,12 @@ "win32" ] }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@tensorflow/tfjs-core": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.7.0.tgz", @@ -1603,21 +1746,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "debug": "4" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">= 6.0.0" } }, "node_modules/ansi-styles": { @@ -1680,6 +1818,12 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.27", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", @@ -1716,6 +1860,18 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1803,6 +1959,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1915,6 +2084,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1989,7 +2170,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2010,6 +2190,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2022,12 +2211,71 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.307", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "license": "ISC" }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", @@ -2212,6 +2460,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -2296,7 +2568,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -2430,6 +2702,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -2503,6 +2811,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2528,6 +2873,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gsap": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", @@ -2544,10 +2901,37 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2573,6 +2957,19 @@ "hermes-estree": "0.25.1" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2723,13 +3120,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -2865,6 +3255,15 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2899,6 +3298,27 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -2931,7 +3351,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -3318,6 +3737,15 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3370,9 +3798,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.71.2", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", - "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", + "version": "7.88.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.88.0.tgz", + "integrity": "sha512-QRaLOWhX93YCnMiRfnOFRSwWXZNt8qhm2JTZwypoDvKpSffTJHmpzMXt8U6PV5UThvL3IiiLDUWe2nHMtz6Mmw==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3988,10 +4416,9 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index feb6dc7..e2a746c 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "@hookform/resolvers": "^5.9.1", "@use-gesture/react": "^10.3.1", "autoprefixer": "^10.4.27", + "axios": "^1.20.0", "face-api.js": "^0.22.2", "framer-motion": "^12.40.0", "gsap": "^3.15.0", @@ -22,11 +24,12 @@ "postprocessing": "^6.39.1", "react": "^19.2.0", "react-dom": "^19.2.0", - "react-hook-form": "^7.71.2", + "react-hook-form": "^7.88.0", "react-icons": "^5.6.0", "react-router-dom": "^7.13.1", "tailwindcss": "^3.4.19", - "three": "^0.184.0" + "three": "^0.184.0", + "zod": "^4.6.5" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/src/components/auth/AuthCard.jsx b/src/components/auth/AuthCard.jsx index 29a15f4..ca9ef93 100644 --- a/src/components/auth/AuthCard.jsx +++ b/src/components/auth/AuthCard.jsx @@ -2,31 +2,34 @@ import { useState } from 'react'; import { X } from 'lucide-react'; import HoloCard from '../ui/HoloCard'; import LoginForm from './LoginForm'; -import SignUpForm from './SignUpForm'; +import RegisterForm from './RegisterForm'; /** * AuthCard — single container that toggles between the "Log in" and - * "Create Account" views, matching the reference screenshots structurally - * while using the site's existing HoloCard / NeoButton / design tokens. + * "Create Account" views, using the site's existing HoloCard / NeoButton + * design tokens throughout. + * + * - Login is RTF ID + password only (see LoginForm.jsx). + * - "Create Account" uses the existing member-application RegisterForm.jsx + * (name, collegeEnrollmentNo, collegeEmail, personalEmail, branch, + * yearOfPassing, phone, domain, password) — same component the /register + * page already drives, so both entry points submit through one + * validated path (see utils/validators.js + services/authService.js). * * Usage: * { ... }} - * onSignUp={async ({ email, password }) => { ... }} - * onGoogleAuth={() => { ... }} + * onLogin={async ({ rtfId, password }) => { ... }} * onForgotPassword={() => { ... }} * /> * * Pass `onClose` if this is rendered inside a modal/dialog — it renders - * the top-right "X" seen in the reference screenshots. Omit it (e.g. when - * used as a full page like pages/Login.jsx) and the close button is hidden. + * the top-right "X". Omit it (e.g. used as a full page like pages/Login.jsx) + * and the close button is hidden. * * @param {object} props * @param {'login'|'signup'} [props.initialView='login'] * @param {() => void} [props.onClose] - * @param {(credentials: { identifier: string, password: string }) => void|Promise} [props.onLogin] - * @param {(data: { email: string, password: string }) => void|Promise} [props.onSignUp] - * @param {() => void} [props.onGoogleAuth] + * @param {(credentials: { rtfId: string, password: string }) => void|Promise} [props.onLogin] * @param {() => void} [props.onForgotPassword] * @param {string} [props.className] */ @@ -34,15 +37,24 @@ export default function AuthCard({ initialView = 'login', onClose, onLogin, - onSignUp, - onGoogleAuth, onForgotPassword, className = '', }) { const [view, setView] = useState(initialView); + const [successMessage, setSuccessMessage] = useState(null); + + const switchToLogin = () => { + setSuccessMessage(null); + setView('login'); + }; return ( - + {onClose && ( +
+ )} + + {view === 'signup' && !successMessage && ( +
+
+

Create Account

+

+ For existing RTF members only +

+

+ Already have an account?{' '} + +

+
+ +
)} ); diff --git a/src/components/auth/LoginForm.jsx b/src/components/auth/LoginForm.jsx index c3dacbb..2f12ee4 100644 --- a/src/components/auth/LoginForm.jsx +++ b/src/components/auth/LoginForm.jsx @@ -1,6 +1,5 @@ import { useState } from 'react'; -import { Lock, Mail, Eye, EyeOff } from 'lucide-react'; -import { FcGoogle } from 'react-icons/fc'; +import { Lock, User, Eye, EyeOff } from 'lucide-react'; import NeoButton from '../ui/NeoButton'; /** @@ -9,27 +8,21 @@ import NeoButton from '../ui/NeoButton'; * Structure (content only — styling pulled entirely from the existing * RTF design system: HoloCard-compatible spacing, `input` field classes * from Login.jsx, NeoButton, cyan accents): - * 1. Header — title + "New user? Register Now" toggle - * 2. Full-width "Continue with Google" button - * 3. "or" divider - * 4. Email field - * 5. Password field w/ show/hide toggle - * 6. "Forgot password" link (left-aligned) - * 7. Full-width "Sign In" submit button + * 1. Header — title + "Activate Account" / "Register Account" toggle + * 2. RTF ID field + * 3. Password field w/ show/hide toggle + * 4. "Forgot password" link (left-aligned) + * 5. Full-width "Sign In" submit button + * + * No third-party (Google) sign-in — login is RTF ID + password only. * * @param {object} props - * @param {() => void} props.onSwitchToSignup - called when "Register Now" is clicked - * @param {(credentials: { email: string, password: string }) => void|Promise} [props.onSubmit] - * @param {() => void} [props.onGoogleAuth] + * @param {() => void} props.onSwitchToSignup - called when account registration is clicked + * @param {(credentials: { rtfId: string, password: string }) => void|Promise} [props.onSubmit] * @param {() => void} [props.onForgotPassword] */ -export default function LoginForm({ - onSwitchToSignup, - onSubmit, - onGoogleAuth, - onForgotPassword, -}) { - const [email, setEmail] = useState(''); +export default function LoginForm({ onSwitchToSignup, onSubmit, onForgotPassword }) { + const [rtfId, setRtfId] = useState(''); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [error, setError] = useState(''); @@ -39,7 +32,7 @@ export default function LoginForm({ e.preventDefault(); setError(''); - if (!email.trim() || !password) { + if (!rtfId.trim() || !password) { setError('Please fill in both fields.'); return; } @@ -53,7 +46,7 @@ export default function LoginForm({ try { setIsSubmitting(true); - await onSubmit({ email: email.trim(), password }); + await onSubmit({ rtfId: rtfId.trim(), password }); } catch (err) { setError(err?.message || 'Unable to sign in. Please try again.'); } finally { @@ -67,50 +60,33 @@ export default function LoginForm({

Log in

- New user?{' '} + Existing RTF member?{' '}

- {/* Continue with Google */} - - - {/* Divider */} -
-
- or -
-
-
- {/* Email */} + {/* RTF ID */}
-