Skip to content

Commit

Permalink
fix email verification screen flow
Browse files Browse the repository at this point in the history
  • Loading branch information
liberty-rising committed Jan 18, 2024
1 parent 14f1ba2 commit 6beaaf3
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 32 deletions.
1 change: 1 addition & 0 deletions backend/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class UserOut(BaseModel):
organization_id: Optional[int]
role: Optional[str]
requires_password_update: bool
email_verified: bool


class UserUpdate(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions backend/routes/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ async def read_users_me(current_user: User = Depends(get_current_user)):
organization_id=current_user.organization_id,
role=current_user.role,
requires_password_update=current_user.requires_password_update,
email_verified=current_user.email_verified,
)
return user_out

Expand Down
7 changes: 3 additions & 4 deletions backend/utils/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ async def send_verification_email_with_sendgrid(email: List[str], token: str):
html_content=f'Click on the link to verify your email: <a href="https://{APP_HOST}/verify-email?token={token}">https://{APP_HOST}/verify-email</a>',
)
# Disable click tracking in development
# if APP_ENV == "dev":
print("Disabling click tracking")
message.tracking_settings = TrackingSettings()
message.tracking_settings.click_tracking = ClickTracking(False, False)
if APP_ENV == "dev":
message.tracking_settings = TrackingSettings()
message.tracking_settings.click_tracking = ClickTracking(False, False)
try:
sg = SendGridAPIClient(SENDGRID_API_KEY)
response = await sg.send(message)
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/components/auth/RequireAuth.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Navigate } from "react-router-dom";
import { useAuth } from "../../contexts/AuthContext";

function RequireAuth({ children }) {
const { isAuthenticated, isLoading, isEmailVerified } = useAuth();
const { isAuthenticated, isLoading, isEmailVerified, loginProcessCompleted } =
useAuth();

// if (isLoading || !loginProcessCompleted) {
if (isLoading) {
return <div>Loading...</div>; // Or your preferred loading indicator/component
}
Expand All @@ -13,8 +15,13 @@ function RequireAuth({ children }) {
return <Navigate to="/login" />;
}

if (!isEmailVerified) {
console.log("from require auth");
if (isAuthenticated && !isEmailVerified) {
// Redirect to the verify-email page if email is not verified
console.log("triggered");
return <Navigate to="/verify-email" />;
}

if (loginProcessCompleted && !isEmailVerified) {
// Redirect to the verify-email page if email is not verified
return <Navigate to="/verify-email" />;
}
Expand Down
19 changes: 12 additions & 7 deletions frontend/src/contexts/AuthContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,19 @@ export const AuthProvider = ({ children }) => {
const [isEmailVerified, setIsEmailVerified] = useState(false); // Add a state for email verification
const [userRole, setUserRole] = useState(null);
const [isLoading, setIsLoading] = useState(true); // Add a loading state
const [loginProcessCompleted, setLoginProcessCompleted] = useState(false);

useEffect(() => {
const verifyToken = async () => {
try {
// Replace '/api/verifyToken' with your actual API endpoint
const response = await axios.get(`${API_URL}verify-token/`);
// Update based on the response message
if (response.data.message === "User is authenticated") {
setIsAuthenticated(true);

const userResponse = await axios.get(`${API_URL}users/me/`);
setUserRole(userResponse.data.role);

const emailResponse = await axios.get(
`${API_URL}users/is-email-verified/`,
);
setIsEmailVerified(emailResponse.data.email_verified);
setIsEmailVerified(userResponse.data.email_verified);
} else {
setIsAuthenticated(false);
}
Expand All @@ -57,14 +53,21 @@ export const AuthProvider = ({ children }) => {
setIsAuthenticated(newAuthState);
};

const updateEmailVerification = (newEmailVerificationState) => {
const updateEmailVerification = (newEmailVerificationState, callback) => {
setIsEmailVerified(newEmailVerificationState);
if (callback) {
callback();
}
};

const updateUserRole = (newUserRole) => {
setUserRole(newUserRole);
};

const markLoginProcessCompleted = (newState) => {
setLoginProcessCompleted(newState);
};

// The Provider component from our created context is used here.
// It makes the `isAuthenticated` state and `updateAuth` function available to any descendants of this component
return (
Expand All @@ -77,6 +80,8 @@ export const AuthProvider = ({ children }) => {
updateUserRole,
userRole,
isLoading,
loginProcessCompleted,
markLoginProcessCompleted,
}}
>
{children}
Expand Down
23 changes: 13 additions & 10 deletions frontend/src/pages/login/LoginPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ function LoginPage({ onLogin }) {
updateEmailVerification,
updateUserRole,
isAuthenticated,
isEmailVerified,
markLoginProcessCompleted,
} = useAuth();
const [errorMessage, setErrorMessage] = useState("");
const location = useLocation();
const [emailVerifiedMessage, setEmailVerifiedMessage] = useState("");

useEffect(() => {
if (isAuthenticated) {
if (isAuthenticated && isEmailVerified) {
console.log("Navigating to dashboards");
navigate("/dashboards");
}
}, [isAuthenticated, navigate]);
}, [isAuthenticated, isEmailVerified, navigate]);

useEffect(() => {
if (location.state?.emailVerified) {
Expand Down Expand Up @@ -70,24 +73,24 @@ function LoginPage({ onLogin }) {
);

if (response.status === 200) {
updateAuth(true);
markLoginProcessCompleted(true);
const userResponse = await axios.get(`${API_URL}users/me/`, {
headers: {
Authorization: `Bearer ${response.data.access_token}`,
},
});
console.log(userResponse.data.role);
updateUserRole(userResponse.data.role);
updateAuth(true);

if (userResponse.data.requires_password_update) {
navigate("/change-password");
} else if (userResponse.data.email_verified == false) {
console.log("email_verified is false");
navigate("/verify-email");
} else {
console.log("email_verified is true");
updateEmailVerification(true);
navigate("/dashboards");
updateEmailVerification(userResponse.data.email_verified);
if (userResponse.data.email_verified) {
navigate("/dashboards");
} else {
navigate("/verify-email");
}
}
}
} catch (error) {
Expand Down
18 changes: 16 additions & 2 deletions frontend/src/pages/logout/LogoutPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { useAuth } from "../../contexts/AuthContext";

function Logout() {
const navigate = useNavigate();
const { updateAuth } = useAuth();
const {
updateAuth,
updateEmailVerification,
updateUserRole,
markLoginProcessCompleted,
} = useAuth();

useEffect(() => {
// Clear the authentication cookie
Expand All @@ -15,10 +20,19 @@ function Logout() {

// Update authentication state
updateAuth(false);
updateEmailVerification(false);
updateUserRole(null);
markLoginProcessCompleted(false);

// Redirect to the login page
navigate("/");
}, [navigate, updateAuth]);
}, [
navigate,
updateAuth,
updateEmailVerification,
updateUserRole,
markLoginProcessCompleted,
]);

// Optionally, you can render a message or a spinner here
return <div>Logging out...</div>;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/register/RegisterPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function RegisterPage() {
});

// Navigate to the verify-email page instead of login
navigate("/verify-email", { state: { email } });
navigate("/verify-email");
}
} catch (error) {
if (error.response) {
Expand Down
62 changes: 57 additions & 5 deletions frontend/src/pages/verify-email/VerifyEmailPage.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
import React, { useState, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { Box, Button, Container, Typography } from "@mui/material";
import {
Alert,
Box,
Button,
Container,
Snackbar,
Typography,
} from "@mui/material";
import { useAuth } from "../../contexts/AuthContext";
import { API_URL } from "../../utils/constants";
import axios from "axios";
import { set } from "date-fns";

const VerifyEmailPage = () => {
const location = useLocation();
const navigate = useNavigate();
const email = location.state?.email || null;
const [userResponse, setUserResponse] = useState(null);
const [token, setToken] = useState(null);
const { updateAuth, updateEmailVerification } = useAuth();
const { updateAuth, updateEmailVerification, loginProcessCompleted } =
useAuth();
const [openAlert, setOpenAlert] = useState(false);

useEffect(() => {
const params = new URLSearchParams(location.search);
const token = params.get("token");
setToken(token);
}, [location]);

useEffect(() => {
if (!token) {
axios
.get(`${API_URL}users/me/`)
.then((response) => {
if (response.data) {
setUserResponse(response.data);
} else {
// If no user data is returned, navigate to the login page
navigate("/login");
}
})
.catch((error) => {
// If an error occurs, navigate to the login page
navigate("/login");
});
}
}, [token]);

useEffect(() => {
if (token) {
console.log("token", token);
Expand All @@ -38,7 +67,7 @@ const VerifyEmailPage = () => {
.catch((error) => {
// Handle failed token verification
console.log("Failed to verify token");
navigate("/login", { state: { emailVerified: true } });
navigate("/login");
});
})
.catch((error) => {
Expand All @@ -51,17 +80,27 @@ const VerifyEmailPage = () => {
const handleResendEmail = async (event) => {
event.preventDefault();
axios
.post(`${API_URL}users/send-verification-email/`, { email })
.post(`${API_URL}users/send-verification-email/`, {
email: userResponse.email,
})
.then((response) => {
// Handle successful email resend
console.log("Verification email sent successfully");
setOpenAlert(true);
})
.catch((error) => {
// Handle failed email resend
console.log("Failed to send verification email");
});
};

const handleCloseAlert = (event, reason) => {
if (reason === "clickaway") {
return;
}
setOpenAlert(false);
};

return (
<Container maxWidth="sm">
<Box
Expand All @@ -87,6 +126,19 @@ const VerifyEmailPage = () => {
>
Resend Verification Email
</Button>
<Snackbar
open={openAlert}
autoHideDuration={6000}
onClose={handleCloseAlert}
>
<Alert
onClose={handleCloseAlert}
severity="success"
sx={{ width: "100%" }}
>
Verification email has been resent!
</Alert>
</Snackbar>
</Box>
</Container>
);
Expand Down

0 comments on commit 6beaaf3

Please sign in to comment.