diff --git a/assets/recipe_banner.png b/assets/recipe_banner.png new file mode 100644 index 0000000..daa1c49 Binary files /dev/null and b/assets/recipe_banner.png differ diff --git a/components/AcctRecipeBar.jsx b/components/AcctRecipeBar.jsx index 53e10ff..5fe12a6 100644 --- a/components/AcctRecipeBar.jsx +++ b/components/AcctRecipeBar.jsx @@ -1,8 +1,13 @@ import React, { useEffect, useState } from 'react'; -import { StyleSheet, View, Image, ActivityIndicator } from 'react-native'; +import { + StyleSheet, + View, + Image, + ActivityIndicator, + ScrollView, +} from 'react-native'; import { MaterialCommunityIcons, FontAwesome } from '@expo/vector-icons'; import { Title, Caption, HeaderTitle, BodySmall } from './Typography'; -import Button from '../components/Button'; const RecipeSummary = ({ image, name, savedOn }) => { const placeholderImage = require('../assets/generic_recipe.png'); @@ -73,10 +78,10 @@ const AcctRecipeBar = (props) => { } const savedRecipesResponse = await response.json(); setSavedRecipes(savedRecipesResponse); - setLoading(false); // Cambiar estado a false cuando la carga se complete + setLoading(false); } catch (error) { console.log('Error fetching saved recipes: ', error); - setLoading(false); // Cambiar estado a false en caso de error + setLoading(false); } }; fetchSavedRecipes(); @@ -103,12 +108,9 @@ const AcctRecipeBar = (props) => { Saved Recipes {savedRecipes.map((recipe) => ( - + - + ))} ); diff --git a/components/CategoryButton.jsx b/components/CategoryButton.jsx index cdd035b..8a90cbe 100644 --- a/components/CategoryButton.jsx +++ b/components/CategoryButton.jsx @@ -1,10 +1,11 @@ import { StyleSheet, TouchableOpacity } from 'react-native'; -import { ButtonText } from './Typography'; +import { ButtonLarge } from './Typography'; +import { MaterialIcons } from '@expo/vector-icons'; -const CategoryButton = ({ title, onPress }) => { +const CategoryButton = ({ title, icon, onPress }) => { return ( @@ -13,14 +14,15 @@ const CategoryButton = ({ title, onPress }) => { )) } > - {title || 'Button'} + + {title || 'Button'} ); }; export default CategoryButton; -const stlyes = StyleSheet.create({ +const styes = StyleSheet.create({ categoryButton: { alignItems: 'center', backgroundColor: '#F7FCF8', @@ -29,6 +31,7 @@ const stlyes = StyleSheet.create({ borderWidth: 1, justifyContent: 'center', paddingHorizontal: 60, - paddingVertical: 40, + paddingVertical: 30, + gap: 5, }, }); diff --git a/components/DietFilter.jsx b/components/DietFilter.jsx index 65e9cfb..6f6ed45 100644 --- a/components/DietFilter.jsx +++ b/components/DietFilter.jsx @@ -1,5 +1,10 @@ import { useState } from 'react'; -import { FlatList, StyleSheet, TouchableOpacity, View } from 'react-native'; +import { + FlatList, + Pressable, + StyleSheet, + View, +} from 'react-native'; import { SPOONACULAR_API_KEY } from '@env'; import { BodySmall } from './Typography'; @@ -72,8 +77,8 @@ const DietFilter = () => { }); }; const renderItem = ({ item }) => ( - - + { > {item.category} - + ); return ( - <> + - + ); }; @@ -105,19 +110,21 @@ const styles = StyleSheet.create({ categoryContainer: { marginRight: 16, marginBottom: 8, + marginTop: 8, }, categoryButton: { borderRadius: 4, - backgroundColor: '#d2d2d2', + backgroundColor: '#F2F2F2', paddingVertical: 4, - paddingHorizontal: 2, + paddingHorizontal: 10, }, categoryText: { - color: '#121212', + color: '#000', + fontWeight: 'bold', }, titleText: { fontSize: 20, - fontWeight: 'bold', + fontWeight: '600', marginTop: 15, }, selectedCategory: { diff --git a/components/FavoriteRecipes.jsx b/components/FavoriteRecipes.jsx index 5c624e8..e33ff1a 100644 --- a/components/FavoriteRecipes.jsx +++ b/components/FavoriteRecipes.jsx @@ -1,9 +1,10 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Pressable, StyleSheet, Text, View } from 'react-native'; +import { Pressable, StyleSheet, View } from 'react-native'; import RecipeCard from './RecipeCard'; import { FlatList } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { USER_API_IP_URL } from '@env'; +import { Caption } from './Typography'; const FavoriteRecipesList = ({ title, scrollEnabled, numberOfRecipes }) => { const [recipes, setRecipes] = useState([]); @@ -47,7 +48,7 @@ const FavoriteRecipesList = ({ title, scrollEnabled, numberOfRecipes }) => { <> {recipes?.length === 0 ? ( - No Favorite Recipes + No Favorite Recipes ) : ( { return ( - {pageTitle} + {pageTitle} ); }; diff --git a/components/IngredientCard.jsx b/components/IngredientCard.jsx index 9f49ac3..54f78b3 100644 --- a/components/IngredientCard.jsx +++ b/components/IngredientCard.jsx @@ -1,6 +1,6 @@ -import { StyleSheet, Text, View, Image, Pressable } from 'react-native'; +import { StyleSheet, View, Image } from 'react-native'; import React from 'react'; -import { Body, ButtonLarge, HeaderTitle } from './Typography'; +import { ButtonLarge } from './Typography'; export default IngredientCard = ({ ingredient }) => { const { id, name, image } = ingredient; @@ -20,7 +20,7 @@ export default IngredientCard = ({ ingredient }) => { const styles = StyleSheet.create({ container: { flexDirection: 'column', - justifyContent: 'flex-start', + justifyContent: 'center', alignItems: 'center', borderColor: '#e7e7e7', borderRadius: 18, diff --git a/screens/AddIngredient.jsx b/screens/AddIngredient.jsx index 42bae47..8fc60a3 100644 --- a/screens/AddIngredient.jsx +++ b/screens/AddIngredient.jsx @@ -1,20 +1,24 @@ import React, { useContext, useState } from 'react'; -import { StyleSheet, View, TextInput, Alert } from 'react-native'; +import { StyleSheet, View, TextInput } from 'react-native'; import { Modal } from '../components/Modal'; import Button from '../components/Button'; import { AntDesign } from '@expo/vector-icons'; import { BodySmall } from '../components/Typography'; import { USER_API_IP_URL } from '@env'; +const defaultIngredientImage = + 'https://cdn-icons-png.freepik.com/512/6981/6981367.png'; + export const AddIngredientModal = ({ modalVisible, setModalVisible, onClose, + fetchIngredients, }) => { const { userId } = '1'; // fix when backend integrated const [showError, setShowError] = useState(''); const [productData, setProductData] = useState({ - productName: '', + name: '', quantity: 0, }); @@ -35,25 +39,31 @@ export const AddIngredientModal = ({ }; const handleSaveProduct = async () => { - if (!productData.productName || productData.quantity === 0) { + if (!productData.name || productData.quantity === 0) { setShowError('Please, fill in all fields'); return; } try { const response = await fetch( - `http://${USER_API_IP_URL}$:8000/api/user/1/ingredients/`, + `http://localhost:8000/api/users/1/ingredients/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(productData), + body: JSON.stringify({ + ...productData, + user: '1', + image: defaultIngredientImage, + }), } ); if (!response.ok) { setShowError('Failed to save ingredient'); } + fetchIngredients(); + setModalVisible(false); } catch (error) { setShowError('Error saving ingredient'); console.error('Error saving ingredient:', error); @@ -64,7 +74,7 @@ export const AddIngredientModal = ({ - setProductData({ ...productData, productName: text }) + setProductData({ ...productData, name: text }) } placeholderTextColor='gray' /> @@ -163,7 +173,7 @@ const styles = StyleSheet.create({ }, counterContainer: { alignSelf: 'center', - justifyContent: 'space-between', + justifyContent: 'space-evenly', alignItems: 'center', width: '80%', height: 60, diff --git a/screens/FavoriteHomeSection.jsx b/screens/FavoriteHomeSection.jsx new file mode 100644 index 0000000..b31a1db --- /dev/null +++ b/screens/FavoriteHomeSection.jsx @@ -0,0 +1,27 @@ +import { ScrollView } from 'react-native-gesture-handler'; +import DietFilter from '../components/DietFilter'; +import FavoriteRecipesList from '../components/FavoriteRecipes'; +import { StyleSheet, View } from 'react-native'; +import { Title } from '../components/Typography'; + +export const FavoriteHomeSection = (props) => { + return ( + + Favorite Recipes + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + favoriteRecipesContainer: {}, + + dietFilterContainer: { + marginBottom: 32, + }, +}); diff --git a/screens/Filter.jsx b/screens/Filter.jsx index 6459b96..1e5bd97 100644 --- a/screens/Filter.jsx +++ b/screens/Filter.jsx @@ -1,12 +1,12 @@ import { useState } from 'react'; -import { StyleSheet, TouchableOpacity } from 'react-native'; -import { Text, TextInput } from 'react-native'; +import { StyleSheet } from 'react-native'; +import { TextInput } from 'react-native'; import { View } from 'react-native'; import { Calendar } from 'react-native-calendars'; import { useNavigation } from '@react-navigation/native'; -import Header from '../components/Header'; import RadioButton from '../components/RadioButton'; import Button from '../components/Button'; +import { Title } from '../components/Typography'; export const FilterScreen = () => { const [ingredientText, setIngredientText] = useState(''); @@ -38,26 +38,15 @@ export const FilterScreen = () => { return ( <> - - navigation.navigate('Home')} - style={{ marginHorizontal: 10, marginRight: 150 }} - > - X - -
- - Ingredient + Ingredient - Meal Type + Meal Type {mealTypes.map((meal, index) => ( @@ -71,7 +60,7 @@ export const FilterScreen = () => { ))} - Planned Meal Day + Planned Meal Day handleDayPressed(day)} minDate={new Date().toISOString().split('T')[0]} @@ -97,9 +86,12 @@ const styles = StyleSheet.create({ alignItems: 'center', marginTop: 50, }, + filterTitles: { + marginBottom: 20, + }, mainContainer: { padding: 15, - backgroundColor: '#DEDEDE', + backgroundColor: '#fff', }, title: { fontSize: 30, @@ -107,12 +99,15 @@ const styles = StyleSheet.create({ marginBottom: 20, }, input: { - padding: 5, height: 40, - borderRadius: 5, - width: 250, - backgroundColor: '#fff', marginBottom: 10, + backgroundColor: '#fff', + paddingHorizontal: 10, + borderRadius: 5, + borderWidth: 1, + borderColor: '#ccc', + fontFamily: 'Gilroy-Medium', + marginBottom: 20, }, mealTypeContainer: { flexDirection: 'row', diff --git a/screens/Home.jsx b/screens/Home.jsx index 3c9cff4..ba34081 100644 --- a/screens/Home.jsx +++ b/screens/Home.jsx @@ -1,4 +1,5 @@ import { + Image, SafeAreaView, ScrollView, StyleSheet, @@ -6,15 +7,15 @@ import { View, } from 'react-native'; import Nav from '../components/Nav'; -import FavoriteRecipesList from '../components/FavoriteRecipes'; import CategoryButton from '../components/CategoryButton'; import Macro from '../components/Macro'; import { useNavigation } from '@react-navigation/native'; -import DietFilter from '../components/DietFilter'; import { Title } from '../components/Typography'; import { StatusBar } from 'react-native'; import { useEffect, useState } from 'react'; import { USER_API_IP_URL } from '@env'; +import { FontAwesome5 } from '@expo/vector-icons'; +import { FavoriteHomeSection } from './FavoriteHomeSection'; export const HomeScreen = () => { const navigation = useNavigation(); @@ -43,6 +44,35 @@ export const HomeScreen = () => { return ( + + + + + Welcome to Zest! + + + @@ -76,23 +106,15 @@ export const HomeScreen = () => { navigation.navigate('Ingredient')} title='Pantry' + icon='kitchen' /> navigation.navigate('Filter')} /> - - - Favorite Recipes - - - - - - - - + @@ -104,10 +126,17 @@ export const HomeScreen = () => { const styles = StyleSheet.create({ homeContainer: { paddingHorizontal: 16, - backgroundColor: '#f2f2f2', + backgroundColor: '#fff', paddingTop: 16, flex: 1, }, + columnContainer: { + backgroundColor: '#fff', + width: '100%', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-between', + }, macrosContainer: { backgroundColor: '#fff', flexDirection: 'row', @@ -120,10 +149,10 @@ const styles = StyleSheet.create({ mainButtonsContainer: { flexDirection: 'row', justifyContent: 'space-around', - marginVertical: 48, + // marginVertical: 48, }, favoriteRecipesContainer: { - marginBottom: 16, + // marginBottom: 16, }, favoriteRecipesTitle: { borderBottomWidth: 1, diff --git a/screens/Ingredients.jsx b/screens/Ingredients.jsx index 7dbc83f..33efc34 100644 --- a/screens/Ingredients.jsx +++ b/screens/Ingredients.jsx @@ -1,124 +1,53 @@ -import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native'; +import { + ActivityIndicator, + SafeAreaView, + ScrollView, + StyleSheet, + View, +} from 'react-native'; import IngredientCard from '../components/IngredientCard'; import Nav from '../components/Nav'; import Search from '../components/SearchBar'; -import { useContext, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Body } from '../components/Typography'; import Button from '../components/Button'; import { FontAwesome5 } from '@expo/vector-icons'; import { AddIngredientModal } from './AddIngredient'; import { USER_API_IP_URL } from '@env'; -const imageUrl = 'https://cdn-icons-png.freepik.com/512/6981/6981367.png'; export const IngredientScreen = () => { - const { userId } = '1'; // update later when backend working + const userId = '1'; // update later when backend working const [modalVisible, setModalVisible] = useState(false); const [search, setSearch] = useState(''); const [searchResults, setSearchResults] = useState([]); const [ingredients, setIngredients] = useState([]); + const [loading, setLoading] = useState(true); - const ingredientList = [ - { - id: 1, - name: 'Banana chips', - image: imageUrl, - quantity: '200g', - user: 123, - preference: 1, - }, - { - id: 2, - name: 'Lucuma', - image: imageUrl, - quantity: '100ml', - user: 456, - preference: 2, - }, - { - id: 3, - name: 'Apple', - image: imageUrl, - quantity: '1', - user: 789, - preference: 1, - }, - { - id: 4, - name: 'Orange', - image: imageUrl, - quantity: '2', - user: 123, - preference: 3, - }, - { - id: 5, - name: 'Lemon', - image: imageUrl, - quantity: '1', - user: 456, - preference: 2, - }, - { - id: 6, - name: 'Strawberry', - image: imageUrl, - quantity: '150g', - user: 789, - preference: 1, - }, - { - id: 7, - name: 'Blueberry', - image: imageUrl, - quantity: '100g', - user: 123, - preference: 3, - }, - { - id: 8, - name: 'Grape', - image: imageUrl, - quantity: '300g', - user: 456, - preference: 2, - }, - { - id: 9, - name: 'Kiwi', - image: imageUrl, - quantity: '2', - user: 789, - preference: 1, - }, - { - id: 10, - name: 'Watermelon', - image: imageUrl, - quantity: '1', - user: 123, - preference: 3, - }, - ]; + const fetchIngredients = async () => { + try { + const response = await fetch( + `http://localhost:8000/api/users/${userId}/ingredients/` + ); + const data = await response.json(); + console.log(data); + setIngredients(data); + setSearchResults(data); + setLoading(false); + } catch (error) { + console.error('Error fetching ingredients:', error); + setLoading(false); + } + }; - // useEffect(() => { - // const fetchIngredients = async () => { - // setIngredients(ingredientList); - // // try { - // // const response = await fetch( `http://${USER_API_IP_URL}/user/${userId}/ingredients/`,); - // // const data = await response.json(); - // // setIngredients(data); - // // } catch (error) { - // // console.error('Error fetching ingredients:', error); - // // } - // }; - // fetchIngredients(); - // }, []); + useEffect(() => { + fetchIngredients(); + }, [userId, fetchIngredients]); const updateSearch = (query) => { setSearch(query); - const filteredResults = ingredientList.filter((ingredient) => + const filteredResults = ingredients.filter((ingredient) => ingredient.name.toLowerCase().includes(query.toLowerCase()) ); setSearchResults(filteredResults); @@ -132,22 +61,38 @@ export const IngredientScreen = () => { )} - - - {searchResults.length > 0 ? ( - searchResults.map((ingredient) => ( - - )) - ) : ( - No ingredients found - )} + {loading ? ( + + - + ) : ( + + + {searchResults.length > 0 ? ( + searchResults.map((ingredient) => ( + + )) + ) : ( + + No ingredients found + + )} + + + )}