diff --git a/app/(main)/(authenticated)/create-trip/_layout.tsx b/app/(main)/(authenticated)/create-trip/_layout.tsx
index d7dd19b..b700a45 100644
--- a/app/(main)/(authenticated)/create-trip/_layout.tsx
+++ b/app/(main)/(authenticated)/create-trip/_layout.tsx
@@ -73,6 +73,7 @@ export default function CreateTripLayout() {
}}
/>
+
);
}
diff --git a/app/(main)/(authenticated)/create-trip/dish-details-modal.tsx b/app/(main)/(authenticated)/create-trip/dish-details-modal.tsx
new file mode 100644
index 0000000..3652a94
--- /dev/null
+++ b/app/(main)/(authenticated)/create-trip/dish-details-modal.tsx
@@ -0,0 +1,7 @@
+import { DishDetailsModalPage } from '@/features/trips/pages';
+
+const DishDetailsModal = () => {
+ return ;
+};
+
+export default DishDetailsModal;
diff --git a/convex/validators/Trips.ts b/convex/validators/Trips.ts
index 3b1fc37..2ab054c 100644
--- a/convex/validators/Trips.ts
+++ b/convex/validators/Trips.ts
@@ -54,6 +54,7 @@ export const TypicalDish = v.object({
ingredients: v.array(v.string()),
isGlutenFree: v.boolean(),
isVegetarian: v.boolean(),
+ isVegan: v.boolean(),
});
export const Food = v.object({
diff --git a/features/core/navigation/data/services/NavigationService.ts b/features/core/navigation/data/services/NavigationService.ts
index 612ab21..550563a 100644
--- a/features/core/navigation/data/services/NavigationService.ts
+++ b/features/core/navigation/data/services/NavigationService.ts
@@ -61,8 +61,11 @@ export class NavigationService implements INavigationService {
toChangeLanguage() {
this.client.push(`/${Stacks.Profile}/${Routes.ChangeLanguage}`);
}
- toTypicalDishesModal({ tripId }: { tripId: string }) {
- this.client.push({ pathname: `/${Stacks.CreateTrip}/${Modals.TypicalDishes}`, params: { tripId } });
+ toTypicalDishesModal(params: { tripId: string }) {
+ this.client.push({ pathname: `/${Stacks.CreateTrip}/${Modals.TypicalDishes}`, params });
+ }
+ toDishDetailsModal(params: { tripId: string; searchTerm: string }) {
+ this.client.push({ pathname: `/${Stacks.CreateTrip}/${Modals.DishDetails}`, params });
}
back() {
diff --git a/features/core/navigation/domain/entities/Modals.ts b/features/core/navigation/domain/entities/Modals.ts
index 5356b1a..bcd73e0 100644
--- a/features/core/navigation/domain/entities/Modals.ts
+++ b/features/core/navigation/domain/entities/Modals.ts
@@ -1,5 +1,6 @@
export const Modals = {
TypicalDishes: 'typical-dishes-modal',
+ DishDetails: 'dish-details-modal',
} as const;
export type Modals = (typeof Modals)[keyof typeof Modals];
diff --git a/features/core/navigation/domain/entities/services/INavigationService.ts b/features/core/navigation/domain/entities/services/INavigationService.ts
index 64a3b28..c559fc3 100644
--- a/features/core/navigation/domain/entities/services/INavigationService.ts
+++ b/features/core/navigation/domain/entities/services/INavigationService.ts
@@ -25,6 +25,7 @@ export interface INavigationService {
toTripList(): void;
toChangeLanguage(): void;
toTypicalDishesModal(params: { tripId: string }): void;
+ toDishDetailsModal(params: { tripId: string; searchTerm: string }): void;
back(): void;
diff --git a/features/core/translations/libraries/locales/en.json b/features/core/translations/libraries/locales/en.json
index 8fd3086..f544eeb 100644
--- a/features/core/translations/libraries/locales/en.json
+++ b/features/core/translations/libraries/locales/en.json
@@ -35,10 +35,15 @@
"DISHES_one": "dish",
"DISHES_other": "dishes",
"TYPICAL_DISHES": "Typical Dishes",
+ "INGREDIENTS": "INGREDIENTS",
+ "GLUTEN_FREE": "GLUTEN-FREE",
+ "VEGAN": "VEGAN",
+ "VEGETARIAN": "VEGETARIAN",
"PROFILE": "Profile",
"MY_TRIPS": "My Trips",
"LOGO": "HolidAI",
- "UPCOMING_TRIP": "Upcoming trip:"
+ "UPCOMING_TRIP": "Upcoming trip:",
+ "MORE_DETAILS": "More details"
},
"ACTIVITY_DETAILS": {
"USEFUL_TIPS": "Useful Tips",
diff --git a/features/core/translations/libraries/locales/it.json b/features/core/translations/libraries/locales/it.json
index c1e8ff4..1997724 100644
--- a/features/core/translations/libraries/locales/it.json
+++ b/features/core/translations/libraries/locales/it.json
@@ -35,10 +35,15 @@
"DISHES_one": "piatto",
"DISHES_other": "piatti",
"TYPICAL_DISHES": "Piatti tipici",
+ "INGREDIENTS": "INGREDIENTI",
+ "GLUTEN_FREE": "SENZA GLUTINE",
+ "VEGAN": "VEGANO",
+ "VEGETARIAN": "VEGETARIANO",
"PROFILE": "Profilo",
"MY_TRIPS": "Viaggi",
"LOGO": "HolidAI",
- "UPCOMING_TRIP": "Prossimo viaggio:"
+ "UPCOMING_TRIP": "Prossimo viaggio:",
+ "MORE_DETAILS": "Più dettagli"
},
"ACTIVITY_DETAILS": {
"USEFUL_TIPS": "Consigli utili",
diff --git a/features/core/ui/assets/images/gluten_free.png b/features/core/ui/assets/images/gluten_free.png
new file mode 100644
index 0000000..9d8686a
Binary files /dev/null and b/features/core/ui/assets/images/gluten_free.png differ
diff --git a/features/core/ui/assets/images/vegan.png b/features/core/ui/assets/images/vegan.png
new file mode 100644
index 0000000..d9b007e
Binary files /dev/null and b/features/core/ui/assets/images/vegan.png differ
diff --git a/features/core/ui/assets/images/vegetarian.png b/features/core/ui/assets/images/vegetarian.png
new file mode 100644
index 0000000..4259caa
Binary files /dev/null and b/features/core/ui/assets/images/vegetarian.png differ
diff --git a/features/core/ui/components/basic/Cheap/Cheap.tsx b/features/core/ui/components/basic/Cheap/Cheap.tsx
index 5920d65..01ee667 100644
--- a/features/core/ui/components/basic/Cheap/Cheap.tsx
+++ b/features/core/ui/components/basic/Cheap/Cheap.tsx
@@ -10,14 +10,16 @@ type CheapProps = {
title: string;
color: string;
icon?: IoniconsName;
+ uppercase?: boolean;
};
-export const Cheap: FC = ({ title, color, icon }) => {
+export const Cheap: FC = ({ title, color, icon, uppercase = true }) => {
const styles = cheapStyles(color);
+
return (
{icon && }
-
+
);
};
diff --git a/features/core/ui/components/composite/Badge/Badge.style.ts b/features/core/ui/components/composite/Badge/Badge.style.ts
new file mode 100644
index 0000000..d954a2a
--- /dev/null
+++ b/features/core/ui/components/composite/Badge/Badge.style.ts
@@ -0,0 +1,47 @@
+import { colors } from '@/features/core/ui/style/colors';
+import { components } from '@/features/core/ui/style/dimensions/components';
+import { fontSize } from '@/features/core/ui/style/dimensions/fontSize';
+import { spacing } from '@/features/core/ui/style/dimensions/spacing';
+import { fontFamily } from '@/features/core/ui/style/fontFamily';
+import { StyleSheet } from 'react-native';
+
+export const styles = (active: boolean, backgroundColor: string) =>
+ StyleSheet.create({
+ container: {
+ alignItems: 'center',
+ gap: spacing.Single,
+ },
+ circleWrapper: {
+ width: components.badgeCircleSize,
+ height: components.badgeCircleSize,
+ },
+ circle: {
+ width: components.badgeCircleSize,
+ height: components.badgeCircleSize,
+ borderRadius: components.badgeCircleSize / 2,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: active ? backgroundColor : colors.primaryRed,
+ },
+ checkBadge: {
+ position: 'absolute',
+ bottom: 0,
+ right: 0,
+ width: components.badgeCheckSize,
+ height: components.badgeCheckSize,
+ borderRadius: components.badgeCheckSize / 2,
+ backgroundColor: colors.primaryWhite,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ badgeImage: {
+ width: components.badgeCircleSize * 0.6,
+ height: components.badgeCircleSize * 0.6,
+ },
+ label: {
+ fontSize: fontSize.XS,
+ fontFamily: fontFamily.interBold,
+ color: colors.primaryBlack,
+ textAlign: 'center',
+ },
+ });
diff --git a/features/core/ui/components/composite/Badge/Badge.tsx b/features/core/ui/components/composite/Badge/Badge.tsx
new file mode 100644
index 0000000..c743e81
--- /dev/null
+++ b/features/core/ui/components/composite/Badge/Badge.tsx
@@ -0,0 +1,39 @@
+import { CustomIcon } from '@/features/core/ui/components/basic/CustomIcon/CustomIcon';
+import { CustomImage } from '@/features/core/ui/components/basic/CustomImage/CustomImage';
+import { CustomText } from '@/features/core/ui/components/basic/CustomText/CustomText';
+import { styles as badgeStyle } from '@/features/core/ui/components/composite/Badge/Badge.style';
+import { colors } from '@/features/core/ui/style/colors';
+import { spacing } from '@/features/core/ui/style/dimensions/spacing';
+import { icons } from '@/features/core/ui/style/icons';
+import type { FC } from 'react';
+import type { ImageSourcePropType } from 'react-native';
+import { View } from 'react-native';
+
+type BadgeProps = {
+ label: string;
+ image: ImageSourcePropType;
+ backgroundColor: string;
+ active: boolean;
+};
+
+export const Badge: FC = ({ label, image, backgroundColor, active }) => {
+ const styles = badgeStyle(active, backgroundColor);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.style.ts b/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.style.ts
new file mode 100644
index 0000000..578ef2c
--- /dev/null
+++ b/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.style.ts
@@ -0,0 +1,19 @@
+import { fontSize } from '@/features/core/ui/style/dimensions/fontSize';
+import { spacing } from '@/features/core/ui/style/dimensions/spacing';
+import { fontFamily } from '@/features/core/ui/style/fontFamily';
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingBottom: spacing.Double,
+ marginBottom: spacing.Fourfold,
+ },
+ title: {
+ fontFamily: fontFamily.interExtraBold,
+ fontSize: fontSize.XL2,
+ maxWidth: '90%',
+ },
+});
diff --git a/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.tsx b/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.tsx
new file mode 100644
index 0000000..a28d2d1
--- /dev/null
+++ b/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.tsx
@@ -0,0 +1,19 @@
+import { ButtonType } from '@/features/core/ui/components/basic/CustomButton/CustomButton.logic';
+import { CustomIconButtonMedium } from '@/features/core/ui/components/basic/CustomIconButton/CustomIconButtonMedium';
+import { CustomText } from '@/features/core/ui/components/basic/CustomText/CustomText';
+import { styles } from '@/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.style';
+import { icons } from '@/features/core/ui/style/icons';
+import type { FC } from 'react';
+import { View } from 'react-native';
+
+type BottomSheetHeaderProps = {
+ title: string;
+ onClose: () => void;
+};
+
+export const BottomSheetHeader: FC = ({ title, onClose }) => (
+
+
+
+
+);
diff --git a/features/core/ui/index.ts b/features/core/ui/index.ts
index fb161d6..8f8bda6 100644
--- a/features/core/ui/index.ts
+++ b/features/core/ui/index.ts
@@ -41,6 +41,8 @@ export * from '@/features/core/ui/components/basic/LinearGradientText/LinearGrad
export * from '@/features/core/ui/components/basic/LottieAnimation/LottieAnimation';
// Composite components
+export * from '@/features/core/ui/components/composite/Badge/Badge';
+export * from '@/features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader';
export * from '@/features/core/ui/components/composite/AnimatedHeaderImage/AnimatedHeaderImage';
export * from '@/features/core/ui/components/composite/CardWithImage/CardWithImage';
export * from '@/features/core/ui/components/composite/CustomHeader/CustomHeader';
diff --git a/features/core/ui/style/colors.ts b/features/core/ui/style/colors.ts
index cc226c4..2ba4741 100644
--- a/features/core/ui/style/colors.ts
+++ b/features/core/ui/style/colors.ts
@@ -19,4 +19,5 @@ export const colors = {
tertiaryBlue: '#63b3ff',
primaryRed: '#FFD9D9',
secondaryGreen: '#8AFF8A',
+ tertiaryGreen: '#57C17B',
} as const;
diff --git a/features/core/ui/style/dimensions/components.ts b/features/core/ui/style/dimensions/components.ts
index 1f9b583..9460c44 100644
--- a/features/core/ui/style/dimensions/components.ts
+++ b/features/core/ui/style/dimensions/components.ts
@@ -26,6 +26,8 @@ export const components = {
buttonNumberHeight: 30,
animatedWordsHeight: 30,
carouselImageSize: 200,
+ badgeCircleSize: 80,
+ badgeCheckSize: 22,
welcomeCardHeightSmall: 130,
welcomeCardHeightMedium: 180,
welcomeCardHeightLarge: 230,
diff --git a/features/core/ui/style/dimensions/images.ts b/features/core/ui/style/dimensions/images.ts
index fa95a72..3a309ce 100644
--- a/features/core/ui/style/dimensions/images.ts
+++ b/features/core/ui/style/dimensions/images.ts
@@ -5,4 +5,5 @@ export const images = {
logoRoundHeight: 80,
logoRoundWidth: 80,
dishImageSize: 80,
+ dishFullImageSize: 110,
};
diff --git a/features/core/ui/style/icons.ts b/features/core/ui/style/icons.ts
index 0190c46..4f75651 100644
--- a/features/core/ui/style/icons.ts
+++ b/features/core/ui/style/icons.ts
@@ -2,9 +2,11 @@ import type { Ionicons } from '@expo/vector-icons';
export const icons: Record = {
close: 'close',
+ closeCircle: 'close-circle',
location: 'location',
locationOutline: 'location-outline',
globeOutline: 'globe-outline',
+ checkmark: 'checkmark',
personCircleOutline: 'person-circle-outline',
arrowBack: 'arrow-back',
arrowRight: 'chevron-forward',
diff --git a/features/trip-generation/domain/schemas/GenerateTripSchema.ts b/features/trip-generation/domain/schemas/GenerateTripSchema.ts
index f44c3b5..174c5d4 100644
--- a/features/trip-generation/domain/schemas/GenerateTripSchema.ts
+++ b/features/trip-generation/domain/schemas/GenerateTripSchema.ts
@@ -78,6 +78,7 @@ export const generatedTripSchema = z.object({
ingredients: z.array(z.string()).describe('Main ingredients of the dish.'),
isGlutenFree: z.boolean().describe('Whether the dish is gluten free.'),
isVegetarian: z.boolean().describe('Whether the dish is vegetarian.'),
+ isVegan: z.boolean().describe('Whether the dish is vegan.'),
}),
),
}),
diff --git a/features/trips/domain/entities/TypicalDish.ts b/features/trips/domain/entities/TypicalDish.ts
index 502a0f4..ba6af8a 100644
--- a/features/trips/domain/entities/TypicalDish.ts
+++ b/features/trips/domain/entities/TypicalDish.ts
@@ -5,4 +5,5 @@ export interface TypicalDish {
ingredients: string[];
isGlutenFree: boolean;
isVegetarian: boolean;
+ isVegan: boolean;
}
diff --git a/features/trips/pages.ts b/features/trips/pages.ts
index 32d8177..c043fac 100644
--- a/features/trips/pages.ts
+++ b/features/trips/pages.ts
@@ -1,4 +1,5 @@
export { ActivityDetailsPage } from '@/features/trips/ui/pages/ActivityDetailsPage/ActivityDetailsPage';
+export { DishDetailsModalPage } from '@/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage';
export { TripDetailsPage } from '@/features/trips/ui/pages/TripDetailsPage/TripDetailsPage';
export { TripListPage } from '@/features/trips/ui/pages/TripListPage/TripListPage';
export { TypicalDishesModalPage } from '@/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage';
diff --git a/features/trips/ui/components/FoodCard/FoodCard.style.ts b/features/trips/ui/components/FoodCard/FoodCard.style.ts
index 93a7bfe..3db5914 100644
--- a/features/trips/ui/components/FoodCard/FoodCard.style.ts
+++ b/features/trips/ui/components/FoodCard/FoodCard.style.ts
@@ -1,4 +1,4 @@
-import { colors, fontFamily, fontSize, spacing } from '@/features/core/ui';
+import { colors, fontFamily, fontSize, opacity, spacing } from '@/features/core/ui';
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
@@ -56,4 +56,31 @@ export const styles = StyleSheet.create({
marginTop: spacing.Double,
paddingHorizontal: spacing.Double,
},
+ titleContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: spacing.Single,
+ },
+ typicalDishesBox: {
+ padding: spacing.Double,
+ backgroundColor: colors.secondaryGrey,
+ borderRadius: spacing.Double,
+ marginTop: spacing.Double,
+ marginHorizontal: spacing.Double,
+ },
+ pressed: {
+ opacity: opacity.default,
+ },
+ boxText: {
+ fontFamily: fontFamily.interMedium,
+ color: colors.primaryBlack,
+ },
+ boxButton: {
+ marginTop: spacing.Single,
+ fontFamily: fontFamily.interBold,
+ alignSelf: 'flex-end',
+ backgroundColor: colors.primaryWhite,
+ padding: spacing.Single,
+ borderRadius: spacing.Double,
+ },
});
diff --git a/features/trips/ui/components/FoodCard/FoodCard.tsx b/features/trips/ui/components/FoodCard/FoodCard.tsx
index 6037c92..3430d22 100644
--- a/features/trips/ui/components/FoodCard/FoodCard.tsx
+++ b/features/trips/ui/components/FoodCard/FoodCard.tsx
@@ -1,10 +1,10 @@
-import { CustomButtonMedium, CustomIcon, CustomText, colors, icons, spacing } from '@/features/core/ui';
+import { CustomIcon, CustomText, colors, icons, spacing } from '@/features/core/ui';
import type { Food } from '@/features/trips/domain/entities/Food';
import { useFoodCardLogic } from '@/features/trips/ui/components/FoodCard/FoodCard.logic';
import { styles } from '@/features/trips/ui/components/FoodCard/FoodCard.style';
import { LinearGradient } from 'expo-linear-gradient';
import type { FC } from 'react';
-import { View } from 'react-native';
+import { Pressable, View } from 'react-native';
type FoodCardProps = {
food: Food;
@@ -36,9 +36,16 @@ export const FoodCard: FC = ({ food, tripId }) => {
-
-
-
+ [styles.typicalDishesBox, pressed && styles.pressed]}
+ onPress={handleOpenModal}
+ >
+
+
+
+
+
+
);
diff --git a/features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts b/features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts
index d060e70..ba1857c 100644
--- a/features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts
+++ b/features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts
@@ -1,6 +1,26 @@
import { useGetWikimediaDishImage } from '@/features/core/images';
+import type { TypicalDish } from '@/features/trips';
-export const useDishItemLogic = (searchTerm: string) => {
- const { data, isLoading } = useGetWikimediaDishImage(searchTerm);
- return { image: data?.url, isLoading };
+const glutenFreeImage = require('@/features/core/ui/assets/images/gluten_free.png');
+const veganImage = require('@/features/core/ui/assets/images/vegan.png');
+const vegetarianImage = require('@/features/core/ui/assets/images/vegetarian.png');
+
+export const useDishItemLogic = (dish: TypicalDish) => {
+ const { data, isLoading } = useGetWikimediaDishImage(dish.searchTerm);
+ const hasBadge = dish.isGlutenFree || dish.isVegan || dish.isVegetarian;
+
+ return {
+ image: data?.url,
+ isLoading,
+ glutenFreeImage,
+ veganImage,
+ vegetarianImage,
+ hasBadge,
+ isGlutenFree: dish.isGlutenFree,
+ isVegan: dish.isVegan,
+ isVegetarian: dish.isVegetarian,
+ glutenFreeLabel: 'MY_TRIP.GLUTEN_FREE',
+ veganLabel: 'MY_TRIP.VEGAN',
+ vegetarianLabel: 'MY_TRIP.VEGETARIAN',
+ };
};
diff --git a/features/trips/ui/components/FoodCard/components/DishItem/DishItem.style.ts b/features/trips/ui/components/FoodCard/components/DishItem/DishItem.style.ts
index 08c9dc5..384793b 100644
--- a/features/trips/ui/components/FoodCard/components/DishItem/DishItem.style.ts
+++ b/features/trips/ui/components/FoodCard/components/DishItem/DishItem.style.ts
@@ -1,4 +1,4 @@
-import { colors, fontFamily, images, spacing } from '@/features/core/ui';
+import { colors, fontFamily, images, opacity, spacing } from '@/features/core/ui';
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
@@ -6,11 +6,16 @@ export const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
gap: spacing.Double,
+ backgroundColor: colors.secondaryGrey,
+ padding: spacing.Double,
+ borderRadius: spacing.Fourfold,
},
image: {
width: images.dishImageSize,
height: images.dishImageSize,
borderRadius: images.dishImageSize,
+ borderWidth: spacing.Minimal,
+ borderColor: colors.primaryBlack,
},
skeleton: {
width: images.dishImageSize,
@@ -31,4 +36,16 @@ export const styles = StyleSheet.create({
flex: 1,
alignItems: 'flex-start',
},
+ pressed: {
+ opacity: opacity.default,
+ },
+ badge: {
+ width: spacing.FourfoldAndHalf,
+ height: spacing.FourfoldAndHalf,
+ },
+ badgeContainer: {
+ flexDirection: 'row',
+ columnGap: spacing.Minimal,
+ left: -spacing.MinimalDouble,
+ },
});
diff --git a/features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx b/features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx
index 3262bfa..e867e51 100644
--- a/features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx
+++ b/features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx
@@ -3,15 +3,33 @@ import type { TypicalDish } from '@/features/trips/domain/entities/TypicalDish';
import { useDishItemLogic } from '@/features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic';
import { styles } from '@/features/trips/ui/components/FoodCard/components/DishItem/DishItem.style';
import type { FC } from 'react';
-import { View } from 'react-native';
+import { Pressable, View } from 'react-native';
-type DishItemProps = { dish: TypicalDish };
+type DishItemProps = { dish: TypicalDish; onPress: () => void };
-export const DishItem: FC = ({ dish }) => {
- const { image, isLoading } = useDishItemLogic(dish.searchTerm);
+export const DishItem: FC = ({ dish, onPress }) => {
+ const {
+ image,
+ isLoading,
+ glutenFreeImage,
+ veganImage,
+ vegetarianImage,
+ hasBadge,
+ isGlutenFree,
+ isVegan,
+ isVegetarian,
+ glutenFreeLabel,
+ veganLabel,
+ vegetarianLabel,
+ } = useDishItemLogic(dish);
return (
-
+ [styles.container, pressed && styles.pressed]}
+ onPress={onPress}
+ accessibilityRole="button"
+ accessibilityLabel={dish.name}
+ >
{isLoading ? (
@@ -21,8 +39,24 @@ export const DishItem: FC = ({ dish }) => {
-
+
+ {hasBadge && (
+
+ {isGlutenFree && (
+
+ )}
+ {isVegan && }
+ {isVegetarian && (
+
+ )}
+
+ )}
-
+
);
};
diff --git a/features/trips/ui/components/IngredientsList/IngredientsList.style.ts b/features/trips/ui/components/IngredientsList/IngredientsList.style.ts
new file mode 100644
index 0000000..2632914
--- /dev/null
+++ b/features/trips/ui/components/IngredientsList/IngredientsList.style.ts
@@ -0,0 +1,20 @@
+import { colors, fontFamily, fontSize, spacing } from '@/features/core/ui';
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ gap: spacing.Double,
+ },
+ title: {
+ fontFamily: fontFamily.interBold,
+ fontSize: fontSize.SM,
+ color: colors.primaryGrey,
+ },
+ chipsRow: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: spacing.SingleAndHalf,
+ width: '100%',
+ },
+});
diff --git a/features/trips/ui/components/IngredientsList/IngredientsList.tsx b/features/trips/ui/components/IngredientsList/IngredientsList.tsx
new file mode 100644
index 0000000..980719f
--- /dev/null
+++ b/features/trips/ui/components/IngredientsList/IngredientsList.tsx
@@ -0,0 +1,26 @@
+import { Cheap, CustomText, colors, icons } from '@/features/core/ui';
+import { styles } from '@/features/trips/ui/components/IngredientsList/IngredientsList.style';
+import type { FC } from 'react';
+import { View } from 'react-native';
+
+type IngredientsListProps = {
+ title: string;
+ ingredients: string[];
+};
+
+export const IngredientsList: FC = ({ title, ingredients }) => (
+
+
+
+ {ingredients.map(ingredient => (
+
+ ))}
+
+
+);
diff --git a/features/trips/ui/components/TypicalDishesList/TypicalDishesList.style.ts b/features/trips/ui/components/TypicalDishesList/TypicalDishesList.style.ts
index a350883..b8c00ac 100644
--- a/features/trips/ui/components/TypicalDishesList/TypicalDishesList.style.ts
+++ b/features/trips/ui/components/TypicalDishesList/TypicalDishesList.style.ts
@@ -4,7 +4,7 @@ import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
separator: {
height: 1,
- backgroundColor: colors.secondaryGreen,
- marginVertical: spacing.Fourfold,
+ backgroundColor: colors.secondaryGrey,
+ marginVertical: spacing.Double,
},
});
diff --git a/features/trips/ui/components/TypicalDishesList/TypicalDishesList.tsx b/features/trips/ui/components/TypicalDishesList/TypicalDishesList.tsx
index b3348f8..f7d4eed 100644
--- a/features/trips/ui/components/TypicalDishesList/TypicalDishesList.tsx
+++ b/features/trips/ui/components/TypicalDishesList/TypicalDishesList.tsx
@@ -7,16 +7,17 @@ import { View } from 'react-native';
type TypicalDishesListProps = {
dishItems: TypicalDish[] | undefined;
+ onDishPress: (searchTerm: string) => void;
};
const Separator = () => ;
-export const TypicalDishesList: FC = ({ dishItems }) => (
+export const TypicalDishesList: FC = ({ dishItems, onDishPress }) => (
<>
{dishItems?.map((item, index) => (
{index > 0 && }
-
+ onDishPress(item.searchTerm)} />
))}
>
diff --git a/features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts b/features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts
index 072e1af..3d26e90 100644
--- a/features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts
+++ b/features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts
@@ -7,9 +7,6 @@ export const styles = StyleSheet.create({
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.Quintuple,
- borderBottomWidth: 1,
- borderBottomColor: colors.secondaryGreen,
- paddingBottom: spacing.Double,
},
headerContent: {
gap: spacing.Single,
diff --git a/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.logic.ts b/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.logic.ts
new file mode 100644
index 0000000..0f655a6
--- /dev/null
+++ b/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.logic.ts
@@ -0,0 +1,33 @@
+import { useGetWikimediaDishImage } from '@/features/core/images';
+import { navigationService } from '@/features/core/navigation';
+import { useGetTripById } from '@/features/trips/facades/useGetTripById';
+import { useLocalSearchParams } from 'expo-router';
+
+const glutenFreeImage = require('@/features/core/ui/assets/images/gluten_free.png');
+const veganImage = require('@/features/core/ui/assets/images/vegan.png');
+const vegetarianImage = require('@/features/core/ui/assets/images/vegetarian.png');
+
+export const useDishDetailsModalPageLogic = () => {
+ const { tripId, searchTerm } = useLocalSearchParams<{ tripId: string; searchTerm: string }>();
+ const { data, isLoading } = useGetWikimediaDishImage(searchTerm);
+ const { trip } = useGetTripById(tripId);
+
+ const dish = trip?.tripAiResp?.food?.typicalDishes.find(d => d.searchTerm === searchTerm);
+
+ const handleClose = () => navigationService.back();
+
+ return {
+ dishName: dish?.name ?? '',
+ dishDescription: dish?.description ?? '',
+ dishIngredients: dish?.ingredients ?? [],
+ handleClose,
+ image: data?.url,
+ imageIsLoading: isLoading,
+ isVegetarian: dish?.isVegetarian ?? false,
+ isGlutenFree: dish?.isGlutenFree ?? false,
+ isVegan: dish?.isVegan ?? false,
+ glutenFreeImage,
+ veganImage,
+ vegetarianImage,
+ };
+};
diff --git a/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.style.ts b/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.style.ts
new file mode 100644
index 0000000..cfccfa6
--- /dev/null
+++ b/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.style.ts
@@ -0,0 +1,40 @@
+import { colors, fontFamily, fontSize, images, spacing } from '@/features/core/ui';
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+ contentContainer: {
+ paddingVertical: spacing.Quintuple,
+ paddingHorizontal: spacing.Quintuple,
+ },
+ container: {
+ backgroundColor: colors.primaryWhite,
+ },
+ image: {
+ width: images.dishFullImageSize,
+ height: images.dishFullImageSize,
+ borderRadius: images.dishFullImageSize,
+ borderWidth: spacing.MinimalDouble,
+ borderColor: colors.primaryBlack,
+ },
+ bodyContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ columnGap: spacing.Double,
+ marginBottom: spacing.Triple,
+ alignItems: 'center',
+ },
+ description: {
+ flex: 1,
+ fontFamily: fontFamily.interRegular,
+ fontSize: fontSize.MD,
+ lineHeight: fontSize.MD * 1.5,
+ },
+ badgesContainer: {
+ flexDirection: 'row',
+ gap: spacing.Single,
+ marginTop: spacing.Double,
+ justifyContent: 'space-between',
+ paddingHorizontal: spacing.Double,
+ paddingVertical: spacing.Single,
+ },
+});
diff --git a/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx b/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx
new file mode 100644
index 0000000..4d5525a
--- /dev/null
+++ b/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx
@@ -0,0 +1,52 @@
+import { Badge, BaseSkeleton, BottomSheetHeader, CustomImage, CustomText, colors } from '@/features/core/ui';
+import { IngredientsList } from '@/features/trips/ui/components/IngredientsList/IngredientsList';
+import { useDishDetailsModalPageLogic } from '@/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.logic';
+import { styles } from '@/features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.style';
+import { ScrollView, View } from 'react-native';
+
+export const DishDetailsModalPage = () => {
+ const {
+ dishName,
+ dishDescription,
+ dishIngredients,
+ handleClose,
+ image,
+ imageIsLoading,
+ isVegetarian,
+ isGlutenFree,
+ isVegan,
+ glutenFreeImage,
+ veganImage,
+ vegetarianImage,
+ } = useDishDetailsModalPageLogic();
+
+ return (
+
+
+
+ {imageIsLoading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.logic.ts b/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.logic.ts
index 5b21d14..2ed7cf4 100644
--- a/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.logic.ts
+++ b/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.logic.ts
@@ -11,6 +11,7 @@ export const useTypicalDishesModalPageLogic = () => {
const dishNumber = food?.typicalDishes.length ?? 0;
const handleClose = () => navigationService.back();
+ const handleDishPress = (searchTerm: string) => navigationService.toDishDetailsModal({ tripId, searchTerm });
- return { handleClose, location, dishNumber, dishItems: food?.typicalDishes };
+ return { handleClose, handleDishPress, location, dishNumber, dishItems: food?.typicalDishes };
};
diff --git a/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.tsx b/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.tsx
index 1438b31..c306cfc 100644
--- a/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.tsx
+++ b/features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.tsx
@@ -5,12 +5,12 @@ import { styles } from '@/features/trips/ui/pages/TypicalDishesModalPage/Typical
import { ScrollView } from 'react-native';
export const TypicalDishesModalPage = () => {
- const { handleClose, location, dishNumber, dishItems } = useTypicalDishesModalPageLogic();
+ const { handleClose, handleDishPress, location, dishNumber, dishItems } = useTypicalDishesModalPageLogic();
return (
-
+
);
};