diff --git a/backend/users/views.py b/backend/users/views.py
index 1b1ca1c..fae206a 100644
--- a/backend/users/views.py
+++ b/backend/users/views.py
@@ -114,11 +114,19 @@ def post(self, request):
if email is None:
return Response({"detail": "email not provided"}, status=400)
if get_user_model().objects.filter(email=email).exists():
+
+ # Check if the email is verified
+ user = get_user_model().objects.get(email=email)
+ if not user.is_active:
+ return Response(
+ {"detail": "email not verified", "is_verified": "false"}, status=400
+ )
+
from_mail = settings.EMAIL_HOST_USER
to_mail = email
send_reset_email(to_mail=to_mail, from_mail=from_mail)
- return Response({"detail": "Email sent"})
+ return Response({"detail": "Email sent", "is_verified": "true"})
else:
return Response({"error": "Email not found"}, status=400)
diff --git a/frontend/src/App.js b/frontend/src/App.js
index cc76183..4a63dfe 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -1,89 +1,89 @@
-import './App.css';
-import LandingPage from './pages/LandingPage';
-import Auth from './pages/Auth';
-import EmailVerify from './pages/EmailVerify';
+import "./App.css";
+import LandingPage from "./pages/LandingPage";
+import Auth from "./pages/Auth";
+import EmailVerify from "./pages/EmailVerify";
import {
- BrowserRouter as Router,
- Route,
- Switch,
- Redirect,
-} from 'react-router-dom';
-
-import Dashboard from './pages/Dashboard';
-import { useContext, useEffect, useState } from 'react';
-import AuthContext from './store/auth-context';
-import Modal from './components/Alert';
-import URL from './URL';
+ BrowserRouter as Router,
+ Route,
+ Switch,
+ Redirect,
+} from "react-router-dom";
+import Dashboard from "./pages/Dashboard";
+import { useContext, useEffect, useState } from "react";
+import AuthContext from "./store/auth-context";
+import Modal from "./components/Alert";
+import ForgotPassword from "./components/ForgotPassword";
+import URL from "./URL";
function App() {
- const authCtx = useContext(AuthContext);
- const [key, setKey] = useState();
- const [isError, setIsError] = useState(false)
+ const authCtx = useContext(AuthContext);
+ const [key, setKey] = useState();
+ const [isError, setIsError] = useState(false);
- useEffect(()=>{
+ useEffect(() => {
+ var requestOptions = {
+ method: "GET",
+ redirect: "follow",
+ };
- var requestOptions = {
- method: 'GET',
- redirect: 'follow'
- };
-
- fetch(`${URL}/`, requestOptions)
- .then(response => {
- if(response.status!==404)
- {
- setIsError(true)
- }
- else
- {
- setIsError(false)
- }
- console.log(response);
- return response;
- })
- .catch(error => {
- console.log(!!error)
- if(error)
- {
- setIsError(true)
- }
- else
- {
- setIsError(false)
- }
- });
- },[])
+ fetch(`${URL}/`, requestOptions)
+ .then(response => {
+ if (response.status !== 404) {
+ setIsError(true);
+ } else {
+ setIsError(false);
+ }
+ console.log(response);
+ return response;
+ })
+ .catch(error => {
+ console.log(!!error);
+ if (error) {
+ setIsError(true);
+ } else {
+ setIsError(false);
+ }
+ });
+ }, []);
- return (
-
-
+ return (
+
+
+
+ {authCtx.isLoggedIn ? (
+
+ ) : (
+
+ )}
+
-
- {authCtx.isLoggedIn ? :
- }
-
+ {authCtx.isLoggedIn && (
+
+
+
+ )}
- {authCtx.isLoggedIn && (
-
-
-
- )}
+ {!authCtx.isLoggedIn && (
+
+
+
+ )}
+
+
+
- {!authCtx.isLoggedIn && (
-
-
-
- )}
-
-
-
+
-
-
-
-
-
- );
+
+
+
+
+
+ );
}
export default App;
diff --git a/frontend/src/assets/css/ForgotPassword.css b/frontend/src/assets/css/ForgotPassword.css
new file mode 100644
index 0000000..026edee
--- /dev/null
+++ b/frontend/src/assets/css/ForgotPassword.css
@@ -0,0 +1,68 @@
+.password-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.password-container h1, .error-message {
+ font-size: 2.5rem;
+ margin: 0;
+ color: var(--textcolor);
+}
+
+.password-container p {
+ font-size: 1.3rem;
+}
+
+.password-container a, .try-again {
+ color: rgb(243, 233, 140);
+ cursor: pointer;
+ font-weight: 600;
+}
+
+.try-again:hover {
+ text-decoration: underline;
+}
+
+label,
+input {
+ display: block;
+ margin: 5px 0;
+}
+
+.password-container label {
+ color: var(--textcolor);
+ font-weight: 600;
+}
+
+.password-container input {
+ background-color: rgba(139, 137, 137, 0.39);
+ color: #e8f0fe;
+ border-radius: 5px;
+ border: none;
+ padding: 8px 10px;
+ font-size: 1.2rem;
+ margin: 10px 0 20px;
+ width: 90%;
+ min-width: 200px;
+}
+
+input:focus {
+ outline: none;
+}
+
+.password-container button {
+ display: block;
+ margin: 20px 0px;
+ padding: 8px 20px;
+ font-size: 1.2rem;
+ cursor: pointer;
+ background-color: rgb(243, 233, 140);
+ border: none;
+ border-radius: 2px;
+ font-weight: 600;
+}
+
+.password-container button:hover {
+ background-color: #f5e44f;
+}
diff --git a/frontend/src/components/ForgotPassword.js b/frontend/src/components/ForgotPassword.js
new file mode 100644
index 0000000..bbd3de4
--- /dev/null
+++ b/frontend/src/components/ForgotPassword.js
@@ -0,0 +1,187 @@
+import '../assets/css/ForgotPassword.css';
+
+import React, { useState } from 'react';
+
+import Alert from './Alert';
+import URL from '../URL';
+import axios from 'axios';
+import { Link, useHistory } from 'react-router-dom';
+
+const FORGOT_PW_ENDPOINT = '/api/users/forgot_password/';
+const NEW_PW_ENDPOINT = '/api/users/new_password/';
+const UNVERIFIED_MSG = 'There was an issue verifying your email.'
+
+const ForgotPassword = () => {
+ const history = useHistory();
+
+ // use isEmailVerified to render the appropriate content
+ // null return emailForm, true return passwordForm, false return an error message on the page
+ const [isEmailVerified, setIsEmailVerified] = useState(null)
+ const [loading, setLoading] = useState(false);
+ const [errorMsg, setErrorMsg] = useState(false);
+ const [successMsg, setSuccessMsg] = useState(false);
+ const [emailState, setEmailState] = useState({ email: '' })
+ const [passwordState, setPasswordState] = useState({
+ password1: '',
+ password2: '',
+ });
+ const { password1, password2 } = passwordState;
+
+ const handleEmailChange = e => {
+ const { value } = e.target;
+ setEmailState({ email: value})
+ }
+
+ const handlePasswordChange = e => {
+ const { name, value } = e.target;
+ setPasswordState({
+ ...passwordState,
+ [name]: value,
+ });
+ };
+
+ const submitEmail = email => fetch(`${URL}${FORGOT_PW_ENDPOINT}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email: email }),
+ })
+
+ const handleEmailSubmit = async e => {
+ e.preventDefault()
+ try {
+ if (!emailState.email) {
+ setErrorMsg('Enter a valid email address.');
+ return;
+ }
+ const response = await submitEmail(emailState.email)
+ console.log('response:', response)
+ if (response.status !== 200) {
+ setIsEmailVerified('unverified')
+ setErrorMsg(response.statusText)
+ }
+ setSuccessMsg('Please check your email to reset for a link to reset your password.')
+ } catch (err) {
+ setIsEmailVerified('unverified')
+ setErrorMsg(err.message)
+ console.error(err)
+ }
+ setEmailState({email: ''})
+ }
+
+ const sendNewPassword = newPassword =>
+ axios.post(URL + NEW_PW_ENDPOINT, newPassword);
+
+ const handleNewPasswordSubmit = async e => {
+ e.preventDefault();
+ setLoading(true);
+ try {
+ if (password1 === password2) {
+ const response = await sendNewPassword(password2);
+ if (response.statusText === 'OK') {
+ history.push('/');
+ }
+ }
+ if (password1 !== password2) {
+ setErrorMsg('The passwords do not match.');
+ }
+ } catch (err) {
+ setErrorMsg(err.message);
+ console.error(err);
+ }
+ setLoading(false);
+ setPasswordState({ password1: '', password2: '' });
+ };
+
+ const submitBtn = (
+
+ )
+
+ const emailForm = (
+ <>
+
Enter email
+ Enter your email to reset your password.
+
+ >
+ )
+
+ const passwordForm = (
+ <>
+ Reset password
+ Forgot your password? Reset it here.
+
+ >
+ )
+
+ const errorMessage = (
+ <>
+ {UNVERIFIED_MSG}
+
+ window.location.reload()}>Try again?
+
+ >
+ )
+
+ const renderContent = (isEmailVerified) => {
+ switch (isEmailVerified) {
+ case true:
+ return passwordForm;
+ case 'unverified':
+ return errorMessage;
+ default:
+ return emailForm;
+ }
+ }
+
+ return (
+ <>
+
+ {renderContent(isEmailVerified)}
+ {errorMsg && (
+
+ {errorMsg}
+ Tap to dismiss.
+
+ )}
+ {successMsg && (
+
+ {successMsg}
+ Tap to dismiss.
+
+ )}
+
+ >
+ );
+};
+
+export default ForgotPassword;
diff --git a/frontend/src/components/Login.js b/frontend/src/components/Login.js
index 136086a..5c25f76 100644
--- a/frontend/src/components/Login.js
+++ b/frontend/src/components/Login.js
@@ -8,293 +8,304 @@ import { useHistory } from "react-router";
import AuthContext from "../store/auth-context";
const Login = () => {
- const authCtx = useContext(AuthContext);
- const history = useHistory();
- const mq = window.matchMedia("(mix-width: 480px)");
+ const authCtx = useContext(AuthContext);
+ const history = useHistory();
+ const mq = window.matchMedia("(mix-width: 480px)");
- const [coverClass, setCoverClass] = useState("cover cover-register");
+ const [coverClass, setCoverClass] = useState("cover cover-register");
- const forgetPasswordHandler = () => {
- swal({
- text: "Take a deep breath and try to remember your password.",
- confirmButtonColor: "#f5e44f",
- confirmButtonText: "Yes, delete it!",
- });
- };
+ const forgetPasswordHandler = () => {
+ swal({
+ text: "Take a deep breath and try to remember your password.\nDo you still need to reset your password?",
+ confirmButtonColor: "#f5e44f",
+ confirmButtonText: "Yes, delete it!",
+ buttons: ["Nope!", "Yes!"],
+ })
+ .then((value) => {
+ value ? history.push('/forgot-password') : swal.close()
+ })
+ };
- const [loading, setLoading] = useState(false);
- const [created, setCreated] = useState(false);
- const [error, setError] = useState({
- status: false,
- body: "",
- });
+ const [loading, setLoading] = useState(false);
+ const [created, setCreated] = useState(false);
+ const [error, setError] = useState({
+ status: false,
+ body: "",
+ });
- const [email, setEmail] = useState("");
- const emailChangeHandler = (event) => {
- setEmail(event.target.value);
- };
+ const [email, setEmail] = useState("");
+ const emailChangeHandler = event => {
+ setEmail(event.target.value);
+ };
- const [pass, setPass] = useState("");
- const passChangeHandler = (event) => {
- setPass(event.target.value);
- };
+ const [pass, setPass] = useState("");
+ const passChangeHandler = event => {
+ setPass(event.target.value);
+ };
- const [pass2, setPass2] = useState("");
- const pass2ChangeHandler = (event) => {
- setPass2(event.target.value);
- };
+ const [pass2, setPass2] = useState("");
+ const pass2ChangeHandler = event => {
+ setPass2(event.target.value);
+ };
- const [name, setName] = useState("");
- const nameChangeHandler = (event) => {
- setName(event.target.value);
- };
+ const [name, setName] = useState("");
+ const nameChangeHandler = event => {
+ setName(event.target.value);
+ };
- const [warning, setWarning] = useState({
- status: false,
- body: "",
- });
+ const [warning, setWarning] = useState({
+ status: false,
+ body: "",
+ });
- //Register handler
- const registerHandler = (e) => {
- e.preventDefault();
- setCreated(false);
+ //Register handler
+ const registerHandler = e => {
+ e.preventDefault();
+ setCreated(false);
- if (pass == pass2) {
- setLoading(true);
- setError({
- status: false,
- body: "",
- });
+ if (pass == pass2) {
+ setLoading(true);
+ setError({
+ status: false,
+ body: "",
+ });
- var myHeaders = new Headers();
- myHeaders.append("Content-Type", "application/json");
+ var myHeaders = new Headers();
+ myHeaders.append("Content-Type", "application/json");
- let registerDetails = JSON.stringify({
- email: email,
- password: pass,
- name: name,
- });
+ let registerDetails = JSON.stringify({
+ email: email,
+ password: pass,
+ name: name,
+ });
- var requestOptions = {
- method: "POST",
- headers: myHeaders,
- body: registerDetails,
- redirect: "follow",
- };
+ var requestOptions = {
+ method: "POST",
+ headers: myHeaders,
+ body: registerDetails,
+ redirect: "follow",
+ };
- fetch(`${URL}/api/users/register/`, requestOptions)
- .then((response) => {
- const data = response.json();
- setLoading(false);
+ fetch(`${URL}/api/users/register/`, requestOptions)
+ .then(response => {
+ const data = response.json();
+ setLoading(false);
- if (response.status == 200) setCreated(true);
- else setCreated(false);
+ if (response.status == 200) setCreated(true);
+ else setCreated(false);
- return data;
- })
- .then((result) => {
- console.log(result);
+ return data;
+ })
+ .then(result => {
+ console.log(result);
- let firstkey = Object.keys(result)[0];
+ let firstkey = Object.keys(result)[0];
- setError({
- status: true,
- body: result[firstkey],
- });
- })
- .catch((error) => console.log("error", error));
- } else {
- setError({
- status: true,
- body: "The passwords dont match!",
- });
- }
- };
+ setError({
+ status: true,
+ body: result[firstkey],
+ });
+ })
+ .catch(error => console.log("error", error));
+ } else {
+ setError({
+ status: true,
+ body: "The passwords dont match!",
+ });
+ }
+ };
- // Login handler
- const loginHandler = (e) => {
- e.preventDefault();
- setLoading(true);
+ // Login handler
+ const loginHandler = e => {
+ e.preventDefault();
+ setLoading(true);
- var myHeaders = new Headers();
- myHeaders.append("Content-Type", "application/json");
+ var myHeaders = new Headers();
+ myHeaders.append("Content-Type", "application/json");
- let authDetails = JSON.stringify({
- email: email,
- password: pass,
- });
+ let authDetails = JSON.stringify({
+ email: email,
+ password: pass,
+ });
- var requestOptions = {
- method: "POST",
- headers: myHeaders,
- body: authDetails,
- redirect: "follow",
- };
+ var requestOptions = {
+ method: "POST",
+ headers: myHeaders,
+ body: authDetails,
+ redirect: "follow",
+ };
- fetch(`${URL}/api/auth/login/`, requestOptions)
- .then((response) => {
- setLoading(false)
- if (response.ok) {
- return response.json();
- }
- return Promise.reject(response);
- })
- .then((result) => {
- authCtx.login(result.key);
+ fetch(`${URL}/api/auth/login/`, requestOptions)
+ .then(response => {
+ setLoading(false);
+ if (response.ok) {
+ return response.json();
+ }
+ return Promise.reject(response);
+ })
+ .then(result => {
+ authCtx.login(result.key);
- if (result.key) history.replace("/dashboard");
+ if (result.key) history.replace("/dashboard");
- let firstkey = Object.keys(result)[0];
+ let firstkey = Object.keys(result)[0];
- setWarning({
- status: true,
- body: result[firstkey],
- });
+ setWarning({
+ status: true,
+ body: result[firstkey],
+ });
- if (result.key)
- setWarning({
- status: true,
- body: "Loggin successful",
- });
- })
- .catch((error) => {
- error.json().then((result)=>{
- let firstkey = Object.keys(result)[0];
+ if (result.key)
+ setWarning({
+ status: true,
+ body: "Loggin successful",
+ });
+ })
+ .catch(error => {
+ error.json().then(result => {
+ let firstkey = Object.keys(result)[0];
- setWarning({
- status: true,
- body: result[firstkey],
- });
- });
- })
- };
- var width = Math.max(window.screen.width);
+ setWarning({
+ status: true,
+ body: result[firstkey],
+ });
+ });
+ });
+ };
+ var width = Math.max(window.screen.width);
- return (
-
-

-
-
-
-
- );
+ return (
+
+

+
+
+ {" "}
+
+
+
+ );
};
export default Login;