From cc41bd994f96185a7dd0c9c28587a29f22dbc3e8 Mon Sep 17 00:00:00 2001 From: Garrett Mitchell Ladley Date: Sat, 29 Mar 2025 08:53:50 -0400 Subject: [PATCH 01/10] feat: FEATURETHON.md --- FEATURETHON.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 FEATURETHON.md diff --git a/FEATURETHON.md b/FEATURETHON.md new file mode 100644 index 0000000..5af7cc3 --- /dev/null +++ b/FEATURETHON.md @@ -0,0 +1,3 @@ +# Featurethon + + From 11838926b5858bcce3a0ddfd3f23defd9ec2d073 Mon Sep 17 00:00:00 2001 From: Ben Petrillo Date: Sat, 29 Mar 2025 11:16:18 -0400 Subject: [PATCH 02/10] chore: refactor frontend hierarchy --- frontend/app/MenuItemView.tsx | 4 +- frontend/app/RestaurantView.tsx | 60 ++++++-- frontend/components/Cards/MenuItemCard.tsx | 4 +- frontend/components/MyReview.tsx | 2 +- frontend/components/icons/Icons.tsx | 62 +++++++- .../components/restaurant/HighlightCard.tsx | 54 +++++++ .../restaurant/RestaurantDetailItem.tsx | 2 +- .../restaurant/RestaurantReviewSummary.tsx | 141 ++++++++++++++++++ frontend/components/review/ReviewDetail.tsx | 2 +- frontend/components/{ => ui}/StarReview.tsx | 15 +- frontend/components/ui/Tag.tsx | 25 ++++ 11 files changed, 344 insertions(+), 27 deletions(-) create mode 100644 frontend/components/restaurant/HighlightCard.tsx create mode 100644 frontend/components/restaurant/RestaurantReviewSummary.tsx rename frontend/components/{ => ui}/StarReview.tsx (87%) create mode 100644 frontend/components/ui/Tag.tsx diff --git a/frontend/app/MenuItemView.tsx b/frontend/app/MenuItemView.tsx index 84f9795..ea30ee3 100644 --- a/frontend/app/MenuItemView.tsx +++ b/frontend/app/MenuItemView.tsx @@ -1,7 +1,7 @@ import { ThemedView } from "@/components/themed/ThemedView"; import { ScrollView, StyleSheet, View, Image, Pressable } from "react-native"; import { ThemedText } from "@/components/themed/ThemedText"; -import { StarReview } from "@/components/StarReview"; +import { StarRating } from "@/components/ui/StarReview"; import React from "react"; import { Ionicons } from "@expo/vector-icons"; import ReviewPreview from "@/components/review/ReviewPreview"; @@ -128,7 +128,7 @@ export default function MenuItemView() { 4/5 - + diff --git a/frontend/app/RestaurantView.tsx b/frontend/app/RestaurantView.tsx index 9f7af20..64291ab 100644 --- a/frontend/app/RestaurantView.tsx +++ b/frontend/app/RestaurantView.tsx @@ -1,13 +1,15 @@ import { ThemedView } from "@/components/themed/ThemedView"; import { ScrollView, StyleSheet, View } from "react-native"; import { ThemedText } from "@/components/themed/ThemedText"; -import { RestaurantTags } from "@/components/RestaurantTags"; -import { StarReview } from "@/components/StarReview"; import React from "react"; -import { PhoneIcon, WebsiteIcon } from "@/components/icons/Icons"; +import { PersonWavingIcon, PhoneIcon, ThumbsUpIcon, WebsiteIcon } from "@/components/icons/Icons"; import { RestaurantDetailItem } from "@/components/restaurant/RestaurantDetailItem"; import BannerAndAvatar from "@/components/restaurant/RestaurantBanner"; +import Tag from "@/components/ui/Tag"; +import { StarRating } from "@/components/ui/StarReview"; +import RestaurantReviewSummary from "@/components/restaurant/RestaurantReviewSummary"; +import HighlightCard from "@/components/restaurant/HighlightCard"; export default function RestaurantView() { const restaurantTags = ["Fast Food", "Fried Chicken", "Chicken Sandwiches", "Order Online"]; @@ -25,7 +27,7 @@ export default function RestaurantView() { - + @@ -33,9 +35,34 @@ export default function RestaurantView() { - - - + + {restaurantTags.map((tag, index) => ( + + + + ))} + + + + + + } + /> + } /> + + ); @@ -60,9 +87,13 @@ const styles = StyleSheet.create({ ratingContainer: { paddingVertical: 4, }, - tagsContainer: { - paddingVertical: 8, - gap: 4, + tagsScrollViewContent: { + flexDirection: "row", + paddingTop: 8, + paddingBottom: 12, + }, + tagWrapper: { + marginRight: 4, }, chefsPickContainer: { paddingVertical: 4, @@ -86,4 +117,11 @@ const styles = StyleSheet.create({ borderTopLeftRadius: 32, borderTopRightRadius: 32, }, -}); + highlightsContainer: { + flex: 1, + paddingVertical: 12, + flexDirection: "row", + justifyContent: "space-between", + gap: 12, + }, +}); \ No newline at end of file diff --git a/frontend/components/Cards/MenuItemCard.tsx b/frontend/components/Cards/MenuItemCard.tsx index 1d666ea..792af5f 100644 --- a/frontend/components/Cards/MenuItemCard.tsx +++ b/frontend/components/Cards/MenuItemCard.tsx @@ -1,8 +1,8 @@ import React from "react"; import { View, Text } from "react-native"; import { StyleSheet } from "react-native"; -import { StarReview } from "@/components/StarReview"; -import { StarReviewProps } from "@/components/StarReview"; +import { StarReview } from "@/components/ui/StarReview"; +import { StarReviewProps } from "@/components/ui/StarReview"; import { Image } from "react-native"; interface MenuItemProp { diff --git a/frontend/components/MyReview.tsx b/frontend/components/MyReview.tsx index 3404fab..498075a 100644 --- a/frontend/components/MyReview.tsx +++ b/frontend/components/MyReview.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, Image, TextInput, TouchableOpacity, SafeAreaVie import { IconSymbol } from "../components/ui/IconSymbol"; import { ProgressBar } from "./ProgressBar"; import { EmojiTagsGrid } from "./EmojiTagsGrid"; -import { InteractiveStars } from "./StarReview"; +import { InteractiveStars } from "./ui/StarReview"; export function MyReview() { const [step, setStep] = useState(1); diff --git a/frontend/components/icons/Icons.tsx b/frontend/components/icons/Icons.tsx index 4239eac..1b6058c 100644 --- a/frontend/components/icons/Icons.tsx +++ b/frontend/components/icons/Icons.tsx @@ -34,7 +34,7 @@ export const WebsiteIcon = ({ width = 20, height = 20, color = "black", strokeWi ); }; -export const MarkerIcon = ({ width = 20, height = 20, color = "black", ...props }) => { +export const MarkerIcon = ({ width = 20, height = 20, color = "#fc0", ...props }) => { return ( { +export const ClockIcon = ({ width = 20, height = 20, color = "black", ...props }) => { return ( { ); }; + +export const StarIcon = ({ width = 12, height = 12, color = "#fc0", strokeWidth = 0.8, filled = false, ...props }) => { + return ( + + + + ); +}; + +export const SmileyIcon = ({ width = 35, height = 35, color = "#FFCF0F", ...props }) => { + return ( + + + + + + ); +}; + +export const ThumbsUpIcon = ({ width = 35, height = 35, color = "#FFCF0F", ...props }) => { + return ( + + + + ); +}; + +export const PersonWavingIcon = ({ width = 35, height = 35, color = "#FDD329", ...props }) => { + return ( + + + + + ); +}; diff --git a/frontend/components/restaurant/HighlightCard.tsx b/frontend/components/restaurant/HighlightCard.tsx new file mode 100644 index 0000000..432ed5f --- /dev/null +++ b/frontend/components/restaurant/HighlightCard.tsx @@ -0,0 +1,54 @@ +import { SmileyIcon } from "@/components/icons/Icons"; +import { View, Text, StyleSheet } from "react-native"; + +const HighlightCard = ({ + icon = , + title = "Super Stars", + subtitle = "200+ Five Stars", + backgroundColor = "#F7F9FC", + }) => { + return ( + + {icon} + + {title} + {subtitle} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + padding: 8, + borderRadius: 8, + width: 120, + height: 120, + flex: 1, + }, + iconContainer: { + marginBottom: 8, + }, + textContainer: { + alignItems: "center", + }, + title: { + fontSize: 12, + fontWeight: "bold", + color: "#000000", + textAlign: "center", + marginBottom: 4, + fontFamily: "Outfit", + }, + subtitle: { + fontSize: 12, + color: "#666666", + textAlign: "center", + fontFamily: "Outfit", + }, +}); + +export default HighlightCard; \ No newline at end of file diff --git a/frontend/components/restaurant/RestaurantDetailItem.tsx b/frontend/components/restaurant/RestaurantDetailItem.tsx index 970064d..7236301 100644 --- a/frontend/components/restaurant/RestaurantDetailItem.tsx +++ b/frontend/components/restaurant/RestaurantDetailItem.tsx @@ -24,7 +24,7 @@ const styles = StyleSheet.create({ }, text: { fontSize: 14, - color: "#285852", + color: "black", fontFamily: "Outfit", fontWeight: 500, }, diff --git a/frontend/components/restaurant/RestaurantReviewSummary.tsx b/frontend/components/restaurant/RestaurantReviewSummary.tsx new file mode 100644 index 0000000..1fa4ade --- /dev/null +++ b/frontend/components/restaurant/RestaurantReviewSummary.tsx @@ -0,0 +1,141 @@ +import React from "react"; +import { View, Text, StyleSheet } from "react-native"; +import { StarIcon } from "@/components/icons/Icons"; + +const ReviewSummary = ({ + rating = 4, + maxRating = 5, + reviewCount = 300, + friendsReviewCount = 3, + highlight = "Best Pad Thai in Boston. I'm serious.", + }) => { + return ( + + + + {rating} + /{maxRating} + + + + + {[...Array(maxRating)].map((_, index) => ( + + ))} + + {reviewCount} total reviews + + + + + + + "{highlight}" + + + {[...Array(Math.min(friendsReviewCount, 3))].map((_, index) => ( + + ))} + + + {friendsReviewCount} friend{friendsReviewCount !== 1 ? "s" : ""} reviewed recently + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: "#F9FAFB", + borderRadius: 12, + padding: 16, + width: "100%", + }, + ratingSection: { + flexDirection: "row", + alignItems: "center", + }, + ratingCircle: { + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: "#F8F8F8", + justifyContent: "center", + alignItems: "center", + flexDirection: "row", + marginRight: 16, + }, + ratingText: { + fontSize: 28, + fontWeight: "bold", + color: "#222222", + fontFamily: "Outfit", + }, + maxRatingText: { + fontSize: 18, + color: "#666666", + fontWeight: "500", + marginTop: 2, + fontFamily: "Outfit", + }, + starsAndCountSection: { + flex: 1, + }, + starsRow: { + flexDirection: "row", + alignItems: "center", + marginBottom: 6, + gap: 4, + }, + reviewCount: { + fontSize: 14, + color: "#666666", + fontWeight: "500", + fontFamily: "Outfit", + }, + divider: { + height: 1, + backgroundColor: "#EEEEEE", + marginVertical: 8, + }, + highlightSection: { + flexDirection: "column", + }, + highlightText: { + fontSize: 16, + fontStyle: "italic", + color: "#222222", + marginBottom: 12, + lineHeight: 22, + fontFamily: "Outfit", + }, + friendsContainer: { + flexDirection: "row", + alignItems: "center", + }, + avatarStack: { + height: 30, + width: 50, + position: "relative", + }, + friendAvatar: { + width: 30, + height: 30, + borderRadius: 15, + backgroundColor: "#E0E0E0", + borderWidth: 2, + borderColor: "#FFFFFF", + position: "absolute", + top: 0, + }, + friendsText: { + fontSize: 13, + color: "#666666", + marginLeft: 6, + fontFamily: "Outfit", + }, +}); + +export default ReviewSummary; \ No newline at end of file diff --git a/frontend/components/review/ReviewDetail.tsx b/frontend/components/review/ReviewDetail.tsx index 5cef478..ba90dc0 100644 --- a/frontend/components/review/ReviewDetail.tsx +++ b/frontend/components/review/ReviewDetail.tsx @@ -2,7 +2,7 @@ import React from "react"; import { View, StyleSheet, ScrollView, Image, TouchableOpacity } from "react-native"; import { ThemedView } from "@/components/themed/ThemedView"; import { ThemedText } from "@/components/themed/ThemedText"; -import { StarReview } from "@/components/StarReview"; +import { StarReview } from "@/components/ui/StarReview"; import { Ionicons } from "@expo/vector-icons"; // import { RestaurantTags } from "@/components/RestaurantTags"; import { Entypo } from "@expo/vector-icons"; diff --git a/frontend/components/StarReview.tsx b/frontend/components/ui/StarReview.tsx similarity index 87% rename from frontend/components/StarReview.tsx rename to frontend/components/ui/StarReview.tsx index 15a6762..8e589c3 100644 --- a/frontend/components/StarReview.tsx +++ b/frontend/components/ui/StarReview.tsx @@ -3,6 +3,7 @@ import { View, Text, TouchableOpacity } from "react-native"; import ShadedStar from "@/assets/icons/shaded_star_rate.svg"; import UnshadedStar from "@/assets/icons/unshaded_star_rate.svg"; import { StyleSheet } from "react-native"; +import { StarIcon } from "@/components/icons/Icons"; export interface StarReviewProps { avgRating: number; @@ -26,7 +27,7 @@ interface StarProps { full?: boolean; } -export function StarReview({ +export function StarRating({ avgRating, numRatings, full = true, @@ -49,18 +50,18 @@ export function StarReview({ } export function Stars({ avgRating, full = true }: StarProps) { - const stars = []; + const stars: React.JSX.Element[] = []; const maxStars = full ? 5 : 1; if (full) { for (let i = 0; i < maxStars; i++) { if (i < Math.floor(avgRating)) { - stars.push(); + stars.push(); } else { - stars.push(); + stars.push(); } } } else { - stars.push(); + stars.push(); } return {stars}; @@ -81,7 +82,7 @@ export function InteractiveStars({ const StarIcon = isFilled ? ShadedStar : UnshadedStar; return ( onChange(i + 1)}> - + ); })} @@ -109,7 +110,7 @@ const styles = StyleSheet.create({ flexDirection: "row", alignItems: "center", justifyContent: "center", - gap: 5, + gap: 3, }, starRow: { flexDirection: "row", diff --git a/frontend/components/ui/Tag.tsx b/frontend/components/ui/Tag.tsx new file mode 100644 index 0000000..f3fa031 --- /dev/null +++ b/frontend/components/ui/Tag.tsx @@ -0,0 +1,25 @@ +import { StyleSheet, Text } from "react-native"; + +type TagProps = { + text: string; + color?: string; +}; + +const Tag = ({ text, color = "#fc0" }: TagProps) => { + return {text}; +}; + +const styles = StyleSheet.create({ + tag: { + color: "#000", + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 20, + fontSize: 12, + fontFamily: "Outfit", + fontWeight: "bold", + marginRight: 0, + }, +}); + +export default Tag; \ No newline at end of file From 0f9b89f02029fd0182eb57ec62be856aa762b342 Mon Sep 17 00:00:00 2001 From: Ben Petrillo Date: Sat, 29 Mar 2025 11:46:28 -0400 Subject: [PATCH 03/10] chore: menu item view styling fixes --- frontend/app/MenuItemView.tsx | 48 ++++++++++++++++---- frontend/app/RestaurantView.tsx | 25 ++++++++++ frontend/components/review/ReviewPreview.tsx | 3 +- 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/frontend/app/MenuItemView.tsx b/frontend/app/MenuItemView.tsx index ea30ee3..62c3a5e 100644 --- a/frontend/app/MenuItemView.tsx +++ b/frontend/app/MenuItemView.tsx @@ -10,6 +10,8 @@ import { ThemedTag } from "@/components/themed/ThemedTag"; // import { RestaurantTags } from "@/components/RestaurantTags"; import { StatCard } from "@/components/Cards/StatCard"; import { ReviewButton } from "@/components/review/ReviewButton"; +import HighlightCard from "@/components/restaurant/HighlightCard"; +import { PersonWavingIcon, ThumbsUpIcon } from "@/components/icons/Icons"; // Temporary icons - you may want to create proper icons const FriendsIcon = () => ( @@ -96,11 +98,13 @@ export default function MenuItemView() { flavorful Thai stir-fried noodle dish with a perfect sweet-savory balance. - - Rice noodles, eggs, tofu/shrimp, peanuts, tamarind. - - - see allergy + + + Rice noodles, eggs, tofu/shrimp, peanuts, tamarind + + + + see allergens @@ -113,9 +117,13 @@ export default function MenuItemView() { - } title="Friends' Fav" subtitle="100+ Friends' refers" /> - } title="Super Stars" subtitle="200+ Five Stars" /> - } title="Satisfaction" subtitle="70% revisited" /> + } + /> + } /> + {/* Reviews Section */} @@ -224,15 +232,34 @@ const styles = StyleSheet.create({ fontSize: 16, lineHeight: 16, }, + allergyLabel: { + fontSize: 14, + fontWeight: "600", + color: "#444", + lineHeight: 18, + }, allergyText: { fontSize: 14, color: "#666", - lineHeight: 14, + lineHeight: 18, + flexShrink: 1, }, allergyRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", + marginTop: 4, + }, + allergyItemsContainer: { + flexDirection: "row", + alignItems: "center", + flexShrink: 1, + flexWrap: "wrap", + maxWidth: "75%", + }, + allergyButton: { + paddingVertical: 4, + paddingHorizontal: 8, }, sectionHeader: { flexDirection: "row", @@ -247,6 +274,7 @@ const styles = StyleSheet.create({ viewAllText: { color: "#007AFF", textDecorationLine: "underline", + fontSize: 14, }, statsContainer: { flexDirection: "row", @@ -301,4 +329,4 @@ const styles = StyleSheet.create({ tagRow: { flexDirection: "row", }, -}); +}); \ No newline at end of file diff --git a/frontend/app/RestaurantView.tsx b/frontend/app/RestaurantView.tsx index 64291ab..e2ff7fa 100644 --- a/frontend/app/RestaurantView.tsx +++ b/frontend/app/RestaurantView.tsx @@ -10,9 +10,15 @@ import Tag from "@/components/ui/Tag"; import { StarRating } from "@/components/ui/StarReview"; import RestaurantReviewSummary from "@/components/restaurant/RestaurantReviewSummary"; import HighlightCard from "@/components/restaurant/HighlightCard"; +import FeedTabs from "@/components/Feed/FeedTabs"; +import { filter } from "domutils"; +import ReviewPreview from "@/components/review/ReviewPreview"; +import MenuItemPreview from "@/components/Cards/MenuItemPreview"; export default function RestaurantView() { const restaurantTags = ["Fast Food", "Fried Chicken", "Chicken Sandwiches", "Order Online"]; + const [activeTab, setActiveTab] = React.useState(0); + const [filterTab, setFilterTab] = React.useState(0); return ( @@ -63,6 +69,25 @@ export default function RestaurantView() { } /> + + + + + { filterTab == 0 && ( + <> + + + + + + )} + + { filterTab == 1 && ( + <> + + + )} + ); diff --git a/frontend/components/review/ReviewPreview.tsx b/frontend/components/review/ReviewPreview.tsx index 4edb916..2ed188a 100644 --- a/frontend/components/review/ReviewPreview.tsx +++ b/frontend/components/review/ReviewPreview.tsx @@ -27,7 +27,7 @@ const ReviewPreview = ({ plateName, restaurantName, tags, rating, content }: Pro borderRadius: 12, paddingTop: 24, // width: Dimensions.get("window").width * 0.75, - height: Dimensions.get("window").height * 0.4, + height: Dimensions.get("window").height * 0.35, }}> Date: Sat, 29 Mar 2025 11:46:47 -0400 Subject: [PATCH 04/10] chore: format code --- frontend/app/MenuItemView.tsx | 2 +- frontend/app/RestaurantView.tsx | 33 +++++++++++++++---- .../components/restaurant/HighlightCard.tsx | 12 +++---- .../restaurant/RestaurantReviewSummary.tsx | 14 ++++---- frontend/components/ui/StarReview.tsx | 11 +++++-- frontend/components/ui/Tag.tsx | 2 +- 6 files changed, 50 insertions(+), 24 deletions(-) diff --git a/frontend/app/MenuItemView.tsx b/frontend/app/MenuItemView.tsx index 62c3a5e..e563470 100644 --- a/frontend/app/MenuItemView.tsx +++ b/frontend/app/MenuItemView.tsx @@ -329,4 +329,4 @@ const styles = StyleSheet.create({ tagRow: { flexDirection: "row", }, -}); \ No newline at end of file +}); diff --git a/frontend/app/RestaurantView.tsx b/frontend/app/RestaurantView.tsx index e2ff7fa..4103bdb 100644 --- a/frontend/app/RestaurantView.tsx +++ b/frontend/app/RestaurantView.tsx @@ -73,18 +73,39 @@ export default function RestaurantView() { - { filterTab == 0 && ( + {filterTab == 0 && ( <> - + - + )} - { filterTab == 1 && ( + {filterTab == 1 && ( <> - + )} @@ -149,4 +170,4 @@ const styles = StyleSheet.create({ justifyContent: "space-between", gap: 12, }, -}); \ No newline at end of file +}); diff --git a/frontend/components/restaurant/HighlightCard.tsx b/frontend/components/restaurant/HighlightCard.tsx index 432ed5f..91e0869 100644 --- a/frontend/components/restaurant/HighlightCard.tsx +++ b/frontend/components/restaurant/HighlightCard.tsx @@ -2,11 +2,11 @@ import { SmileyIcon } from "@/components/icons/Icons"; import { View, Text, StyleSheet } from "react-native"; const HighlightCard = ({ - icon = , - title = "Super Stars", - subtitle = "200+ Five Stars", - backgroundColor = "#F7F9FC", - }) => { + icon = , + title = "Super Stars", + subtitle = "200+ Five Stars", + backgroundColor = "#F7F9FC", +}) => { return ( {icon} @@ -51,4 +51,4 @@ const styles = StyleSheet.create({ }, }); -export default HighlightCard; \ No newline at end of file +export default HighlightCard; diff --git a/frontend/components/restaurant/RestaurantReviewSummary.tsx b/frontend/components/restaurant/RestaurantReviewSummary.tsx index 1fa4ade..0974b4e 100644 --- a/frontend/components/restaurant/RestaurantReviewSummary.tsx +++ b/frontend/components/restaurant/RestaurantReviewSummary.tsx @@ -3,12 +3,12 @@ import { View, Text, StyleSheet } from "react-native"; import { StarIcon } from "@/components/icons/Icons"; const ReviewSummary = ({ - rating = 4, - maxRating = 5, - reviewCount = 300, - friendsReviewCount = 3, - highlight = "Best Pad Thai in Boston. I'm serious.", - }) => { + rating = 4, + maxRating = 5, + reviewCount = 300, + friendsReviewCount = 3, + highlight = "Best Pad Thai in Boston. I'm serious.", +}) => { return ( @@ -138,4 +138,4 @@ const styles = StyleSheet.create({ }, }); -export default ReviewSummary; \ No newline at end of file +export default ReviewSummary; diff --git a/frontend/components/ui/StarReview.tsx b/frontend/components/ui/StarReview.tsx index 8e589c3..670e01f 100644 --- a/frontend/components/ui/StarReview.tsx +++ b/frontend/components/ui/StarReview.tsx @@ -55,9 +55,9 @@ export function Stars({ avgRating, full = true }: StarProps) { if (full) { for (let i = 0; i < maxStars; i++) { if (i < Math.floor(avgRating)) { - stars.push(); + stars.push(); } else { - stars.push(); + stars.push(); } } } else { @@ -82,7 +82,12 @@ export function InteractiveStars({ const StarIcon = isFilled ? ShadedStar : UnshadedStar; return ( onChange(i + 1)}> - + ); })} diff --git a/frontend/components/ui/Tag.tsx b/frontend/components/ui/Tag.tsx index f3fa031..56a0e03 100644 --- a/frontend/components/ui/Tag.tsx +++ b/frontend/components/ui/Tag.tsx @@ -22,4 +22,4 @@ const styles = StyleSheet.create({ }, }); -export default Tag; \ No newline at end of file +export default Tag; From a257d952ca2b6eb98c833ce4128308aef482289a Mon Sep 17 00:00:00 2001 From: Sierra Welsch Date: Sat, 29 Mar 2025 13:51:04 -0400 Subject: [PATCH 05/10] added getDietaryRestrictions endpoint --- backend/internal/handlers/users/routes.go | 4 ++++ backend/internal/handlers/users/service.go | 20 +++++++++++++++++ backend/internal/handlers/users/types.go | 2 ++ .../handlers/users/user_connections.go | 16 ++++++++++++++ frontend/app/(tabs)/profile/followers.tsx | 2 +- frontend/app/(tabs)/profile/settings.tsx | 22 ++++++++----------- frontend/app/friend.tsx | 3 +-- .../components/profile/ProfileMetrics.tsx | 10 ++++++--- .../profile/followers/FollowButton.tsx | 4 ++-- 9 files changed, 62 insertions(+), 21 deletions(-) diff --git a/backend/internal/handlers/users/routes.go b/backend/internal/handlers/users/routes.go index 7023247..69fdee9 100644 --- a/backend/internal/handlers/users/routes.go +++ b/backend/internal/handlers/users/routes.go @@ -24,4 +24,8 @@ func Routes(app *fiber.App, collections map[string]*mongo.Collection) { item := apiV1.Group("/item") item.Get("/:id/followReviews", handler.GetFollowingReviewsForItem) item.Get("/:id/friendReviews", handler.GetFriendReviewsForItem) + + // User settings + settings := apiV1.Group("/settings") + settings.Get("/:id/dietaryPreferences", handler.GetDietaryPreferences) } diff --git a/backend/internal/handlers/users/service.go b/backend/internal/handlers/users/service.go index eb77a89..02956e5 100644 --- a/backend/internal/handlers/users/service.go +++ b/backend/internal/handlers/users/service.go @@ -412,3 +412,23 @@ func (s *Service) GetFriendReviewsForItem(userObjID primitive.ObjectID, menuItem return reviews, nil } + +func (s *Service) GetDietaryPreferences(userId string) ([]string, error) { + ctx := context.Background() + userObjID, err := primitive.ObjectIDFromHex(userId) + if err != nil { + badReq := xerr.BadRequest(err) + return nil, &badReq + } + + // Find the user and get their followers + var user User + err = s.users.FindOne(ctx, bson.M{"_id": userObjID}).Decode(&user) + if err != nil { + return nil, err + } + + dietaryRestrictions := user.Preferences + + return dietaryRestrictions, nil +} diff --git a/backend/internal/handlers/users/types.go b/backend/internal/handlers/users/types.go index 2c42162..2decf2f 100644 --- a/backend/internal/handlers/users/types.go +++ b/backend/internal/handlers/users/types.go @@ -15,6 +15,7 @@ type User struct { FollowersCount int `bson:"followersCount"` ProfilePicture string `bson:"profile_picture,omitempty"` Name string `bson:"name,omitempty"` + Preferences []string `bson:"preferences,omitempty"` } type UserResponse struct { @@ -26,6 +27,7 @@ type UserResponse struct { FollowingCount int `json:"followingCount"` Reviews []string `json:"reviews,omitempty"` Name string `json:"name,omitempty"` + Preferences []string `json:"preferences,omitempty"` } type FollowRequest struct { diff --git a/backend/internal/handlers/users/user_connections.go b/backend/internal/handlers/users/user_connections.go index 33c7b87..5c024fb 100644 --- a/backend/internal/handlers/users/user_connections.go +++ b/backend/internal/handlers/users/user_connections.go @@ -2,6 +2,7 @@ package users import ( "errors" + "github.com/GenerateNU/platemate/internal/xerr" "github.com/GenerateNU/platemate/internal/xvalidator" "github.com/gofiber/fiber/v2" @@ -180,3 +181,18 @@ func (h *Handler) UnfollowUser(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusNoContent) } + +// GetDietaryPreferences retrieves the dietary preferences of a user +func (h *Handler) GetDietaryPreferences(c *fiber.Ctx) error { + userId := c.Params("id") + + preferences, err := h.service.GetDietaryPreferences(userId) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", userId)) + } + return err + } + + return c.JSON(preferences) +} diff --git a/frontend/app/(tabs)/profile/followers.tsx b/frontend/app/(tabs)/profile/followers.tsx index 8711f2c..abd8091 100644 --- a/frontend/app/(tabs)/profile/followers.tsx +++ b/frontend/app/(tabs)/profile/followers.tsx @@ -122,7 +122,7 @@ export default function FollowersScreen() { {loading && page === 1 ? ( - Loading followers... + Loading friends... ) : ( { setSettings((prevSettings) => ({ ...prevSettings, @@ -116,14 +115,6 @@ export default function SettingsScreen() { onPress: () => router.push("/(tabs)/profile/followers"), showChevron: true, }, - { - label: "Logout", - onPress: () => { - logout(); - router.replace("/(onboarding)"); - }, - showChevron: false, - }, ], additional: [ { label: "Blocked Users", onPress: () => console.log("navigating to blocked users") }, @@ -131,6 +122,11 @@ export default function SettingsScreen() { ], }; + const handleLogOut = () => { + logout(); + router.replace("/(onboarding)"); + } + return ( @@ -196,7 +192,7 @@ export default function SettingsScreen() { ))} - + @@ -221,13 +217,13 @@ const styles = StyleSheet.create({ alignItems: "center", gap: 4, borderRadius: 25, - backgroundColor: "#285852", + backgroundColor: "#FFCF0F", alignSelf: "center", - width: 100, + width: 90, height: 30, }, buttonText: { - color: "#FFF", + color: "#000", textAlign: "center", fontFamily: "Source Sans 3", fontSize: 14, diff --git a/frontend/app/friend.tsx b/frontend/app/friend.tsx index 5b99e4b..e43f10e 100644 --- a/frontend/app/friend.tsx +++ b/frontend/app/friend.tsx @@ -8,7 +8,6 @@ import { Ionicons } from "@expo/vector-icons"; import ProfileAvatar from "@/components/profile/ProfileAvatar"; import ProfileIdentity from "@/components/profile/ProfileIdentity"; import ProfileMetrics from "@/components/profile/ProfileMetrics"; -import EditProfileSheet from "@/components/profile/EditProfileSheet"; import ReviewPreview from "@/components/review/ReviewPreview"; import { SearchBoxFilter } from "@/components/SearchBoxFilter"; import EditFriendSheet from "@/components/profile/followers/FriendProfileOptions"; @@ -68,7 +67,7 @@ const ProfileScreen = () => { - + diff --git a/frontend/components/profile/ProfileMetrics.tsx b/frontend/components/profile/ProfileMetrics.tsx index a49d531..a123078 100644 --- a/frontend/components/profile/ProfileMetrics.tsx +++ b/frontend/components/profile/ProfileMetrics.tsx @@ -1,7 +1,8 @@ -import { StyleSheet, View } from "react-native"; +import { StyleSheet, View, TouchableOpacity } from "react-native"; import { ThemedText } from "@/components/themed/ThemedText"; import { ThemedView } from "@/components/themed/ThemedView"; import React from "react"; +import { useRouter } from "expo-router"; type ProfileMetricProps = { numFriends: number; @@ -10,6 +11,7 @@ type ProfileMetricProps = { }; const ProfileMetrics = (props: ProfileMetricProps) => { + const router = useRouter(); return ( @@ -18,8 +20,10 @@ const ProfileMetrics = (props: ProfileMetricProps) => { - {props.numFriends} - friends + { router.push("/(tabs)/profile/followers") }}> + {props.numFriends} + friends + diff --git a/frontend/components/profile/followers/FollowButton.tsx b/frontend/components/profile/followers/FollowButton.tsx index 26a696b..333436c 100644 --- a/frontend/components/profile/followers/FollowButton.tsx +++ b/frontend/components/profile/followers/FollowButton.tsx @@ -8,12 +8,12 @@ export const FollowButton: React.FC<{ text: string }> = ({ text }) => { const [buttonText, setButtonText] = useState(text); const handlePress = () => { - if (buttonText == "Following") { + if (buttonText == "Friends") { setIsPressed(false); setButtonText("Follow"); } else { setIsPressed(true); - setButtonText("Following"); + setButtonText("Friends"); } }; From 73f836d0855fcacbbd4bf032d643b576dc4bc56c Mon Sep 17 00:00:00 2001 From: Sierra Welsch Date: Sat, 29 Mar 2025 18:05:39 -0400 Subject: [PATCH 06/10] integrated the backend for the dietary preferences --- backend/internal/handlers/users/routes.go | 2 + backend/internal/handlers/users/service.go | 42 +++++ backend/internal/handlers/users/types.go | 4 + .../handlers/users/user_connections.go | 32 +++- frontend/app/(tabs)/profile/settings.tsx | 148 +++++++++++------- 5 files changed, 171 insertions(+), 57 deletions(-) diff --git a/backend/internal/handlers/users/routes.go b/backend/internal/handlers/users/routes.go index 69fdee9..a91f4d2 100644 --- a/backend/internal/handlers/users/routes.go +++ b/backend/internal/handlers/users/routes.go @@ -28,4 +28,6 @@ func Routes(app *fiber.App, collections map[string]*mongo.Collection) { // User settings settings := apiV1.Group("/settings") settings.Get("/:id/dietaryPreferences", handler.GetDietaryPreferences) + settings.Post("/:id/dietaryPreferences", handler.PostDietaryPreferences) + settings.Delete("/:id/dietaryPreferences", handler.DeleteDietaryPreferences) } diff --git a/backend/internal/handlers/users/service.go b/backend/internal/handlers/users/service.go index 02956e5..ad2d5fb 100644 --- a/backend/internal/handlers/users/service.go +++ b/backend/internal/handlers/users/service.go @@ -432,3 +432,45 @@ func (s *Service) GetDietaryPreferences(userId string) ([]string, error) { return dietaryRestrictions, nil } + +func (s *Service) PostDietaryPreferences(userId string, preference string) error { + ctx := context.Background() + userObjID, err := primitive.ObjectIDFromHex(userId) + if err != nil { + badReq := xerr.BadRequest(err) + return &badReq + } + + update := bson.M{ + "$push": bson.M{"preferences": preference}, + } + + // Update the user's dietary preferences in the database + _, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, update) + if err != nil { + return err + } + + return nil +} + +func (s *Service) DeleteDietaryPreferences(userId string, preference string) error { + ctx := context.Background() + userObjID, err := primitive.ObjectIDFromHex(userId) + if err != nil { + badReq := xerr.BadRequest(err) + return &badReq + } + + delete := bson.M{ + "$pull": bson.M{"preferences": preference}, + } + + // Update the user's dietary preferences in the database + _, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, delete) + if err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/backend/internal/handlers/users/types.go b/backend/internal/handlers/users/types.go index 2decf2f..e02935c 100644 --- a/backend/internal/handlers/users/types.go +++ b/backend/internal/handlers/users/types.go @@ -53,3 +53,7 @@ type ReviewQuery struct { UserId string `query:"userId" validate:"required"` ItemId string `params:"id" validate:"required"` } + +type PostDietaryPreferencesQuery struct { + Preference string `json:"preference"` +} diff --git a/backend/internal/handlers/users/user_connections.go b/backend/internal/handlers/users/user_connections.go index 5c024fb..9dbb115 100644 --- a/backend/internal/handlers/users/user_connections.go +++ b/backend/internal/handlers/users/user_connections.go @@ -182,7 +182,7 @@ func (h *Handler) UnfollowUser(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusNoContent) } -// GetDietaryPreferences retrieves the dietary preferences of a user +// GetDietaryPreferences retrieves the dietary preferences of a user func (h *Handler) GetDietaryPreferences(c *fiber.Ctx) error { userId := c.Params("id") @@ -196,3 +196,33 @@ func (h *Handler) GetDietaryPreferences(c *fiber.Ctx) error { return c.JSON(preferences) } + +func (h *Handler) PostDietaryPreferences(c *fiber.Ctx) error { + userId := c.Params("id") + preference := c.Query("preference") + + err := h.service.PostDietaryPreferences(userId, preference) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", "specified")) + } + return err + } + + return c.SendStatus(fiber.StatusCreated) +} + +func (h *Handler) DeleteDietaryPreferences(c *fiber.Ctx) error { + userId := c.Params("id") + preference := c.Query("preference") + + err := h.service.DeleteDietaryPreferences(userId, preference) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", "specified")) + } + return err + } + + return c.SendStatus(fiber.StatusCreated) +} diff --git a/frontend/app/(tabs)/profile/settings.tsx b/frontend/app/(tabs)/profile/settings.tsx index 31732e5..73041b4 100644 --- a/frontend/app/(tabs)/profile/settings.tsx +++ b/frontend/app/(tabs)/profile/settings.tsx @@ -10,79 +10,115 @@ import SettingsMenuItem from "@/components/profile/settings/SettingsMenuItem"; import { TSettingsData } from "@/types/settingsData"; import useAuthStore from "@/auth/store"; import { Button } from "@/components/Button"; +import axios from 'axios'; export default function SettingsScreen() { const insets = useSafeAreaInsets(); const router = useRouter(); - const { email } = useAuthStore(); + const { email, userId } = useAuthStore(); const { logout } = useAuthStore(); - const dietaryOptions = [ - "vegetarian", - "vegan", - "nutFree", - "shellfishAllergy", - "glutenFree", - "dairyFree", - "kosher", - "halal", - "pescatarian", - "keto", - "diabetic", - "soyFree", - "porkFree", - "beefFree", - ]; + const dietaryOptions: Record = { + "vegetarian": "Vegetarian", + "vegan": "Vegan", + "nutFree": "Nut-free", + "shellfishAllergy": "Shellfish Allergy", + "glutenFree": "Gluten-free", + "dairyFree": "Dairy-free", + "kosher": "Kosher", + "halal": "Halal", + "pescatarian": "Pescatarian", + "keto": "Keto", + "diabetic": "Diabetic", + "soyFree": "Soy-free", + "porkFree": "Pork-free", + "beefFree": "Beef-free", + }; + + const [dietaryRestrictions, setDietaryRestrictions] = useState([]); const [settings, setSettings] = useState>( - Object.fromEntries(dietaryOptions.map((option) => [option, false])), + Object.fromEntries(Object.values(dietaryOptions).map((option) => [option, false])), ); - // useEffect(() => { - // const fetchPreferences = async () => { - // try { - // const userData = await fetchUserProfile(); // Fetch user profile using the context function - - // if (!userData) return; // Ensure userData exists before proceeding - - // const userRestrictions: string[] = userData.preferences || []; + useEffect(() => { + console.log("fetched restrictions!"); + const fetchDietaryRestrictions = async () => { + try { + const response = await axios.get( + `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences` + ); + setDietaryRestrictions(response.data); + } + catch (err) { + console.log('Failed to fetch dietary restrictions'); + console.error(err); + } + }; + + fetchDietaryRestrictions(); + console.log(dietaryRestrictions); + }, [userId]); - // // Convert restrictions array to object { vegetarian: true, glutenFree: true, keto: true, ... } - // const updatedSettings = dietaryOptions.reduce((acc, option) => { - // acc[option] = userRestrictions.includes(option); - // return acc; - // }, {} as Record); - - // setSettings(updatedSettings); - // } catch (error) { - // console.error("Error fetching user preferences:", error); - // } - // }; - - // fetchPreferences(); - // }, [fetchUserProfile]); + useEffect(() => { + console.log("reloaded!"); + console.log(dietaryRestrictions); + setSettings( + Object.fromEntries( + Object.keys(dietaryOptions).map((option) => [ + option, + dietaryRestrictions.includes(dietaryOptions[option]), + ]) + ) + ); + }, [dietaryRestrictions]) const updateSetting = (key: string, value: boolean) => { - setSettings((prevSettings) => ({ - ...prevSettings, - [`${key}`]: value, - })); + if (value == true) { + setDietaryRestrictions((prevRestrictions) => [ + ...prevRestrictions, + dietaryOptions[key], + ]); + handleAddDietaryPreference(key); + } else { + setDietaryRestrictions((prevRestrictions) => + prevRestrictions.filter((item) => item !== dietaryOptions[key]) + ); + handleRemoveDietaryPreference(key); + } + }; + + const handleAddDietaryPreference = async (preference:string) => { + try { + const response = await axios.post( + `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences?preference=${dietaryOptions[preference]}` + ); + if (response.status === 201) { + console.log('Preference added successfully:', preference); + } + } + catch (err) { + console.log('Failed to add dietary preference'); + console.error(err); + } }; - // const updateSetting = async (key: string, value: boolean) => { - // try { - // const updatedSettings = { ...settings, [key]: value }; - // setSettings(updatedSettings); - // // Send updated preferences to the server - // await axios.put(`${process.env.API_BASE_URL}/api/v1/user/${userId}`, { - // preferences: { restrictions: Object.keys(updatedSettings).filter((k) => updatedSettings[k]) }, - // }); - // } catch (error) { - // console.error("Error saving setting:", error); - // } - // }; + const handleRemoveDietaryPreference = async (preference:string) => { + try { + const response = await axios.delete( + `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences?preference=${dietaryOptions[preference]}` + ); + if (response.status === 201) { + console.log('Preference deleted successfully:', preference); + } + } + catch (err) { + console.log('Failed to delete dietary preference'); + console.error(err); + } + }; const settingsData: TSettingsData = { credentials: [ From 90c32c58698796cad41a1bd40d3a4e8a179a6b5d Mon Sep 17 00:00:00 2001 From: Sierra Welsch Date: Sat, 29 Mar 2025 19:03:00 -0400 Subject: [PATCH 07/10] can view your friends! --- backend/internal/handlers/users/routes.go | 1 + backend/internal/handlers/users/service.go | 111 +++++++++++++++--- backend/internal/handlers/users/types.go | 5 + .../handlers/users/user_connections.go | 25 +++- .../profile/{followers.tsx => friends.tsx} | 73 ++++++------ frontend/app/(tabs)/profile/settings.tsx | 18 +-- frontend/types/follower.ts | 2 +- 7 files changed, 174 insertions(+), 61 deletions(-) rename frontend/app/(tabs)/profile/{followers.tsx => friends.tsx} (76%) diff --git a/backend/internal/handlers/users/routes.go b/backend/internal/handlers/users/routes.go index a91f4d2..81e10b5 100644 --- a/backend/internal/handlers/users/routes.go +++ b/backend/internal/handlers/users/routes.go @@ -17,6 +17,7 @@ func Routes(app *fiber.App, collections map[string]*mongo.Collection) { user.Get("/:id", handler.GetUserById) user.Get("/followers", handler.GetFollowers) + user.Get("/:id/following", handler.GetFollowing) user.Post("/follow", handler.FollowUser) user.Delete("/follow", handler.UnfollowUser) diff --git a/backend/internal/handlers/users/service.go b/backend/internal/handlers/users/service.go index ad2d5fb..4969679 100644 --- a/backend/internal/handlers/users/service.go +++ b/backend/internal/handlers/users/service.go @@ -3,6 +3,7 @@ package users import ( "context" "errors" + "fmt" "github.com/GenerateNU/platemate/internal/handlers/menu_items" "github.com/GenerateNU/platemate/internal/handlers/review" @@ -134,6 +135,86 @@ func (s *Service) GetUserFollowers(userId string, page, limit int) ([]UserRespon FollowersCount: follower.FollowersCount, FollowingCount: follower.FollowingCount, Reviews: reviews, + Preferences: follower.Preferences, + } + } + + return response, nil +} + +func (s *Service) GetUserFollowing(userId string, page, limit int) ([]UserResponse, error) { + ctx := context.Background() + fmt.Println((userId)) + userObjID, err := primitive.ObjectIDFromHex(userId) + if err != nil { + badReq := xerr.BadRequest(err) + return nil, &badReq + } + + // Find the user and get their followers + var user User + err = s.users.FindOne(ctx, bson.M{"_id": userObjID}).Decode(&user) + if err != nil { + return nil, err + } + + // Calculate total pages and adjust page number if out of bounds + totalFollowing := len(user.Following) + totalPages := (totalFollowing + limit - 1) / limit // Ceiling division + + if totalPages == 0 { + return []UserResponse{}, nil + } + + // Adjust page to be within bounds + if page > totalPages { + page = totalPages + } + if page < 1 { + page = 1 + } + + // Calculate pagination bounds + skip := (page - 1) * limit + end := skip + limit + if end > totalFollowing { + end = totalFollowing + } + + // Get the slice of follower IDs for this page, could be an issue if the value is 0 + pageFollowers := user.Following[skip:end] + + // Fetch the actual user documents for these followers + cursor, err := s.users.Find(ctx, bson.M{ + "_id": bson.M{"$in": pageFollowers}, + }) + if err != nil { + return nil, err + } + defer cursor.Close(ctx) + + var following []User + if err = cursor.All(ctx, &following); err != nil { + return nil, err + } + + // Convert to response format + response := make([]UserResponse, len(following)) + for i, followingUser := range following { + reviews := make([]string, len(followingUser.Reviews)) + for j, reviewID := range followingUser.Reviews { + reviews[j] = reviewID.Hex() + } + + response[i] = UserResponse{ + ID: followingUser.ID.Hex(), + Name: followingUser.Name, + Username: followingUser.Username, + ProfilePicture: followingUser.ProfilePicture, + FollowersCount: followingUser.FollowersCount, + FollowingCount: followingUser.FollowingCount, + Reviews: reviews, + Preferences: followingUser.Preferences, } } @@ -442,14 +523,14 @@ func (s *Service) PostDietaryPreferences(userId string, preference string) error } update := bson.M{ - "$push": bson.M{"preferences": preference}, - } + "$push": bson.M{"preferences": preference}, + } - // Update the user's dietary preferences in the database - _, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, update) - if err != nil { - return err - } + // Update the user's dietary preferences in the database + _, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, update) + if err != nil { + return err + } return nil } @@ -463,14 +544,14 @@ func (s *Service) DeleteDietaryPreferences(userId string, preference string) err } delete := bson.M{ - "$pull": bson.M{"preferences": preference}, - } + "$pull": bson.M{"preferences": preference}, + } - // Update the user's dietary preferences in the database - _, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, delete) - if err != nil { - return err - } + // Update the user's dietary preferences in the database + _, err = s.users.UpdateOne(ctx, bson.M{"_id": userObjID}, delete) + if err != nil { + return err + } return nil -} \ No newline at end of file +} diff --git a/backend/internal/handlers/users/types.go b/backend/internal/handlers/users/types.go index e02935c..abcc1f5 100644 --- a/backend/internal/handlers/users/types.go +++ b/backend/internal/handlers/users/types.go @@ -49,6 +49,11 @@ type GetFollowersQuery struct { UserId string `query:"userId" validate:"required"` } +type GetFollowingQuery struct { + PaginationQuery + UserId string `query:"userId" validate:"required"` +} + type ReviewQuery struct { UserId string `query:"userId" validate:"required"` ItemId string `params:"id" validate:"required"` diff --git a/backend/internal/handlers/users/user_connections.go b/backend/internal/handlers/users/user_connections.go index 9dbb115..f291324 100644 --- a/backend/internal/handlers/users/user_connections.go +++ b/backend/internal/handlers/users/user_connections.go @@ -2,7 +2,6 @@ package users import ( "errors" - "github.com/GenerateNU/platemate/internal/xerr" "github.com/GenerateNU/platemate/internal/xvalidator" "github.com/gofiber/fiber/v2" @@ -69,6 +68,30 @@ func (h *Handler) GetFollowers(c *fiber.Ctx) error { return c.JSON(followers) } +// GetFollowing returns a paginated list of who the user is following +func (h *Handler) GetFollowing(c *fiber.Ctx) error { + var query GetFollowingQuery + userId := c.Params("id") + + // Set defaults if not provided + if query.Page < 1 { + query.Page = 1 + } + if query.Limit < 1 { + query.Limit = 20 + } + + followers, err := h.service.GetUserFollowing(userId, query.Page, query.Limit) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return c.Status(fiber.StatusNotFound).JSON(xerr.NotFound("User", "id", query.UserId)) + } + return err + } + + return c.JSON(followers) +} + // GetFollowingReviewsForItem gets reviews for a menu item from users that the current user follows func (h *Handler) GetFollowingReviewsForItem(c *fiber.Ctx) error { diff --git a/frontend/app/(tabs)/profile/followers.tsx b/frontend/app/(tabs)/profile/friends.tsx similarity index 76% rename from frontend/app/(tabs)/profile/followers.tsx rename to frontend/app/(tabs)/profile/friends.tsx index abd8091..f2aea07 100644 --- a/frontend/app/(tabs)/profile/followers.tsx +++ b/frontend/app/(tabs)/profile/friends.tsx @@ -4,58 +4,61 @@ import React, { useState, useEffect, useCallback } from "react"; import { View, Text, StyleSheet, FlatList, TextInput, ActivityIndicator } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import FollowerItem from "@/components/profile/followers/FollowerItem"; -import { TFollower } from "@/types/follower"; +import { TFriend } from "@/types/follower"; +import useAuthStore from "@/auth/store"; -export default function FollowersScreen() { +export default function FriendsScreen() { const insets = useSafeAreaInsets(); const [searchQuery, setSearchQuery] = useState(""); - const [followers, setFollowers] = useState([]); + const [friends, setFriends] = useState([]); const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); const [page, setPage] = useState(1); const [isLoadingMore, setIsLoadingMore] = useState(false); const [hasMoreData, setHasMoreData] = useState(true); + const { userId } = useAuthStore(); useEffect(() => { - fetchRandomUsers(); + fetchFriends(); }, []); - const fetchRandomUsers = async (pageNum = 1, isRefresh = false) => { - if (pageNum === 1) { + const fetchFriends = async (pageNum = 1, isRefresh = false) => { + if (friends.length == 0) { setLoading(true); - } else { - setIsLoadingMore(true); } try { - const response = await fetch(`https://randomuser.me/api/?results=10&page=${pageNum}`); + const response = await fetch(`https://externally-exotic-orca.ngrok-free.app/api/v1/user/${userId}/following`); const data = await response.json(); - if (data.results && data.results.length > 0) { - const formattedUsers = data.results.map((user) => ({ - id: user.login.uuid, - name: `${user.name.first} ${user.name.last}`, - username: `@${user.login.username}`, - avatar: user.picture.medium, + if (data && data.length > 0) { + const formattedUsers = data.map((user) => ({ + id: user.id, + name: `${user.name}`, + username: `@${user.username}`, + avatar: user.profile_picture, })); - if (isRefresh) { - setFollowers(formattedUsers); - setPage(2); - } else if (pageNum === 1) { - setFollowers(formattedUsers); - setPage(2); - } else { - setFollowers((prevFollowers) => [...prevFollowers, ...formattedUsers]); - setPage(pageNum + 1); - } - - setHasMoreData(pageNum < 15); + setFriends(formattedUsers); + + // dont think this stuff is needed anymore but left it in case + // if (isRefresh) { + // setFollowers(formattedUsers); + // setPage(2); + // } else if (pageNum === 1) { + // setFollowers(formattedUsers); + // setPage(2); + // } else { + // setFollowers((prevFollowers) => [...prevFollowers, ...formattedUsers]); + // setPage(pageNum + 1); + // } + + // setHasMoreData(pageNum < 15); } else { - setHasMoreData(false); + // setHasMoreData(false); } } catch (error) { - console.error("Error fetching random users:", error); + console.error("Error fetching users:", error); } finally { setLoading(false); setIsLoadingMore(false); @@ -67,23 +70,23 @@ export default function FollowersScreen() { if (!refreshing) { setRefreshing(true); setHasMoreData(true); - fetchRandomUsers(1, true); + fetchFriends(1, true); } }, [refreshing]); const handleLoadMore = useCallback(() => { if (!isLoadingMore && hasMoreData && !loading && !refreshing) { - fetchRandomUsers(page); + fetchFriends(page); } }, [isLoadingMore, hasMoreData, loading, refreshing, page]); - const filteredFollowers = followers.filter( + const filteredFollowers = friends.filter( (follower) => follower.name.toLowerCase().includes(searchQuery.toLowerCase()) || follower.username.toLowerCase().includes(searchQuery.toLowerCase()), ); - const renderFollower = ({ item }: { item: TFollower }) => ; + const renderFollower = ({ item }: { item: TFriend }) => ; const renderFooter = () => { if (!isLoadingMore) return null; @@ -97,7 +100,7 @@ export default function FollowersScreen() { }; const renderNoMoreData = () => { - if (followers.length > 0 && !hasMoreData && !isLoadingMore) { + if (friends.length > 0 && !hasMoreData && !isLoadingMore) { return ( No more followers to load. @@ -138,7 +141,7 @@ export default function FollowersScreen() { } ListHeaderComponent={ - {followers.length} {followers.length === 1 ? "Friend" : "Friends"} + {friends.length} {friends.length === 1 ? "Friend" : "Friends"} } ListFooterComponent={ diff --git a/frontend/app/(tabs)/profile/settings.tsx b/frontend/app/(tabs)/profile/settings.tsx index 73041b4..3b129bf 100644 --- a/frontend/app/(tabs)/profile/settings.tsx +++ b/frontend/app/(tabs)/profile/settings.tsx @@ -36,7 +36,7 @@ export default function SettingsScreen() { "beefFree": "Beef-free", }; - const [dietaryRestrictions, setDietaryRestrictions] = useState([]); + const [dietaryPreferences, setDietaryPreferences] = useState([]); const [settings, setSettings] = useState>( Object.fromEntries(Object.values(dietaryOptions).map((option) => [option, false])), @@ -49,7 +49,7 @@ export default function SettingsScreen() { const response = await axios.get( `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences` ); - setDietaryRestrictions(response.data); + setDietaryPreferences(response.data); } catch (err) { console.log('Failed to fetch dietary restrictions'); @@ -58,31 +58,31 @@ export default function SettingsScreen() { }; fetchDietaryRestrictions(); - console.log(dietaryRestrictions); + console.log(dietaryPreferences); }, [userId]); useEffect(() => { console.log("reloaded!"); - console.log(dietaryRestrictions); + console.log(dietaryPreferences); setSettings( Object.fromEntries( Object.keys(dietaryOptions).map((option) => [ option, - dietaryRestrictions.includes(dietaryOptions[option]), + dietaryPreferences.includes(dietaryOptions[option]), ]) ) ); - }, [dietaryRestrictions]) + }, [dietaryPreferences]) const updateSetting = (key: string, value: boolean) => { if (value == true) { - setDietaryRestrictions((prevRestrictions) => [ + setDietaryPreferences((prevRestrictions) => [ ...prevRestrictions, dietaryOptions[key], ]); handleAddDietaryPreference(key); } else { - setDietaryRestrictions((prevRestrictions) => + setDietaryPreferences((prevRestrictions) => prevRestrictions.filter((item) => item !== dietaryOptions[key]) ); handleRemoveDietaryPreference(key); @@ -148,7 +148,7 @@ export default function SettingsScreen() { account: [ { label: "View Friends", - onPress: () => router.push("/(tabs)/profile/followers"), + onPress: () => router.push("/(tabs)/profile/friends"), showChevron: true, }, ], diff --git a/frontend/types/follower.ts b/frontend/types/follower.ts index bfeabd5..1aa7aaf 100644 --- a/frontend/types/follower.ts +++ b/frontend/types/follower.ts @@ -1,4 +1,4 @@ -export type TFollower = { +export type TFriend = { id: string; name: string; username: string; From 9e3e3ee4ee46bd11df7d5f822859f269c9948f1d Mon Sep 17 00:00:00 2001 From: Sierra Welsch Date: Sat, 29 Mar 2025 19:35:18 -0400 Subject: [PATCH 08/10] loaded friends --- frontend/app/(tabs)/profile/friends.tsx | 4 ++-- frontend/app/(tabs)/profile/settings.tsx | 6 +++++- frontend/components/profile/ProfileMetrics.tsx | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/app/(tabs)/profile/friends.tsx b/frontend/app/(tabs)/profile/friends.tsx index f2aea07..6f530a5 100644 --- a/frontend/app/(tabs)/profile/friends.tsx +++ b/frontend/app/(tabs)/profile/friends.tsx @@ -103,7 +103,7 @@ export default function FriendsScreen() { if (friends.length > 0 && !hasMoreData && !isLoadingMore) { return ( - No more followers to load. + No more friends to load. ); } @@ -136,7 +136,7 @@ export default function FriendsScreen() { showsVerticalScrollIndicator={false} ListEmptyComponent={ - No followers found. + No friends found. } ListHeaderComponent={ diff --git a/frontend/app/(tabs)/profile/settings.tsx b/frontend/app/(tabs)/profile/settings.tsx index 3b129bf..7ef912a 100644 --- a/frontend/app/(tabs)/profile/settings.tsx +++ b/frontend/app/(tabs)/profile/settings.tsx @@ -17,6 +17,9 @@ export default function SettingsScreen() { const router = useRouter(); const { email, userId } = useAuthStore(); + // const userIdStr = JSON.stringify(userId); + console.log(userId); + const { logout } = useAuthStore(); const dietaryOptions: Record = { @@ -148,7 +151,8 @@ export default function SettingsScreen() { account: [ { label: "View Friends", - onPress: () => router.push("/(tabs)/profile/friends"), + //confused... + onPress: () => router.push(`/profile/friends?userId=${userId}`), showChevron: true, }, ], diff --git a/frontend/components/profile/ProfileMetrics.tsx b/frontend/components/profile/ProfileMetrics.tsx index a123078..cb39f3e 100644 --- a/frontend/components/profile/ProfileMetrics.tsx +++ b/frontend/components/profile/ProfileMetrics.tsx @@ -20,7 +20,7 @@ const ProfileMetrics = (props: ProfileMetricProps) => { - { router.push("/(tabs)/profile/followers") }}> + { router.push("/(tabs)/profile/friends") }}> {props.numFriends} friends From 1b93175bdbbc70657a876646557363d4a0352afa Mon Sep 17 00:00:00 2001 From: Sierra Welsch Date: Sat, 29 Mar 2025 19:40:29 -0400 Subject: [PATCH 09/10] linted and fixed formatting: --- backend/internal/handlers/users/types.go | 2 +- frontend/app/(tabs)/profile/friends.tsx | 6 +- frontend/app/(tabs)/profile/settings.tsx | 124 +++++++++--------- .../components/profile/ProfileMetrics.tsx | 5 +- 4 files changed, 70 insertions(+), 67 deletions(-) diff --git a/backend/internal/handlers/users/types.go b/backend/internal/handlers/users/types.go index abcc1f5..76a6443 100644 --- a/backend/internal/handlers/users/types.go +++ b/backend/internal/handlers/users/types.go @@ -15,7 +15,7 @@ type User struct { FollowersCount int `bson:"followersCount"` ProfilePicture string `bson:"profile_picture,omitempty"` Name string `bson:"name,omitempty"` - Preferences []string `bson:"preferences,omitempty"` + Preferences []string `bson:"preferences,omitempty"` } type UserResponse struct { diff --git a/frontend/app/(tabs)/profile/friends.tsx b/frontend/app/(tabs)/profile/friends.tsx index 6f530a5..8550f83 100644 --- a/frontend/app/(tabs)/profile/friends.tsx +++ b/frontend/app/(tabs)/profile/friends.tsx @@ -28,7 +28,9 @@ export default function FriendsScreen() { } try { - const response = await fetch(`https://externally-exotic-orca.ngrok-free.app/api/v1/user/${userId}/following`); + const response = await fetch( + `https://externally-exotic-orca.ngrok-free.app/api/v1/user/${userId}/following`, + ); const data = await response.json(); if (data && data.length > 0) { @@ -41,7 +43,7 @@ export default function FriendsScreen() { setFriends(formattedUsers); - // dont think this stuff is needed anymore but left it in case + // dont think this stuff is needed anymore but left it in case // if (isRefresh) { // setFollowers(formattedUsers); // setPage(2); diff --git a/frontend/app/(tabs)/profile/settings.tsx b/frontend/app/(tabs)/profile/settings.tsx index 7ef912a..da62145 100644 --- a/frontend/app/(tabs)/profile/settings.tsx +++ b/frontend/app/(tabs)/profile/settings.tsx @@ -10,7 +10,7 @@ import SettingsMenuItem from "@/components/profile/settings/SettingsMenuItem"; import { TSettingsData } from "@/types/settingsData"; import useAuthStore from "@/auth/store"; import { Button } from "@/components/Button"; -import axios from 'axios'; +import axios from "axios"; export default function SettingsScreen() { const insets = useSafeAreaInsets(); @@ -23,20 +23,20 @@ export default function SettingsScreen() { const { logout } = useAuthStore(); const dietaryOptions: Record = { - "vegetarian": "Vegetarian", - "vegan": "Vegan", - "nutFree": "Nut-free", - "shellfishAllergy": "Shellfish Allergy", - "glutenFree": "Gluten-free", - "dairyFree": "Dairy-free", - "kosher": "Kosher", - "halal": "Halal", - "pescatarian": "Pescatarian", - "keto": "Keto", - "diabetic": "Diabetic", - "soyFree": "Soy-free", - "porkFree": "Pork-free", - "beefFree": "Beef-free", + vegetarian: "Vegetarian", + vegan: "Vegan", + nutFree: "Nut-free", + shellfishAllergy: "Shellfish Allergy", + glutenFree: "Gluten-free", + dairyFree: "Dairy-free", + kosher: "Kosher", + halal: "Halal", + pescatarian: "Pescatarian", + keto: "Keto", + diabetic: "Diabetic", + soyFree: "Soy-free", + porkFree: "Pork-free", + beefFree: "Beef-free", }; const [dietaryPreferences, setDietaryPreferences] = useState([]); @@ -48,80 +48,73 @@ export default function SettingsScreen() { useEffect(() => { console.log("fetched restrictions!"); const fetchDietaryRestrictions = async () => { - try { - const response = await axios.get( - `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences` - ); - setDietaryPreferences(response.data); - } - catch (err) { - console.log('Failed to fetch dietary restrictions'); - console.error(err); - } + try { + const response = await axios.get( + `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences`, + ); + setDietaryPreferences(response.data); + } catch (err) { + console.log("Failed to fetch dietary restrictions"); + console.error(err); + } }; - + fetchDietaryRestrictions(); console.log(dietaryPreferences); - }, [userId]); + }, [userId]); - useEffect(() => { + useEffect(() => { console.log("reloaded!"); console.log(dietaryPreferences); setSettings( Object.fromEntries( - Object.keys(dietaryOptions).map((option) => [ - option, - dietaryPreferences.includes(dietaryOptions[option]), - ]) - ) + Object.keys(dietaryOptions).map((option) => [ + option, + dietaryPreferences.includes(dietaryOptions[option]), + ]), + ), ); - }, [dietaryPreferences]) + }, [dietaryPreferences]); const updateSetting = (key: string, value: boolean) => { if (value == true) { - setDietaryPreferences((prevRestrictions) => [ - ...prevRestrictions, - dietaryOptions[key], - ]); + setDietaryPreferences((prevRestrictions) => [...prevRestrictions, dietaryOptions[key]]); handleAddDietaryPreference(key); } else { setDietaryPreferences((prevRestrictions) => - prevRestrictions.filter((item) => item !== dietaryOptions[key]) + prevRestrictions.filter((item) => item !== dietaryOptions[key]), ); handleRemoveDietaryPreference(key); } }; - const handleAddDietaryPreference = async (preference:string) => { - try { + const handleAddDietaryPreference = async (preference: string) => { + try { const response = await axios.post( - `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences?preference=${dietaryOptions[preference]}` + `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences?preference=${dietaryOptions[preference]}`, ); if (response.status === 201) { - console.log('Preference added successfully:', preference); + console.log("Preference added successfully:", preference); } - } - catch (err) { - console.log('Failed to add dietary preference'); - console.error(err); - } + } catch (err) { + console.log("Failed to add dietary preference"); + console.error(err); + } }; - - const handleRemoveDietaryPreference = async (preference:string) => { + const handleRemoveDietaryPreference = async (preference: string) => { try { - const response = await axios.delete( - `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences?preference=${dietaryOptions[preference]}` - ); - if (response.status === 201) { - console.log('Preference deleted successfully:', preference); - } - } - catch (err) { - console.log('Failed to delete dietary preference'); - console.error(err); + const response = await axios.delete( + `https://externally-exotic-orca.ngrok-free.app/api/v1/settings/${userId}/dietaryPreferences?preference=${dietaryOptions[preference]}`, + ); + if (response.status === 201) { + console.log("Preference deleted successfully:", preference); + } + } catch (err) { + console.log("Failed to delete dietary preference"); + console.error(err); } - }; + }; const settingsData: TSettingsData = { credentials: [ @@ -165,7 +158,7 @@ export default function SettingsScreen() { const handleLogOut = () => { logout(); router.replace("/(onboarding)"); - } + }; return ( @@ -232,7 +225,12 @@ export default function SettingsScreen() { ))} - + diff --git a/frontend/components/profile/ProfileMetrics.tsx b/frontend/components/profile/ProfileMetrics.tsx index cb39f3e..95fae35 100644 --- a/frontend/components/profile/ProfileMetrics.tsx +++ b/frontend/components/profile/ProfileMetrics.tsx @@ -20,7 +20,10 @@ const ProfileMetrics = (props: ProfileMetricProps) => { - { router.push("/(tabs)/profile/friends") }}> + { + router.push("/(tabs)/profile/friends"); + }}> {props.numFriends} friends From 981208f21f6def674c51d4af8bee67bcb73860f3 Mon Sep 17 00:00:00 2001 From: Sierra Welsch Date: Sat, 5 Apr 2025 14:35:45 -0400 Subject: [PATCH 10/10] connected user's friends and reviews to the backend --- frontend/app/(tabs)/profile/profile.tsx | 34 ++++-- .../app/{friend.tsx => friend/[userId].tsx} | 111 +++++++++++++----- .../profile/followers/FollowerItem.tsx | 6 +- frontend/components/review/ReviewPreview.tsx | 15 ++- frontend/context/user-context.tsx | 2 +- frontend/types/review.ts | 14 +++ 6 files changed, 136 insertions(+), 46 deletions(-) rename frontend/app/{friend.tsx => friend/[userId].tsx} (54%) create mode 100644 frontend/types/review.ts diff --git a/frontend/app/(tabs)/profile/profile.tsx b/frontend/app/(tabs)/profile/profile.tsx index 2c8b829..6431a96 100644 --- a/frontend/app/(tabs)/profile/profile.tsx +++ b/frontend/app/(tabs)/profile/profile.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useUser } from "@/context/user-context"; import { ThemedView } from "@/components/themed/ThemedView"; import { ActivityIndicator, Dimensions, ScrollView, StatusBar, StyleSheet, TouchableOpacity } from "react-native"; @@ -13,12 +13,14 @@ import { router } from "expo-router"; import EditProfileSheet from "@/components/profile/EditProfileSheet"; import ReviewPreview from "@/components/review/ReviewPreview"; import { SearchBoxFilter } from "@/components/SearchBoxFilter"; +import type { Review } from '@/types/review'; const { width } = Dimensions.get("window"); const ProfileScreen = () => { const { user, isLoading, error, fetchUserProfile } = useUser(); const [searchText, setSearchText] = React.useState(""); + const [userReviews, setUserReviews] = useState([]); const editProfileRef = useRef<{ open: () => void; close: () => void }>(null); @@ -27,6 +29,20 @@ const ProfileScreen = () => { console.log("User data not available, fetching..."); fetchUserProfile().then(() => {}); } + const fetchReviews = async () => { + if (!user?.id) return ; + + try { + const reviewsRes = await fetch( + `https://externally-exotic-orca.ngrok-free.app/api/v1/review/user/${user.id}`); + const reviewData = await reviewsRes.json(); + console.log(reviewData); + setUserReviews(reviewData); + } catch (err) { + console.error("Failed to fetch user by ID", err); + } + }; + fetchReviews(); }, [user, isLoading]); if (isLoading) { @@ -76,12 +92,16 @@ const ProfileScreen = () => { value={searchText} onChangeText={(text) => setSearchText(text)} /> - + {userReviews.map((review) => ( + + + ))} diff --git a/frontend/app/friend.tsx b/frontend/app/friend/[userId].tsx similarity index 54% rename from frontend/app/friend.tsx rename to frontend/app/friend/[userId].tsx index e43f10e..e9c0d5b 100644 --- a/frontend/app/friend.tsx +++ b/frontend/app/friend/[userId].tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useUser } from "@/context/user-context"; import { ThemedView } from "@/components/themed/ThemedView"; import { ActivityIndicator, Dimensions, ScrollView, StatusBar, StyleSheet, TouchableOpacity } from "react-native"; @@ -12,21 +12,68 @@ import ReviewPreview from "@/components/review/ReviewPreview"; import { SearchBoxFilter } from "@/components/SearchBoxFilter"; import EditFriendSheet from "@/components/profile/followers/FriendProfileOptions"; import { FollowButton } from "@/components/profile/followers/FollowButton"; +import { useLocalSearchParams } from 'expo-router'; +import type { User } from '@/context/user-context'; +import type { Review } from '@/types/review'; const { width } = Dimensions.get("window"); const ProfileScreen = () => { - const { user, isLoading, error, fetchUserProfile } = useUser(); - const [searchText, setSearchText] = React.useState(""); - + console.log("hi"); + const {userId} = useLocalSearchParams(); + console.log(userId); + const [searchText, setSearchText] = useState(""); const editFriend = useRef<{ open: () => void; close: () => void }>(null); - + + const [user, setUser] = useState({ + id: "", + email: "", + name: "", + profile_picture: "", + username: "", + followersCount: 0, + followingCount: 0, + preferences: [] + }); //initialziing the user to an empty user + const [userReviews, setUserReviews] = useState([]); + const [isLoading, setLoading] = useState(true); + useEffect(() => { - if (!user && !isLoading) { - console.log("User data not available, fetching..."); - fetchUserProfile().then(() => {}); + const fetchUserAndReviews = async () => { + if (!userId) return ; + + setLoading(true); + try { + const userRes = await fetch( + `https://externally-exotic-orca.ngrok-free.app/api/v1/user/${userId}`); + const userData = await userRes.json(); + console.log(userData); + const newUser: User = { + id: userData.id, + email: userData.email, + name: userData.name, + profile_picture: userData.profile_picture, + username: userData.username, + followersCount: userData.followersCount, + followingCount: userData.followingCount, + preferences: userData.preferences, + }; + setUser(newUser); + + const reviewsRes = await fetch( + `https://externally-exotic-orca.ngrok-free.app/api/v1/review/user/${userId}`); + const reviewData = await reviewsRes.json(); + console.log(reviewData); + setUserReviews(reviewData); + } catch (err) { + console.error("Failed to fetch user by ID", err); + } finally { + setLoading(false); } - }, [user, isLoading]); + }; + + fetchUserAndReviews(); + }, [userId]); if (isLoading) { return ( @@ -36,14 +83,14 @@ const ProfileScreen = () => { ); } - - if (!user || error) { - return ( - - An error occurred. - - ); - } + // for now!!! + // if (!user || error) { + // return ( + // + // An error occurred. + // + // ); + // } return ( @@ -58,35 +105,39 @@ const ProfileScreen = () => { style={styles.ellipseButton} onPress={() => { console.log("Button Pressed!"); - console.log("editFriend Ref:", editFriend.current); - editFriend.current?.open(); }}> - - - + {/* inserted a default profile picture because profile_picture is string | undefined */} + + + - Ben's Food Journal + {user.name}'s Food Journal console.log("submit")} value={searchText} onChangeText={(text) => setSearchText(text)} /> - + {userReviews.map((review) => ( + + + ))} diff --git a/frontend/components/profile/followers/FollowerItem.tsx b/frontend/components/profile/followers/FollowerItem.tsx index 404ed77..f8764be 100644 --- a/frontend/components/profile/followers/FollowerItem.tsx +++ b/frontend/components/profile/followers/FollowerItem.tsx @@ -1,8 +1,10 @@ import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native"; import React from "react"; import { FollowButton } from "./FollowButton"; +import { router } from "expo-router"; type Follower = { + id: string; name: string; username: string; avatar: string; @@ -13,12 +15,12 @@ type FollowerItemProps = { }; const FollowerItem = ({ follower }: FollowerItemProps) => { + console.log(follower.id); return ( - + router.push(`/friend/${follower.id}`)}> {follower.username} diff --git a/frontend/components/review/ReviewPreview.tsx b/frontend/components/review/ReviewPreview.tsx index 2ed188a..ae1883a 100644 --- a/frontend/components/review/ReviewPreview.tsx +++ b/frontend/components/review/ReviewPreview.tsx @@ -6,16 +6,18 @@ import { Colors } from "@/constants/Colors"; import Entypo from "@expo/vector-icons/build/Entypo"; import { router } from "expo-router"; import { ReviewComponentStarIcon } from "../icons/Icons"; +import type { User } from '@/context/user-context'; -type Props = { +type ReviewProps = { plateName: string; restaurantName: string; tags: string[]; rating: number; content: string; + user: User; }; -const ReviewPreview = ({ plateName, restaurantName, tags, rating, content }: Props) => { +const ReviewPreview = ({ plateName, restaurantName, tags, rating, content, user }: ReviewProps) => { return ( } - icon={"https://avatars.githubusercontent.com/u/66958528?v=4"} - onPress={() => router.push(`/friend`)} + // default profile picture + icon={user?.profile_picture || "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png"} + onPress={() => router.push(`/friend/${user?.id || "67e300bc43b432515e2dd8ba"}`)} /> diff --git a/frontend/context/user-context.tsx b/frontend/context/user-context.tsx index 519e4ba..4a66f51 100644 --- a/frontend/context/user-context.tsx +++ b/frontend/context/user-context.tsx @@ -12,7 +12,7 @@ export interface User { username: string; followersCount: number; followingCount: number; - // preferences: string[]; + preferences: string[]; } interface UserContextType { diff --git a/frontend/types/review.ts b/frontend/types/review.ts new file mode 100644 index 0000000..42ed4c0 --- /dev/null +++ b/frontend/types/review.ts @@ -0,0 +1,14 @@ +export type Review = { + id: string; + plateName: string; + restaurantName: string; + tags: string[]; + rating: { + overall: number, + portion: number, + return: boolean, + taste: number, + value: number, + }, + content: string; +}; \ No newline at end of file