Skip to content

Commit 46f92b7

Browse files
authored
Merge pull request #130 from HungrySlugs-CSE115A/profile-page
Profile page
2 parents 0e3d1fa + 30bd91a commit 46f92b7

File tree

7 files changed

+200
-33
lines changed

7 files changed

+200
-33
lines changed

app/locations/[location]/page.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import {
77
import { Location } from "@/interfaces/Location";
88
import { FrontEndReviews } from "@/interfaces/Review";
99

10-
import { useState, useEffect } from "react";
10+
import { useState, useEffect, useRef } from "react";
11+
import { useCookies } from "react-cookie";
1112

1213
import LocationCategories from "@/components/location/categories";
1314
import Link from "next/link";
1415

1516
export default function Page({ params }: { params: { location: number } }) {
1617
const [location, setLocation] = useState<Location | null>(null);
1718
const [foodReviews, setFoodReviews] = useState<FrontEndReviews | null>(null);
19+
const [cookies] = useCookies(["userEmail", "notificationsEnabled"]);
20+
const alertShown = useRef(false);
21+
22+
const notificationsEnabled = cookies.notificationsEnabled === true;
23+
24+
const user_email = cookies.userEmail || "anonymous";
1825

1926
useEffect(() => {
2027
fetchLocations().then(async (locations: Location[]) => {
@@ -41,13 +48,31 @@ export default function Page({ params }: { params: { location: number } }) {
4148

4249
fetchFoodReviewsBulk({
4350
food_names: food_names,
44-
user_id: username,
51+
user_id: user_email,
4552
}).then((reviews: FrontEndReviews) => {
4653
setLocation(location);
4754
setFoodReviews(reviews);
4855
});
4956
});
5057
}, [params.location]);
58+
useEffect(() => {
59+
if (
60+
location &&
61+
foodReviews &&
62+
notificationsEnabled &&
63+
!alertShown.current
64+
) {
65+
Object.keys(foodReviews).forEach((foodName) => {
66+
const review = foodReviews[foodName];
67+
if (user_email !== "anonymous" && review.user_rating === 5) {
68+
alert(
69+
`One of your favorite foods is being served! Food: ${foodName}`,
70+
);
71+
alertShown.current = true;
72+
}
73+
});
74+
}
75+
}, [location, foodReviews, notificationsEnabled, user_email]);
5176

5277
if (!location || !foodReviews) {
5378
return <h1>Loading...</h1>;

app/profile/page.tsx

Lines changed: 124 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,166 @@
11
"use client";
2-
import React from "react";
3-
import { useEffect, useState } from "react";
2+
import React, { useEffect, useState } from "react";
43
import { googleLogout } from "@react-oauth/google";
54
import axios from "axios";
6-
import { fetchUserInfo } from "@/app/requests";
5+
import { useCookies } from "react-cookie";
6+
import Image from "next/image";
7+
import { FrontEndReviews } from "@/interfaces/Review";
8+
import { Location } from "@/interfaces/Location";
9+
import { fetchLocations, fetchFoodReviewsBulk, fetchUserInfo } from "@/app/requests";
710

811
interface User {
912
name: string;
1013
email: string;
1114
picture: string;
1215
}
13-
import Image from "next/image";
16+
1417
const imageWidth = 100;
1518
const imageHeight = 100;
1619

1720
const Page = () => {
1821
const [user, setUser] = useState<User | null>(null);
19-
const getUserInfo = async () => {
20-
try {
21-
const userInfo = await fetchUserInfo();
22-
setUser(userInfo);
23-
} catch (error) {
24-
console.error("Failed to fetch user info:", error);
25-
}
26-
};
22+
const [cookies, setCookie, removeCookie] = useCookies([
23+
"authToken",
24+
"userEmail",
25+
"notificationsEnabled",
26+
]);
27+
const [reviews, setReviews] = useState<FrontEndReviews>({});
28+
const [notificationsEnabled, setNotificationsEnabled] = useState(
29+
cookies.notificationsEnabled === "true",
30+
);
31+
const [foodNames, setFoodNames] = useState<string[]>([]);
32+
const [loading, setLoading] = useState(true);
2733
useEffect(() => {
34+
const getUserInfo = async () => {
35+
try {
36+
const access_token = cookies.authToken;
37+
38+
if (!access_token) {
39+
console.error("No access token found");
40+
return;
41+
}
42+
43+
const userInfo = await axios
44+
.get("https://www.googleapis.com/oauth2/v3/userinfo", {
45+
headers: { Authorization: `Bearer ${access_token}` },
46+
})
47+
.then((res) => res.data);
48+
49+
setUser({
50+
name: userInfo.name,
51+
email: userInfo.email,
52+
picture: userInfo.picture,
53+
});
54+
setCookie("userEmail", userInfo.email, { path: "/" });
55+
} catch (error) {
56+
console.error("Error fetching user info:", error);
57+
}
58+
};
59+
const getLocationsAndFoodNames = async () => {
60+
try {
61+
const locations = await fetchLocations();
62+
const allFoodNames = locations.flatMap((location) =>
63+
location.categories.flatMap((category) =>
64+
category.sub_categories.flatMap((sub_category) =>
65+
sub_category.foods.map((food) => food.name),
66+
),
67+
),
68+
);
69+
setFoodNames(allFoodNames);
70+
console.log("Food Names:", allFoodNames);
71+
} catch (error) {
72+
console.error("Error fetching locations and food names:", error);
73+
}
74+
};
2875
getUserInfo();
29-
}, []);
76+
77+
getLocationsAndFoodNames();
78+
}, [cookies.authToken, cookies.notificationsEnabled, setCookie]);
3079

3180
const handleLogout = () => {
3281
googleLogout();
33-
axios
34-
.post("http://localhost:8000/api/logout/")
35-
.then((res) => console.log("Backend logout successful", res))
36-
.catch((err) => console.error("Backend logout failed", err));
37-
38-
// Remove the token from sessionStorage
39-
sessionStorage.removeItem("token");
40-
// Redirect the user to the main page after logging out
82+
removeCookie("authToken", { path: "/" });
83+
removeCookie("userEmail", { path: "/" });
4184
window.location.href = "/";
4285
console.log("Logged out successfully");
4386
};
87+
useEffect(() => {
88+
if (user && user.email) {
89+
fetchReviews(user.email);
90+
}
91+
}, [user, foodNames]);
92+
93+
const fetchReviews = async (userEmail: string) => {
94+
try {
95+
const reviews = await fetchFoodReviewsBulk({
96+
food_names: foodNames,
97+
user_id: userEmail,
98+
});
99+
const filteredReviews = Object.fromEntries(
100+
Object.entries(reviews).filter(
101+
([_, review]) => review.user_rating != null,
102+
),
103+
);
104+
setReviews(filteredReviews);
105+
setLoading(false);
106+
console.log("Fetched Reviews:", filteredReviews);
107+
} catch (error) {
108+
console.error("Error fetching reviews:", error);
109+
}
110+
};
111+
112+
const toggleNotifications = () => {
113+
const newState = !notificationsEnabled;
114+
setNotificationsEnabled(newState);
115+
setCookie("notificationsEnabled", newState.toString(), { path: "/" });
116+
117+
console.log(`Notifications are now ${newState ? "enabled" : "disabled"}`);
118+
};
44119

45120
return (
46121
<div>
47-
<h1>Profile</h1>
122+
<h1 className="text-[#003C6C] font-medium text-xl">Profile</h1>
48123
{user && (
49124
<div>
50125
<Image
51126
src={user.picture}
52127
alt="User profile"
53128
width={imageWidth}
54129
height={imageHeight}
130+
className="rounded-full border-4 border-[#003C6C]"
55131
/>
56132
<h2>
57133
Welcome, {user.name} - {user.email}
58134
</h2>
59135
</div>
60136
)}
137+
<div>
138+
<h2 className="text-[#003C6C] font-medium text-xl">
139+
Your Food Reviews:
140+
</h2>
141+
{loading ? (
142+
<h1>Loading...</h1>
143+
) : (
144+
<ul className="bg-gray-100 p-4 rounded-lg">
145+
{Object.entries(reviews).map(([food_name, review]) => (
146+
<li key={food_name} className="mb-2 text-[#003C6C]">
147+
<h3 className="font-bold">{food_name}</h3>
148+
<p>Rating: {review.user_rating}</p>
149+
</li>
150+
))}
151+
</ul>
152+
)}
153+
</div>
154+
<button
155+
onClick={toggleNotifications}
156+
className="hover:underline decoration-yellow-400 underline-offset-8 top-0 right-0 m-5 p-2 text-[#003C6C] font-medium text-xl"
157+
>
158+
{notificationsEnabled
159+
? "Disable Notifications"
160+
: "Enable Notifications"}
161+
</button>
61162
<button
62-
onClick={() => handleLogout()}
163+
onClick={handleLogout}
63164
className="hover:underline decoration-yellow-400 underline-offset-8 top-0 right-0 m-5 p-2 text-[#003C6C] font-medium text-xl"
64165
>
65166
Logout

backend/api/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ def validate_user(request):
169169
# add user_info to database using get or create possibly
170170

171171
# add current user
172-
current_user = CurrentUser(request.session)
173-
request.session["current_user"] = user_info["email"]
172+
# current_user = CurrentUser(request.session)
173+
# request.session["current_user"] = user_info["email"]
174174

175175
except requests.RequestException as e:
176176
return JsonResponse({"error": "Failed to validate access token"}, status=500)

backend/backend/settings.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212

1313
from pathlib import Path
1414

15-
from private.private_settings import DJANGO_SECRET_KEY, IS_DEV
15+
from private.private_settings import (
16+
DJANGO_SECRET_KEY,
17+
IS_DEV,
18+
DJANGO_GOOGLE_CLIENT_ID,
19+
DJANGO_GOOGLE_CLIENT_SECRET,
20+
)
1621

1722
# Build paths inside the project like this: BASE_DIR / 'subdir'.
1823
BASE_DIR = (
@@ -29,6 +34,9 @@
2934
# SECURITY WARNING: don't run with debug turned on in production!
3035
DEBUG = IS_DEV
3136

37+
GOOGLE_CLIENT_ID = DJANGO_GOOGLE_CLIENT_ID
38+
GOOGLE_SECRET_KEY = DJANGO_GOOGLE_CLIENT_SECRET
39+
3240
ALLOWED_HOSTS = [
3341
"127.0.0.1",
3442
"localhost",
@@ -61,6 +69,12 @@
6169
]
6270

6371
CORS_ORIGIN_ALLOW_ALL = True
72+
CORS_ALLOW_CREDENTIALS = True
73+
CORS_ALLOW_HEADERS = [
74+
"authorization",
75+
"content-type",
76+
"Access-Control-Allow-Origin",
77+
]
6478

6579
ROOT_URLCONF = "backend.urls"
6680

components/login.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import React, { useEffect, useState } from "react";
33
import { GoogleOAuthProvider, useGoogleLogin } from "@react-oauth/google";
44
import axios from "axios";
5+
import { useCookies } from "react-cookie";
6+
57
import { GOOGLE_CLIENT_ID } from "@/private/secrets";
68

79
interface User {
@@ -18,21 +20,38 @@ const LoginPage = () => {
1820
};
1921

2022
const LoginComponent = () => {
23+
const [user, setUser] = useState<User | null>(null);
24+
const [loggedIn, setLoggedIn] = useState(false);
25+
const [cookies, setCookie] = useCookies(["authToken", "userEmail"]);
26+
2127
useEffect(() => {
2228
console.log("LoginPage component mounted");
2329
}, []);
2430

2531
const handleLogin = useGoogleLogin({
2632
flow: "implicit",
2733

28-
onSuccess: (tokenResponse) => {
34+
onSuccess: async (tokenResponse) => {
2935
console.log(tokenResponse);
3036
// Store authentication token in the browser's storage for navigation bar use
31-
sessionStorage.setItem("token", tokenResponse.access_token);
37+
//sessionStorage.setItem("token", tokenResponse.access_token);
38+
const expires = new Date();
39+
expires.setHours(expires.getHours() + 3);
40+
setCookie("authToken", tokenResponse.access_token, {
41+
path: "/",
42+
expires,
43+
});
3244
// Redirect the user to main page
3345
window.location.href = "/";
3446
//handleLoginSuccess
3547
//client side authentication retrieve user info from access token
48+
const userInfo = await axios
49+
.get("https://www.googleapis.com/oauth2/v3/userinfo", {
50+
headers: { Authorization: `Bearer ${tokenResponse.access_token}` },
51+
})
52+
.then((res) => res.data);
53+
//console.log("userEmail:", userInfo.email);
54+
setCookie("userEmail", userInfo.email, { path: "/", expires });
3655
//send the token to backend
3756
axios
3857
.post("http://localhost:8000/api/users", {

components/navbar.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
"use client";
22
import { useState, useEffect } from "react";
33
import LoginPage from "./login"; // Used if not logged in
4+
import { useCookies } from "react-cookie";
45
import Link from "next/link";
56

67
export default function Navbar({ height }: { height: string }) {
78
const [isLoggedIn, setIsLoggedIn] = useState(false);
8-
9-
useEffect(() => {
9+
const [cookies] = useCookies(["authToken"]);
10+
/*useEffect(() => {
1011
// Check if sessionStorage is available and if token exists
1112
const token = sessionStorage.getItem("token");
1213
setIsLoggedIn(!!token);
13-
}, []);
14+
}, []);*/
15+
16+
useEffect(() => {
17+
// Check if the authToken exists in cookies
18+
const token = cookies.authToken;
19+
setIsLoggedIn(!!token);
20+
}, [cookies]);
1421

1522
return (
1623
<nav className="bg-white fixed w-full top-0 start-0 ">

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"next": "14.2.3",
1515
"prettier": "^3.2.5",
1616
"react": "^18",
17+
"react-cookie": "^7.1.4",
1718
"react-dom": "^18"
1819
},
1920
"devDependencies": {

0 commit comments

Comments
 (0)