Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f2e8e09
friends fav worksgit add .!
sierrawelsch Mar 16, 2025
6aba904
super stars also worksgit add .!
sierrawelsch Mar 16, 2025
4f06257
sorting menu item reviews worksgit add .!
sierrawelsch Mar 17, 2025
0601a4f
added commens and removed print statements
sierrawelsch Mar 17, 2025
0d9ac1d
linted my code
sierrawelsch Mar 17, 2025
91fe048
made some changes after pr reviewed
sierrawelsch Mar 19, 2025
331e2c5
made all changes from pr
sierrawelsch Mar 26, 2025
87baf20
Merge branch 'restaurant-endpoints'
sierrawelsch Mar 26, 2025
a8609be
changed imports
sierrawelsch Mar 26, 2025
cc41bd9
feat: FEATURETHON.md
garrettladley Mar 29, 2025
1183892
chore: refactor frontend hierarchy
benjaspet Mar 29, 2025
0f9b89f
chore: menu item view styling fixes
benjaspet Mar 29, 2025
fb53bfa
chore: format code
benjaspet Mar 29, 2025
b8ad17e
Merge branch 'main' of https://github.com/GenerateNU/platemate
sierrawelsch Mar 29, 2025
a95778e
Merge branch 'featurethon-frontend-hierarchy' of github.com:GenerateN…
benjaspet Mar 29, 2025
a257d95
added getDietaryRestrictions endpoint
sierrawelsch Mar 29, 2025
73f836d
integrated the backend for the dietary preferences
sierrawelsch Mar 29, 2025
90c32c5
can view your friends!
sierrawelsch Mar 29, 2025
9e3e3ee
loaded friends
sierrawelsch Mar 29, 2025
1b93175
linted and fixed formatting:
sierrawelsch Mar 29, 2025
1c62312
Merge branch 'main' of https://github.com/GenerateNU/platemate
sierrawelsch Apr 2, 2025
981208f
connected user's friends and reviews to the backend
sierrawelsch Apr 5, 2025
632b295
Merge branch 'featurethon-user-profile'
sierrawelsch Apr 7, 2025
d545e60
cleaned up main
sierrawelsch Apr 7, 2025
d36efa6
linted frontend code
sierrawelsch Apr 7, 2025
b73716c
fixed changes
sierrawelsch Apr 8, 2025
3394d92
fixed error in allReviews
sierrawelsch Apr 8, 2025
8c60e5a
linted the frontend
sierrawelsch Apr 8, 2025
7c22b65
Merge branch 'main' of https://github.com/GenerateNU/platemate
sierrawelsch Apr 8, 2025
ced271f
Merge branch 'made-changes-on-main'
sierrawelsch Apr 8, 2025
340844c
Merge branch 'main' of https://github.com/GenerateNU/platemate
sierrawelsch Apr 9, 2025
cd85922
Merge branch 'main' of https://github.com/GenerateNU/platemate
sierrawelsch Apr 9, 2025
7df1ba0
Merge branch 'main' of https://github.com/GenerateNU/platemate
sierrawelsch Apr 9, 2025
7aa75fc
menu items displayed on the feed are from the backend
sierrawelsch Apr 10, 2025
fa59ae9
connected the menu item reviews to the backend
sierrawelsch Apr 10, 2025
fe323aa
completed # 3
sierrawelsch Apr 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/internal/handlers/menu_items/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ func (s *Service) GetPopularWithFriends(userID primitive.ObjectID, limit int) ([
"$match": bson.M{
"$expr": bson.M{
"$in": bson.A{
bson.M{"$toObjectId": "$reviewer.id"},
bson.M{"$toObjectId": "$reviewer._id"},
"$$friendIDs",
},
},
Expand Down
11 changes: 6 additions & 5 deletions backend/internal/handlers/menu_items/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type MenuItemRequest struct {
Name string `json:"name"`
Picture string `json:"picture"`
Reviews []string `json:"reviews"`
AvgRating AvgRatingDocument `json:"avgRating"`
Description string `json:"description"`
Location []float64 `json:"location"`
Tags []string `json:"tags"`
Expand Down Expand Up @@ -86,11 +87,11 @@ type MenuItemDocument struct {
}

type AvgRatingDocument struct {
Portion float64 `bson:"portion"`
Taste float64 `bson:"taste"`
Value float64 `bson:"value"`
Overall float64 `bson:"overall"`
Return float64 `bson:"return"` // @TODO: figure out if boolean or number
Portion float64 `bson:"portion" json:"portion"`
Taste float64 `bson:"taste" json:"taste"`
Value float64 `bson:"value" json:"value"`
Overall float64 `bson:"overall" json:"overall"`
Return float64 `bson:"return" json:"return"` // @TODO: figure out if boolean or number
}

// MenuItemMetrics represents analytics data for a single menu item
Expand Down
9 changes: 9 additions & 0 deletions frontend/api/menu-items.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TMenuItem } from "@/types/menu-item";
import { TReview } from "@/types/review";
import { makeRequest } from "@/api/base";
import { TRestaurantMenuItemsMetrics } from "@/types/restaurant";

Expand Down Expand Up @@ -41,3 +42,11 @@ export const getRestaurantMenuItemsMetrics = async (restaurantId: string): Promi
export const getRandomMenuItems = async (limit: number): Promise<TMenuItem[]> => {
return await makeRequest(`/api/v1/menu-items/random?limit=${limit}`, "GET");
};

export const getFriendMenuItems = async (id: string, limit: number): Promise<TMenuItem[]> => {
return await makeRequest(`/api/v1/menu-items/popular-with-friends?userId=${id}&limit=${limit}`, "GET");
}

export const getMenuItemReviews = async (menuItemId: string): Promise<TReview[]> => {
return await makeRequest(`/api/v1/menu-items/${menuItemId}/reviews`, "GET");
}
17 changes: 15 additions & 2 deletions frontend/api/restaurant.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { TRestaurant } from "@/types/restaurant";
import { FriendsFavInfo } from "@/types/restaurant";
import { makeRequest } from "@/api/base";

export const getRestaurant = async (id: string): Promise<TRestaurant> => {
const res = makeRequest("/api/v1/restaurant/" + id, "GET");
return res;
return await makeRequest(`/api/v1/restaurant/${id}`, "GET");
};

export const getRestaurantFriendsFav = async (userId: string, restaurantId: string): Promise<FriendsFavInfo> => {
const data = await makeRequest(`/api/v1/restaurant/${userId}/${restaurantId}`, "GET");
const formattedFriendsFav: FriendsFavInfo = {
isFriendsFav: data.friends_fav,
numFriends: data.friends_reviewed
};
return formattedFriendsFav;
}

export const getRestaurantSuperStars = async (restaurantId: string): Promise<number> => {
return await makeRequest(`/api/v1/restaurant/${restaurantId}/super-stars`, "GET");
}
2 changes: 1 addition & 1 deletion frontend/api/reviews.tsx → frontend/api/reviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export const getReviewById = async (id: string, userId: string): Promise<TReview
};

export const getFriendsReviews = async (id: string): Promise<TReview[]> => {
return await makeRequest(`/api/v1/item/${id}/followReviews`, "GET");
return await makeRequest(`/api/v1/item/${id}/followingReviews`, "GET");
};
86 changes: 65 additions & 21 deletions frontend/app/(menuItem)/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ import { ThemedView } from "@/components/themed/ThemedView";
import { ScrollView, StyleSheet, View, Image, Pressable, TouchableOpacity } from "react-native";
import { ThemedText } from "@/components/themed/ThemedText";
import { StarRating } from "@/components/ui/StarReview";
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
import { Ionicons } from "@expo/vector-icons";
import ReviewPreview from "@/components/review/ReviewPreview";
import { ThemedTag } from "@/components/themed/ThemedTag";
import { ReviewButton } from "@/components/review/ReviewButton";
import HighlightCard from "@/components/restaurant/HighlightCard";
import { PersonWavingIcon, RestaurantIcon, ThumbsUpIcon } from "@/components/icons/Icons";
import { PersonWavingIcon, RestaurantIcon, SmileyIcon, ThumbsUpIcon } from "@/components/icons/Icons";
import { useLocalSearchParams, useNavigation, useRouter } from "expo-router";
import { getMenuItemById } from "@/api/menu-items";
import { getMenuItemById, getMenuItemReviews } from "@/api/menu-items";
import { TMenuItem } from "@/types/menu-item";
import ReviewFlow from "@/components/review/ReviewFlow";
import AddReviewButton from "@/components/AddReviewButton";
import { Skeleton } from "moti/skeleton";
import { useUser } from "@/context/user-context";
import { getRestaurantFriendsFav, getRestaurantSuperStars } from "@/api/restaurant";
import { FriendsFavInfo } from "@/types/restaurant";
import { TReview } from "@/types/review";

export default function Route() {
const [selectedFilter, setSelectedFilter] = React.useState("My Reviews");
const [friendsFav, setFriendsFav] = useState<FriendsFavInfo>({
isFriendsFav: false,
numFriends: 0,
});
const [superStars, setSuperStars] = useState(0);
const [menuItemReviews, setMenuItemReviews] = useState<TReview[]>([])

const { id } = useLocalSearchParams<{ id: string }>();
const { user } = useUser();

const navigation = useNavigation();
const [menuItem, setMenuItem] = useState<TMenuItem | null>(null);
Expand All @@ -29,13 +40,42 @@ export default function Route() {

const router = useRouter();

const fetchData = useCallback(async () => {
try {
if (!user || !menuItem) {
console.log("USER", user);
console.log("MENUITEM", menuItem);
throw new Error("User and/or menuItem are null. Cannot fetch associated menu item data.");
}
const [friendsFavData, superStarsData, menuItemReviewData] = await Promise.all([getRestaurantFriendsFav(user.id, menuItem.restaurantID), getRestaurantSuperStars(menuItem.restaurantID), getMenuItemReviews(menuItem.id)]);
console.log("MENUITEMID", menuItem);
console.log("MENUREVIEWDATA", menuItemReviewData);
console.log("FRIENDSFAVDATA", friendsFavData);
setFriendsFav(friendsFavData);
setSuperStars(superStarsData);
setMenuItemReviews(menuItemReviewData);

} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
}, [user, menuItem]);

useEffect(() => {
navigation.setOptions({ headerShown: false });
getMenuItemById(id).then((data) => {
setMenuItem(data);
setLoading(false);
});
}, [navigation]);
}, [navigation, id]);

useEffect(() => {
// Only call fetchData when menuItem is initialized
if (menuItem) {
fetchData();
}
}, [menuItem, user, fetchData]);

return (
<>
Expand Down Expand Up @@ -119,15 +159,15 @@ export default function Route() {
<View style={styles.statsContainer}>
<HighlightCard
title={"Friend's Fav"}
subtitle={"100+ friend referrals"}
subtitle={`${friendsFav.numFriends} friend referrals`}
icon={<PersonWavingIcon />}
/>
<HighlightCard
title={"Super Stars"}
subtitle={"200+ 5-star reviews"}
subtitle={`${superStars} 5-star reviews`}
icon={<ThumbsUpIcon />}
/>
<HighlightCard title={"Satisfaction"} subtitle={"70% of guests revisited"} />
<HighlightCard title={"Satisfaction"} subtitle={"100% of guests revisited"} icon={<SmileyIcon/>} />
</View>

<View style={styles.sectionHeader}>
Expand All @@ -138,8 +178,8 @@ export default function Route() {
</View>
<View style={styles.reviewStats}>
<View style={styles.ratingContainer}>
<ThemedText style={styles.ratingText}>4/5</ThemedText>
<StarRating avgRating={4.2} numRatings={428} showAvgRating={false} />
<ThemedText style={styles.ratingText}>{menuItem?.avgRating.overall}</ThemedText>
<StarRating avgRating={menuItem?.avgRating.overall || 0} numRatings={menuItemReviews.length} showAvgRating={false} />
</View>
</View>

Expand All @@ -162,18 +202,22 @@ export default function Route() {
</Pressable>
))}
</View>

<ReviewPreview
plateName="Pad Thai"
restaurantName="Pad Thai Kitchen"
tags={["Vegan", "Healthy", "Green", "Low-Cal"]}
rating={4}
content="The Buddha Bowl at Green Garden exceeded my expectations! Fresh ingredients, perfectly balanced flavors, and generous portions make this a must-try for health-conscious diners. The avocado was perfectly ripe, and the quinoa was cooked to perfection. I especially loved the homemade tahini dressing."
authorId={""}
authorUsername={"username"}
authorName={"First Name"}
authorAvatar={"https://placehold.co/600x400/png?text=P"}
/>
{menuItemReviews.map((item: TReview, index: number) => (
<TouchableOpacity key={index} onPress={() => router.push(`/(review)/${item._id}`)}>
<ReviewPreview
plateName={item.menuItemName}
restaurantName={item.restaurantName}
// hard coded tags because we still do not have a tags fields for a review
tags={["Crunchy", "Fresh"]}
rating={item.rating.overall}
content={item.content}
authorName={item.reviewer.name}
authorId={item.reviewer.id}
authorUsername={item.reviewer.username}
authorAvatar={item.reviewer.pfp}
/>
</TouchableOpacity>
))}
</ThemedView>
</Skeleton>
</ThemedView>
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/(profile)/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ProfileAvatar from "@/components/profile/ProfileAvatar";
import ProfileIdentity from "@/components/profile/ProfileIdentity";
import ProfileMetrics from "@/components/profile/ProfileMetrics";
import ReviewPreview from "@/components/review/ReviewPreview";
import { SearchBoxFilter } from "@/components/SearchBoxFilter";
import { SearchBox } from "@/components/SearchBox";
import EditFriendSheet from "@/components/profile/followers/FriendProfileOptions";
import { FollowButton } from "@/components/profile/followers/FollowButton";
import { useLocalSearchParams } from "expo-router";
Expand Down Expand Up @@ -120,7 +120,7 @@ const ProfileScreen = () => {
{user.name}'s Food Journal
</ThemedText>
{/* Made a search box with a filter/sort component as its own component */}
<SearchBoxFilter
<SearchBox
placeholder={`Search ${user.name}'s Reviews`}
recent={false}
onSubmit={() => console.log("submit")}
Expand Down
62 changes: 57 additions & 5 deletions frontend/app/(tabs)/index/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ThemedView } from "@/components/themed/ThemedView";
import FeedTabs from "@/components/Feed/FeedTabs";
import ReviewPreview from "@/components/review/ReviewPreview";
import MenuItemPreview from "@/components/Cards/MenuItemPreview";
import { getMenuItems, getRandomMenuItems } from "@/api/menu-items";
import { getMenuItems, getFriendMenuItems } from "@/api/menu-items";
import { TMenuItem } from "@/types/menu-item";
import { TReview } from "@/types/review";
import { getReviews } from "@/api/reviews";
Expand All @@ -15,6 +15,7 @@ import { SearchIcon } from "@/components/icons/Icons";
import { FilterContext } from "@/context/filter-context";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { ThemedText } from "@/components/themed/ThemedText";
import { useUser } from "@/context/user-context";

// Define a type for our feed items
type FeedItem = {
Expand All @@ -30,6 +31,7 @@ export default function Feed() {
const [feedItems, setFeedItems] = useState<FeedItem[]>([]);
const [reviews, setReviews] = useState<TReview[]>([]);
const [menuItems, setMenuItems] = useState<TMenuItem[]>([]);
const { user } = useUser();

const insets = useSafeAreaInsets();
const router = useRouter();
Expand All @@ -47,11 +49,14 @@ export default function Feed() {

const fetchData = useCallback(async () => {
try {
const [reviewsData, menuItemsData] = await Promise.all([getReviews(2, 20), getRandomMenuItems(20)]);
if (!user) {
throw new Error("User is null. Cannot fetch friend menu items and reviews.");
}
console.log("user id:", user.id);
const [reviewsData, menuItemsData] = await Promise.all([getReviews(2, 20), getFriendMenuItems(user.id, 20)]);

const fetchedReviews = reviewsData.data as TReview[];
const fetchedMenuItems = menuItemsData as TMenuItem[];

setReviews(fetchedReviews);
setMenuItems(fetchedMenuItems);

Expand All @@ -74,12 +79,13 @@ export default function Feed() {
setLoading(false);
setRefreshing(false);
}
}, []);
}, [user]);

useEffect(() => {
setLoading(true);
fetchData();
}, [fetchData]);
console.log("FMI", menuItems);
}, [fetchData, user]);

const onRefresh = useCallback(() => {
setRefreshing(true);
Expand Down Expand Up @@ -186,6 +192,52 @@ export default function Feed() {
filter={true}
/>
</ThemedView>
<ThemedView style={{ flex: 1, width: "100%", gap: 16 }}>
{reviews.length > 0 ? (
<ScrollView contentContainerStyle={{ gap: 16 }} showsHorizontalScrollIndicator={false}>
{reviews.map((item: TReview, index: number) => {
console.log(item);
return (
<TouchableOpacity key={index} onPress={() => router.push(`/(review)/${item._id}`)}>
<ReviewPreview
plateName={item.menuItem}
restaurantName={item.restaurantId}
rating={item.rating.overall}
tags={["Warm", "Tender", "Sweet"]}
content={item.content}
authorName={item.reviewer.id}
authorUsername={item.reviewer.username}
authorAvatar={item.reviewer.pfp}
authorId={item.reviewer.id}
/>
</TouchableOpacity>
);
})}
</ScrollView>
) : (
<ThemedView style={{ paddingVertical: 20, alignItems: "center" }}>
<ThemedView>No reviews available</ThemedView>
</ThemedView>
)}

{menuItems.length > 0 && (
<ScrollView contentContainerStyle={{ gap: 16 }} showsVerticalScrollIndicator={false}>
{/* {menuItems.map((item: TMenuItem, index: number) => (
<TouchableOpacity key={index} onPress={() => router.push(`/(menuItem)/${item.id}`)}>
<MenuItemPreview
id={item.id}
plateName={item.name}
content={item.description}
tags={item.tags}
picture={item.picture}
rating={3}
restaurantName={item.restaurantID || "Restaurant Name"}
/>
</TouchableOpacity>
))} */}
</ScrollView>
)}
</ThemedView>
</ThemedView>
<FlatList
data={feedItems}
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/(tabs)/profile/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { EditProfileButton } from "@/components/profile/EditProfileButton";
import { router } from "expo-router";
import EditProfileSheet from "@/components/profile/EditProfileSheet";
import ReviewPreview from "@/components/review/ReviewPreview";
import { SearchBoxFilter } from "@/components/SearchBoxFilter";
import { SearchBox } from "@/components/SearchBox";
import type { TReview } from "@/types/review";
import { makeRequest } from "@/api/base";

Expand Down Expand Up @@ -92,7 +92,7 @@ const ProfileScreen = () => {
{user.name.split(" ")[0]}'s Food Journal
</ThemedText>

<SearchBoxFilter
<SearchBox
placeholder="Search my reviews"
name={user.username}
recent={false}
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/MenuItemView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ThemedTag } from "@/components/themed/ThemedTag";
import { ReviewButton } from "@/components/review/ReviewButton";
import { ReviewFlow } from "@/components/review/ReviewFlow";
import HighlightCard from "@/components/restaurant/HighlightCard";
import { PersonWavingIcon, ThumbsUpIcon } from "@/components/icons/Icons";
import { PersonWavingIcon, SmileyIcon, ThumbsUpIcon } from "@/components/icons/Icons";

export default function MenuItemView() {
const [selectedFilter, setSelectedFilter] = React.useState("My Reviews");
Expand Down Expand Up @@ -103,7 +103,7 @@ export default function MenuItemView() {
icon={<PersonWavingIcon />}
/>
<HighlightCard title={"Super Stars"} subtitle={"200+ 5-star reviews"} icon={<ThumbsUpIcon />} />
<HighlightCard title={"Satisfaction"} subtitle={"70% of guests revisited"} />
<HighlightCard title={"Satisfaction"} subtitle={"70% of guests revisited"} icon={<SmileyIcon />} />
</View>

{/* Reviews Section */}
Expand Down
Loading