diff --git a/src/components/form/errorMessage.js b/src/components/form/errorMessage.js new file mode 100644 index 00000000..10e27466 --- /dev/null +++ b/src/components/form/errorMessage.js @@ -0,0 +1,11 @@ +const ErrorMessage = ({message}) => { + return ( +
+ {message &&

{message}

} +
+ ) + +} + +export default ErrorMessage + diff --git a/src/context/auth.js b/src/context/auth.js index a7b9462d..a692959b 100644 --- a/src/context/auth.js +++ b/src/context/auth.js @@ -5,85 +5,133 @@ import Modal from "../components/modal"; import Navigation from "../components/navigation"; import useAuth from "../hooks/useAuth"; import { createProfile, login, register } from "../service/apiClient"; -import jwt_decode from "jwt-decode" - -const AuthContext = createContext() +import jwt_decode from "jwt-decode"; +import ERR from "../../src/utils/errors.js"; +const AuthContext = createContext(); const AuthProvider = ({ children }) => { - const navigate = useNavigate() - const location = useLocation() - const [token, setToken] = useState(null) - - useEffect(() => { - const storedToken = localStorage.getItem('token') - - if (storedToken) { - setToken(storedToken) - navigate(location.state?.from?.pathname || "/") - } - }, [location.state?.from?.pathname, navigate]) - - const handleLogin = async (email, password) => { - const res = await login(email, password) - - if (!res.data.token) { - return navigate("/login") - } - - localStorage.setItem('token', res.data.token) - - setToken(res.token) - navigate(location.state?.from?.pathname || "/") - }; - - const handleLogout = () => { - localStorage.removeItem('token') - setToken(null) - }; + const navigate = useNavigate(); + const location = useLocation(); + const [token, setToken] = useState(null); - const handleRegister = async (email, password) => { - const res = await register(email, password) - setToken(res.data.token) + useEffect(() => { + const storedToken = localStorage.getItem("token"); - navigate("/verification") + if (storedToken) { + setToken(storedToken); + navigate(location.state?.from?.pathname || "/"); } + }, [location.state?.from?.pathname, navigate]); + + const handleLogin = async (email, password, setErrors) => { + try { + const validatedEmail = validationEmail(email); + if (!validatedEmail) { + throw new Error(ERR.EMAIL_ERROR_MESSAGE); + } + + const validatedPassword = validationPassword(password); + if (!validatedPassword) { + throw new Error(ERR.PASSWORD_REQUIRMENTS); + } + + const res = await login(email, password); + if (!res.status) { + throw new Error(res.error.message); + } + if (!res.data.token) { + return navigate("/login"); + } + + localStorage.setItem("token", res.data.token); + + setToken(res.data.token); + navigate(location.state?.from?.pathname || "/"); + } catch (error) { + setErrors(error.message); + } + }; + + const handleLogout = () => { + localStorage.removeItem("token"); + setToken(null); + }; + + const handleRegister = async (email, password, setErrors) => { + try { + const validatedEmail = validationEmail(email); + if (!validatedEmail) { + throw new Error(ERR.EMAIL_ERROR_MESSAGE); + } + const validatedPassword = validationPassword(password); + if (!validatedPassword) { + throw new Error(ERR.PASSWORD_REQUIRMENTS); + } + const res = await register(email, password, setErrors); + if (!res.status) { + throw new Error(res.error.message); + } + setToken(res.data.token); + navigate("/verification"); + } catch (error) { + setErrors(error.message); + } + }; - const handleCreateProfile = async (firstName, lastName, githubUrl, bio) => { - const { userId } = jwt_decode(token) + const handleCreateProfile = async (firstName, lastName, githubUrl, bio) => { + const { userId } = jwt_decode(token); - await createProfile(userId, firstName, lastName, githubUrl, bio) + await createProfile(userId, firstName, lastName, githubUrl, bio); - localStorage.setItem('token', token) - navigate('/') - } + navigate("/"); + }; - const value = { - token, - onLogin: handleLogin, - onLogout: handleLogout, - onRegister: handleRegister, - onCreateProfile: handleCreateProfile - }; + const value = { + token, + onLogin: handleLogin, + onLogout: handleLogout, + onRegister: handleRegister, + onCreateProfile: handleCreateProfile, + }; - return {children} + return {children}; }; const ProtectedRoute = ({ children }) => { - const { token } = useAuth() - const location = useLocation() - - if (!token) { - return - } - - return ( -
-
- - - {children} -
- ) + const { token } = useAuth(); + const location = useLocation(); + + if (!token) { + return ; + } + + return ( +
+
+ + + {children} +
+ ); +}; +function validationEmail(email) { + const emailPattern = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/; + + return !email || !emailPattern.test(email) ? false : true; +} + +function validationPassword(password) { + const minLength = 8; + const hasUppercase = /[A-Z]/.test(password); + const hasNumber = /\d/.test(password); + const hasSpecialCharacter = /[!@#$%^&*(),.?":{}|<>]/.test(password); + + return password.length < minLength || + !hasUppercase || + !hasNumber || + !hasSpecialCharacter + ? false + : true; } -export { AuthContext, AuthProvider, ProtectedRoute } +export { AuthContext, AuthProvider, ProtectedRoute }; diff --git a/src/pages/login/index.js b/src/pages/login/index.js index 60566866..91dcfc0e 100644 --- a/src/pages/login/index.js +++ b/src/pages/login/index.js @@ -4,50 +4,55 @@ import TextInput from "../../components/form/textInput"; import useAuth from "../../hooks/useAuth"; import CredentialsCard from "../../components/credentials"; import "./login.css"; +import ErrorMessage from "../../components/form/errorMessage"; const Login = () => { - const { onLogin } = useAuth(); - const [formData, setFormData] = useState({ email: "", password: "" }); + const { onLogin } = useAuth(); + const [formData, setFormData] = useState({ email: "", password: "" }); + const [errors, setErrors] = useState(null); + const onChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + setErrors(null); + }; - const onChange = (e) => { - const { name, value } = e.target; - setFormData({ ...formData, [name]: value }); - }; - - return ( -
- -
-
- - - -
-
-
- ); + return ( +
+ +
+
+ + + + +
+
+
+ ); }; export default Login; diff --git a/src/pages/register/index.js b/src/pages/register/index.js index 8d0f693d..3bfa0983 100644 --- a/src/pages/register/index.js +++ b/src/pages/register/index.js @@ -4,51 +4,58 @@ import TextInput from "../../components/form/textInput"; import useAuth from "../../hooks/useAuth"; import CredentialsCard from "../../components/credentials"; import "./register.css"; +import ErrorMessage from "../../components/form/errorMessage"; const Register = () => { - const { onRegister } = useAuth(); - const [formData, setFormData] = useState({ email: "", password: "" }); + const { onRegister } = useAuth(); + const [formData, setFormData] = useState({ email: "", password: "" }); + const [errors, setErrors] = useState(null); - const onChange = (e) => { - const { name, value } = e.target; - setFormData({ ...formData, [name]: value }); - }; + const onChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + setErrors(null); + }; - return ( -
- -
-
- - - -
-
-
- ); + return ( +
+ +
+
+ + + + + +
+
+
+ ); }; export default Register; diff --git a/src/service/apiClient.js b/src/service/apiClient.js index ae4d87cf..4a391a4f 100644 --- a/src/service/apiClient.js +++ b/src/service/apiClient.js @@ -1,12 +1,34 @@ + import { API_URL } from "./constants" async function login(email, password) { - return await post('login', { email, password }, false) + try { + const res = await post('login', { email, password }, false) + if(res.status !== 'success') { + throw new Error(res.data.error) + } + return res + } catch (error) { + return { status: false, error : error} + } } async function register(email, password) { - await post('users', { email, password }, false) - return await login(email, password) + try { + const res = await post('users', { email, password }, false) + if(res.status === 'fail') { + throw new Error(res.data.error) + } + return login(email, password) + } catch (error) { + if(error === 'Email already in use') { + return { status : false, error : error} + } + if ( error === 'Unable to create new user') { + return { status : false, error : 'There was an issue creating your account. Please try again later or contact support.'} + } + return { status : false, error : error} + } } async function createProfile(userId, firstName, lastName, githubUrl, bio) { diff --git a/src/utils/errors.js b/src/utils/errors.js new file mode 100644 index 00000000..077b0117 --- /dev/null +++ b/src/utils/errors.js @@ -0,0 +1,5 @@ +export default { + PASSWORD_REQUIRMENTS: + "Password must be at least 8 characters long and include at least one uppercase letter, one number, and one special character.", + EMAIL_ERROR_MESSAGE: "Please enter a valid email", +};