85b31e9528
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local> Reviewed-on: #1
247 lines
6.6 KiB
React
247 lines
6.6 KiB
React
import React, { useEffect, useRef, useReducer, useContext } from "react";
|
|
import "./LoginAuth.css";
|
|
import { SERVER_URL } from "../../constants.js";
|
|
import { fetchData } from '../../fetching/Fetch.js';
|
|
import { UserContext } from '../../context/UserContext.jsx';
|
|
import { useNavigate } from "react-router-dom";
|
|
import { getUserData, setSession } from '../../utils/session.js';
|
|
|
|
function doSubmit(submittedValues) {
|
|
console.log(`Submitted: ${submittedValues.join("")}`);
|
|
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, 1500);
|
|
});
|
|
}
|
|
|
|
function clampIndex(index) {
|
|
if (index > 6) {
|
|
return 6;
|
|
} else if (index < 0) {
|
|
return 0;
|
|
} else {
|
|
return index;
|
|
}
|
|
}
|
|
|
|
function reducer(state, action) {
|
|
switch (action.type) {
|
|
case "INPUT":
|
|
return {
|
|
...state,
|
|
inputValues: [
|
|
...state.inputValues.slice(0, action.payload.index),
|
|
action.payload.value,
|
|
...state.inputValues.slice(action.payload.index + 1)
|
|
],
|
|
focusedIndex: clampIndex(state.focusedIndex + 1)
|
|
};
|
|
|
|
case "BACK":
|
|
return {
|
|
...state,
|
|
focusedIndex: clampIndex(state.focusedIndex - 1)
|
|
};
|
|
|
|
case "PASTE":
|
|
return {
|
|
...state,
|
|
inputValues: state.inputValues.map(
|
|
(_, index) => action.payload.pastedValue[index] || ""
|
|
)
|
|
};
|
|
|
|
case "FOCUS":
|
|
return {
|
|
...state,
|
|
focusedIndex: action.payload.focusedIndex
|
|
};
|
|
|
|
case "VERIFY":
|
|
return {
|
|
...state,
|
|
status: "pending"
|
|
};
|
|
|
|
case "VERIFY_SUCCESS":
|
|
return {
|
|
...state,
|
|
status: "idle"
|
|
};
|
|
|
|
case "RESET_INPUTS":
|
|
return {
|
|
...state,
|
|
inputValues: Array(6).fill(""),
|
|
focusedIndex: 0,
|
|
status: "idle"
|
|
};
|
|
|
|
default:
|
|
throw new Error("unknown action");
|
|
}
|
|
}
|
|
|
|
const initialState = {
|
|
inputValues: Array(6).fill(""),
|
|
focusedIndex: 0,
|
|
status: "idle"
|
|
};
|
|
|
|
export default function LoginAuth() {
|
|
const { user, setUser } = useContext(UserContext);
|
|
const [{ inputValues, focusedIndex, status }, dispatch] = useReducer(
|
|
reducer,
|
|
initialState
|
|
);
|
|
|
|
function handleInput(index, value) {
|
|
dispatch({ type: "INPUT", payload: { index, value } });
|
|
}
|
|
|
|
function handleBack() {
|
|
dispatch({ type: "BACK" });
|
|
}
|
|
|
|
function handlePaste(pastedValue) {
|
|
dispatch({ type: "PASTE", payload: { pastedValue } });
|
|
|
|
if (pastedValue.length === 6) {
|
|
dispatch({ type: "VERIFY" });
|
|
doSubmit(pastedValue.split("")).then(() =>
|
|
dispatch({ type: "VERIFY_SUCCESS" })
|
|
);
|
|
}
|
|
}
|
|
|
|
function handleFocus(focusedIndex) {
|
|
dispatch({ type: "FOCUS", payload: { focusedIndex } });
|
|
}
|
|
|
|
async function handleSubmit(e) {
|
|
|
|
//e.preventDefault();
|
|
|
|
dispatch({ type: "VERIFY" });
|
|
|
|
try {
|
|
const userData = getUserData();
|
|
if (!userData) {
|
|
throw new Error("Email not found in session");
|
|
}
|
|
|
|
const url = `${ SERVER_URL }/api/v1/auth/tfa`;
|
|
const { data, success } = await fetchData(url, 'POST', {
|
|
code: inputValues.join(""),
|
|
email: userData.email
|
|
});
|
|
|
|
if (success) {
|
|
setSession({
|
|
token: data.token,
|
|
userData: data.userData,
|
|
isTfa: true,
|
|
});
|
|
setUser(data.userData);
|
|
navigate(`/${ data.userData.tenantCode }/home`);
|
|
} else {
|
|
throw new Error("Code could not be verified. It is incorrect or expired.");
|
|
}
|
|
} catch (error) {
|
|
alert("Code could not be verified. It is incorrect or expired.");
|
|
resetInputs();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function resetInputs() {
|
|
dispatch({ type: "RESET_INPUTS" });
|
|
}
|
|
let navigate = useNavigate();
|
|
function routeChange() {
|
|
handleSubmit({});
|
|
}
|
|
|
|
return (
|
|
<div id="auth-container">
|
|
<form id="authForm" onSubmit={handleSubmit}>
|
|
<div className="inputs">
|
|
{inputValues.map((value, index) => {
|
|
return (
|
|
<Input
|
|
key={index}
|
|
index={index}
|
|
value={value}
|
|
onChange={handleInput}
|
|
onBackspace={handleBack}
|
|
onPaste={handlePaste}
|
|
isFocused={index === focusedIndex}
|
|
onFocus={handleFocus}
|
|
isDisabled={status === "pending"}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
<button className="button-auth" disabled={status === "pending"} onClick={routeChange}>
|
|
{status === "pending" ? "VERIFYING..." : "VERIFY"}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Input({
|
|
index,
|
|
value,
|
|
onChange,
|
|
onPaste,
|
|
onBackspace,
|
|
isFocused,
|
|
onFocus,
|
|
isDisabled
|
|
}) {
|
|
const ref = useRef();
|
|
useEffect(() => {
|
|
requestAnimationFrame(() => {
|
|
if (ref.current !== document.activeElement && isFocused) {
|
|
ref.current.focus();
|
|
}
|
|
});
|
|
}, [isFocused]);
|
|
|
|
function handleChange(e) {
|
|
onChange(index, e.target.value);
|
|
}
|
|
|
|
function handlePaste(e) {
|
|
onPaste(e.clipboardData.getData("text"));
|
|
}
|
|
|
|
function handleKeyDown(e) {
|
|
if (e.key === "Backspace") {
|
|
onBackspace();
|
|
}
|
|
}
|
|
|
|
function handleFocus(e) {
|
|
e.target.setSelectionRange(0, 1);
|
|
onFocus(index);
|
|
}
|
|
|
|
return (
|
|
<input
|
|
ref={ref}
|
|
type="text"
|
|
value={value}
|
|
onChange={handleChange}
|
|
onPaste={handlePaste}
|
|
onKeyDown={handleKeyDown}
|
|
maxLength="1"
|
|
onFocus={handleFocus}
|
|
disabled={isDisabled}
|
|
/>
|
|
);
|
|
} |