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:
@@ -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">
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user