Skip to content
Open
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
REACT_APP_APIENDPOINT="http://localhost:4500/api"
SKIP_PREFLIGHT_CHECK=true
DISABLE_ESLINT_PLUGIN=true
REACT_APP_DEF_PWD=123Welcome!
REACT_APP_DEF_PWD=123Welcome!
REACT_APP_SUPPORT_EMAIL=dev-support@onecommunity.com
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"react-datepicker": "^4.25.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.75.0",
"react-icons": "^4.12.0",
"react-leaflet": "4.2.1",
"react-leaflet-cluster": "^3.1.0",
Expand Down
2 changes: 1 addition & 1 deletion src/components/LBDashboard/Login/LBLogin.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function LBLogin(props) {
// Dispatch login action
const res = await dispatch(loginBMUser({ email: enteredEmail, password: enteredPassword }));

if (res.statusText !== 'OK') {
if (res.status !== 200) {
// Handle backend errors
if (res.status === 422 && res.data?.label) {
setFieldErrors(prev => ({
Expand Down
170 changes: 170 additions & 0 deletions src/components/MaterialEquipmentToolTracker/Login/Login.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Form, Input, Button, FormFeedback, FormGroup } from 'reactstrap';
import { Redirect, useHistory, useLocation } from 'react-router-dom';
import { User, Lock } from 'lucide-react';
import isEmail from 'validator/lib/isEmail';
import { loginBMUser } from '~/actions/authActions';
import styles from './Login.module.css';
import { useForm } from 'react-hook-form';

function Login() {
const dispatch = useDispatch();
const history = useHistory();
const location = useLocation();
const auth = useSelector(state => state.auth);
const darkMode = useSelector(state => state.theme.darkMode);
const supportEmail = process.env.REACT_APP_SUPPORT_EMAIL;
const [showPassword, setShowPassword] = useState(false);
const [backendError, setBackendError] = useState('');
const [hasAccess, setHasAccess] = useState(false);
const prevLocation = location?.state?.from || { pathname: '/bmdashboard' };
const [capsLockOn, setCapsLockOn] = useState(false);

const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm({
mode: 'onChange', // inline validation while typing
});

useEffect(() => {
if (hasAccess || auth.user.access?.canAccessBMPortal) {
history.push(prevLocation.pathname);
}
}, [hasAccess, auth.user.access, history, prevLocation.pathname]);

const onSubmit = async data => {
const res = await dispatch(loginBMUser(data));

if (res.status !== 200) {
if (res.status === 422 && res.data?.label) {
setError(res.data.label, {
type: 'server',
message: res.data.message,
});
} else {
setBackendError(res.data?.message || 'Something went wrong');
}
return;
}
setBackendError('');
setHasAccess(!!res.data.token);
};
// push Dashboard if not authenticated
if (!auth.isAuthenticated) {
return <Redirect to={{ pathname: '/login', state: { from: location } }} />;
}
return (
<div className={styles.loginContainer}>
<h1 className={styles.pageTitle}>Highest Good Network</h1>
<h1 className={styles.pageTitle}> Material Equipemnt Tool Tracker</h1>
<div className={`${styles.formContainer} ${darkMode ? styles.darkBg : ''}`}>
<Form onSubmit={handleSubmit(onSubmit)}>
{/* Backend/general error */}
{backendError && (
<div className="alert alert-danger" role="alert">
{backendError}
</div>
)}
<User size="70" strokeWidth={1.5} aria-hidden="true" />
<h2 className={styles.heading2}>Welcome to HGN</h2>
<FormGroup>
<div className={styles.inputWrapper}>
<User size="30" strokeWidth={1.5} aria-hidden="true" />
<Input
type="text"
id="email"
placeholder="email"
innerRef={
register('email', {
required: 'Email is required',
validate: value => isEmail(value) || 'Invalid email',
}).ref
}
onChange={register('email').onChange}
onBlur={register('email').onBlur}
name="email"
className={`${styles.inputBox} ${darkMode ? styles.darkMail : ''}`}
aria-label="email input field"
invalid={!!errors.email}
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
</div>
{errors.email && (
<FormFeedback className="d-block error" id="email-error">
{errors.email.message}
</FormFeedback>
)}
</FormGroup>
<FormGroup>
<div className={styles.inputWrapper}>
<Lock size="25" strokeWidth={1.5} aria-hidden="true" />
<Input
placeholder="password"
name="password"
id="password"
className={styles.inputBox}
type={showPassword ? 'text' : 'password'}
innerRef={
register('password', {
required: 'Password is required',
minLength: {
value: 8,
message: 'Password must be at least 8 characters',
},
}).ref
}
onChange={register('password').onChange}
onBlur={register('password').onBlur}
onKeyUp={e => setCapsLockOn(e.getModifierState('CapsLock'))}
invalid={!!errors.password}
aria-label="Password"
aria-invalid={!!errors.password}
aria-describedby={errors.password ? 'password-error' : undefined}
/>
{/* eslint-disable-next-line jsx-a11y/prefer-tag-over-role */}
<span
role="button"
tabIndex={0}
aria-label={showPassword ? 'Hide password' : 'Show password'}
title={showPassword ? 'Hide password' : 'Show password'}
onClick={() => setShowPassword(prev => !prev)}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') setShowPassword(prev => !prev);
}}
>
<i className={showPassword ? 'fa fa-eye-slash' : 'fa fa-eye'} />
</span>
</div>
{capsLockOn && <span className={styles.capsMsg}> &nbsp; Caps Lock is ON</span>}
{errors.password && (
<FormFeedback id="password-error" className="d-block error">
{errors.password.message}
</FormFeedback>
)}
</FormGroup>
<div className={styles.forgetPswd}>
<a href="https://www.highestgood.com/forgotpassword">Forgot your password?</a>
</div>
{/* Login Button */}
<div>
<Button className={styles.logInBtn} disabled={isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Login'}
</Button>
</div>
<div>
<p className={styles.infoSec}>
If you do not have login info, contact the admin({supportEmail})
</p>
</div>
</Form>
</div>
</div>
);
}

export default Login;
124 changes: 124 additions & 0 deletions src/components/MaterialEquipmentToolTracker/Login/Login.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
.loginContainer {
background-color: #353a3f;
padding: 2em;
margin-top: -1.5em;
font-family: Inter, sans-serif;
min-height: 100vh;
}

.pageTitle {
font-size: x-large;
text-align: center;
color: #f7f7f7;
font-weight: 400;
}

.formContainer {
background-color: #e8f4f9;
text-align: center;
padding: 4em;
width: 100%;
max-width: 400px;
margin: 1em auto;
border-radius: 3%;
}

.heading2 {
color: #df7801;
font-size: large;
font-weight: 600;
padding: 1em;
}

.inputBox {
border: 0;
background: transparent;
border-radius: 0;
text-align: center;
width: auto;
}

.inputWrapper {
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 1px solid;
margin: 1em 0 0;
}

.inputIcon {
padding-left: 1em;
}

.forgetPswd {
text-align: right;
font-size: small;
padding: 0.5em 0;
}

.forgetPswd a {
color: #000;
}

.logInBtn {
display: block;
margin: 1em;
background-color: #2e5061;
color: #f7f7f7;
padding: 0.5em 2em;
font-weight: 100;
font-size: medium;
}

.infoSec {
color: #df7801;
text-align: center;
font-weight: 400;
font-size: small;
margin: 0;
}

.formContainer .inputWrapper:focus-within {
outline: none;
box-shadow: 0 0 0 2px #cfd5e2;
}

.formContainer .inputWrapper .inputBox:focus {
box-shadow: none;
}

.darkBg {
background-color: #121517c9;
}

.passwordToggle {
background: none;
border: none;
padding: 0.5em;
cursor: pointer;
color: inherit;
display: flex;
align-items: center;
}

:global(body.dark-mode) .infoSec,
:global(body.bm-dashboard-dark) .infoSec {
color: #df7801;
}

.darkMail:-webkit-autofill,
.darkMail:-webkit-autofill:hover,
.darkMail:-webkit-autofill:focus,
.darkMail:-webkit-autofill:active {
/* stylelint-disable-next-line property-no-vendor-prefix */
-webkit-box-shadow: 0 0 0 1000px #1a1a1a inset;
-webkit-text-fill-color: #fff;
caret-color: #fff;
transition: background-color 5000s ease-in-out 0s;
}

.capsMsg {
color: #ff7b00;
font-size: 12px;
float: right;
}
4 changes: 3 additions & 1 deletion src/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ const ProcessingLandingPage = lazy(() =>
);

const SuggestedJobsListBuilder = lazy(() => import('./components/Collaboration/SuggestedJobsList'));
import TrackerLogin from './components/MaterialEquipmentToolTracker/Login/Login';

export default (
<Switch>
{/* ----- LB Dashboard Routing Starts----- */}
Expand Down Expand Up @@ -1006,7 +1008,6 @@ export default (
<LBProtectedRoute path="/lbdashboard" exact component={LBDashboard} />
<LBProtectedRoute path="/lbdashboard/listOverview/:id" exact component={ListOveriew} />
<LBProtectedRoute path="/lbdashboard/masterplan" exact component={MasterPlan} />
<Route path="/lbdashboard/login" component={LBLogin} />
<Route path="/lbdashboard/register" component={LBRegister} />
<LBProtectedRoute path="/lbdashboard/messaging" component={LBMessaging} />
<Route
Expand Down Expand Up @@ -1282,6 +1283,7 @@ export default (
component={PRDashboardTopReviewedPRs}
/>
<ProtectedRoute path="/pr-dashboard/details" exact component={PRDashboardDetails} />
<ProtectedRoute path="/tracker/login" exact component={TrackerLogin} />
<Route path="*" component={NotFoundPage} />
</Switch>
</>
Expand Down
Loading
Loading