210 lines
6.5 KiB
JavaScript
210 lines
6.5 KiB
JavaScript
import React, { useState } from "react";
|
|
import {
|
|
Box,
|
|
Button,
|
|
TextField,
|
|
Typography,
|
|
InputAdornment,
|
|
IconButton,
|
|
Link as MuiLink,
|
|
} from "@mui/material";
|
|
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
|
import { useNavigate, Link } from "react-router-dom";
|
|
import toast from "react-hot-toast";
|
|
import axiosInstance, { setAccessToken } from "../../api/axiosInstance";
|
|
import PromoPanel from "../../components/auth/PromoPanel";
|
|
|
|
const LoginPage = () => {
|
|
const navigate = useNavigate();
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [formData, setFormData] = useState({
|
|
mobile: "",
|
|
password: "",
|
|
});
|
|
const [errors, setErrors] = useState({});
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[name]: value,
|
|
}));
|
|
if (errors[name]) {
|
|
setErrors((prev) => ({ ...prev, [name]: "" }));
|
|
}
|
|
};
|
|
|
|
const validate = () => {
|
|
const newErrors = {};
|
|
if (!formData.mobile) newErrors.mobile = "Mobile number is required";
|
|
else if (!/^\d{10}$/.test(formData.mobile))
|
|
newErrors.mobile = "Enter a valid 10-digit mobile number";
|
|
|
|
if (!formData.password) newErrors.password = "Password is required";
|
|
|
|
setErrors(newErrors);
|
|
return Object.keys(newErrors).length === 0;
|
|
};
|
|
|
|
const handleLogin = async (e) => {
|
|
e.preventDefault();
|
|
if (!validate()) return;
|
|
|
|
setLoading(true);
|
|
|
|
// Retrieve FCM token from localStorage
|
|
const fcmToken = localStorage.getItem("fcm_token") || "";
|
|
|
|
const payload = {
|
|
mobile: formData.mobile,
|
|
password: formData.password,
|
|
web_fcm_token: fcmToken,
|
|
};
|
|
|
|
try {
|
|
const response = await axiosInstance.post("/login", payload);
|
|
const data = response.data;
|
|
|
|
// Extract token from response
|
|
const token = data?.access_token || data?.token || data?.accessToken || data?.data?.access_token;
|
|
|
|
if (token) {
|
|
// Store token in localStorage and axios instance
|
|
localStorage.setItem("access_token", token);
|
|
setAccessToken(token);
|
|
|
|
// Store profile_id and user_id for WebSocket channels
|
|
const profileId = data?.profile_id || data?.data?.profile_id;
|
|
const userId = data?.user_id || data?.data?.user_id;
|
|
|
|
if (profileId) localStorage.setItem("profile_id", profileId);
|
|
if (userId) localStorage.setItem("user_id", userId);
|
|
|
|
toast.success("Login Successful!");
|
|
navigate("/dashboard-home");
|
|
} else {
|
|
toast.error("Login failed: No access token received.");
|
|
}
|
|
} catch (error) {
|
|
console.error("Login error:", error);
|
|
const errorMessage =
|
|
error?.response?.data?.message ||
|
|
error?.response?.data?.error ||
|
|
"Login failed. Please check your credentials.";
|
|
toast.error(errorMessage);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="h-full max-h-dvh w-full max-w-[1100px] mx-auto">
|
|
<div className="my-6 grid grid-cols-1 md:grid-cols-[60%_40%] gap-2">
|
|
<div className="">
|
|
<PromoPanel />
|
|
</div>
|
|
|
|
<div className="">
|
|
<div className="bg-white p-8 rounded-lg shadow-sm h-full flex flex-col justify-center border border-gray-100">
|
|
<Typography variant="h4" fontWeight="700" color="primary" gutterBottom align="center">
|
|
Welcome Back
|
|
</Typography>
|
|
<Typography variant="body1" color="text.secondary" mb={4} align="center">
|
|
Please login to your account
|
|
</Typography>
|
|
|
|
<form onSubmit={handleLogin}>
|
|
<Box display="flex" flexDirection="column" gap={3}>
|
|
<TextField
|
|
fullWidth
|
|
label="Mobile Number"
|
|
name="mobile"
|
|
value={formData.mobile}
|
|
onChange={handleChange}
|
|
error={Boolean(errors.mobile)}
|
|
helperText={errors.mobile}
|
|
variant="outlined"
|
|
placeholder="Enter 10-digit mobile number"
|
|
inputProps={{ maxLength: 10 }}
|
|
/>
|
|
|
|
<TextField
|
|
fullWidth
|
|
label="Password"
|
|
name="password"
|
|
type={showPassword ? "text" : "password"}
|
|
value={formData.password}
|
|
onChange={handleChange}
|
|
error={Boolean(errors.password)}
|
|
helperText={errors.password}
|
|
variant="outlined"
|
|
placeholder="Enter your password"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
edge="end"
|
|
>
|
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
|
|
<Box display="flex" justifyContent="flex-end">
|
|
<MuiLink
|
|
component={Link}
|
|
to="/forgot-password"
|
|
underline="hover"
|
|
variant="body2"
|
|
color="primary"
|
|
>
|
|
Forgot Password?
|
|
</MuiLink>
|
|
</Box>
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="contained"
|
|
color="primary"
|
|
size="large"
|
|
fullWidth
|
|
disabled={loading}
|
|
sx={{
|
|
py: 1.5,
|
|
fontSize: "1rem",
|
|
fontWeight: "bold",
|
|
textTransform: "none",
|
|
borderRadius: 2,
|
|
}}
|
|
>
|
|
{loading ? "Logging in..." : "Login"}
|
|
</Button>
|
|
|
|
<Box mt={2} textAlign="center">
|
|
<Typography variant="body2" color="text.secondary">
|
|
Don't have an account?{" "}
|
|
<MuiLink
|
|
component={Link}
|
|
to="/registration"
|
|
underline="hover"
|
|
fontWeight="bold"
|
|
color="primary"
|
|
>
|
|
Register Now
|
|
</MuiLink>
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoginPage; |