From c8d414ccd6fbd757e9ab8ad1246f37e4c2354ccf Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sun, 11 Jan 2026 21:21:34 +0530 Subject: [PATCH] auth move --- src/app/(agent)/layout.tsx | 7 + src/app/(auth)/forgot-password/page.tsx | 110 +++++++ src/app/(auth)/layout.tsx | 21 ++ src/app/(auth)/login/page.tsx | 184 ++++++++++++ src/app/{ => (auth)}/reset-password/page.tsx | 20 +- src/app/(auth)/signup/page.tsx | 287 ++++++++++++++++++ src/app/{ => (auth)}/verify-email/page.tsx | 20 +- src/app/(user)/layout.tsx | 7 + src/app/forgot-password/page.tsx | 122 -------- src/app/login/page.tsx | 195 ------------ src/app/signup/page.tsx | 298 ------------------- 11 files changed, 622 insertions(+), 649 deletions(-) create mode 100644 src/app/(agent)/layout.tsx create mode 100644 src/app/(auth)/forgot-password/page.tsx create mode 100644 src/app/(auth)/layout.tsx create mode 100644 src/app/(auth)/login/page.tsx rename src/app/{ => (auth)}/reset-password/page.tsx (94%) create mode 100644 src/app/(auth)/signup/page.tsx rename src/app/{ => (auth)}/verify-email/page.tsx (90%) create mode 100644 src/app/(user)/layout.tsx delete mode 100644 src/app/forgot-password/page.tsx delete mode 100644 src/app/login/page.tsx delete mode 100644 src/app/signup/page.tsx diff --git a/src/app/(agent)/layout.tsx b/src/app/(agent)/layout.tsx new file mode 100644 index 0000000..3970146 --- /dev/null +++ b/src/app/(agent)/layout.tsx @@ -0,0 +1,7 @@ +export default function AgentLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(auth)/forgot-password/page.tsx b/src/app/(auth)/forgot-password/page.tsx new file mode 100644 index 0000000..8b7bdc7 --- /dev/null +++ b/src/app/(auth)/forgot-password/page.tsx @@ -0,0 +1,110 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { authService, AuthService } from '@/services'; + +export default function ForgotPasswordPage() { + const [email, setEmail] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [submitted, setSubmitted] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(''); + + try { + await authService.forgotPassword({ email }); + setSubmitted(true); + } catch (err) { + setError(AuthService.getErrorMessage(err)); + } finally { + setLoading(false); + } + }; + + if (submitted) { + return ( +
+
+
+ + + +
+

Check Your Email

+

+ We've sent a password reset link to {email}. Please check your inbox and spam folder. +

+ + Back to Login + + +
+
+ ); + } + + return ( + <> + {/* Forgot Password Heading */} +
+

Forgot Password

+

+ Enter your email address and we'll send you a link to reset your password +

+
+ + {/* Forgot Password Form */} +
+ {error && ( +
+ {error} +
+ )} + + {/* Email Input */} +
+ setEmail(e.target.value)} + required + className="w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all" + /> +
+ + {/* Submit Button */} + +
+ + {/* Back to Login */} +
+ + Back to Login + +
+ + ); +} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..08da1e6 --- /dev/null +++ b/src/app/(auth)/layout.tsx @@ -0,0 +1,21 @@ +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {/* Logo */} +
+ RE-QuestN +
+ {children} +
+
+ ); +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..a66ca75 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,184 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { signIn } from 'next-auth/react'; +import Link from 'next/link'; + +export default function LoginPage() { + const router = useRouter(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(''); + + try { + // First, validate credentials with backend to get proper error messages + const validateRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }), + }); + + const validateData = await validateRes.json(); + + if (!validateRes.ok) { + // Show the actual error message from backend + setError(validateData.message || 'Invalid email or password'); + setLoading(false); + return; + } + + // If validation passed, use NextAuth to create session + const result = await signIn('credentials', { + email, + password, + redirect: false, + }); + + setLoading(false); + + if (result?.error) { + setError('Login failed. Please try again.'); + } else { + router.push('/'); + router.refresh(); + } + } catch (err) { + setLoading(false); + setError('An error occurred. Please try again.'); + } + }; + + const handleSocialLogin = (provider: 'google' | 'facebook' | 'twitter') => { + signIn(provider, { callbackUrl: '/' }); + }; + + return ( + <> + {/* Login Heading */} +
+

Login

+

+ Sign in to your account to access your real estate dashboard and manage your properties +

+
+ + {/* Login Form */} +
+ {error && ( +
+ {error} +
+ )} + + {/* Email Input */} +
+ setEmail(e.target.value)} + required + className="w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all" + /> +
+ + {/* Password Input */} +
+ setPassword(e.target.value)} + required + className="w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all pr-14" + /> + +
+ + {/* Forgot Password Link */} +
+ + Forgot Password? + +
+ + {/* Login Button */} + +
+ + {/* Don't Have Account */} +
+ Don't Have An Account ? + + Sign Up + +
+ + {/* Divider */} +
+
+ Or +
+
+ + {/* Social Login Icons */} +
+ {/* Google */} + + + {/* X (Twitter) */} + + + {/* Facebook */} + +
+ + ); +} diff --git a/src/app/reset-password/page.tsx b/src/app/(auth)/reset-password/page.tsx similarity index 94% rename from src/app/reset-password/page.tsx rename to src/app/(auth)/reset-password/page.tsx index 72a3474..459c5d1 100644 --- a/src/app/reset-password/page.tsx +++ b/src/app/(auth)/reset-password/page.tsx @@ -220,22 +220,8 @@ function LoadingFallback() { export default function ResetPasswordPage() { return ( -
-
- {/* Logo */} -
- RE-QuestN -
- - {/* Content */} - }> - - -
-
+ }> + + ); } diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..c5ba4c6 --- /dev/null +++ b/src/app/(auth)/signup/page.tsx @@ -0,0 +1,287 @@ +'use client'; + +import { useState } from 'react'; +import { signIn } from 'next-auth/react'; +import Link from 'next/link'; +import { authService, AuthService, RegisterRequest } from '@/services'; + +type UserType = 'USER' | 'AGENT'; + +interface ValidationError { + field: string; + errors: string[]; +} + +export default function SignUpPage() { + const [userType, setUserType] = useState('USER'); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(''); + const [fieldErrors, setFieldErrors] = useState([]); + const [loading, setLoading] = useState(false); + const [registrationSuccess, setRegistrationSuccess] = useState(false); + + const getFieldError = (fieldName: string): string | null => { + const fieldError = fieldErrors.find((e) => e.field === fieldName); + return fieldError ? fieldError.errors[0] : null; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(''); + setFieldErrors([]); + + try { + const registerData: RegisterRequest = { + firstName, + lastName, + email, + password, + role: userType, + }; + + await authService.register(registerData); + + // Show success message - user must verify email before logging in + setRegistrationSuccess(true); + } catch (err) { + // Get field errors + const errors = AuthService.getFieldErrors(err); + if (errors.length > 0) { + setFieldErrors(errors); + setError('Please fix the errors below'); + } else { + setError(AuthService.getErrorMessage(err)); + } + } finally { + setLoading(false); + } + }; + + const handleSocialLogin = (provider: 'google' | 'facebook' | 'twitter') => { + signIn(provider, { callbackUrl: '/' }); + }; + + // Registration Success Message + if (registrationSuccess) { + return ( +
+
+
+ + + +
+

Check Your Email

+

+ We've sent a verification link to {email}. Please check your inbox and click the link to verify your account before logging in. +

+ + Go to Login + +

+ Didn't receive the email? Check your spam folder or contact support. +

+
+
+ ); + } + + return ( + <> + {/* Sign Up Heading */} +
+

Sign Up

+

+ Sign in or create an account to showcase your skills & expertise to millions of people starting their real estate journey today" +

+
+ + {/* User Type Toggle */} +
+
+ + +
+
+ + {/* Sign Up Form */} +
+ {error && ( +
+ {error} +
+ )} + + {/* First Name & Last Name */} +
+
+ setFirstName(e.target.value)} + required + className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all ${ + getFieldError('firstName') ? 'ring-2 ring-red-400' : '' + }`} + /> + {getFieldError('firstName') && ( +

{getFieldError('firstName')}

+ )} +
+
+ setLastName(e.target.value)} + className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all ${ + getFieldError('lastName') ? 'ring-2 ring-red-400' : '' + }`} + /> + {getFieldError('lastName') && ( +

{getFieldError('lastName')}

+ )} +
+
+ + {/* Email Input */} +
+ setEmail(e.target.value)} + required + className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all ${ + getFieldError('email') ? 'ring-2 ring-red-400' : '' + }`} + /> + {getFieldError('email') && ( +

{getFieldError('email')}

+ )} +
+ + {/* Password Input */} +
+
+ setPassword(e.target.value)} + required + className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all pr-14 ${ + getFieldError('password') ? 'ring-2 ring-red-400' : '' + }`} + /> + +
+ {getFieldError('password') && ( +

{getFieldError('password')}

+ )} +

+ Min 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special character +

+
+ + {/* Sign Up Button */} + +
+ + {/* Already Have Account */} +
+ Already Have An Account ? + + Login + +
+ + {/* Divider */} +
+
+ Or +
+
+ + {/* Social Login Icons */} +
+ {/* Google */} + + + {/* X (Twitter) */} + + + {/* Facebook */} + +
+ + ); +} diff --git a/src/app/verify-email/page.tsx b/src/app/(auth)/verify-email/page.tsx similarity index 90% rename from src/app/verify-email/page.tsx rename to src/app/(auth)/verify-email/page.tsx index 8285f63..eba0643 100644 --- a/src/app/verify-email/page.tsx +++ b/src/app/(auth)/verify-email/page.tsx @@ -115,22 +115,8 @@ function LoadingFallback() { export default function VerifyEmailPage() { return ( -
-
- {/* Logo */} -
- RE-QuestN -
- - {/* Content Card */} - }> - - -
-
+ }> + + ); } diff --git a/src/app/(user)/layout.tsx b/src/app/(user)/layout.tsx new file mode 100644 index 0000000..f709e7e --- /dev/null +++ b/src/app/(user)/layout.tsx @@ -0,0 +1,7 @@ +export default function UserLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/forgot-password/page.tsx b/src/app/forgot-password/page.tsx deleted file mode 100644 index 98e10ab..0000000 --- a/src/app/forgot-password/page.tsx +++ /dev/null @@ -1,122 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import Link from 'next/link'; -import { authService, AuthService } from '@/services'; - -export default function ForgotPasswordPage() { - const [email, setEmail] = useState(''); - const [error, setError] = useState(''); - const [loading, setLoading] = useState(false); - const [submitted, setSubmitted] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - setError(''); - - try { - await authService.forgotPassword({ email }); - setSubmitted(true); - } catch (err) { - setError(AuthService.getErrorMessage(err)); - } finally { - setLoading(false); - } - }; - - return ( -
-
- {/* Logo */} -
- RE-QuestN -
- - {submitted ? ( - /* Success Message */ -
-
-
- - - -
-

Check Your Email

-

- We've sent a password reset link to {email}. Please check your inbox and spam folder. -

- - Back to Login - - -
-
- ) : ( - <> - {/* Forgot Password Heading */} -
-

Forgot Password

-

- Enter your email address and we'll send you a link to reset your password -

-
- - {/* Forgot Password Form */} -
- {error && ( -
- {error} -
- )} - - {/* Email Input */} -
- setEmail(e.target.value)} - required - className="w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all" - /> -
- - {/* Submit Button */} - -
- - {/* Back to Login */} -
- - Back to Login - -
- - )} -
-
- ); -} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx deleted file mode 100644 index b0273d2..0000000 --- a/src/app/login/page.tsx +++ /dev/null @@ -1,195 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { signIn } from 'next-auth/react'; -import Link from 'next/link'; - -export default function LoginPage() { - const router = useRouter(); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [error, setError] = useState(''); - const [loading, setLoading] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - setError(''); - - try { - // First, validate credentials with backend to get proper error messages - const validateRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email, password }), - }); - - const validateData = await validateRes.json(); - - if (!validateRes.ok) { - // Show the actual error message from backend - setError(validateData.message || 'Invalid email or password'); - setLoading(false); - return; - } - - // If validation passed, use NextAuth to create session - const result = await signIn('credentials', { - email, - password, - redirect: false, - }); - - setLoading(false); - - if (result?.error) { - setError('Login failed. Please try again.'); - } else { - router.push('/'); - router.refresh(); - } - } catch (err) { - setLoading(false); - setError('An error occurred. Please try again.'); - } - }; - - const handleSocialLogin = (provider: 'google' | 'facebook' | 'twitter') => { - signIn(provider, { callbackUrl: '/' }); - }; - - return ( -
-
- {/* Logo */} -
- RE-QuestN -
- - {/* Login Heading */} -
-

Login

-

- Sign in to your account to access your real estate dashboard and manage your properties -

-
- - {/* Login Form */} -
- {error && ( -
- {error} -
- )} - - {/* Email Input */} -
- setEmail(e.target.value)} - required - className="w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all" - /> -
- - {/* Password Input */} -
- setPassword(e.target.value)} - required - className="w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all pr-14" - /> - -
- - {/* Forgot Password Link */} -
- - Forgot Password? - -
- - {/* Login Button */} - -
- - {/* Don't Have Account */} -
- Don't Have An Account ? - - Sign Up - -
- - {/* Divider */} -
-
- Or -
-
- - {/* Social Login Icons */} -
- {/* Google */} - - - {/* X (Twitter) */} - - - {/* Facebook */} - -
-
-
- ); -} diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx deleted file mode 100644 index 3974934..0000000 --- a/src/app/signup/page.tsx +++ /dev/null @@ -1,298 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { signIn } from 'next-auth/react'; -import Link from 'next/link'; -import { authService, AuthService, RegisterRequest } from '@/services'; - -type UserType = 'USER' | 'AGENT'; - -interface ValidationError { - field: string; - errors: string[]; -} - -export default function SignUpPage() { - const [userType, setUserType] = useState('USER'); - const [firstName, setFirstName] = useState(''); - const [lastName, setLastName] = useState(''); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [error, setError] = useState(''); - const [fieldErrors, setFieldErrors] = useState([]); - const [loading, setLoading] = useState(false); - const [registrationSuccess, setRegistrationSuccess] = useState(false); - - const getFieldError = (fieldName: string): string | null => { - const fieldError = fieldErrors.find((e) => e.field === fieldName); - return fieldError ? fieldError.errors[0] : null; - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - setError(''); - setFieldErrors([]); - - try { - const registerData: RegisterRequest = { - firstName, - lastName, - email, - password, - role: userType, - }; - - await authService.register(registerData); - - // Show success message - user must verify email before logging in - setRegistrationSuccess(true); - } catch (err) { - // Get field errors - const errors = AuthService.getFieldErrors(err); - if (errors.length > 0) { - setFieldErrors(errors); - setError('Please fix the errors below'); - } else { - setError(AuthService.getErrorMessage(err)); - } - } finally { - setLoading(false); - } - }; - - const handleSocialLogin = (provider: 'google' | 'facebook' | 'twitter') => { - signIn(provider, { callbackUrl: '/' }); - }; - - return ( -
-
- {/* Logo */} -
- RE-QuestN -
- - {/* Registration Success Message */} - {registrationSuccess ? ( -
-
-
- - - -
-

Check Your Email

-

- We've sent a verification link to {email}. Please check your inbox and click the link to verify your account before logging in. -

- - Go to Login - -

- Didn't receive the email? Check your spam folder or contact support. -

-
-
- ) : ( - <> - {/* Sign Up Heading */} -
-

Sign Up

-

- Sign in or create an account to showcase your skills & expertise to millions of people starting their real estate journey today" -

-
- - {/* User Type Toggle */} -
-
- - -
-
- - {/* Sign Up Form */} -
- {error && ( -
- {error} -
- )} - - {/* First Name & Last Name */} -
-
- setFirstName(e.target.value)} - required - className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all ${ - getFieldError('firstName') ? 'ring-2 ring-red-400' : '' - }`} - /> - {getFieldError('firstName') && ( -

{getFieldError('firstName')}

- )} -
-
- setLastName(e.target.value)} - className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all ${ - getFieldError('lastName') ? 'ring-2 ring-red-400' : '' - }`} - /> - {getFieldError('lastName') && ( -

{getFieldError('lastName')}

- )} -
-
- - {/* Email Input */} -
- setEmail(e.target.value)} - required - className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all ${ - getFieldError('email') ? 'ring-2 ring-red-400' : '' - }`} - /> - {getFieldError('email') && ( -

{getFieldError('email')}

- )} -
- - {/* Password Input */} -
-
- setPassword(e.target.value)} - required - className={`w-full px-6 py-6 bg-[#f0f5fc] rounded-[20px] text-[#00293d] text-sm font-light placeholder:text-[#00293d] focus:outline-none focus:ring-2 focus:ring-[#00293d]/20 transition-all pr-14 ${ - getFieldError('password') ? 'ring-2 ring-red-400' : '' - }`} - /> - -
- {getFieldError('password') && ( -

{getFieldError('password')}

- )} -

- Min 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special character -

-
- - {/* Sign Up Button */} - -
- - {/* Already Have Account */} -
- Already Have An Account ? - - Login - -
- - {/* Divider */} -
-
- Or -
-
- - {/* Social Login Icons */} -
- {/* Google */} - - - {/* X (Twitter) */} - - - {/* Facebook */} - -
- - )} -
-
- ); -}