fix: point the actual active login page (login.js) at Doormile auth

LoginRoutes.js is registered before MainRoutes.js and defines its own
un-prefixed '/login' route pointing at pages/nearle/login — it wins the
route match, so login1.js (fixed in the earlier commit) was never being
rendered. login.js still hit jupiter.nearle.app/live/api/v1/users/console/login
with the old multi-step (email lookup -> setup/enter password) flow.

Collapsed to a single POST /admin/login with { email, password,
userfcmtoken }, matching Doormile's one-shot auth response. Visual
layout (branded two-panel screen, "Welcome back" copy) is unchanged.

Confirmed live: curl against api.doormile.com/api/v1/admin/login,
/admin/milers, /crm/clients, /admin/bookings all return the expected
{success,...} shapes and status codes for this code to handle.
This commit is contained in:
2026-07-08 17:06:01 +05:30
parent c52350df0f
commit ab59421861
2 changed files with 51 additions and 213 deletions

View File

@@ -1,10 +1,11 @@
// UNUSED — login1.js is the active login page (see routes/MainRoutes.js). This // This is the active login page — matched by routes/LoginRoutes.js, which is
// file still targets the old jupiter.nearle.app console-login flow. // registered before MainRoutes.js and wins the route match for both '/' and
// '/login'. (login1.js is dead code — MainRoutes.js's own '/login' route is
// unreachable because LoginRoutes.js's un-prefixed '/login' matches first.)
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { enqueueSnackbar } from 'notistack';
import AnimateButton from 'components/@extended/AnimateButton'; import AnimateButton from 'components/@extended/AnimateButton';
import { Box, Card, CardContent, Stack, TextField, Button, Typography, Link, FormLabel, IconButton, InputAdornment } from '@mui/material'; import { Box, Card, CardContent, Stack, TextField, Button, Typography, Link, IconButton, InputAdornment } from '@mui/material';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import axios from 'axios'; import axios from 'axios';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@@ -26,113 +27,53 @@ const Login = () => {
const theme = useTheme(); const theme = useTheme();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
let navigate = useNavigate(); let navigate = useNavigate();
const [, setOtp] = useState('');
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [passwordStatus, setPasswordStatus] = useState(0);
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isPassword] = useState(false);
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [userid, setUserid] = useState(0);
useEffect(() => { useEffect(() => {
if (localStorage.getItem('firstname')) { if (localStorage.getItem('authname')) {
navigate('/nearle/dispatch'); navigate('/nearle/dispatch');
} }
}, []); }, []);
const loginsend = async () => { const loginsend = async () => {
setLoading(true); if (!username || !password) {
if (!username) {
opentoast('Fill All required fields'); opentoast('Fill All required fields');
setLoading(false);
return; return;
} }
setLoading(true);
try { try {
const res = await axios.post(`https://jupiter.nearle.app/live/api/v1/users/console/login`, { const res = await axios.post(`${process.env.REACT_APP_URL}/admin/login`, {
authname: username, email: username,
configid: 9, // 9 -> config id for nearle console admin password,
userfcmtoken: fcmtoken?.token, userfcmtoken: fcmtoken?.token
password
}); });
// user not found
if (res.data.code == 409 && !res.data.status) { if (res.data.success) {
OpenToast(res.data.message, 'error', 3000); OpenToast('Login Successful', 'success', 1000);
} const { token, data } = res.data;
// user not activated dispatch(setLoginUser(data));
else if (res.data.code == 403) { localStorage.setItem('authname', data.email || data.firstname);
OpenToast(res.data.message, 'warning', 3000); localStorage.setItem('userid', data.userid);
} localStorage.setItem('roleid', data.roleid);
//user found, no password, setup password localStorage.setItem('token', token);
else if (res.data.code == 409 && res.data.status) { axios.defaults.headers.common.Authorization = `Bearer ${token}`;
setPasswordStatus(1); // for password and confirm password ui
setUserid(res.data.details.userid);
OpenToast('User Found', 'success', 3000);
OpenToast(res.data.message, 'success', 3000);
}
//user found, incorrect password
else if (res.data.code == 401 && !res.data.status) {
OpenToast(res.data.message, 'error', 3000);
}
//user found, enter password
else if (res.data.code == 401 && res.data.status) {
OpenToast(res.data.message, 'success', 3000);
fetchAppLocations(res.data.userid);
setPasswordStatus(2);
}
// user found, correct password
else if (res.data.code == 200 && res.data.status) {
OpenToast(res.data.message, 'success', 1000);
const userinfo = res.data.details;
dispatch(setLoginUser(userinfo));
localStorage.setItem('firstname', userinfo.firstname);
localStorage.setItem('authname', userinfo.authname);
localStorage.setItem('roleid', userinfo.roleid);
localStorage.setItem('tenantid', userinfo.tenantid);
localStorage.setItem('partnerid', userinfo.partnerid);
localStorage.setItem('applocationid', userinfo.applocationid);
localStorage.setItem('userid', userinfo.userid);
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
markSessionStart(); markSessionStart();
fetchAppLocations(userinfo.userid);
navigate('/nearle/dispatch'); navigate('/nearle/dispatch');
} else { } else {
OpenToast(res.data.message, 'error', 3000); OpenToast(res.data.message || 'Invalid Data', 'error', 3000);
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
OpenToast(err.message, 'error', 5000); OpenToast(err.response?.data?.message || err.message, 'error', 5000);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const opentoast = (message) => { const opentoast = (message) => {
enqueueSnackbar(message, { OpenToast(message, 'error', 1500);
variant: 'error',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 1500
});
};
const fetchAppLocations = async (id) => {
const response = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${id}`);
const updatedLocations = [...response.data.details, { locationname: 'All', applocationid: 0 }];
localStorage.setItem('applocations', JSON.stringify(updatedLocations));
};
const updateUser = async () => {
const response = await axios.put(`${process.env.REACT_APP_URL2}/users/update`, {
userid,
password
});
if (response.data.status) {
OpenToast(response.data.message, 'success', 3000);
OpenToast('Enter Password to Login', 'success', 3000);
setPasswordStatus(2);
setPassword('');
}
}; };
return ( return (
@@ -254,20 +195,7 @@ const Login = () => {
noValidate noValidate
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
if (passwordStatus == 0) { loginsend();
loginsend();
} else if (passwordStatus == 1) {
if (!password || !confirmPassword || password != confirmPassword) {
OpenToast('Check Password', 'warning', 3000);
} else {
updateUser();
}
} else if (passwordStatus == 2) {
if (!password) {
OpenToast('Invalid Password', 'warning', 3000);
}
loginsend();
}
}} }}
> >
<Stack spacing={3}> <Stack spacing={3}>
@@ -281,122 +209,27 @@ const Login = () => {
required required
value={username} value={username}
onChange={(e) => setUsername(e.target.value.toLocaleLowerCase())} onChange={(e) => setUsername(e.target.value.toLocaleLowerCase())}
InputProps={{ readOnly: passwordStatus }}
/> />
{/* Setup Password */} {/* Password */}
{passwordStatus == 1 && ( <TextField
<Stack display={'flex'} flexDirection={'column'} spacing={3}> fullWidth
<Typography variant="h4" textAlign="start" mb={3}> label="Password"
Setup Password variant="outlined"
</Typography> autoComplete="current-password"
<TextField required
autoFocus type={showPassword ? 'text' : 'password'}
fullWidth value={password}
label="Enter New Password" onChange={(e) => setPassword(e.target.value)}
variant="outlined" InputProps={{
required endAdornment: (
type={showPassword ? 'text' : 'password'} <InputAdornment position="end">
value={password} <IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end">
onChange={(e) => setPassword(e.target.value)} {showPassword ? <VisibilityOff /> : <Visibility />}
InputProps={{ </IconButton>
endAdornment: ( </InputAdornment>
<InputAdornment position="end"> )
<IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end"> }}
{showPassword ? <VisibilityOff /> : <Visibility />} />
</IconButton>
</InputAdornment>
)
}}
/>
<TextField
error={confirmPassword !== '' && password !== confirmPassword}
fullWidth
label="Re-Enter Password"
variant="outlined"
required
type={showConfirmPassword ? 'text' : 'password'}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => setShowConfirmPassword((prev) => !prev)} edge="end">
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
/>
</Stack>
)}
{/* Enter Password */}
{passwordStatus == 2 && (
<Stack display={'flex'} flexDirection={'column'} spacing={3}>
<Typography variant="h4" textAlign="start" mb={3}>
Enter Password
</Typography>
<TextField
autoFocus
fullWidth
label="Enter Password"
variant="outlined"
required
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end">
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
/>
</Stack>
)}
{/* OTP */}
{isPassword && (
<Stack spacing={1.5}>
<Stack direction="row" justifyContent="space-between">
<FormLabel>Enter Password</FormLabel>
<Link
variant="body2"
sx={{ cursor: 'pointer' }}
onClick={() => {
setOtp('');
loginsend();
}}
>
Retry
</Link>
</Stack>
{/* <OtpInput
shouldAutoFocus
value={otp}
onChange={(otp) => setOtp(otp)}
numInputs={4}
containerStyle={{ justifyContent: 'space-between' }}
inputStyle={{
width: 48,
height: 48,
borderRadius: 8,
border: `1px solid ${borderColor}`,
fontSize: 18
}}
focusStyle={{
outline: 'none',
border: `1px solid ${theme.palette.primary.main}`,
boxShadow: theme.customShadows.primary
}}
/> */}
<TextField type="passowrd" value={password} onChange={(e) => setPassword(e.target.value)} />
</Stack>
)}
{/* Submit */} {/* Submit */}
<AnimateButton> <AnimateButton>
<Button fullWidth size="large" type="submit" variant="contained" color="primary"> <Button fullWidth size="large" type="submit" variant="contained" color="primary">

View File

@@ -1,3 +1,8 @@
// UNUSED — routes/LoginRoutes.js is registered before MainRoutes.js and
// defines its own un-prefixed '/login' route pointing at pages/nearle/login,
// which wins the match. This file's '/login' route inside MainRoutes.js is
// unreachable. Kept updated to Doormile endpoints anyway (harmless), but
// pages/nearle/login.js is the one that actually renders.
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { import {
Box, Box,