diff --git a/public/index.html b/public/index.html
index cd59e32..df03186 100644
--- a/public/index.html
+++ b/public/index.html
@@ -12,7 +12,6 @@
-
RECursion
diff --git a/src/api/axios.js b/src/api/axios.js
index 3af7885..408c3e1 100644
--- a/src/api/axios.js
+++ b/src/api/axios.js
@@ -1,6 +1,6 @@
import axios from "axios";
-const api = process.env.REACT_APP_BACKEND_URL + "/api";
+const api = (process.env.REACT_APP_BACKEND_URL || "") + "/api";
export default axios.create({
baseURL: api,
diff --git a/src/api/login.js b/src/api/login.js
index 9dff8c6..d47ed19 100644
--- a/src/api/login.js
+++ b/src/api/login.js
@@ -2,7 +2,7 @@ import axios from "./axios";
export const login = async (formData) => {
const response = await axios.post(
- `${process.env.REACT_APP_BACKEND_URL}/api/token/`,
+ "/token/",
{
username: formData.username,
password: formData.password,
diff --git a/src/api/loginWithGoogle.js b/src/api/loginWithGoogle.js
index 32dde71..0eff48f 100644
--- a/src/api/loginWithGoogle.js
+++ b/src/api/loginWithGoogle.js
@@ -2,7 +2,7 @@
// define apis for login with google
export const loginWithGoogle = async (token) => {
const response = await fetch(
- `${process.env.REACT_APP_BACKEND_URL}/api/token/google/`, // TODO : use axios
+ `${process.env.REACT_APP_BACKEND_URL || ""}/api/token/google/`, // TODO : use axios
{
method: "POST",
headers: {
diff --git a/src/api/register.js b/src/api/register.js
index a4def1f..e6a082a 100644
--- a/src/api/register.js
+++ b/src/api/register.js
@@ -1,7 +1,7 @@
// define apis for login
export const register = async (formData) => {
const response = await fetch(
- `${process.env.REACT_APP_BACKEND_URL}/api/users/register/`, // TODO : use axios
+ `${process.env.REACT_APP_BACKEND_URL || ""}/api/users/register/`, // TODO : use axios
{
method: "POST",
headers: {
diff --git a/src/api/userInfo.js b/src/api/userInfo.js
index 0d211b8..570c277 100644
--- a/src/api/userInfo.js
+++ b/src/api/userInfo.js
@@ -2,19 +2,42 @@
// define apis for profile page
import axios from "./axios";
import { API_ROUTES } from "../utils/api_routes";
+import jwtDecode from "jwt-decode";
const USER_URL = API_ROUTES.USERS;
export const getProfile = async () => {
+ let username = null;
const user = localStorage.getItem("user");
- if (!user) {
- throw new Error("User not found in local storage");
+ if (user) {
+ try {
+ const parsedUser = JSON.parse(user);
+ if (parsedUser && parsedUser.username) {
+ username = parsedUser.username;
+ }
+ } catch (e) {}
+ }
+
+ if (!username) {
+ const authTokens = localStorage.getItem("authTokens");
+ if (authTokens) {
+ try {
+ const parsedTokens = JSON.parse(authTokens);
+ if (parsedTokens?.access) {
+ const decoded = jwtDecode(parsedTokens.access);
+ if (decoded?.username) {
+ username = decoded.username;
+ } else if (decoded?.email) {
+ username = decoded.email.split("@")[0];
+ }
+ }
+ } catch (e) {}
+ }
}
- const parsedUser = JSON.parse(user);
- if (!parsedUser || !parsedUser.username) {
- throw new Error("Invalid user data in local storage");
+
+ if (!username) {
+ throw new Error("User not found in local storage");
}
- const username = parsedUser.username;
const response = await axios.get(`${USER_URL}/${username}/`, {
headers: {
diff --git a/src/components/GoogleLogin/Glogin.jsx b/src/components/GoogleLogin/Glogin.jsx
index 8769189..968be34 100644
--- a/src/components/GoogleLogin/Glogin.jsx
+++ b/src/components/GoogleLogin/Glogin.jsx
@@ -4,23 +4,56 @@ import { useGoogleLogin } from "@react-oauth/google";
import GoogleIcon from "./GoogleIcon";
import { loginWithGoogle } from "../../api/loginWithGoogle";
import useAuth from "../../hooks/useAuth";
+import { useToast } from "@chakra-ui/react";
+
const Glogin = ({ setJustLoggedInWithGoogle }) => {
- const { token, decodeTokens, setStatus, status } = useAuth();
+ const { decodeTokens, setStatus } = useAuth();
+ const toast = useToast();
const login = useGoogleLogin({
- onSuccess: (tokenResponse) => {
- loginWithGoogle(tokenResponse.access_token).then((res) => {
- if (setJustLoggedInWithGoogle && res.is_new_user) {
+ onSuccess: async (tokenResponse) => {
+ try {
+ setStatus("submitting");
+ const res = await loginWithGoogle(tokenResponse.access_token);
+ if (setJustLoggedInWithGoogle && res?.is_new_user) {
setJustLoggedInWithGoogle(true);
}
- decodeTokens(res);
+ await decodeTokens(res);
+ } catch (err) {
+ console.error("Google login backend error:", err);
+ toast({
+ title: "Google Login Failed",
+ description: err.message || "Failed to authenticate with backend",
+ position: "top",
+ status: "error",
+ duration: 3000,
+ isClosable: true,
+ });
+ setStatus("typing");
+ }
+ },
+ onError: (errorResponse) => {
+ console.error("Google OAuth error:", errorResponse);
+ toast({
+ title: "Google Sign-In Error",
+ description:
+ errorResponse?.error_description ||
+ errorResponse?.error ||
+ "Google sign-in was cancelled or popup was blocked. Please enable popups for this site.",
+ position: "top",
+ status: "error",
+ duration: 4000,
+ isClosable: true,
});
+ setStatus("typing");
},
});
+
return (
- login()}
- className="flex items-center justify-center w-full bg-transparent p-2 text-white rounded-lg cursor-pointer hover:bg-[#58cdff] font-opensans border border-[#58cdff]"
+ className="flex items-center justify-center w-full bg-transparent p-2 text-white rounded-lg cursor-pointer hover:bg-[#58cdff] font-opensans border border-[#58cdff] transition duration-200"
>
{/* Google Icon */}
@@ -28,7 +61,7 @@ const Glogin = ({ setJustLoggedInWithGoogle }) => {
{/* Text */}
CONTINUE WITH GOOGLE
-
+
);
};
diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx
index 5a1750b..4cad235 100644
--- a/src/components/Layout.jsx
+++ b/src/components/Layout.jsx
@@ -51,6 +51,9 @@ const Layout = () => {
}
} catch (error) {
console.error("Profile check failed", error);
+ if (error.response?.status === 404) {
+ navigate("/profile/edit", { replace: true });
+ }
}
}
};
diff --git a/src/components/Login.jsx b/src/components/Login.jsx
index 3aeabc1..3f3d1c3 100644
--- a/src/components/Login.jsx
+++ b/src/components/Login.jsx
@@ -19,10 +19,11 @@ const Login = () => {
const stateFrom = location.state?.from;
let from = "/";
if (stateFrom) {
- from = stateFrom.pathname + (stateFrom.search || "");
+ from = stateFrom.pathname + (stateFrom.search || "");
}
+ const targetPath = from === "/login" ? "/" : from;
- const { token, loginUser, setStatus, status } = useAuth();
+ const { token, loginUser, setStatus, status, logoutUser } = useAuth();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [rememberMe, setRememberMe] = useState(false); // State for remember me checkbox
@@ -55,22 +56,27 @@ const Login = () => {
navigate("/profile/edit");
return;
}
-
+
setLoading(true); // Keep loading while we check profile
try {
const profile = await getProfile();
// Check if profile is incomplete (Strict check for Name and College)
- const isNameMissing = !profile.name || (typeof profile.name === "string" && profile.name.trim() === "");
- const isCollegeMissing = !profile.college || (typeof profile.college === "string" && profile.college.trim() === "");
+ const isNameMissing = !profile?.name || (typeof profile.name === "string" && profile.name.trim() === "");
+ const isCollegeMissing = !profile?.college || (typeof profile.college === "string" && profile.college.trim() === "");
if (isNameMissing || isCollegeMissing) {
navigate("/profile/edit");
} else {
- navigate(from);
+ navigate(targetPath);
}
} catch (error) {
console.error("Error checking profile:", error);
- navigate(from); // Default to home/from on error
+ if (error.response?.status === 401) {
+ if (logoutUser) logoutUser();
+ } else {
+ // Profile not found or incomplete - allow user to fill profile details
+ navigate("/profile/edit");
+ }
} finally {
setLoading(false);
setStatus("typing");
@@ -78,7 +84,7 @@ const Login = () => {
}
};
checkProfileAndRedirect();
- }, [token, setLoading, setStatus, navigate, from, justLoggedInWithGoogle]);
+ }, [token, justLoggedInWithGoogle]);
const handleUsernameChange = (e) => setUsername(e.target.value);
const handlePasswordChange = (e) => setPassword(e.target.value);
diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js
index 9618cf1..8d1da48 100644
--- a/src/context/AuthContext.js
+++ b/src/context/AuthContext.js
@@ -18,91 +18,107 @@ export const AuthProvider = ({ children }) => {
const [status, setStatus] = useState("typing");
const toast = useToast();
const navigate = useNavigate();
- const [user, setUser] = useState(
- localStorage.getItem("user")
- ? JSON.parse(localStorage.getItem("user"))
- : null
- );
- const [authToken, setAuthToken] = useState(
- localStorage.getItem("authTokens")
- ? JSON.parse(localStorage.getItem("authTokens"))
- : null
- );
+ const [authToken, setAuthToken] = useState(() => {
+ try {
+ const saved = localStorage.getItem("authTokens");
+ return saved ? JSON.parse(saved) : null;
+ } catch (e) {
+ return null;
+ }
+ });
- const decodeTokens = async (tokens) => {
+ const [user, setUser] = useState(() => {
+ try {
+ const savedUser = localStorage.getItem("user");
+ if (savedUser) return JSON.parse(savedUser);
+
+ const savedTokens = localStorage.getItem("authTokens");
+ if (savedTokens) {
+ const parsed = JSON.parse(savedTokens);
+ if (parsed?.access) {
+ const decoded = jwtDecode(parsed.access);
+ return {
+ id: decoded.user_id,
+ username: decoded.username || decoded.email?.split("@")[0],
+ role: "normal",
+ };
+ }
+ }
+ return null;
+ } catch (e) {
+ return null;
+ }
+ });
- if (!tokens.access) {
+ const decodeTokens = async (tokens) => {
+ if (!tokens || !tokens.access) {
toast({
title: "Cant Authorize",
- description: tokens.response,
+ description: tokens?.response || "Failed to authenticate",
position: "top",
status: "error",
duration: 3000,
isClosable: true,
});
+ setStatus("typing");
return;
}
- setAuthToken({
+
+ setAuthToken({
access: tokens.access,
refresh: tokens.refresh,
});
localStorage.setItem("authTokens", JSON.stringify(tokens));
-
+
+ let role = "normal";
try {
const res = await getProfileRoles(jwtDecode(tokens?.access).user_id);
- localStorage.setItem(
- "user",
- JSON.stringify({
- id: jwtDecode(tokens?.access).user_id,
- username: jwtDecode(tokens?.access).email.split("@")[0],
- role: res.role,
- })
- );
- setUser({
- id: jwtDecode(tokens?.access).user_id,
- username: jwtDecode(tokens?.access).email.split("@")[0],
- email: jwtDecode(tokens?.access).email,
- role: res.role,
- });
+ if (res?.role) role = res.role;
} catch (error) {
- console.error("Error fetching roles:", error);
- // Optional: Toast specific to role fetching failure?
- // For now, allow login but maybe warn? Or just log it.
- // Ensuring it doesn't crash the Google Login flow entirely (though token is valid).
- toast({
- title: "Profile Error",
- description: "Could not fetch user roles.",
- status: "warning",
- duration: 3000,
- isClosable: true,
- });
+ console.error("Error fetching roles:", error);
}
+
+ const decoded = jwtDecode(tokens?.access);
+ const username = decoded.username || decoded.email.split("@")[0];
+ const userData = {
+ id: decoded.user_id,
+ username: username,
+ email: decoded.email,
+ role: role,
+ };
+
+ localStorage.setItem("user", JSON.stringify(userData));
+ setUser(userData);
+ setStatus("typing");
};
const loginUser = async (formData) => {
try {
const data = await login(formData);
- localStorage.setItem("authTokens", JSON.stringify(data));
+ if (!data || !data.access) {
+ throw new Error(data?.response || "Invalid login response");
+ }
- const res = await getProfileRoles(jwtDecode(data?.access).user_id);
+ localStorage.setItem("authTokens", JSON.stringify(data));
+ setAuthToken(data);
- localStorage.setItem(
- "user",
- JSON.stringify({
- id: jwtDecode(data?.access).user_id,
- username: formData.username,
- role: res.role,
- })
- );
+ let role = "normal";
+ try {
+ const res = await getProfileRoles(jwtDecode(data?.access).user_id);
+ if (res?.role) role = res.role;
+ } catch (e) {
+ console.warn("Could not fetch user role:", e);
+ }
- setUser({
+ const userData = {
id: jwtDecode(data?.access).user_id,
username: formData.username,
- role: res.role,
- });
-
- setAuthToken(data);
+ role: role,
+ };
+ localStorage.setItem("user", JSON.stringify(userData));
+ setUser(userData);
+ setStatus("typing");
} catch (err) {
setLoading(false);
const errorMessage = getApiError(err);