diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 4634f1c..f762942 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -35,10 +35,6 @@ export const GET_USER = gql(` email birth_date gender - actualList { - _id - status - } } } `); @@ -82,7 +78,7 @@ export const CREATE_LIST = gql(` `); export default function HomeScreen() { - const { id: userId, setId } = useAuth(); + const { id: userId, setId, currentListId, setCurrentListId } = useAuth(); const [upsertProduct] = useMutation(ADD_HOMESCREEN_PRODUCTS); const [createList] = useMutation(CREATE_LIST); const router = useRouter(); @@ -109,10 +105,7 @@ export default function HomeScreen() { return; } - let listId = - userData.user?.actualList?.status === Status.Active - ? userData.user?.actualList._id - : undefined; + let listId = currentListId; if (!listId) { const result = await createList({ @@ -128,13 +121,14 @@ export default function HomeScreen() { } listId = result.data.createList._id; + setCurrentListId(listId).catch(toastOnError); } await upsertProduct({ variables: { models: EANS.map((ean) => ({ id_composite: { - listId: listId, + listId: listId ?? "", productEan: ean, }, quantity: 1, @@ -254,7 +248,7 @@ export default function HomeScreen() { }} > { await setId(null); router.push("/login"); @@ -262,7 +256,7 @@ export default function HomeScreen() { onProfilePress={() => router.push({ pathname: "/profile", - params: { user: JSON.stringify(userData?.user || {}) }, + params: { user: JSON.stringify(userData?.user ?? {}) }, }) } onHomePress={() => router.push("/(tabs)")} diff --git a/app/(tabs)/navigation.tsx b/app/(tabs)/navigation.tsx index 5b9874e..6098a05 100644 --- a/app/(tabs)/navigation.tsx +++ b/app/(tabs)/navigation.tsx @@ -21,14 +21,12 @@ import Path from "@/assets/images/path.png"; import { useToast } from "@/hooks/useToast"; const GET_FILTERED_SUPERMARKETS = gql(` - query GetFilteredSupermarkets($userId: String!, $coordinateFilter: CoordinateFilter!) { - user(id: $userId) { - actualList { - products { - quantity - supermarketInfo { - price - } + query GetFilteredSupermarkets($listId: String!, $coordinateFilter: CoordinateFilter!) { + list(id: $listId) { + products { + quantity + supermarketInfo { + price } } } @@ -49,15 +47,15 @@ const GET_FILTERED_SUPERMARKETS = gql(` `); const NavigationScreen = () => { - const { id } = useAuth(); - const { toast } = useToast(); + const { currentListId } = useAuth(); + const { toast, toastOnError } = useToast(); const { setState: setBackground } = useBackground(); const iconBackgroundColor = useThemeColor("text"); const [userLocation, setUserLocation] = useState(null); const { loading, error, data } = useQuery(GET_FILTERED_SUPERMARKETS, { variables: { - userId: id!, + listId: currentListId ?? "", coordinateFilter: { op: Operator.Le, value: { @@ -69,8 +67,7 @@ const NavigationScreen = () => { }, }, }, - skip: !userLocation, - onError: (err) => console.error("Query error:", err), + skip: !userLocation || !currentListId, }); const isLoading = loading || !userLocation; @@ -102,14 +99,14 @@ const NavigationScreen = () => { }); }; - getLocation(); - }, [toast]); + getLocation().catch(toastOnError); + }, [toast, toastOnError]); const { totalQuantity, totalPrice } = useMemo(() => { let quantity = 0; let price = 0; - data?.user?.actualList?.products?.forEach( + data?.list?.products?.forEach( (item: { quantity: number; supermarketInfo: { price: number } }) => { quantity += item.quantity; price += (item.supermarketInfo?.price || 0) * item.quantity; diff --git a/app/(tabs)/shopping-list.tsx b/app/(tabs)/shopping-list.tsx index fe7d80e..e1a9c18 100644 --- a/app/(tabs)/shopping-list.tsx +++ b/app/(tabs)/shopping-list.tsx @@ -1,94 +1,62 @@ import { useCallback, useMemo, useRef, useState } from "react"; -import { TouchableOpacity, useColorScheme, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { TouchableOpacity, View } from "react-native"; import { + EllipsisVertical, Mic, NotebookPen, - Pencil, Search, - Sparkles, Trash2, + Users, } from "lucide-react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; import ListTitle from "@/components/ui/list/listTitle"; import List from "@/components/ui/list/list"; import { useFocusEffect, useRouter } from "expo-router"; import { ThemedText } from "@/components/ThemedText"; import Header from "@/components/Header"; import themeConfig from "@/tailwind.config"; -import { useBackground } from "@/hooks/useBackground"; import { useThemeColor } from "@/hooks/useThemeColor"; -import { useLazyQuery, useMutation, useQuery } from "@apollo/client"; -import { ItemType, Status } from "@/graphql/graphql"; -import { Button } from "@/components/Button"; -import NoShoppingCard from "@/assets/images/lucide/shopping-cart.svg"; -import { Skeleton } from "moti/skeleton"; +import { useMutation, useQuery } from "@apollo/client"; +import { Status } from "@/graphql/graphql"; import { gql } from "@/graphql/gql"; import { useAuth } from "@/hooks/useAuth"; -import { ThemedIcon } from "@/components/ThemedIcon"; import { useToast } from "@/hooks/useToast"; import { LegendList } from "@legendapp/list"; import { VoiceBottomSheet } from "@/components/VoiceBottomSheet"; import { BottomSheetModal } from "@gorhom/bottom-sheet"; -import LottieView from "lottie-react-native"; -import SparkleIcon from "@/assets/lottie/sparkle.json"; import { RawBottomSheet } from "@/components/RawBottomSheet"; +import ShoppingListSkeleton from "@/components/skeletons/ShoppingListSkeleton"; +import RecommendationButton from "@/components/RecommendationButton"; +import ErrorComponent from "@/components/ErrorComponent"; +import NoListAvailable from "@/components/NoListAvailable"; +import AvailableListBottomSheet from "@/components/AvailableListBottomSheet"; +import MoreBottomSheet from "@/components/MoreBottomSheet"; +import CollaboratorsBottomSheet from "@/components/CollaboratorsBottomSheet"; export const GET_PRODUCTS_LIST = gql(` - query CurrentSupermarketListWithProducts($userId: String!) { - user(id: $userId) { - actualList { - _id - raw - status - products { - product { - ean - name - genericName - quantity - images - categoryName - brandName - blurhash - } - supermarketInfo { - price - id - } - quantity - type - } - } - } - } -`); - -export const CREATE_LIST = gql(` - mutation CreateList($userId: String!) { - createList(userId: $userId) { + query CurrentSupermarketListWithProducts($listId: String!) { + list(id: $listId) { _id + raw + status + owner_id products { product { + ean name + genericName + quantity + images + categoryName + brandName + blurhash } - } - } - } -`); - -const GET_RECOMMENDATIONS = gql(` - query GetRecommendations($userId: String!) { - rawListByUserAndAi(userId: $userId, k: 5) { - ean - name - genericName - quantity - images - brandName - blurhash - supermarkets { - price - id + supermarketInfo { + price + id + } + quantity + type } } } @@ -112,44 +80,31 @@ const DELETE_PRODUCT_FROM_LIST = gql(` `); const ListScreen = () => { - const { id } = useAuth(); + const { currentListId } = useAuth(); const insets = useSafeAreaInsets(); - const { setState: setBackground } = useBackground(); const router = useRouter(); const itemBackgroundColor = useThemeColor("background"); const iconBackgroundColor = useThemeColor("text"); - const theme = useColorScheme(); - const { toast, toastOnError } = useToast(); + const { toastOnError } = useToast(); + + const availableListBottomSheetRef = useRef(null); + const collaboratorsBottomSheetRef = useRef(null); + const moreBottomSheetRef = useRef(null); const rawBottomSheetRef = useRef(null); const voiceBottomSheetRef = useRef(null); - const lottieRef = useRef(null); const { error, data, loading, refetch } = useQuery(GET_PRODUCTS_LIST, { variables: { - userId: id!, - }, - }); - - const [createList] = useMutation(CREATE_LIST, { - refetchQueries: [ - { - query: GET_PRODUCTS_LIST, - variables: { userId: id }, - }, - ], - }); - - const [getRecommendations] = useLazyQuery(GET_RECOMMENDATIONS, { - variables: { - userId: id!, + listId: currentListId ?? "", }, + skip: !currentListId, }); const [upsertProductFromList] = useMutation(UPSERT_PRODUCT_FROM_LIST, { refetchQueries: [ { query: GET_PRODUCTS_LIST, - variables: { userId: id }, + variables: { listId: currentListId! }, }, ], }); @@ -158,29 +113,23 @@ const ListScreen = () => { refetchQueries: [ { query: GET_PRODUCTS_LIST, - variables: { userId: id }, + variables: { listId: currentListId! }, }, ], onError: toastOnError, }); - useFocusEffect( - useCallback(() => { - refetch().catch(toastOnError); - }, [refetch, toastOnError]), - ); + const refetchList = useCallback(() => { + if (!currentListId) return; + refetch().catch(toastOnError); + }, [refetch, toastOnError, currentListId]); - const handleCreateList = async () => { - await createList({ - variables: { userId: id! }, - onError: toastOnError, - }); - }; + useFocusEffect(() => refetchList()); const groupedData = useMemo(() => { - if (!data?.user?.actualList?.products) return {}; + if (!data?.list?.products) return {}; - return data.user.actualList.products.reduce( + return data.list.products.reduce( (acc, el) => { const category = el.product.categoryName; if (!category) return acc; @@ -188,7 +137,7 @@ const ListScreen = () => { acc[category].push(el); return acc; }, - {} as Record, + {} as Record, ); }, [data]); @@ -208,7 +157,7 @@ const ListScreen = () => { item: { ean: string; listQuantity: number; supermarketId: number }, type: "inc" | "dec", ) => { - if (!data?.user?.actualList?._id) return; + if (!data?.list?._id) return; const newQuantity = type === "inc" @@ -221,7 +170,7 @@ const ListScreen = () => { models: [ { id_composite: { - listId: data.user.actualList._id, + listId: data.list._id, productEan: item.ean, }, quantity: newQuantity, @@ -245,130 +194,14 @@ const ListScreen = () => { }, }); }, - [data?.user?.actualList?._id, upsertProductFromList, toastOnError], + [data?.list?._id, upsertProductFromList, toastOnError], ); - const handleRecommendationPress = useCallback(async () => { - if (!data?.user?.actualList?._id) return; - - setBackground(true); - lottieRef.current?.play(); + if (loading) return ; + if (error) return ; - const result = await getRecommendations(); - if (result.error) { - toast({ - title: "Oops! Something went wrong", - text: result.error.message, - }); - } - if (!result.data?.rawListByUserAndAi) { - toast({ - title: "Oops! Something went wrong", - text: "No recommendations found", - }); - } - - await upsertProductFromList({ - onError: toastOnError, - variables: { - models: - result.data?.rawListByUserAndAi.map((product) => ({ - id_composite: { - listId: data?.user?.actualList?._id ?? "", - productEan: product.ean, - }, - quantity: 1, - supermarket_id: product?.supermarkets?.reduce( - (min, current) => (current.price < min.price ? current : min), - product?.supermarkets?.[0], - ).id, - type: ItemType.Ai, - })) ?? [], - }, - }); - setBackground(false); - lottieRef.current?.pause(); - lottieRef.current?.reset(); - }, [ - data?.user?.actualList?._id, - getRecommendations, - setBackground, - toast, - toastOnError, - upsertProductFromList, - ]); - - if (loading) { - return ( - - - {[1, 2, 3, 4, 5].map((category) => ( - - - - - {[1, 4, 8].map((product) => ( - - ))} - - - ))} - - - ); - } - - if (error) - return ( - - Error: {error.message} - - ); - - if (!data?.user?.actualList?._id) { - return ( - - - Groceries List - - - - - - - It looks like your {"\n"} groceries list is empty. - - -