Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 62 additions & 245 deletions LocalMind-Frontend/src/shared/component/v1/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,266 +1,83 @@
import React, { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import robotImg from '../../../assets/login.png'
import React, { useState, useEffect } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import robotImg from '../../../assets/robot.png'
import aiImg from '../../../assets/Artificial intelligence.png'
import apiService from '../../../core/api.service'
import { useNavigate } from 'react-router-dom'

const LoginPage: React.FC = () => {
const navigate = useNavigate()
const glowStyles = `
@keyframes glow {
0%, 100% { filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5)); }
50% { filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.8)); }
}
.logo-glow {
animation: glow 3s ease-in-out infinite;
}
`
interface ApiService {
login: (
email: string,
password: string
) => Promise<{ success: boolean; data?: unknown; error?: string }>
}

interface LoginPageProps {
apiService: ApiService
}

const LoginPage: React.FC<LoginPageProps> = ({ apiService }) => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [rememberMe, setRememberMe] = useState(false)
const [loading, setLoading] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve user experience and prevent multiple form submissions, it's a good practice to add a loading state to manage the asynchronous login process. This state can be used to disable the submit button and provide visual feedback to the user while the API call is in progress.

Suggested change
const [error, setError] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)

const [success, setSuccess] = useState('')

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
setSuccess('')
setLoading(true)

try {
const response = await fetch('/api/v1/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
})

const data = await response.json()

if (!response.ok) {
setError(data.message || 'Login failed. Please try again.')
setLoading(false)
return
}

// Store token in localStorage
if (data.data?.token) {
localStorage.setItem('token', data.data.token)
localStorage.setItem('user', JSON.stringify(data.data.user || {}))

// Store remember me preference
if (rememberMe) {
localStorage.setItem('rememberMe', 'true')
localStorage.setItem('savedEmail', email)
}

setSuccess('Login successful! Redirecting...')

// Redirect after brief delay
setTimeout(() => {
navigate('/dashboard')
}, 1000)
}
} catch (err) {
setError('An error occurred. Please check your connection and try again.')
console.error('Login error:', err)
} finally {
setLoading(false)
}
}
setIsLoading(true)
const navigate = useNavigate()

const handleLogin = async () => {
try {
setError('')
const response = await apiService.login(email, password)

if (!response.success) {
throw new Error(response.message || 'Login failed. Please try again.')
}

setSuccess('Login successful! Redirecting...')

// Store token if provided
if (response.data?.token) {
localStorage.setItem('authToken', response.data.token)
}

// Store user info if remember me is checked
if (rememberMe) {
localStorage.setItem('rememberedEmail', email)
if (response.success) {
navigate('/dashboard')
} else {
localStorage.removeItem('rememberedEmail')
setError(response.error || 'Login failed')
}

// Redirect to dashboard after a short delay
setTimeout(() => {
navigate('/dashboard')
}, 1000)
} catch (err: any) {
setError(err.message || 'An error occurred during login. Please try again.')
console.error('Login error:', err)
} finally {
setIsLoading(false)
} catch (err) {
setError('An error occurred during login')
console.error(err)
}
}

// Load remembered email on component mount
useEffect(() => {
const rememberedEmail = localStorage.getItem('rememberedEmail')
if (rememberedEmail) {
setEmail(rememberedEmail)
setRememberMe(true)
}
}, [])
<img
src={aiImg}
alt="Artificial Intelligence Logo"
className="logo-glow w-20 sm:w-24 h-20 sm:h-24 object-cover rounded-full shadow-2xl"
/>
</div>

<div className="w-full max-w-7xl bg-[#181818] rounded-3xl shadow-2xl overflow-hidden grid grid-cols-1 md:grid-cols-2">
{/* Left Section */}
<div className="bg-[#181818] p-6 sm:p-8 md:p-12 lg:p-16 xl:p-20 flex flex-col justify-center">
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-white mb-1 sm:mb-2">
Welcome Back
</h1>
<p className="text-gray-400 text-xs sm:text-sm md:text-base mb-6 md:mb-8">
Sign in to your account to continue
</p>

<form
onSubmit={handleSubmit}
className="space-y-3 sm:space-y-4 md:space-y-5 lg:space-y-6"
>
{/* Error Message */}
{error && (
<div className="p-3 sm:p-4 bg-red-900 bg-opacity-30 border border-red-500 rounded-lg">
<p className="text-red-400 text-xs sm:text-sm">{error}</p>
<div className="p-3 bg-red-900 border border-red-700 rounded-lg text-red-100 text-xs sm:text-sm">
{error}
</div>
)}

{/* Success Message */}
{success && (
<div className="p-3 sm:p-4 bg-green-900 bg-opacity-30 border border-green-500 rounded-lg">
<p className="text-green-400 text-xs sm:text-sm">{success}</p>
<div className="p-3 bg-green-900 border border-green-700 rounded-lg text-green-100 text-xs sm:text-sm">
{success}
</div>
)}

{/* Email Input */}
<div>
<label
htmlFor="email"
className="block text-gray-200 text-xs sm:text-sm font-semibold mb-2"
>
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="[email protected]"
className="w-full px-4 py-2.5 bg-[#2a2a2a] border border-gray-600 rounded-lg text-sm sm:text-base text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition disabled:opacity-50"
required
disabled={loading}
disabled={isLoading}
/>
</div>

{/* Password Input */}
<div>
<label
htmlFor="password"
className="block text-gray-200 text-xs sm:text-sm font-semibold mb-2"
>
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="••••••••"
className="w-full px-4 py-2.5 bg-[#2a2a2a] border border-gray-600 rounded-lg text-sm sm:text-base text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition disabled:opacity-50"
required
disabled={loading}
disabled={isLoading}
/>

{/* Remember Me & Forgot Password */}
<div className="flex flex-row items-center justify-between gap-2 mt-2 sm:mt-3">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={rememberMe}
onChange={e => setRememberMe(e.target.checked)}
className="w-4 h-4 bg-gray-700 border border-gray-600 rounded cursor-pointer accent-gray-500 disabled:opacity-50"
disabled={loading}
className="w-4 h-4 bg-gray-700 border border-gray-600 rounded cursor-pointer accent-gray-500"
disabled={isLoading}
/>
<span className="text-gray-300 text-xs sm:text-sm">Remember me</span>
</label>
<Link
to="/forgot-password"
className="text-gray-300 hover:text-white hover:underline text-xs sm:text-sm transition-all duration-200"
>
Forgot password?
</Link>
</div>
</div>

{/* Submit Button */}
<button
type="submit"
disabled={loading}
className="w-full bg-gray-400 hover:bg-gray-500 disabled:bg-gray-600 disabled:cursor-not-allowed text-black font-bold py-2.5 text-sm sm:text-base rounded-lg transition-colors duration-200 mt-6 sm:mt-7 md:mt-8 flex items-center justify-center gap-2"
>
{loading ? (
<>
<span className="inline-block w-4 h-4 border-2 border-black border-t-transparent rounded-full animate-spin"></span>
Logging in...
</>
) : (
'Log In'
)}
disabled={isLoading}
className={`w-full font-bold py-2.5 text-sm sm:text-base rounded-lg transition-colors duration-200 mt-6 sm:mt-7 md:mt-8 ${
isLoading
? 'bg-gray-600 text-gray-300 cursor-not-allowed'
: 'bg-gray-400 hover:bg-gray-500 text-black'
}`}
>
{isLoading ? 'Logging in...' : 'Log In'}
</button>
</form>
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
handleLogin()
}

<p className="text-gray-400 text-xs sm:text-sm mt-4 sm:mt-5 md:mt-6 text-center">
Don't have an account?{' '}
<Link
to="/register"
className="text-white hover:text-gray-300 hover:underline transition-all duration-200"
>
Create Account
</Link>
</p>
return (
<div className="min-h-screen flex items-center justify-center bg-zinc-900">
<form
onSubmit={handleSubmit}
className="bg-zinc-800 p-8 rounded-lg border border-zinc-500/50"
>
<h2 className="text-2xl font-bold text-white mb-6">Login</h2>

{error && <div className="bg-red-500/20 text-red-400 p-3 rounded mb-4">{error}</div>}

<div className="mb-4">
<label className="block text-white mb-2">Email</label>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
className="w-full bg-zinc-700 text-white px-4 py-2 rounded"
required
/>
Comment on lines +53 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better accessibility, you should associate labels with their corresponding input fields using htmlFor on the label and id on the input. This helps screen readers and improves the user experience, as clicking the label will focus the input.

          <label htmlFor="email" className="block text-white mb-2">Email</label>
          <input
            id="email"
            type="email"
            value={email}
            onChange={e => setEmail(e.target.value)}
            className="w-full bg-zinc-700 text-white px-4 py-2 rounded"
            required
          />

</div>

{/* Right Section */}
<div className="bg-gradient-to-br from-gray-700 to-gray-900 p-0 items-center justify-center hidden md:flex overflow-hidden">
<img src={robotImg} alt="Robot" className="w-full h-full object-cover" />
<div className="mb-6">
<label className="block text-white mb-2">Password</label>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
className="w-full bg-zinc-700 text-white px-4 py-2 rounded"
required
/>
Comment on lines +64 to +71
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better accessibility, you should associate labels with their corresponding input fields using htmlFor on the label and id on the input. This helps screen readers and improves the user experience, as clicking the label will focus the input.

          <label htmlFor="password" className="block text-white mb-2">Password</label>
          <input
            id="password"
            type="password"
            value={password}
            onChange={e => setPassword(e.target.value)}
            className="w-full bg-zinc-700 text-white px-4 py-2 rounded"
            required
          />

</div>
</div>

<button
type="submit"
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 transition-colors"
>
Login
</button>
Comment on lines +74 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To provide feedback to the user and prevent multiple submissions, the login button should be disabled and its text updated while the login request is in progress. You can also add styles for the disabled state using Tailwind CSS.

        <button
          type="submit"
          className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 transition-colors disabled:bg-blue-400 disabled:cursor-not-allowed"
          disabled={loading}
        >
          {loading ? 'Logging in...' : 'Login'}
        </button>

</form>
</div>
)
}
Expand Down