diff --git a/index.html b/index.html
index 03b7350..0584822 100644
--- a/index.html
+++ b/index.html
@@ -4,6 +4,7 @@
+
thirukalyanam
diff --git a/package-lock.json b/package-lock.json
index f832c6b..d19a387 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
+ "@lottiefiles/dotlottie-react": "^0.17.8",
"@mui/icons-material": "^7.3.5",
"@mui/material": "^7.3.5",
"@mui/styled-engine-sc": "^7.3.5",
@@ -1181,6 +1182,24 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@lottiefiles/dotlottie-react": {
+ "version": "0.17.8",
+ "resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-react/-/dotlottie-react-0.17.8.tgz",
+ "integrity": "sha512-Hk0bISNURSqL7t+H7S5lW2NQVa1hiibnqRRg6kOWZpswBxfQk+/6WBPc9EfuetdoZmiMoDsmcI0HR4I20oTBRg==",
+ "license": "MIT",
+ "dependencies": {
+ "@lottiefiles/dotlottie-web": "0.57.0"
+ },
+ "peerDependencies": {
+ "react": "^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/@lottiefiles/dotlottie-web": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-web/-/dotlottie-web-0.57.0.tgz",
+ "integrity": "sha512-gcgvu9T21YzeY3JjHCZrxftucsxzMH6e9h+8NMv8mbfo1y1M9/jdcsdu40S+pnSLz9/OyiSBQ/EjDsbSOHZy0w==",
+ "license": "MIT"
+ },
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.17",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
diff --git a/package.json b/package.json
index 6646263..a5c8b94 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
+ "@lottiefiles/dotlottie-react": "^0.17.8",
"@mui/icons-material": "^7.3.5",
"@mui/material": "^7.3.5",
"@mui/styled-engine-sc": "^7.3.5",
diff --git a/src/assets/images/appstore-badge.svg b/src/assets/images/appstore-badge.svg
new file mode 100644
index 0000000..9a07d5b
--- /dev/null
+++ b/src/assets/images/appstore-badge.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/googleplay-badge.svg b/src/assets/images/googleplay-badge.svg
new file mode 100644
index 0000000..f2cce61
--- /dev/null
+++ b/src/assets/images/googleplay-badge.svg
@@ -0,0 +1,40 @@
+
diff --git a/src/assets/images/phone-left.avif b/src/assets/images/phone-left.avif
new file mode 100644
index 0000000..7a86279
Binary files /dev/null and b/src/assets/images/phone-left.avif differ
diff --git a/src/assets/images/success.gif b/src/assets/images/success.gif
new file mode 100644
index 0000000..c450135
Binary files /dev/null and b/src/assets/images/success.gif differ
diff --git a/src/components/auth/ChangePasswordForm.jsx b/src/components/auth/ChangePasswordForm.jsx
new file mode 100644
index 0000000..2c0f0c1
--- /dev/null
+++ b/src/components/auth/ChangePasswordForm.jsx
@@ -0,0 +1,613 @@
+import * as React from 'react'
+import {
+ Box,
+ Card,
+ Typography,
+ Button,
+ Container,
+ Dialog,
+ DialogContent,
+ Zoom,
+ Fade,
+ LinearProgress,
+ CircularProgress,
+ IconButton,
+} from '@mui/material'
+import CheckCircleIcon from '@mui/icons-material/CheckCircle'
+import LockIcon from '@mui/icons-material/Lock'
+import ArrowBackIcon from '@mui/icons-material/ArrowBack'
+import CheckIcon from '@mui/icons-material/Check'
+import CloseIcon from '@mui/icons-material/Close'
+import { keyframes } from '@mui/system'
+import MuiDynamicInput from '../../utills/MuiDynamicInput'
+
+// Keyframe animations
+const scaleIn = keyframes`
+ 0% { transform: scale(0); opacity: 0; }
+ 50% { transform: scale(1.2); }
+ 100% { transform: scale(1); opacity: 1; }
+`
+
+const pulse = keyframes`
+ 0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4); }
+ 70% { box-shadow: 0 0 0 20px rgba(76, 175, 80, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
+`
+
+const fadeInUp = keyframes`
+ 0% { opacity: 0; transform: translateY(20px); }
+ 100% { opacity: 1; transform: translateY(0); }
+`
+
+const float = keyframes`
+ 0%, 100% { transform: translateY(0px); }
+ 50% { transform: translateY(-20px); }
+`
+
+// Success Modal Component
+function SuccessModal({ open, onClose, title, message }) {
+ return (
+
+ )
+}
+
+// Password Strength Indicator
+function PasswordStrength({ password }) {
+ const getStrength = () => {
+ let strength = 0
+ if (password.length >= 8) strength += 25
+ if (/[a-z]/.test(password)) strength += 25
+ if (/[A-Z]/.test(password)) strength += 25
+ if (/\d/.test(password)) strength += 15
+ if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) strength += 10
+ return Math.min(strength, 100)
+ }
+
+ const strength = getStrength()
+
+ const getColor = () => {
+ if (strength < 30) return 'error'
+ if (strength < 60) return 'warning'
+ if (strength < 80) return 'info'
+ return 'success'
+ }
+
+ const getLabel = () => {
+ if (strength < 30) return 'Weak'
+ if (strength < 60) return 'Fair'
+ if (strength < 80) return 'Good'
+ return 'Strong'
+ }
+
+ if (!password) return null
+
+ return (
+
+
+
+ Password Strength
+
+
+ {getLabel()}
+
+
+
+
+ )
+}
+
+// Password Requirements Component
+function PasswordRequirements({ password }) {
+ const requirements = [
+ { label: 'At least 8 characters', met: password.length >= 8 },
+ { label: 'One lowercase letter', met: /[a-z]/.test(password) },
+ { label: 'One uppercase letter', met: /[A-Z]/.test(password) },
+ { label: 'One number', met: /\d/.test(password) },
+ { label: 'One special character', met: /[!@#$%^&*(),.?":{}|<>]/.test(password) },
+ ]
+
+ return (
+
+ {requirements.map((req, index) => (
+
+ {req.met ? (
+
+ ) : (
+
+ )}
+
+ {req.label}
+
+
+ ))}
+
+ )
+}
+
+const ChangePasswordPage = () => {
+ const [loading, setLoading] = React.useState(false)
+ const [successModal, setSuccessModal] = React.useState(false)
+
+ const [formData, setFormData] = React.useState({
+ currentPassword: '',
+ newPassword: '',
+ confirmPassword: '',
+ })
+
+ const [errors, setErrors] = React.useState({})
+
+ const handleChange = (e) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ if (errors[name]) {
+ setErrors((prev) => ({ ...prev, [name]: '' }))
+ }
+ }
+
+ const validateForm = () => {
+ const newErrors = {}
+
+ if (!formData.currentPassword) {
+ newErrors.currentPassword = 'Current password is required'
+ }
+
+ if (!formData.newPassword) {
+ newErrors.newPassword = 'New password is required'
+ } else if (formData.newPassword.length < 8) {
+ newErrors.newPassword = 'Password must be at least 8 characters'
+ } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(formData.newPassword)) {
+ newErrors.newPassword = 'Must include uppercase, lowercase, and number'
+ }
+
+ if (!formData.confirmPassword) {
+ newErrors.confirmPassword = 'Please confirm your password'
+ } else if (formData.newPassword !== formData.confirmPassword) {
+ newErrors.confirmPassword = 'Passwords do not match'
+ }
+
+ if (formData.currentPassword === formData.newPassword) {
+ newErrors.newPassword = 'New password must be different from current'
+ }
+
+ setErrors(newErrors)
+ return Object.keys(newErrors).length === 0
+ }
+
+ const handleSubmit = async () => {
+ if (!validateForm()) return
+
+ setLoading(true)
+ setTimeout(() => {
+ setLoading(false)
+ setSuccessModal(true)
+ console.log('Change Password Data:', {
+ currentPassword: formData.currentPassword,
+ newPassword: formData.newPassword,
+ })
+ }, 1500)
+ }
+
+ const handleSuccessClose = () => {
+ setSuccessModal(false)
+ setFormData({ currentPassword: '', newPassword: '', confirmPassword: '' })
+ console.log('Navigate to profile or dashboard...')
+ }
+
+ const handleBack = () => {
+ console.log('Navigate back...')
+ }
+
+ return (
+
+
+
+ {/* Left Side - Image */}
+
+ {/* Background Decorations */}
+
+
+
+ {/* Lock Icon with Animation */}
+
+
+
+
+
+ Secure Your Account
+
+
+
+ Keep your account safe by regularly updating your password. Choose
+ a strong password that you don't use elsewhere.
+
+
+ {/* Security Tips */}
+
+
+ Security Tips:
+
+
+ • Use a mix of letters, numbers & symbols
+
+
+ • Avoid personal information
+
+
+ • Don't reuse passwords from other sites
+
+
+
+
+ {/* Right Side - Form */}
+
+ {/* Header */}
+
+
+
+
+
+ Change Password
+
+
+
+
+ Your password must be different from previously used passwords.
+
+
+ {/* Form Fields */}
+
+
+
+
+
+ {/* Password Strength Indicator */}
+
+
+ {/* Password Requirements */}
+
+
+
+
+ {/* Match Indicator */}
+ {formData.confirmPassword && (
+
+ {formData.newPassword === formData.confirmPassword ? (
+ <>
+
+
+ Passwords match
+
+ >
+ ) : (
+ <>
+
+
+ Passwords do not match
+
+ >
+ )}
+
+ )}
+
+
+ {/* Submit Button */}
+
+
+ {/* Cancel Link */}
+
+
+
+
+
+ {/* Success Modal */}
+
+
+ )
+}
+
+export default ChangePasswordPage
\ No newline at end of file
diff --git a/src/components/auth/ForgotPassworForm.jsx b/src/components/auth/ForgotPassworForm.jsx
new file mode 100644
index 0000000..2a55b53
--- /dev/null
+++ b/src/components/auth/ForgotPassworForm.jsx
@@ -0,0 +1,772 @@
+import {
+ Box,
+ Card,
+ Typography,
+ Button,
+ Link,
+ Container,
+ Dialog,
+ DialogContent,
+ Zoom,
+ Fade,
+ Stepper,
+ Step,
+ StepLabel,
+ IconButton,
+ CircularProgress,
+} from '@mui/material'
+import CheckCircleIcon from '@mui/icons-material/CheckCircle'
+import ArrowBackIcon from '@mui/icons-material/ArrowBack'
+import LockResetIcon from '@mui/icons-material/LockReset'
+import EmailIcon from '@mui/icons-material/Email'
+import VpnKeyIcon from '@mui/icons-material/VpnKey'
+import { keyframes } from '@mui/system'
+import { useEffect, useRef, useState } from 'react'
+import MuiDynamicInput from '../../utills/MuiDynamicInput'
+
+// Keyframe animations
+const scaleIn = keyframes`
+ 0% { transform: scale(0); opacity: 0; }
+ 50% { transform: scale(1.2); }
+ 100% { transform: scale(1); opacity: 1; }
+`
+
+const pulse = keyframes`
+ 0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4); }
+ 70% { box-shadow: 0 0 0 20px rgba(76, 175, 80, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
+`
+
+const fadeInUp = keyframes`
+ 0% { opacity: 0; transform: translateY(20px); }
+ 100% { opacity: 1; transform: translateY(0); }
+`
+
+const shake = keyframes`
+ 0%, 100% { transform: translateX(0); }
+ 25% { transform: translateX(-5px); }
+ 75% { transform: translateX(5px); }
+`
+
+// Success Modal Component
+function SuccessModal({ open, onClose, title, message }) {
+ return (
+
+ )
+}
+
+// OTP Input Component
+function OTPInput({ value, onChange, error, disabled }) {
+ const inputRefs = useRef([])
+ const otpLength = 6
+
+ const handleChange = (index, e) => {
+ const val = e.target.value
+ if (!/^\d*$/.test(val)) return
+
+ const newOtp = value.split('')
+ newOtp[index] = val.slice(-1)
+ const otpString = newOtp.join('')
+ onChange({ target: { name: 'otp', value: otpString } })
+
+ // Move to next input
+ if (val && index < otpLength - 1) {
+ inputRefs.current[index + 1]?.focus()
+ }
+ }
+
+ const handleKeyDown = (index, e) => {
+ if (e.key === 'Backspace' && !value[index] && index > 0) {
+ inputRefs.current[index - 1]?.focus()
+ }
+ }
+
+ const handlePaste = (e) => {
+ e.preventDefault()
+ const pastedData = e.clipboardData.getData('text').slice(0, otpLength)
+ if (/^\d+$/.test(pastedData)) {
+ onChange({ target: { name: 'otp', value: pastedData } })
+ const focusIndex = Math.min(pastedData.length, otpLength - 1)
+ inputRefs.current[focusIndex]?.focus()
+ }
+ }
+
+ return (
+
+
+ {Array.from({ length: otpLength }).map((_, index) => (
+ (inputRefs.current[index] = el)}
+ type="text"
+ inputMode="numeric"
+ maxLength={1}
+ value={value[index] || ''}
+ onChange={(e) => handleChange(index, e)}
+ onKeyDown={(e) => handleKeyDown(index, e)}
+ onPaste={handlePaste}
+ disabled={disabled}
+ style={{
+ width: '45px',
+ height: '55px',
+ textAlign: 'center',
+ fontSize: '24px',
+ fontWeight: 600,
+ border: `2px solid ${error ? '#f44336' : value[index] ? '#4CAF50' : '#ddd'}`,
+ borderRadius: '12px',
+ outline: 'none',
+ transition: 'all 0.2s',
+ backgroundColor: disabled ? '#f5f5f5' : 'white',
+ }}
+ onFocus={(e) => {
+ e.target.style.borderColor = '#4CAF50'
+ e.target.style.boxShadow = '0 0 0 3px rgba(76, 175, 80, 0.2)'
+ }}
+ onBlur={(e) => {
+ e.target.style.borderColor = value[index] ? '#4CAF50' : '#ddd'
+ e.target.style.boxShadow = 'none'
+ }}
+ />
+ ))}
+
+ {error && (
+
+ {error}
+
+ )}
+
+ )
+}
+
+
+const ForgotPassworForm = () => {
+
+ const steps = ['Enter Email', 'Verify OTP', 'Reset Password']
+
+ const [activeStep, setActiveStep] = useState(0)
+ const [loading, setLoading] = useState(false)
+ const [successModal, setSuccessModal] =useState(false)
+ const [resendTimer, setResendTimer] = useState(0)
+
+ const [formData, setFormData] = useState({
+ email: '',
+ otp: '',
+ newPassword: '',
+ confirmPassword: '',
+ })
+
+ const [errors, setErrors] = useState({})
+
+ // Resend OTP Timer
+ useEffect(() => {
+ let interval
+ if (resendTimer > 0) {
+ interval = setInterval(() => {
+ setResendTimer((prev) => prev - 1)
+ }, 1000)
+ }
+ return () => clearInterval(interval)
+ }, [resendTimer])
+
+ const handleChange = (e) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ if (errors[name]) {
+ setErrors((prev) => ({ ...prev, [name]: '' }))
+ }
+ }
+
+ // Step 1: Validate Email
+ const validateEmail = () => {
+ const newErrors = {}
+ if (!formData.email) {
+ newErrors.email = 'Email is required'
+ } else if (!/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(formData.email)) {
+ newErrors.email = 'Enter a valid email address'
+ }
+ setErrors(newErrors)
+ return Object.keys(newErrors).length === 0
+ }
+
+ // Step 2: Validate OTP
+ const validateOTP = () => {
+ const newErrors = {}
+ if (!formData.otp || formData.otp.length !== 6) {
+ newErrors.otp = 'Please enter the 6-digit OTP'
+ }
+ setErrors(newErrors)
+ return Object.keys(newErrors).length === 0
+ }
+
+ // Step 3: Validate Password
+ const validatePassword = () => {
+ const newErrors = {}
+ if (!formData.newPassword) {
+ newErrors.newPassword = 'New password is required'
+ } else if (formData.newPassword.length < 8) {
+ newErrors.newPassword = 'Password must be at least 8 characters'
+ } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(formData.newPassword)) {
+ newErrors.newPassword = 'Must include uppercase, lowercase, and number'
+ }
+
+ if (!formData.confirmPassword) {
+ newErrors.confirmPassword = 'Please confirm your password'
+ } else if (formData.newPassword !== formData.confirmPassword) {
+ newErrors.confirmPassword = 'Passwords do not match'
+ }
+
+ setErrors(newErrors)
+ return Object.keys(newErrors).length === 0
+ }
+
+ // Submit Email
+ const handleEmailSubmit = async () => {
+ if (!validateEmail()) return
+
+ setLoading(true)
+ setTimeout(() => {
+ setLoading(false)
+ setActiveStep(1)
+ setResendTimer(120) // 2 minutes timer
+ console.log('OTP sent to:', formData.email)
+ }, 1500)
+ }
+
+ // Verify OTP
+ const handleOTPSubmit = async () => {
+ if (!validateOTP()) return
+
+ setLoading(true)
+ setTimeout(() => {
+ setLoading(false)
+ setActiveStep(2)
+ console.log('OTP Verified:', formData.otp)
+ }, 1500)
+ }
+
+ // Resend OTP
+ const handleResendOTP = () => {
+ setResendTimer(120)
+ setFormData((prev) => ({ ...prev, otp: '' }))
+ console.log('OTP Resent to:', formData.email)
+ }
+
+ // Submit New Password
+ const handlePasswordSubmit = async () => {
+ if (!validatePassword()) return
+
+ setLoading(true)
+ setTimeout(() => {
+ setLoading(false)
+ setSuccessModal(true)
+ console.log('Password Reset Data:', {
+ email: formData.email,
+ otp: formData.otp,
+ newPassword: formData.newPassword,
+ })
+ }, 1500)
+ }
+
+ const handleBack = () => {
+ if (activeStep > 0) {
+ setActiveStep((prev) => prev - 1)
+ setErrors({})
+ }
+ }
+
+ const handleSuccessClose = () => {
+ setSuccessModal(false)
+ setActiveStep(0)
+ setFormData({ email: '', otp: '', newPassword: '', confirmPassword: '' })
+ // Navigate to login page
+ console.log('Navigate to login...')
+ }
+
+ const formatTime = (seconds) => {
+ const mins = Math.floor(seconds / 60)
+ const secs = seconds % 60
+ return `${mins}:${secs.toString().padStart(2, '0')}`
+ }
+
+ // Render step icon
+ const getStepIcon = (step) => {
+ switch (step) {
+ case 0:
+ return
+ case 1:
+ return
+ case 2:
+ return
+ default:
+ return null
+ }
+ }
+
+
+
+ return (
+ <>
+
+
+ {/* Header */}
+
+ {activeStep > 0 && (
+
+
+
+ )}
+
+ Forgot Password
+
+
+
+ {/* Stepper */}
+
+
+ {steps.map((label, index) => (
+
+
+
+ {label}
+
+
+
+ ))}
+
+
+
+ {/* Form Content */}
+
+ {/* Step 1: Email */}
+ {activeStep === 0 && (
+
+
+
+
+
+
+
+
+
+ Enter your email address and we'll send you an OTP to reset
+ your password.
+
+
+
+
+
+
+
+
+ Back to Login
+
+
+
+
+ )}
+
+ {/* Step 2: OTP Verification */}
+ {activeStep === 1 && (
+
+
+
+
+
+
+
+
+
+ We've sent a 6-digit OTP to
+
+
+ {formData.email}
+
+
+
+
+ {/* Timer and Resend */}
+
+ {resendTimer > 0 ? (
+
+ Resend OTP in{' '}
+
+ {formatTime(resendTimer)}
+
+
+ ) : (
+
+ Resend OTP
+
+ )}
+
+
+
+
+
+ )}
+
+ {/* Step 3: Reset Password */}
+ {activeStep === 2 && (
+
+
+
+
+
+
+
+
+
+ Create a strong password with at least 8 characters including
+ uppercase, lowercase, and numbers.
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ {/* Success Modal */}
+
+
+ >
+ )
+}
+
+export default ForgotPassworForm
diff --git a/src/components/auth/LoginPanel.jsx b/src/components/auth/LoginPanel.jsx
new file mode 100644
index 0000000..a51e1da
--- /dev/null
+++ b/src/components/auth/LoginPanel.jsx
@@ -0,0 +1,412 @@
+import {
+ Box,
+ Card,
+ Typography,
+ Button,
+ Link,
+ Divider,
+ Container,
+ Dialog,
+ DialogContent,
+ Zoom,
+ Fade,
+} from '@mui/material'
+import { useState } from 'react'
+import MuiDynamicInput from '../../utills/MuiDynamicInput.jsx'
+import CheckCircleIcon from '@mui/icons-material/CheckCircle'
+import { keyframes } from '@mui/system'
+
+
+// Define keyframe animations
+const scaleIn = keyframes`
+ 0% {
+ transform: scale(0);
+ opacity: 0;
+ }
+ 50% {
+ transform: scale(1.2);
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+`
+
+const checkmarkDraw = keyframes`
+ 0% {
+ stroke-dashoffset: 100;
+ }
+ 100% {
+ stroke-dashoffset: 0;
+ }
+`
+
+const pulse = keyframes`
+ 0% {
+ box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4);
+ }
+ 70% {
+ box-shadow: 0 0 0 20px rgba(76, 175, 80, 0);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);
+ }
+`
+
+const fadeInUp = keyframes`
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+`
+
+// Success Modal Component
+function SuccessModal({ open, onClose, title, message }) {
+ return (
+
+ )
+}
+const LoginPanel = () => {
+
+ const [formData, setFormData] = useState({
+ emailOrMobile: '',
+ password: '',
+ keepLoggedIn: false,
+ })
+
+ const [errors, setErrors] =useState({})
+ const [loading, setLoading] = useState(false)
+ const [successModal, setSuccessModal] = useState(false)
+ const handleChange = (e) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ // Clear error when user types
+ if (errors[name]) {
+ setErrors((prev) => ({ ...prev, [name]: '' }))
+ }
+ }
+
+ const validateForm = () => {
+ const newErrors = {}
+
+ if (!formData.emailOrMobile) {
+ newErrors.emailOrMobile = 'Email or Mobile Number is required'
+ } else if (
+ !/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(formData.emailOrMobile) &&
+ !/^\d{10}$/.test(formData.emailOrMobile)
+ ) {
+ newErrors.emailOrMobile = 'Enter a valid email or 10-digit mobile number'
+ }
+
+ if (!formData.password) {
+ newErrors.password = 'Password is required'
+ } else if (formData.password.length < 6) {
+ newErrors.password = 'Password must be at least 6 characters'
+ }
+
+ setErrors(newErrors)
+ return Object.keys(newErrors).length === 0
+ }
+
+ const handleSubmit = async () => {
+ if (!validateForm()) return
+
+ setLoading(true)
+ // Simulate API call
+ setTimeout(() => {
+ setLoading(false)
+ setSuccessModal(true) // Show success modal
+ console.log('Form Data:', formData)
+ }, 1500)
+ }
+
+ const handleCloseSuccessModal = () => {
+ setSuccessModal(false)
+ // Reset form after successful login
+ setFormData({
+ emailOrMobile: '',
+ password: '',
+ keepLoggedIn: false,
+ })
+ // Navigate to dashboard or next page
+ console.log('Navigate to dashboard...')
+ }
+
+ const handleOTPLogin = () => {
+ console.log('Navigate to OTP Login')
+ }
+
+ const handleForgotPassword = () => {
+ console.log('Navigate to Forgot Password')
+ }
+
+
+ return (
+ <>
+
+
+
+ {/* Header */}
+
+
+ Member Login
+
+
+
+ {/* Form Content */}
+
+ {/* Email or Mobile Input */}
+
+
+ {/* Password Input */}
+
+
+ {/* Keep me logged in Checkbox */}
+
+
+ {/* Login Button */}
+
+
+ {/* Trouble logging in section */}
+
+
+ Trouble logging in?
+
+
+
+
+ Login with OTP
+
+
+
+
+
+ Forgot Password?
+
+
+
+
+
+ {/* Animated Success Modal */}
+
+
+
+
+ >
+ )
+}
+
+export default LoginPanel
diff --git a/src/components/auth/PromoPanel.jsx b/src/components/auth/PromoPanel.jsx
new file mode 100644
index 0000000..9724bdc
--- /dev/null
+++ b/src/components/auth/PromoPanel.jsx
@@ -0,0 +1,85 @@
+import React from 'react'
+import { motion } from 'framer-motion'
+import Rating from '@mui/material/Rating'
+import phoneImage from '../../assets/images/phone-left.avif'
+import appstoreBadge from '../../assets/images/appstore-badge.svg'
+import googleplayBadge from '../../assets/images/googleplay-badge.svg'
+
+const PromoPanel = () => {
+ return (
+ <>
+
+
+
+
+
+
+ To speed up your partner search, download Thirukalyanam App
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+ ThirukalyanamMatrimony®
+ Trusted Matrimony App
+
+
+
+
+
+ 4.3
+
+
+
+
10M+ Downloads | Based on Customer Reviews
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default PromoPanel
diff --git a/src/pages/auth/ForgotPasswordPage.jsx b/src/pages/auth/ForgotPasswordPage.jsx
new file mode 100644
index 0000000..2e613bd
--- /dev/null
+++ b/src/pages/auth/ForgotPasswordPage.jsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import PromoPanel from '../../components/auth/PromoPanel'
+import ForgotPassworForm from '../../components/auth/ForgotPassworForm'
+
+const ForgotPasswordPage = () => {
+ return (
+ <>
+
+
+
+
+ {/* Left: Promo */}
+
+
+
+
+ {/* Right: Login */}
+
+
+
+
+ >
+ )
+}
+
+export default ForgotPasswordPage
diff --git a/src/pages/auth/LoginPage.jsx b/src/pages/auth/LoginPage.jsx
new file mode 100644
index 0000000..07e00f7
--- /dev/null
+++ b/src/pages/auth/LoginPage.jsx
@@ -0,0 +1,25 @@
+import LoginPanel from "../../components/auth/LoginPanel"
+import PromoPanel from "../../components/auth/PromoPanel"
+
+const LoginPage = () => {
+ return (
+ <>
+
+
+
+
+ {/* Left: Promo */}
+
+
+
+
+ {/* Right: Login */}
+
+
+
+
+ >
+ )
+}
+
+export default LoginPage
diff --git a/src/routes/PublicRoutes.jsx b/src/routes/PublicRoutes.jsx
index b0b93da..68bcafb 100644
--- a/src/routes/PublicRoutes.jsx
+++ b/src/routes/PublicRoutes.jsx
@@ -1,13 +1,21 @@
import { Route } from "react-router-dom";
import HomePage from "../pages/HomePage";
import LandingLayout from "../layout/LandingLayout";
+import LoginPage from "../pages/auth/LoginPage";
+import ForgotPasswordPage from "../pages/auth/ForgotPasswordPage";
+import ChangePasswordPage from "../components/auth/ChangePasswordForm";
const PublicRoutes = () => {
return (
<>
}>
} />
+
-
+
+ } />
+ } />
+ } />
+
>
)
}
diff --git a/src/utills/MuiDynamicInput.jsx b/src/utills/MuiDynamicInput.jsx
new file mode 100644
index 0000000..078bc97
--- /dev/null
+++ b/src/utills/MuiDynamicInput.jsx
@@ -0,0 +1,703 @@
+import {
+ TextField,
+ FormControl,
+ InputLabel,
+ OutlinedInput,
+ InputAdornment,
+ IconButton,
+ Select,
+ MenuItem,
+ Checkbox,
+ FormControlLabel,
+ RadioGroup,
+ Radio,
+ FormGroup,
+ Chip,
+ Box,
+ FormHelperText,
+ FormLabel,
+ Switch,
+ Slider,
+ Autocomplete,
+ Rating,
+} from '@mui/material'
+import Visibility from '@mui/icons-material/Visibility'
+import VisibilityOff from '@mui/icons-material/VisibilityOff'
+import CloudUploadIcon from '@mui/icons-material/CloudUpload'
+import { useState } from 'react'
+
+/**
+ * MuiDynamicInput - A comprehensive reusable input component
+ *
+ * Supported types:
+ * - text, email, number, tel, url (standard text inputs)
+ * - password (with optional show/hide toggle)
+ * - select (single selection dropdown)
+ * - multiselect (multiple selection dropdown)
+ * - tags (chip-style multi-select)
+ * - radio (radio button group)
+ * - checkbox (checkbox group or single)
+ * - switch (toggle switch)
+ * - date, time, datetime-local (date/time pickers)
+ * - textarea (multi-line text)
+ * - file (file upload)
+ * - slider (range slider)
+ * - autocomplete (searchable dropdown)
+ * - rating (star rating)
+ */
+
+export default function MuiDynamicInput({
+ type = 'text',
+ name = '',
+ label = '',
+ value,
+ onChange,
+ options = [],
+ error = '',
+ helperText = '',
+ fullWidth = true,
+ autoFocus = false,
+ showPasswordToggle = true,
+ placeholder = '',
+ disabled = false,
+ required = false,
+ size = 'medium', // 'small' | 'medium'
+ variant = 'outlined', // 'outlined' | 'filled' | 'standard'
+ margin = 'normal', // 'none' | 'dense' | 'normal'
+ multiline = false,
+ rows = 4,
+ min = 0,
+ max = 100,
+ step = 1,
+ accept = '*', // for file input
+ multiple = false, // for file input
+ color = 'primary', // 'primary' | 'secondary' | 'success' | 'error' | 'warning'
+ sx = {},
+}) {
+ const [showPassword, setShowPassword] = useState(false)
+ const [fileName, setFileName] = useState('')
+
+ const handleTogglePassword = () => setShowPassword((prev) => !prev)
+
+ const handleFileChange = (event) => {
+ const files = event.target.files
+ if (files && files.length > 0) {
+ const names = Array.from(files).map(f => f.name).join(', ')
+ setFileName(names)
+ }
+ onChange && onChange(event)
+ }
+
+ // Common props for TextField-based inputs
+ const commonTextFieldProps = {
+ name,
+ label,
+ value,
+ onChange,
+ error: !!error,
+ helperText: error || helperText,
+ fullWidth,
+ autoFocus,
+ placeholder,
+ disabled,
+ required,
+ size,
+ variant,
+ margin,
+ color,
+ sx,
+ }
+
+ switch (type) {
+ // ==================== PASSWORD ====================
+ case 'password':
+ return (
+
+ {label}
+
+
+ {showPassword ? : }
+
+
+ )
+ }
+ label={label}
+ />
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== SELECT ====================
+ case 'select':
+ return (
+
+ {label}
+
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== MULTISELECT ====================
+ case 'multiselect':
+ return (
+
+ {label}
+
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== TAGS (Chips) ====================
+ case 'tags':
+ return (
+
+ {label}
+
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== RADIO ====================
+ case 'radio':
+ return (
+
+ {label}
+
+ {options.map((opt) => (
+ }
+ label={opt.label}
+ />
+ ))}
+
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== CHECKBOX ====================
+ case 'checkbox':
+ // Single checkbox (boolean value)
+ if (options.length === 0) {
+ return (
+
+ onChange({
+ target: { name, value: e.target.checked }
+ })}
+ color={color}
+ size={size}
+ />
+ }
+ label={label}
+ />
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+ }
+ // Multiple checkboxes (array value)
+ return (
+
+ {label}
+
+ {options.map((opt) => (
+ {
+ const currentValue = value || []
+ const newValue = e.target.checked
+ ? [...currentValue, opt.value]
+ : currentValue.filter((v) => v !== opt.value)
+ onChange({ target: { name, value: newValue } })
+ }}
+ color={color}
+ size={size}
+ />
+ }
+ label={opt.label}
+ />
+ ))}
+
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== SWITCH ====================
+ case 'switch':
+ return (
+
+ onChange({
+ target: { name, value: e.target.checked }
+ })}
+ color={color}
+ size={size}
+ />
+ }
+ label={label}
+ />
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== DATE / TIME / DATETIME ====================
+ case 'date':
+ case 'time':
+ case 'datetime-local':
+ return (
+
+ )
+
+ // ==================== TEXTAREA ====================
+ case 'textarea':
+ return (
+
+ )
+
+ // ==================== FILE ====================
+ case 'file':
+ return (
+
+
+
+
+ }
+ sx={{
+ '& input::file-selector-button': {
+ display: 'none',
+ },
+ }}
+ />
+
+ {error || helperText || (fileName ? `Selected: ${fileName}` : 'Choose file(s)')}
+
+
+ )
+
+ // ==================== SLIDER ====================
+ case 'slider':
+ return (
+
+ {label}
+ onChange({
+ target: { name, value: newValue }
+ })}
+ min={min}
+ max={max}
+ step={step}
+ valueLabelDisplay="auto"
+ disabled={disabled}
+ color={color}
+ size={size}
+ />
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== AUTOCOMPLETE ====================
+ case 'autocomplete':
+ return (
+ option.label || ''}
+ value={options.find(o => o.value === value) || null}
+ onChange={(_, newValue) => onChange({
+ target: { name, value: newValue?.value || '' }
+ })}
+ disabled={disabled}
+ fullWidth={fullWidth}
+ size={size}
+ sx={sx}
+ renderInput={(params) => (
+
+ )}
+ />
+ )
+
+ // ==================== RATING ====================
+ case 'rating':
+ return (
+
+ {label}
+ onChange({
+ target: { name, value: newValue }
+ })}
+ disabled={disabled}
+ size={size}
+ max={max || 5}
+ />
+ {(error || helperText) && (
+ {error || helperText}
+ )}
+
+ )
+
+ // ==================== DEFAULT (text, email, number, tel, url) ====================
+ default:
+ return (
+
+ )
+ }
+}
+
+// ==================== USAGE EXAMPLE ====================
+/*
+import MuiDynamicInput from './MuiDynamicInput'
+
+function MyForm() {
+ const [formData, setFormData] = React.useState({
+ email: '',
+ password: '',
+ country: '',
+ skills: [],
+ gender: '',
+ newsletter: false,
+ bio: '',
+ birthDate: '',
+ rating: 0,
+ })
+
+ const handleChange = (e) => {
+ const { name, value } = e.target
+ setFormData(prev => ({ ...prev, [name]: value }))
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+*/
\ No newline at end of file