149 lines
3.8 KiB
TypeScript
149 lines
3.8 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import { AuthLayout } from '@/components/layouts/AuthLayout'
|
|
import { Input } from '@/components/common/Input'
|
|
import { Button } from '@/components/common/Button'
|
|
import { Checkbox } from '@/components/common/Checkbox'
|
|
import { Link } from '@/components/common/Link'
|
|
import { useAuth } from '@/lib/hooks/useAuth'
|
|
|
|
export default function LoginPage() {
|
|
const { login, isLoading, error } = useAuth()
|
|
const [formData, setFormData] = useState({
|
|
email: '',
|
|
password: '',
|
|
rememberMe: false,
|
|
})
|
|
const [formErrors, setFormErrors] = useState<Record<string, string>>({})
|
|
|
|
const validateForm = () => {
|
|
const errors: Record<string, string> = {}
|
|
|
|
if (!formData.email) {
|
|
errors.email = 'Email is required'
|
|
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
|
errors.email = 'Email is invalid'
|
|
}
|
|
|
|
if (!formData.password) {
|
|
errors.password = 'Password is required'
|
|
} else if (formData.password.length < 8) {
|
|
errors.password = 'Password must be at least 8 characters'
|
|
}
|
|
|
|
setFormErrors(errors)
|
|
return Object.keys(errors).length === 0
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
if (!validateForm()) {
|
|
return
|
|
}
|
|
|
|
await login({
|
|
email: formData.email,
|
|
password: formData.password,
|
|
})
|
|
}
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { name, value, type, checked } = e.target
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[name]: type === 'checkbox' ? checked : value,
|
|
}))
|
|
|
|
// Clear error for this field
|
|
if (formErrors[name]) {
|
|
setFormErrors((prev) => {
|
|
const newErrors = { ...prev }
|
|
delete newErrors[name]
|
|
return newErrors
|
|
})
|
|
}
|
|
}
|
|
|
|
return (
|
|
<AuthLayout
|
|
title="Welcome back"
|
|
subtitle="Sign in to your account to continue"
|
|
>
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{error && (
|
|
<div
|
|
className="bg-error-50 border border-error-200 text-error-800 px-4 py-3 rounded-md"
|
|
role="alert"
|
|
>
|
|
<p className="text-sm">
|
|
{error.message || 'Login failed. Please check your credentials and try again.'}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<Input
|
|
type="email"
|
|
name="email"
|
|
label="Email address"
|
|
placeholder="you@example.com"
|
|
value={formData.email}
|
|
onChange={handleChange}
|
|
error={formErrors.email}
|
|
required
|
|
fullWidth
|
|
autoComplete="email"
|
|
/>
|
|
|
|
<Input
|
|
type="password"
|
|
name="password"
|
|
label="Password"
|
|
placeholder="••••••••"
|
|
value={formData.password}
|
|
onChange={handleChange}
|
|
error={formErrors.password}
|
|
required
|
|
fullWidth
|
|
autoComplete="current-password"
|
|
/>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Checkbox
|
|
name="rememberMe"
|
|
label="Remember me"
|
|
checked={formData.rememberMe}
|
|
onChange={handleChange}
|
|
/>
|
|
|
|
<Link
|
|
href="/auth/reset-password"
|
|
variant="primary"
|
|
className="text-sm"
|
|
>
|
|
Forgot password?
|
|
</Link>
|
|
</div>
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="primary"
|
|
size="lg"
|
|
fullWidth
|
|
isLoading={isLoading}
|
|
disabled={isLoading}
|
|
>
|
|
Sign in
|
|
</Button>
|
|
|
|
<div className="text-center text-sm text-gray-600 dark:text-gray-400">
|
|
Don't have an account?{' '}
|
|
<Link href="/signup" variant="primary">
|
|
Sign up
|
|
</Link>
|
|
</div>
|
|
</form>
|
|
</AuthLayout>
|
|
)
|
|
}
|