diff --git a/android/gradle.properties b/android/gradle.properties index 9afe615..126768a 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -32,7 +32,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=true +newArchEnabled=false # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. @@ -42,3 +42,4 @@ hermesEnabled=true # This allows your app to draw behind system bars for an immersive UI. # Note: Only works with ReactActivity and should not be used with custom Activity. edgeToEdgeEnabled=false +android.experimental.cxx.cmake.arguments=-DCMAKE_OBJECT_PATH_MAX=128 diff --git a/app/App.tsx b/app/App.tsx index 0aa7559..311e27e 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -1,38 +1,24 @@ - -import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native'; -import { - SafeAreaProvider, - useSafeAreaInsets, -} from 'react-native-safe-area-context'; -import { LoginScreen } from '@features/screens'; -import { useAppTheme } from '@theme'; +import React from 'react'; +import { StatusBar, useColorScheme } from 'react-native'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { NavigationContainer } from '@react-navigation/native'; +import { Provider } from 'react-redux'; +import { store } from './store'; +import { RootNavigator } from './navigation/rootNavigator'; function App() { const isDarkMode = useColorScheme() === 'dark'; return ( - - - - + + + + + + + + ); } -function AppContent() { - const safeAreaInsets = useSafeAreaInsets(); - const { colors } = useAppTheme(); - - return ( - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, -}); - export default App; diff --git a/app/components/CustomInput/CustomInput.tsx b/app/components/CustomInput/CustomInput.tsx index 118e8fc..7216143 100644 --- a/app/components/CustomInput/CustomInput.tsx +++ b/app/components/CustomInput/CustomInput.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { View, Text, TextInput, TouchableOpacity } from 'react-native'; -import { CustomInputProps } from './CustomInput.props'; -import { getStyles } from './CustomInput.styles'; +import { CustomInputProps } from './customInput.props'; +import { getStyles } from './customInput.styles'; import { useAppTheme } from '@theme'; export const CustomInput: React.FC = ({ diff --git a/app/components/CustomInput/index.ts b/app/components/CustomInput/index.ts index d483952..60e1ef1 100644 --- a/app/components/CustomInput/index.ts +++ b/app/components/CustomInput/index.ts @@ -1,2 +1,2 @@ -export * from './CustomInput'; -export * from './CustomInput.props'; +export * from './customInput'; +export * from './customInput.props'; diff --git a/app/components/PrimaryButton/PrimaryButton.tsx b/app/components/PrimaryButton/PrimaryButton.tsx index 6aee4b2..91bb7b2 100644 --- a/app/components/PrimaryButton/PrimaryButton.tsx +++ b/app/components/PrimaryButton/PrimaryButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { TouchableOpacity, Text, ActivityIndicator } from 'react-native'; -import { PrimaryButtonProps } from './PrimaryButton.props'; -import { styles } from './PrimaryButton.styles'; +import { PrimaryButtonProps } from './primaryButton.props'; +import { styles } from './primaryButton.styles'; import { colors } from '@theme'; export const PrimaryButton: React.FC = ({ diff --git a/app/components/PrimaryButton/index.ts b/app/components/PrimaryButton/index.ts index 78d30a4..a841b05 100644 --- a/app/components/PrimaryButton/index.ts +++ b/app/components/PrimaryButton/index.ts @@ -1,2 +1,2 @@ -export * from './PrimaryButton'; -export * from './PrimaryButton.props'; +export * from './primaryButton'; +export * from './primaryButton.props'; diff --git a/app/components/SocialButton/SocialButton.tsx b/app/components/SocialButton/SocialButton.tsx index ae9a614..1d1ffe4 100644 --- a/app/components/SocialButton/SocialButton.tsx +++ b/app/components/SocialButton/SocialButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { TouchableOpacity, Text } from 'react-native'; -import { SocialButtonProps } from './SocialButton.props'; -import { getStyles } from './SocialButton.styles'; +import { SocialButtonProps } from './socialButton.props'; +import { getStyles } from './socialButton.styles'; import { useAppTheme } from '@theme'; import Svg, { Path } from 'react-native-svg'; diff --git a/app/components/SocialButton/index.ts b/app/components/SocialButton/index.ts index 15529da..c65c305 100644 --- a/app/components/SocialButton/index.ts +++ b/app/components/SocialButton/index.ts @@ -1,2 +1,2 @@ -export * from './SocialButton'; -export * from './SocialButton.props'; +export * from './socialButton'; +export * from './socialButton.props'; diff --git a/app/components/badge/badge.props.ts b/app/components/badge/badge.props.ts new file mode 100644 index 0000000..3541459 --- /dev/null +++ b/app/components/badge/badge.props.ts @@ -0,0 +1,4 @@ +export interface BadgeProps { + text: string; + variant?: 'primary' | 'success' | 'warning' | 'danger'; +} diff --git a/app/components/badge/badge.styles.ts b/app/components/badge/badge.styles.ts new file mode 100644 index 0000000..60b7524 --- /dev/null +++ b/app/components/badge/badge.styles.ts @@ -0,0 +1,30 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +const variantColors = { + primary: { bg: '#E8F5E9', text: '#05824C' }, + success: { bg: '#E8F5E9', text: '#2E7D32' }, + warning: { bg: '#FFF8E1', text: '#F57F17' }, + danger: { bg: '#FFE7E7', text: '#D32F2F' }, +}; + +export const getStyles = () => StyleSheet.create({ + badge: { + paddingHorizontal: 8, + paddingVertical: 3, + borderRadius: 12, + alignSelf: 'flex-start', + }, + text: { + fontSize: typography.fontSize.xs, + fontWeight: typography.fontWeight.semibold, + }, +}); + +export const getVariantStyle = (_colors: any, variant: string) => { + const v = variantColors[variant as keyof typeof variantColors] || variantColors.primary; + return { + backgroundColor: v.bg, + color: v.text, + }; +}; diff --git a/app/components/badge/badge.tsx b/app/components/badge/badge.tsx new file mode 100644 index 0000000..cc2c749 --- /dev/null +++ b/app/components/badge/badge.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { BadgeProps } from './badge.props'; +import { getStyles, getVariantStyle } from './badge.styles'; +import { useAppTheme } from '@theme'; + +export const Badge: React.FC = ({ text, variant = 'primary' }) => { + const { colors } = useAppTheme(); + const styles = getStyles(); + const variantStyle = getVariantStyle(colors, variant); + + return ( + + {text} + + ); +}; diff --git a/app/components/badge/index.ts b/app/components/badge/index.ts new file mode 100644 index 0000000..96b8971 --- /dev/null +++ b/app/components/badge/index.ts @@ -0,0 +1,2 @@ +export * from './badge'; +export * from './badge.props'; diff --git a/app/components/catalogItemRow/catalogItemRow.props.ts b/app/components/catalogItemRow/catalogItemRow.props.ts new file mode 100644 index 0000000..5f93f58 --- /dev/null +++ b/app/components/catalogItemRow/catalogItemRow.props.ts @@ -0,0 +1,9 @@ +export interface CatalogItemRowProps { + imageUrl: string; + title: string; + description: string; + price: number; + quantity: number; + onAdd: () => void; + onRemove: () => void; +} diff --git a/app/components/catalogItemRow/catalogItemRow.styles.ts b/app/components/catalogItemRow/catalogItemRow.styles.ts new file mode 100644 index 0000000..312f5a5 --- /dev/null +++ b/app/components/catalogItemRow/catalogItemRow.styles.ts @@ -0,0 +1,75 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + paddingVertical: 12, + paddingHorizontal: 16, + borderBottomWidth: 1, + borderBottomColor: colors.border, + backgroundColor: colors.background, + }, + image: { + width: 70, + height: 70, + borderRadius: 8, + backgroundColor: colors.border, + justifyContent: 'center', + alignItems: 'center', + }, + imagePlaceholder: { + fontSize: 28, + }, + info: { + flex: 1, + marginLeft: 12, + justifyContent: 'center', + }, + title: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 2, + }, + description: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + marginBottom: 4, + }, + price: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.bold, + color: colors.text, + }, + actions: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginLeft: 8, + }, + stepperButton: { + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: colors.primary, + justifyContent: 'center', + alignItems: 'center', + }, + stepperButtonDisabled: { + backgroundColor: colors.border, + }, + stepperButtonText: { + color: colors.white, + fontSize: 18, + fontWeight: typography.fontWeight.bold, + }, + quantity: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginHorizontal: 12, + minWidth: 20, + textAlign: 'center', + }, +}); diff --git a/app/components/catalogItemRow/catalogItemRow.tsx b/app/components/catalogItemRow/catalogItemRow.tsx new file mode 100644 index 0000000..475bb24 --- /dev/null +++ b/app/components/catalogItemRow/catalogItemRow.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { CatalogItemRowProps } from './catalogItemRow.props'; +import { getStyles } from './catalogItemRow.styles'; +import { useAppTheme } from '@theme'; + +export const CatalogItemRow: React.FC = ({ + imageUrl, + title, + description, + price, + quantity, + onAdd, + onRemove, +}) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + + {imageUrl || 'πŸ•'} + + + {title} + {description} + β‚Ή{price} + + + {quantity > 0 ? ( + <> + + - + + {quantity} + + + + + + ) : ( + + + + + )} + + + ); +}; diff --git a/app/components/catalogItemRow/index.ts b/app/components/catalogItemRow/index.ts new file mode 100644 index 0000000..4f4dca7 --- /dev/null +++ b/app/components/catalogItemRow/index.ts @@ -0,0 +1,2 @@ +export * from './catalogItemRow'; +export * from './catalogItemRow.props'; diff --git a/app/components/categoryChip/categoryChip.props.ts b/app/components/categoryChip/categoryChip.props.ts new file mode 100644 index 0000000..c53b693 --- /dev/null +++ b/app/components/categoryChip/categoryChip.props.ts @@ -0,0 +1,6 @@ +export interface CategoryChipProps { + icon: string; + label: string; + isSelected: boolean; + onPress: () => void; +} diff --git a/app/components/categoryChip/categoryChip.styles.ts b/app/components/categoryChip/categoryChip.styles.ts new file mode 100644 index 0000000..cd9f167 --- /dev/null +++ b/app/components/categoryChip/categoryChip.styles.ts @@ -0,0 +1,33 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 24, + borderWidth: 1.5, + borderColor: colors.border, + backgroundColor: colors.background, + marginRight: 10, + }, + selected: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + icon: { + fontSize: 18, + marginRight: 6, + }, + label: { + fontSize: typography.fontSize.sm, + fontWeight: typography.fontWeight.medium, + color: colors.textSecondary, + }, + labelSelected: { + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, +}); diff --git a/app/components/categoryChip/categoryChip.tsx b/app/components/categoryChip/categoryChip.tsx new file mode 100644 index 0000000..7f00036 --- /dev/null +++ b/app/components/categoryChip/categoryChip.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Text, TouchableOpacity } from 'react-native'; +import { CategoryChipProps } from './categoryChip.props'; +import { getStyles } from './categoryChip.styles'; +import { useAppTheme } from '@theme'; + +export const CategoryChip: React.FC = ({ + icon, + label, + isSelected, + onPress, +}) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + {icon} + + {label} + + + ); +}; diff --git a/app/components/categoryChip/index.ts b/app/components/categoryChip/index.ts new file mode 100644 index 0000000..6500022 --- /dev/null +++ b/app/components/categoryChip/index.ts @@ -0,0 +1,2 @@ +export * from './categoryChip'; +export * from './categoryChip.props'; diff --git a/app/components/header/header.props.ts b/app/components/header/header.props.ts new file mode 100644 index 0000000..5271130 --- /dev/null +++ b/app/components/header/header.props.ts @@ -0,0 +1,7 @@ +import { ReactNode } from 'react'; + +export interface HeaderProps { + title: string; + onBack?: () => void; + rightComponent?: ReactNode; +} diff --git a/app/components/header/header.styles.ts b/app/components/header/header.styles.ts new file mode 100644 index 0000000..975ae6e --- /dev/null +++ b/app/components/header/header.styles.ts @@ -0,0 +1,36 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 12, + backgroundColor: colors.background, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + backButton: { + padding: 4, + marginRight: 8, + }, + backText: { + fontSize: 24, + color: colors.text, + }, + titleContainer: { + flex: 1, + alignItems: 'center', + }, + title: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + }, + rightContainer: { + minWidth: 40, + alignItems: 'flex-end', + }, +}); diff --git a/app/components/header/header.tsx b/app/components/header/header.tsx new file mode 100644 index 0000000..e05987d --- /dev/null +++ b/app/components/header/header.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { HeaderProps } from './header.props'; +import { getStyles } from './header.styles'; +import { useAppTheme } from '@theme'; + +export const Header: React.FC = ({ title, onBack, rightComponent }) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + + {onBack && ( + + {'<'} + + )} + + + {title} + + + {rightComponent} + + + ); +}; diff --git a/app/components/header/index.ts b/app/components/header/index.ts new file mode 100644 index 0000000..1d85bf9 --- /dev/null +++ b/app/components/header/index.ts @@ -0,0 +1,2 @@ +export * from './header'; +export * from './header.props'; diff --git a/app/components/index.ts b/app/components/index.ts index a4b2da5..494c65f 100644 --- a/app/components/index.ts +++ b/app/components/index.ts @@ -1,3 +1,14 @@ -export * from './CustomInput'; -export * from './PrimaryButton'; -export * from './SocialButton'; \ No newline at end of file +export * from './customInput'; +export * from './primaryButton'; +export * from './socialButton'; +export * from './header'; +export * from './searchBar'; +export * from './providerCard'; +export * from './catalogItemRow'; +export * from './stepProgress'; +export * from './badge'; +export * from './quantitySelector'; +export * from './categoryChip'; +export * from './ratingStars'; +export * from './orderHistoryCard'; +export * from './paymentOption'; diff --git a/app/components/orderHistoryCard/index.ts b/app/components/orderHistoryCard/index.ts new file mode 100644 index 0000000..4d91d70 --- /dev/null +++ b/app/components/orderHistoryCard/index.ts @@ -0,0 +1,2 @@ +export * from './orderHistoryCard'; +export * from './orderHistoryCard.props'; diff --git a/app/components/orderHistoryCard/orderHistoryCard.props.ts b/app/components/orderHistoryCard/orderHistoryCard.props.ts new file mode 100644 index 0000000..196c1ca --- /dev/null +++ b/app/components/orderHistoryCard/orderHistoryCard.props.ts @@ -0,0 +1,9 @@ +export interface OrderHistoryCardProps { + providerName: string; + providerImage: string; + orderDate: string; + status: string; + total: number; + onReorder?: () => void; + onDetails?: () => void; +} diff --git a/app/components/orderHistoryCard/orderHistoryCard.styles.ts b/app/components/orderHistoryCard/orderHistoryCard.styles.ts new file mode 100644 index 0000000..a8bb88a --- /dev/null +++ b/app/components/orderHistoryCard/orderHistoryCard.styles.ts @@ -0,0 +1,86 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 16, + marginHorizontal: 16, + marginVertical: 6, + borderWidth: 1, + borderColor: colors.border, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 10, + }, + image: { + width: 44, + height: 44, + borderRadius: 22, + backgroundColor: colors.border, + justifyContent: 'center', + alignItems: 'center', + }, + imagePlaceholder: { + fontSize: 22, + }, + headerInfo: { + flex: 1, + marginLeft: 12, + }, + providerName: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + }, + orderDate: { + fontSize: typography.fontSize.xs, + color: colors.textSecondary, + marginTop: 2, + }, + statusBadge: { + alignSelf: 'flex-start', + paddingHorizontal: 10, + paddingVertical: 3, + borderRadius: 12, + backgroundColor: '#E8F5E9', + }, + statusText: { + fontSize: typography.fontSize.xs, + fontWeight: typography.fontWeight.medium, + color: colors.primary, + }, + footer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginTop: 10, + paddingTop: 10, + borderTopWidth: 1, + borderTopColor: colors.border, + }, + total: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.bold, + color: colors.text, + }, + actions: { + flexDirection: 'row', + gap: 8, + }, + actionButton: { + paddingHorizontal: 14, + paddingVertical: 6, + borderRadius: 8, + borderWidth: 1, + borderColor: colors.primary, + }, + actionButtonText: { + fontSize: typography.fontSize.sm, + color: colors.primary, + fontWeight: typography.fontWeight.medium, + }, +}); diff --git a/app/components/orderHistoryCard/orderHistoryCard.tsx b/app/components/orderHistoryCard/orderHistoryCard.tsx new file mode 100644 index 0000000..098a362 --- /dev/null +++ b/app/components/orderHistoryCard/orderHistoryCard.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { OrderHistoryCardProps } from './orderHistoryCard.props'; +import { getStyles } from './orderHistoryCard.styles'; +import { useAppTheme } from '@theme'; + +export const OrderHistoryCard: React.FC = ({ + providerName, + providerImage, + orderDate, + status, + total, + onReorder, + onDetails, +}) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + + + {providerImage || 'πŸͺ'} + + + {providerName} + {orderDate} + + + {status} + + + + β‚Ή{total} + + {onReorder && ( + + Reorder + + )} + {onDetails && ( + + Details + + )} + + + + ); +}; diff --git a/app/components/paymentOption/index.ts b/app/components/paymentOption/index.ts new file mode 100644 index 0000000..5c2703a --- /dev/null +++ b/app/components/paymentOption/index.ts @@ -0,0 +1,2 @@ +export * from './paymentOption'; +export * from './paymentOption.props'; diff --git a/app/components/paymentOption/paymentOption.props.ts b/app/components/paymentOption/paymentOption.props.ts new file mode 100644 index 0000000..1b993ff --- /dev/null +++ b/app/components/paymentOption/paymentOption.props.ts @@ -0,0 +1,6 @@ +export interface PaymentOptionProps { + type: string; + label: string; + isSelected: boolean; + onSelect: () => void; +} diff --git a/app/components/paymentOption/paymentOption.styles.ts b/app/components/paymentOption/paymentOption.styles.ts new file mode 100644 index 0000000..f44a9fd --- /dev/null +++ b/app/components/paymentOption/paymentOption.styles.ts @@ -0,0 +1,48 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 14, + paddingHorizontal: 16, + borderWidth: 1.5, + borderColor: colors.border, + borderRadius: 12, + marginBottom: 10, + backgroundColor: colors.background, + }, + selected: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + icon: { + fontSize: 24, + marginRight: 12, + }, + label: { + flex: 1, + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.medium, + color: colors.text, + }, + radio: { + width: 22, + height: 22, + borderRadius: 11, + borderWidth: 2, + borderColor: colors.border, + justifyContent: 'center', + alignItems: 'center', + }, + radioSelected: { + borderColor: colors.primary, + }, + radioInner: { + width: 12, + height: 12, + borderRadius: 6, + backgroundColor: colors.primary, + }, +}); diff --git a/app/components/paymentOption/paymentOption.tsx b/app/components/paymentOption/paymentOption.tsx new file mode 100644 index 0000000..5fea9a2 --- /dev/null +++ b/app/components/paymentOption/paymentOption.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { PaymentOptionProps } from './paymentOption.props'; +import { getStyles } from './paymentOption.styles'; +import { useAppTheme } from '@theme'; + +export const PaymentOption: React.FC = ({ + type, + label, + isSelected, + onSelect, +}) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + {type === 'UPI' ? 'πŸ“±' : type === 'Card' ? 'πŸ’³' : type === 'Wallet' ? 'πŸ‘›' : 'πŸ’΅'} + {label} + + {isSelected && } + + + ); +}; diff --git a/app/components/providerCard/index.ts b/app/components/providerCard/index.ts new file mode 100644 index 0000000..6456569 --- /dev/null +++ b/app/components/providerCard/index.ts @@ -0,0 +1,2 @@ +export * from './providerCard'; +export * from './providerCard.props'; diff --git a/app/components/providerCard/providerCard.props.ts b/app/components/providerCard/providerCard.props.ts new file mode 100644 index 0000000..e88311f --- /dev/null +++ b/app/components/providerCard/providerCard.props.ts @@ -0,0 +1,9 @@ +export interface ProviderCardProps { + imageUrl: string; + name: string; + rating: number; + deliveryTime: string; + tag: string; + discountText?: string; + onPress?: () => void; +} diff --git a/app/components/providerCard/providerCard.styles.ts b/app/components/providerCard/providerCard.styles.ts new file mode 100644 index 0000000..d60589a --- /dev/null +++ b/app/components/providerCard/providerCard.styles.ts @@ -0,0 +1,69 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 12, + marginHorizontal: 16, + marginVertical: 6, + borderWidth: 1, + borderColor: colors.border, + }, + image: { + width: 80, + height: 80, + borderRadius: 8, + backgroundColor: colors.border, + justifyContent: 'center', + alignItems: 'center', + }, + imagePlaceholder: { + fontSize: 32, + }, + info: { + flex: 1, + marginLeft: 12, + justifyContent: 'center', + }, + name: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 4, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 2, + }, + rating: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + marginRight: 8, + }, + deliveryTime: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + tag: { + fontSize: typography.fontSize.xs, + color: colors.primary, + fontWeight: typography.fontWeight.medium, + }, + discountBadge: { + alignSelf: 'flex-start', + backgroundColor: '#FFE7E7', + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 4, + marginTop: 4, + }, + discountText: { + fontSize: typography.fontSize.xs, + color: '#D32F2F', + fontWeight: typography.fontWeight.semibold, + }, +}); diff --git a/app/components/providerCard/providerCard.tsx b/app/components/providerCard/providerCard.tsx new file mode 100644 index 0000000..82af2e8 --- /dev/null +++ b/app/components/providerCard/providerCard.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { ProviderCardProps } from './providerCard.props'; +import { getStyles } from './providerCard.styles'; +import { useAppTheme } from '@theme'; + +export const ProviderCard: React.FC = ({ + imageUrl, + name, + rating, + deliveryTime, + tag, + discountText, + onPress, +}) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + + {imageUrl || '🍽️'} + + + {name} + + ⭐ {rating.toFixed(1)} + πŸ• {deliveryTime} + + {tag} + {discountText && ( + + {discountText} + + )} + + + ); +}; diff --git a/app/components/quantitySelector/index.ts b/app/components/quantitySelector/index.ts new file mode 100644 index 0000000..c3c7677 --- /dev/null +++ b/app/components/quantitySelector/index.ts @@ -0,0 +1,2 @@ +export * from './quantitySelector'; +export * from './quantitySelector.props'; diff --git a/app/components/quantitySelector/quantitySelector.props.ts b/app/components/quantitySelector/quantitySelector.props.ts new file mode 100644 index 0000000..bd8a338 --- /dev/null +++ b/app/components/quantitySelector/quantitySelector.props.ts @@ -0,0 +1,7 @@ +export interface QuantitySelectorProps { + value: number; + onIncrement: () => void; + onDecrement: () => void; + min?: number; + max?: number; +} diff --git a/app/components/quantitySelector/quantitySelector.styles.ts b/app/components/quantitySelector/quantitySelector.styles.ts new file mode 100644 index 0000000..a8b41a9 --- /dev/null +++ b/app/components/quantitySelector/quantitySelector.styles.ts @@ -0,0 +1,33 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + }, + button: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: colors.primary, + justifyContent: 'center', + alignItems: 'center', + }, + buttonDisabled: { + backgroundColor: colors.border, + }, + buttonText: { + color: colors.white, + fontSize: 20, + fontWeight: typography.fontWeight.bold, + }, + value: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginHorizontal: 16, + minWidth: 24, + textAlign: 'center', + }, +}); diff --git a/app/components/quantitySelector/quantitySelector.tsx b/app/components/quantitySelector/quantitySelector.tsx new file mode 100644 index 0000000..5eb5665 --- /dev/null +++ b/app/components/quantitySelector/quantitySelector.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { QuantitySelectorProps } from './quantitySelector.props'; +import { getStyles } from './quantitySelector.styles'; +import { useAppTheme } from '@theme'; + +export const QuantitySelector: React.FC = ({ + value, + onIncrement, + onDecrement, + min = 0, + max = 99, +}) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + + - + + {value} + = max && styles.buttonDisabled]} + onPress={onIncrement} + disabled={value >= max} + activeOpacity={0.7} + > + + + + + ); +}; diff --git a/app/components/ratingStars/index.ts b/app/components/ratingStars/index.ts new file mode 100644 index 0000000..7e97b82 --- /dev/null +++ b/app/components/ratingStars/index.ts @@ -0,0 +1,2 @@ +export * from './ratingStars'; +export * from './ratingStars.props'; diff --git a/app/components/ratingStars/ratingStars.props.ts b/app/components/ratingStars/ratingStars.props.ts new file mode 100644 index 0000000..f2fea91 --- /dev/null +++ b/app/components/ratingStars/ratingStars.props.ts @@ -0,0 +1,5 @@ +export interface RatingStarsProps { + rating: number; + onRate?: (rating: number) => void; + size?: number; +} diff --git a/app/components/ratingStars/ratingStars.styles.ts b/app/components/ratingStars/ratingStars.styles.ts new file mode 100644 index 0000000..a8dbf33 --- /dev/null +++ b/app/components/ratingStars/ratingStars.styles.ts @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native'; + +export const getStyles = () => StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + star: { + marginHorizontal: 4, + }, +}); diff --git a/app/components/ratingStars/ratingStars.tsx b/app/components/ratingStars/ratingStars.tsx new file mode 100644 index 0000000..0fb8f18 --- /dev/null +++ b/app/components/ratingStars/ratingStars.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { RatingStarsProps } from './ratingStars.props'; +import { getStyles } from './ratingStars.styles'; + +export const RatingStars: React.FC = ({ + rating, + onRate, + size = 32, +}) => { + const styles = getStyles(); + + return ( + + {[1, 2, 3, 4, 5].map((star) => ( + onRate?.(star)} + disabled={!onRate} + activeOpacity={0.7} + > + + {star <= rating ? '⭐' : 'β˜†'} + + + ))} + + ); +}; diff --git a/app/components/searchBar/index.ts b/app/components/searchBar/index.ts new file mode 100644 index 0000000..e5e40ec --- /dev/null +++ b/app/components/searchBar/index.ts @@ -0,0 +1,2 @@ +export * from './searchBar'; +export * from './searchBar.props'; diff --git a/app/components/searchBar/searchBar.props.ts b/app/components/searchBar/searchBar.props.ts new file mode 100644 index 0000000..f7ba489 --- /dev/null +++ b/app/components/searchBar/searchBar.props.ts @@ -0,0 +1,7 @@ +export interface SearchBarProps { + value: string; + onChangeText: (text: string) => void; + placeholder?: string; + onFocus?: () => void; + onSubmit?: () => void; +} diff --git a/app/components/searchBar/searchBar.styles.ts b/app/components/searchBar/searchBar.styles.ts new file mode 100644 index 0000000..445ebe1 --- /dev/null +++ b/app/components/searchBar/searchBar.styles.ts @@ -0,0 +1,28 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.inputBg, + borderRadius: 12, + borderWidth: 1, + borderColor: colors.border, + paddingHorizontal: 12, + height: 48, + marginHorizontal: 16, + marginVertical: 8, + }, + icon: { + fontSize: 18, + marginRight: 8, + }, + input: { + flex: 1, + height: '100%', + color: colors.text, + fontSize: typography.fontSize.md, + paddingVertical: 0, + }, +}); diff --git a/app/components/searchBar/searchBar.tsx b/app/components/searchBar/searchBar.tsx new file mode 100644 index 0000000..8b6a603 --- /dev/null +++ b/app/components/searchBar/searchBar.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { View, TextInput, Text } from 'react-native'; +import { SearchBarProps } from './searchBar.props'; +import { getStyles } from './searchBar.styles'; +import { useAppTheme } from '@theme'; + +export const SearchBar: React.FC = ({ + value, + onChangeText, + placeholder = 'Search...', + onFocus, + onSubmit, +}) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + πŸ” + + + ); +}; diff --git a/app/components/stepProgress/index.ts b/app/components/stepProgress/index.ts new file mode 100644 index 0000000..e39b473 --- /dev/null +++ b/app/components/stepProgress/index.ts @@ -0,0 +1,2 @@ +export * from './stepProgress'; +export * from './stepProgress.props'; diff --git a/app/components/stepProgress/stepProgress.props.ts b/app/components/stepProgress/stepProgress.props.ts new file mode 100644 index 0000000..338e648 --- /dev/null +++ b/app/components/stepProgress/stepProgress.props.ts @@ -0,0 +1,4 @@ +export interface StepProgressProps { + steps: { key: string; label: string }[]; + activeStep: number; +} diff --git a/app/components/stepProgress/stepProgress.styles.ts b/app/components/stepProgress/stepProgress.styles.ts new file mode 100644 index 0000000..8446deb --- /dev/null +++ b/app/components/stepProgress/stepProgress.styles.ts @@ -0,0 +1,55 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 16, + paddingHorizontal: 24, + }, + stepContainer: { + alignItems: 'center', + flex: 1, + }, + circle: { + width: 28, + height: 28, + borderRadius: 14, + backgroundColor: colors.border, + justifyContent: 'center', + alignItems: 'center', + }, + circleActive: { + backgroundColor: colors.primary, + }, + circleCompleted: { + backgroundColor: colors.primary, + }, + circleText: { + fontSize: typography.fontSize.xs, + color: colors.white, + fontWeight: typography.fontWeight.bold, + }, + label: { + fontSize: typography.fontSize.xs, + color: colors.textSecondary, + marginTop: 4, + textAlign: 'center', + }, + labelActive: { + color: colors.primary, + fontWeight: typography.fontWeight.medium, + }, + connector: { + flex: 1, + height: 2, + backgroundColor: colors.border, + marginHorizontal: -4, + marginBottom: 20, + }, + connectorActive: { + backgroundColor: colors.primary, + }, +}); diff --git a/app/components/stepProgress/stepProgress.tsx b/app/components/stepProgress/stepProgress.tsx new file mode 100644 index 0000000..7e1547c --- /dev/null +++ b/app/components/stepProgress/stepProgress.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { StepProgressProps } from './stepProgress.props'; +import { getStyles } from './stepProgress.styles'; +import { useAppTheme } from '@theme'; + +export const StepProgress: React.FC = ({ steps, activeStep }) => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + + {steps.map((step, index) => ( + + + + {index + 1} + + + {step.label} + + + {index < steps.length - 1 && ( + + )} + + ))} + + ); +}; diff --git a/app/features/screens/LoginScreen/LoginScreen.styles.ts b/app/features/screens/LoginScreen/LoginScreen.styles.ts index bb5ab70..51a8b22 100644 --- a/app/features/screens/LoginScreen/LoginScreen.styles.ts +++ b/app/features/screens/LoginScreen/LoginScreen.styles.ts @@ -29,51 +29,4 @@ export const getStyles = (colors: any) => StyleSheet.create({ formContainer: { width: '100%', }, - row: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: 24, - marginTop: 4, - }, - rememberMeContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - - checkboxText: { - fontSize: typography.fontSize.sm, - color: colors.textSecondary, - fontWeight: typography.fontWeight.medium, - }, - dividerContainer: { - flexDirection: 'row', - alignItems: 'center', - marginVertical: 24, - }, - line: { - flex: 1, - height: 1, - backgroundColor: colors.border, - }, - dividerText: { - marginHorizontal: 16, - fontSize: typography.fontSize.sm, - color: colors.textSecondary, - }, - socialContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - gap: 16, - marginBottom: 40, - }, - footerText: { - textAlign: 'center', - fontSize: typography.fontSize.sm, - color: colors.textSecondary, - }, - signUpLink: { - color: colors.primary, - fontWeight: typography.fontWeight.bold, - }, }); diff --git a/app/features/screens/LoginScreen/LoginScreen.tsx b/app/features/screens/LoginScreen/LoginScreen.tsx index 00424fe..c2dfb75 100644 --- a/app/features/screens/LoginScreen/LoginScreen.tsx +++ b/app/features/screens/LoginScreen/LoginScreen.tsx @@ -5,63 +5,39 @@ import { KeyboardAvoidingView, Platform, ScrollView, - TouchableOpacity, - Alert, } from 'react-native'; -import { getStyles } from './LoginScreen.styles'; -import { CustomInput, PrimaryButton, SocialButton } from '@components'; -import CheckBox from '@react-native-community/checkbox'; +import { useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { getStyles } from './loginScreen.styles'; +import { CustomInput, PrimaryButton } from '@components'; import { useAppTheme } from '@theme'; +import { useAppDispatch } from '../../../hooks/useAppDispatch'; +import { loginWithPhone } from '../../../store/authSlice'; +import { AuthStackParamList } from '../../../navigation/authStack'; + +type LoginNavProp = StackNavigationProp; export const LoginScreen: React.FC = () => { const { colors } = useAppTheme(); const styles = getStyles(colors); + const navigation = useNavigation(); + const dispatch = useAppDispatch(); const [mobileNumber, setMobileNumber] = useState(''); - const [password, setPassword] = useState(''); - const [rememberMe, setRememberMe] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [errors, setErrors] = useState<{ mobile?: string; password?: string }>({}); - - const validate = () => { - const newErrors: { mobile?: string; password?: string } = {}; - if (!mobileNumber) { - newErrors.mobile = 'Mobile number is required'; - } else if (mobileNumber.length < 9) { - newErrors.mobile = 'Please enter a valid mobile number'; - } - - if (!password) { - newErrors.password = 'Password is required'; - } else if (password.length < 6) { - newErrors.password = 'Password must be at least 6 characters'; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; + const [error, setError] = useState(undefined); const handleLogin = () => { - if (!validate()) return; - - setIsLoading(true); - // Simulate API request - setTimeout(() => { - setIsLoading(false); - Alert.alert('Success', 'Logged in successfully!'); - }, 1500); - }; - - const handleSocialLogin = (provider: 'google' | 'apple') => { - Alert.alert('Social Login', `Initiating ${provider} login...`); - }; - - const handleForgotPassword = () => { - Alert.alert('Forgot Password', 'Redirecting to reset password...'); - }; - - const handleSignUp = () => { - Alert.alert('Sign Up', 'Redirecting to registration...'); + if (!mobileNumber) { + setError('Mobile number is required'); + return; + } + if (mobileNumber.length < 10) { + setError('Please enter a valid mobile number'); + return; + } + setError(undefined); + dispatch(loginWithPhone(mobileNumber)); + navigation.navigate('OtpScreen', { mobileNumber }); }; return ( @@ -74,7 +50,7 @@ export const LoginScreen: React.FC = () => { showsVerticalScrollIndicator={false} > - Welcome back πŸ‘‹ + Welcome back Login to continue @@ -86,68 +62,16 @@ export const LoginScreen: React.FC = () => { value={mobileNumber} onChangeText={(text) => { setMobileNumber(text); - if (errors.mobile) setErrors({ ...errors, mobile: undefined }); + if (error) setError(undefined); }} - error={errors.mobile} + error={error} /> - { - setPassword(text); - if (errors.password) setErrors({ ...errors, password: undefined }); - }} - error={errors.password} - /> - - - setRememberMe(!rememberMe)} - activeOpacity={0.8} - > - - Remember me - - - - - - - or continue with - - - - - handleSocialLogin('google')} /> - handleSocialLogin('apple')} /> - - - - - Don’t have an account? Sign up - - diff --git a/app/features/screens/LoginScreen/index.ts b/app/features/screens/LoginScreen/index.ts index 95c95bb..5c766db 100644 --- a/app/features/screens/LoginScreen/index.ts +++ b/app/features/screens/LoginScreen/index.ts @@ -1,2 +1,2 @@ -export * from './LoginScreen'; -export * from './LoginScreen.styles'; +export * from './loginScreen'; +export * from './loginScreen.styles'; diff --git a/app/features/screens/accountScreen/accountScreen.styles.ts b/app/features/screens/accountScreen/accountScreen.styles.ts new file mode 100644 index 0000000..17045d4 --- /dev/null +++ b/app/features/screens/accountScreen/accountScreen.styles.ts @@ -0,0 +1,68 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + paddingBottom: 40, + }, + profileCard: { + alignItems: 'center', + paddingVertical: 32, + borderBottomWidth: 1, + borderBottomColor: colors.border, + marginHorizontal: 16, + }, + avatar: { + width: 72, + height: 72, + borderRadius: 36, + backgroundColor: colors.primary, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 12, + }, + avatarText: { + fontSize: 28, + fontWeight: typography.fontWeight.bold, + color: '#FFFFFF', + }, + profileName: { + fontSize: typography.fontSize.xl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 4, + }, + profilePhone: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + }, + menuSection: { + marginTop: 16, + paddingHorizontal: 16, + }, + menuItem: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 16, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + menuIcon: { + fontSize: 20, + marginRight: 14, + }, + menuLabel: { + flex: 1, + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.medium, + color: colors.text, + }, + menuArrow: { + fontSize: 18, + color: colors.textSecondary, + }, +}); diff --git a/app/features/screens/accountScreen/accountScreen.tsx b/app/features/screens/accountScreen/accountScreen.tsx new file mode 100644 index 0000000..5846909 --- /dev/null +++ b/app/features/screens/accountScreen/accountScreen.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, ScrollView } from 'react-native'; +import { getStyles } from './accountScreen.styles'; +import { Header, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; +import { useAppDispatch, useAppSelector } from '../../../hooks/useAppDispatch'; +import { logout } from '../../../store/authSlice'; + +const MENU_ITEMS = [ + { icon: 'πŸ“', label: 'My Addresses' }, + { icon: 'πŸ’³', label: 'Payment Methods' }, + { icon: 'πŸ””', label: 'Notifications' }, + { icon: '❓', label: 'Help & Support' }, + { icon: 'πŸ“‹', label: 'Terms & Conditions' }, + { icon: 'πŸ”’', label: 'Privacy Policy' }, +]; + +export const AccountScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const dispatch = useAppDispatch(); + const user = useAppSelector((state) => state.auth.user); + + return ( + +
+ + + + + {user?.name?.charAt(0)?.toUpperCase() || 'U'} + + + {user?.name || 'User'} + {user?.mobileNumber || '+91 98765 43210'} + + + + {MENU_ITEMS.map((item, index) => ( + + {item.icon} + {item.label} + {'>'} + + ))} + + + dispatch(logout())} + style={{ marginTop: 24 }} + /> + + + ); +}; diff --git a/app/features/screens/accountScreen/index.ts b/app/features/screens/accountScreen/index.ts new file mode 100644 index 0000000..efe9de3 --- /dev/null +++ b/app/features/screens/accountScreen/index.ts @@ -0,0 +1 @@ +export * from './accountScreen'; diff --git a/app/features/screens/cartScreen/cartScreen.styles.ts b/app/features/screens/cartScreen/cartScreen.styles.ts new file mode 100644 index 0000000..91c2dbc --- /dev/null +++ b/app/features/screens/cartScreen/cartScreen.styles.ts @@ -0,0 +1,109 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + list: { + paddingBottom: 100, + }, + cartItem: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: 14, + paddingHorizontal: 16, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + itemInfo: { + flex: 1, + }, + itemTitle: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.medium, + color: colors.text, + marginBottom: 4, + }, + itemPrice: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.bold, + color: colors.text, + }, + footer: { + padding: 16, + }, + couponRow: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 20, + }, + couponInput: { + flex: 1, + height: 48, + borderWidth: 1, + borderColor: colors.border, + borderRadius: 12, + paddingHorizontal: 16, + fontSize: typography.fontSize.md, + color: colors.text, + backgroundColor: colors.inputBg, + marginRight: 8, + }, + applyButton: { + paddingHorizontal: 20, + paddingVertical: 14, + borderRadius: 12, + backgroundColor: colors.primary, + }, + applyButtonText: { + color: '#FFFFFF', + fontWeight: typography.fontWeight.semibold, + fontSize: typography.fontSize.sm, + }, + feeRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 8, + }, + feeLabel: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + feeValue: { + fontSize: typography.fontSize.sm, + fontWeight: typography.fontWeight.medium, + color: colors.text, + }, + totalRow: { + borderTopWidth: 1, + borderTopColor: colors.border, + paddingTop: 12, + marginTop: 4, + }, + totalLabel: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.bold, + color: colors.text, + }, + totalValue: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.bold, + color: colors.text, + }, + bottomPanel: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + borderTopWidth: 1, + borderTopColor: colors.border, + backgroundColor: colors.background, + }, + bottomTotal: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.bold, + color: colors.text, + }, +}); diff --git a/app/features/screens/cartScreen/cartScreen.tsx b/app/features/screens/cartScreen/cartScreen.tsx new file mode 100644 index 0000000..72bc185 --- /dev/null +++ b/app/features/screens/cartScreen/cartScreen.tsx @@ -0,0 +1,110 @@ +import React, { useState } from 'react'; +import { + View, + Text, + FlatList, + TextInput, + TouchableOpacity, +} from 'react-native'; +import { getStyles } from './cartScreen.styles'; +import { Header, QuantitySelector, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; +import { useAppDispatch, useAppSelector } from '../../../hooks/useAppDispatch'; +import { removeItem, applyCoupon, removeCoupon, selectCartTotal } from '../../../store/cartSlice'; + +export const CartScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const dispatch = useAppDispatch(); + const { items, couponCode } = useAppSelector((state) => state.cart); + const totals = useAppSelector(selectCartTotal); + const [couponInput, setCouponInput] = useState(''); + + return ( + +
{}} /> + item.id} + renderItem={({ item }) => ( + + + {item.item.title} + β‚Ή{item.item.price} + + {}} + onDecrement={() => dispatch(removeItem(item.item.id))} + /> + + )} + ListFooterComponent={ + + + + { + if (couponCode) { + dispatch(removeCoupon()); + setCouponInput(''); + } else if (couponInput) { + dispatch(applyCoupon(couponInput)); + } + }} + activeOpacity={0.7} + > + + {couponCode ? 'Remove' : 'Apply'} + + + + + + Subtotal + β‚Ή{totals.subtotal} + + + Delivery Fee + β‚Ή{totals.deliveryFee} + + + Platform Fee + β‚Ή{totals.platformFee} + + {totals.discount > 0 && ( + + + Discount + + + -β‚Ή{totals.discount} + + + )} + + Total + β‚Ή{totals.total} + + + } + contentContainerStyle={styles.list} + /> + + Total: β‚Ή{totals.total} + {}} + style={{ flex: 1, marginLeft: 12 }} + /> + + + ); +}; diff --git a/app/features/screens/cartScreen/index.ts b/app/features/screens/cartScreen/index.ts new file mode 100644 index 0000000..2740d95 --- /dev/null +++ b/app/features/screens/cartScreen/index.ts @@ -0,0 +1 @@ +export * from './cartScreen'; diff --git a/app/features/screens/checkoutAddressScreen/checkoutAddressScreen.styles.ts b/app/features/screens/checkoutAddressScreen/checkoutAddressScreen.styles.ts new file mode 100644 index 0000000..29caf10 --- /dev/null +++ b/app/features/screens/checkoutAddressScreen/checkoutAddressScreen.styles.ts @@ -0,0 +1,92 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + padding: 16, + }, + addressCard: { + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 16, + borderWidth: 1, + borderColor: colors.border, + marginBottom: 24, + }, + addressLabel: { + fontSize: typography.fontSize.xs, + color: colors.textSecondary, + marginBottom: 4, + }, + addressText: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 4, + }, + addressSubtext: { + fontSize: typography.fontSize.sm, + color: colors.primary, + fontWeight: typography.fontWeight.medium, + }, + sectionTitle: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 12, + }, + optionCard: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + borderRadius: 12, + borderWidth: 1.5, + borderColor: colors.border, + marginBottom: 10, + backgroundColor: colors.background, + }, + optionCardSelected: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + optionInfo: { + flex: 1, + }, + optionTitle: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.medium, + color: colors.text, + marginBottom: 2, + }, + optionSubtext: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + radio: { + width: 22, + height: 22, + borderRadius: 11, + borderWidth: 2, + borderColor: colors.border, + justifyContent: 'center', + alignItems: 'center', + }, + radioSelected: { + borderColor: colors.primary, + }, + radioInner: { + width: 12, + height: 12, + borderRadius: 6, + backgroundColor: colors.primary, + }, + footer: { + padding: 16, + borderTopWidth: 1, + borderTopColor: colors.border, + }, +}); diff --git a/app/features/screens/checkoutAddressScreen/checkoutAddressScreen.tsx b/app/features/screens/checkoutAddressScreen/checkoutAddressScreen.tsx new file mode 100644 index 0000000..70f2ee2 --- /dev/null +++ b/app/features/screens/checkoutAddressScreen/checkoutAddressScreen.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + ScrollView, +} from 'react-native'; +import { getStyles } from './checkoutAddressScreen.styles'; +import { Header, StepProgress, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; + +const CHECKOUT_STEPS = [ + { key: 'address', label: 'Address' }, + { key: 'payment', label: 'Payment' }, + { key: 'confirm', label: 'Confirm' }, +]; + +export const CheckoutAddressScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const [deliveryOption, setDeliveryOption] = useState<'standard' | 'express'>('standard'); + + return ( + +
{}} /> + + + + Deliver to + + Koramangala 4th Block, Bengaluru, 560034 + + Home + + + Delivery Option + setDeliveryOption('standard')} + activeOpacity={0.7} + > + + Standard Delivery + Free β€’ 25-30 min + + + {deliveryOption === 'standard' && } + + + + setDeliveryOption('express')} + activeOpacity={0.7} + > + + Express Delivery + β‚Ή40 β€’ 10-15 min + + + {deliveryOption === 'express' && } + + + + + {}} /> + + + ); +}; diff --git a/app/features/screens/checkoutAddressScreen/index.ts b/app/features/screens/checkoutAddressScreen/index.ts new file mode 100644 index 0000000..46caf4c --- /dev/null +++ b/app/features/screens/checkoutAddressScreen/index.ts @@ -0,0 +1 @@ +export * from './checkoutAddressScreen'; diff --git a/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.styles.ts b/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.styles.ts new file mode 100644 index 0000000..578b1af --- /dev/null +++ b/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.styles.ts @@ -0,0 +1,23 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + padding: 16, + }, + sectionTitle: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 16, + }, + footer: { + padding: 16, + borderTopWidth: 1, + borderTopColor: colors.border, + }, +}); diff --git a/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.tsx b/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.tsx new file mode 100644 index 0000000..dd937bd --- /dev/null +++ b/app/features/screens/checkoutPaymentScreen/checkoutPaymentScreen.tsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { View, Text, ScrollView } from 'react-native'; +import { getStyles } from './checkoutPaymentScreen.styles'; +import { Header, StepProgress, PaymentOption, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; + +const CHECKOUT_STEPS = [ + { key: 'address', label: 'Address' }, + { key: 'payment', label: 'Payment' }, + { key: 'confirm', label: 'Confirm' }, +]; + +const PAYMENT_METHODS = [ + { id: 'upi', type: 'UPI' as const, label: 'UPI (Google Pay, PhonePe)' }, + { id: 'card', type: 'Card' as const, label: 'Credit / Debit Card' }, + { id: 'wallet', type: 'Wallet' as const, label: 'Wallet' }, + { id: 'cod', type: 'COD' as const, label: 'Cash on Delivery' }, +]; + +export const CheckoutPaymentScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const [selectedMethod, setSelectedMethod] = useState('upi'); + + return ( + +
{}} /> + + + Select Payment Method + {PAYMENT_METHODS.map((method) => ( + setSelectedMethod(method.id)} + /> + ))} + + + {}} /> + + + ); +}; diff --git a/app/features/screens/checkoutPaymentScreen/index.ts b/app/features/screens/checkoutPaymentScreen/index.ts new file mode 100644 index 0000000..6d7df67 --- /dev/null +++ b/app/features/screens/checkoutPaymentScreen/index.ts @@ -0,0 +1 @@ +export * from './checkoutPaymentScreen'; diff --git a/app/features/screens/completeProfileScreen/completeProfileScreen.styles.ts b/app/features/screens/completeProfileScreen/completeProfileScreen.styles.ts new file mode 100644 index 0000000..fcd2624 --- /dev/null +++ b/app/features/screens/completeProfileScreen/completeProfileScreen.styles.ts @@ -0,0 +1,62 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + paddingHorizontal: 24, + }, + scrollContainer: { + flexGrow: 1, + paddingTop: 60, + paddingBottom: 24, + }, + title: { + fontSize: typography.fontSize.xxl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 8, + }, + subtitle: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + marginBottom: 32, + }, + form: { + marginBottom: 24, + }, + labelText: { + fontSize: typography.fontSize.sm, + fontWeight: typography.fontWeight.medium, + color: colors.textSecondary, + marginBottom: 10, + marginTop: 4, + }, + labelRow: { + flexDirection: 'row', + gap: 12, + marginBottom: 16, + }, + labelChip: { + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 20, + borderWidth: 1.5, + borderColor: colors.border, + backgroundColor: colors.background, + }, + labelChipSelected: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + labelChipText: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + fontWeight: typography.fontWeight.medium, + }, + labelChipTextSelected: { + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, +}); diff --git a/app/features/screens/completeProfileScreen/completeProfileScreen.tsx b/app/features/screens/completeProfileScreen/completeProfileScreen.tsx new file mode 100644 index 0000000..8ae1d2e --- /dev/null +++ b/app/features/screens/completeProfileScreen/completeProfileScreen.tsx @@ -0,0 +1,95 @@ +import React, { useState } from 'react'; +import { + View, + Text, + KeyboardAvoidingView, + Platform, + ScrollView, + TouchableOpacity, +} from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { getStyles } from './completeProfileScreen.styles'; +import { CustomInput, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; +import { useAppDispatch } from '../../../hooks/useAppDispatch'; +import { completeProfile } from '../../../store/authSlice'; +import { AuthStackParamList } from '../../../navigation/authStack'; + +type NavProp = StackNavigationProp; + +const LOCATION_LABELS = ['Home', 'Work', 'Other']; + +export const CompleteProfileScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const navigation = useNavigation(); + const dispatch = useAppDispatch(); + + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [selectedLabel, setSelectedLabel] = useState('Home'); + + const handleSave = () => { + dispatch(completeProfile({ name, email, locationLabels: [selectedLabel] })); + navigation.navigate('PreferencesScreen'); + }; + + return ( + + + Complete Profile + Tell us about yourself + + + + + + Location Label + + {LOCATION_LABELS.map((label) => ( + setSelectedLabel(label)} + activeOpacity={0.7} + > + + {label} + + + ))} + + + + + + + ); +}; diff --git a/app/features/screens/completeProfileScreen/index.ts b/app/features/screens/completeProfileScreen/index.ts new file mode 100644 index 0000000..1aaebda --- /dev/null +++ b/app/features/screens/completeProfileScreen/index.ts @@ -0,0 +1 @@ +export * from './completeProfileScreen'; diff --git a/app/features/screens/helpSupportScreen/helpSupportScreen.styles.ts b/app/features/screens/helpSupportScreen/helpSupportScreen.styles.ts new file mode 100644 index 0000000..9983ee6 --- /dev/null +++ b/app/features/screens/helpSupportScreen/helpSupportScreen.styles.ts @@ -0,0 +1,62 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + padding: 16, + paddingBottom: 40, + }, + sectionTitle: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 16, + marginTop: 8, + }, + faqItem: { + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 16, + marginBottom: 10, + borderWidth: 1, + borderColor: colors.border, + }, + faqQuestion: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 4, + }, + faqAnswer: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + lineHeight: 20, + }, + supportCard: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 16, + marginBottom: 10, + borderWidth: 1, + borderColor: colors.border, + }, + supportIcon: { + fontSize: 24, + marginRight: 12, + }, + supportLabel: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + supportValue: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + }, +}); diff --git a/app/features/screens/helpSupportScreen/helpSupportScreen.tsx b/app/features/screens/helpSupportScreen/helpSupportScreen.tsx new file mode 100644 index 0000000..ec1d8d2 --- /dev/null +++ b/app/features/screens/helpSupportScreen/helpSupportScreen.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { View, Text, ScrollView, TouchableOpacity } from 'react-native'; +import { getStyles } from './helpSupportScreen.styles'; +import { Header } from '@components'; +import { useAppTheme } from '@theme'; + +const FAQS = [ + { q: 'How do I track my order?', a: 'Go to Orders tab and tap on your active order to see tracking.' }, + { q: 'What is the delivery time?', a: 'Standard delivery takes 25-30 min, Express takes 10-15 min.' }, + { q: 'How do I cancel an order?', a: 'Contact support to cancel an order before it is dispatched.' }, + { q: 'What payment methods are accepted?', a: 'UPI, Credit/Debit Card, Wallet, and Cash on Delivery.' }, +]; + +export const HelpSupportScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + +
+ + Frequently Asked Questions + {FAQS.map((faq, index) => ( + + {faq.q} + {faq.a} + + ))} + + Contact Support + + πŸ“ž + + Call us + +91 1800 123 4567 + + + + βœ‰οΈ + + Email us + support@sgdelivery.com + + + + + ); +}; diff --git a/app/features/screens/helpSupportScreen/index.ts b/app/features/screens/helpSupportScreen/index.ts new file mode 100644 index 0000000..cad255b --- /dev/null +++ b/app/features/screens/helpSupportScreen/index.ts @@ -0,0 +1 @@ +export * from './helpSupportScreen'; diff --git a/app/features/screens/homeScreen/homeScreen.styles.ts b/app/features/screens/homeScreen/homeScreen.styles.ts new file mode 100644 index 0000000..8fa542b --- /dev/null +++ b/app/features/screens/homeScreen/homeScreen.styles.ts @@ -0,0 +1,64 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + addressBar: { + paddingHorizontal: 16, + paddingTop: 12, + paddingBottom: 4, + }, + addressLabel: { + fontSize: typography.fontSize.xs, + color: colors.textSecondary, + marginBottom: 2, + }, + addressRow: { + flexDirection: 'row', + alignItems: 'center', + }, + addressText: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + flex: 1, + }, + dropdownArrow: { + fontSize: 12, + color: colors.textSecondary, + marginLeft: 4, + }, + banner: { + backgroundColor: '#05824C', + marginHorizontal: 16, + borderRadius: 12, + padding: 20, + marginVertical: 8, + }, + bannerText: { + fontSize: typography.fontSize.xxl, + fontWeight: typography.fontWeight.bold, + color: '#FFFFFF', + }, + bannerSubtext: { + fontSize: typography.fontSize.sm, + color: '#FFFFFF', + opacity: 0.9, + marginTop: 4, + }, + chipRow: { + paddingHorizontal: 16, + paddingVertical: 12, + }, + sectionTitle: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.bold, + color: colors.text, + paddingHorizontal: 16, + marginBottom: 8, + marginTop: 4, + }, +}); diff --git a/app/features/screens/homeScreen/homeScreen.tsx b/app/features/screens/homeScreen/homeScreen.tsx new file mode 100644 index 0000000..aaec42e --- /dev/null +++ b/app/features/screens/homeScreen/homeScreen.tsx @@ -0,0 +1,106 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + View, + Text, + ScrollView, + TouchableOpacity, + FlatList, +} from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; +import { getStyles } from './homeScreen.styles'; +import { SearchBar, ProviderCard, CategoryChip } from '@components'; +import { useAppTheme } from '@theme'; +import { deliveryService } from '../../../services/deliveryService'; +import { Provider } from '../../../interfaces'; +import { AppStackParamList } from '../../../navigation/appStack'; + +type NavProp = BottomTabNavigationProp; + +const CATEGORIES = [ + { key: 'all', icon: '🏠', label: 'All' }, + { key: 'Food', icon: 'πŸ”', label: 'Food' }, + { key: 'Groceries', icon: 'πŸ›’', label: 'Groceries' }, + { key: 'Pharmacy', icon: 'πŸ’Š', label: 'Pharmacy' }, + { key: 'More', icon: 'πŸ“¦', label: 'More' }, +]; + +export const HomeScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const navigation = useNavigation(); + + const [providers, setProviders] = useState([]); + const [selectedCategory, setSelectedCategory] = useState('all'); + + useEffect(() => { + deliveryService.getProviders().then(setProviders); + }, []); + + const filteredProviders = selectedCategory === 'all' + ? providers + : providers.filter((p) => p.tag === selectedCategory); + + const handleSearchFocus = useCallback(() => { + navigation.navigate('SearchScreen'); + }, [navigation]); + + return ( + + + Deliver to + + Koramangala 4th Block, B... + β–Ό + + + + {}} + placeholder="Search providers or items..." + onFocus={handleSearchFocus} + /> + + + 50% OFF + on your first order + + + item.key} + showsHorizontalScrollIndicator={false} + contentContainerStyle={styles.chipRow} + renderItem={({ item }) => ( + setSelectedCategory(item.key)} + /> + )} + /> + + + {selectedCategory === 'all' ? 'Popular Providers' : selectedCategory} + + + {filteredProviders.map((provider) => ( + navigation.navigate('SearchScreen')} + /> + ))} + + + + ); +}; diff --git a/app/features/screens/homeScreen/index.ts b/app/features/screens/homeScreen/index.ts new file mode 100644 index 0000000..eacd088 --- /dev/null +++ b/app/features/screens/homeScreen/index.ts @@ -0,0 +1 @@ +export * from './homeScreen'; diff --git a/app/features/screens/index.ts b/app/features/screens/index.ts index 2c7fb45..fcbffc6 100644 --- a/app/features/screens/index.ts +++ b/app/features/screens/index.ts @@ -1 +1,21 @@ -export * from './LoginScreen'; +export * from './loginScreen'; +export * from './otpScreen'; +export * from './setLocationScreen'; +export * from './completeProfileScreen'; +export * from './preferencesScreen'; +export * from './onboardingCompleteScreen'; +export * from './homeScreen'; +export * from './searchScreen'; +export * from './providerListScreen'; +export * from './providerDetailsScreen'; +export * from './cartScreen'; +export * from './checkoutAddressScreen'; +export * from './checkoutPaymentScreen'; +export * from './orderConfirmedScreen'; +export * from './orderTrackingScreen'; +export * from './liveTrackingScreen'; +export * from './orderDeliveredScreen'; +export * from './myOrdersScreen'; +export * from './offersScreen'; +export * from './helpSupportScreen'; +export * from './accountScreen'; diff --git a/app/features/screens/liveTrackingScreen/index.ts b/app/features/screens/liveTrackingScreen/index.ts new file mode 100644 index 0000000..293397b --- /dev/null +++ b/app/features/screens/liveTrackingScreen/index.ts @@ -0,0 +1 @@ +export * from './liveTrackingScreen'; diff --git a/app/features/screens/liveTrackingScreen/liveTrackingScreen.styles.ts b/app/features/screens/liveTrackingScreen/liveTrackingScreen.styles.ts new file mode 100644 index 0000000..5c80750 --- /dev/null +++ b/app/features/screens/liveTrackingScreen/liveTrackingScreen.styles.ts @@ -0,0 +1,72 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + mapPlaceholder: { + flex: 1, + backgroundColor: colors.inputBg, + justifyContent: 'center', + alignItems: 'center', + margin: 16, + borderRadius: 16, + borderWidth: 1, + borderColor: colors.border, + }, + mapIcon: { + fontSize: 48, + marginBottom: 8, + }, + mapText: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.semibold, + color: colors.textSecondary, + }, + mapSubtext: { + fontSize: typography.fontSize.xs, + color: colors.placeholder, + marginTop: 4, + marginBottom: 16, + }, + blinkingBadge: { + backgroundColor: '#4CAF50', + paddingHorizontal: 12, + paddingVertical: 4, + borderRadius: 12, + }, + blinkingText: { + fontSize: typography.fontSize.sm, + color: '#FFFFFF', + fontWeight: typography.fontWeight.semibold, + }, + agentSheet: { + padding: 20, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + backgroundColor: colors.cardBg, + borderTopWidth: 1, + borderTopColor: colors.border, + }, + agentName: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 4, + }, + agentRating: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + marginBottom: 4, + }, + agentVehicle: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + marginBottom: 16, + }, + agentActions: { + flexDirection: 'row', + }, +}); diff --git a/app/features/screens/liveTrackingScreen/liveTrackingScreen.tsx b/app/features/screens/liveTrackingScreen/liveTrackingScreen.tsx new file mode 100644 index 0000000..a31fab9 --- /dev/null +++ b/app/features/screens/liveTrackingScreen/liveTrackingScreen.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { getStyles } from './liveTrackingScreen.styles'; +import { Header, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; + +export const LiveTrackingScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + +
{}} /> + + πŸ—ΊοΈ + Live Map + + Delivery partner location tracking + + + ● Live + + + + Rahul Sharma + ⭐ 4.8 + KA-01-AB-1234 β€’ Honda Activa + + {}} + style={{ flex: 1, marginRight: 8 }} + /> + {}} + style={{ flex: 1, marginLeft: 8 }} + /> + + + + ); +}; diff --git a/app/features/screens/myOrdersScreen/index.ts b/app/features/screens/myOrdersScreen/index.ts new file mode 100644 index 0000000..b31e5a4 --- /dev/null +++ b/app/features/screens/myOrdersScreen/index.ts @@ -0,0 +1 @@ +export * from './myOrdersScreen'; diff --git a/app/features/screens/myOrdersScreen/myOrdersScreen.styles.ts b/app/features/screens/myOrdersScreen/myOrdersScreen.styles.ts new file mode 100644 index 0000000..8fddd74 --- /dev/null +++ b/app/features/screens/myOrdersScreen/myOrdersScreen.styles.ts @@ -0,0 +1,46 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + tabRow: { + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 8, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + tab: { + paddingHorizontal: 20, + paddingVertical: 8, + marginRight: 8, + borderRadius: 20, + backgroundColor: colors.inputBg, + }, + tabActive: { + backgroundColor: colors.primary, + }, + tabText: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + fontWeight: typography.fontWeight.medium, + }, + tabTextActive: { + color: '#FFFFFF', + fontWeight: typography.fontWeight.semibold, + }, + list: { + paddingVertical: 8, + }, + empty: { + alignItems: 'center', + paddingTop: 60, + }, + emptyText: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + }, +}); diff --git a/app/features/screens/myOrdersScreen/myOrdersScreen.tsx b/app/features/screens/myOrdersScreen/myOrdersScreen.tsx new file mode 100644 index 0000000..8bb9be8 --- /dev/null +++ b/app/features/screens/myOrdersScreen/myOrdersScreen.tsx @@ -0,0 +1,78 @@ +import React, { useState } from 'react'; +import { + View, + Text, + FlatList, + TouchableOpacity, +} from 'react-native'; +import { getStyles } from './myOrdersScreen.styles'; +import { Header, OrderHistoryCard } from '@components'; +import { useAppTheme } from '@theme'; + +const TABS = ['All', 'Ongoing', 'Completed']; +const mockOrders = [ + { id: '1', providerName: 'Pizza Planet', providerImage: '', orderDate: '29 Jun 2026', status: 'Delivered', total: 599 }, + { id: '2', providerName: 'Fresh Mart', providerImage: '', orderDate: '28 Jun 2026', status: 'Ongoing', total: 349 }, + { id: '3', providerName: 'Burger Barn', providerImage: '', orderDate: '27 Jun 2026', status: 'Delivered', total: 449 }, +]; + +export const MyOrdersScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const [activeTab, setActiveTab] = useState('All'); + + const filteredOrders = activeTab === 'All' + ? mockOrders + : activeTab === 'Ongoing' + ? mockOrders.filter((o) => o.status === 'Ongoing') + : mockOrders.filter((o) => o.status === 'Delivered'); + + return ( + +
+ + {TABS.map((tab) => ( + setActiveTab(tab)} + activeOpacity={0.7} + > + + {tab} + + + ))} + + item.id} + renderItem={({ item }) => ( + {}} + onDetails={() => {}} + /> + )} + contentContainerStyle={styles.list} + ListEmptyComponent={ + + No orders found + + } + /> + + ); +}; diff --git a/app/features/screens/offersScreen/index.ts b/app/features/screens/offersScreen/index.ts new file mode 100644 index 0000000..4df59cb --- /dev/null +++ b/app/features/screens/offersScreen/index.ts @@ -0,0 +1 @@ +export * from './offersScreen'; diff --git a/app/features/screens/offersScreen/offersScreen.styles.ts b/app/features/screens/offersScreen/offersScreen.styles.ts new file mode 100644 index 0000000..819124f --- /dev/null +++ b/app/features/screens/offersScreen/offersScreen.styles.ts @@ -0,0 +1,51 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + list: { + padding: 16, + }, + offerCard: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 16, + marginBottom: 12, + borderWidth: 1, + borderColor: colors.border, + }, + offerInfo: { + flex: 1, + }, + offerCode: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.bold, + color: colors.primary, + marginBottom: 4, + }, + offerDescription: { + fontSize: typography.fontSize.sm, + color: colors.text, + marginBottom: 4, + }, + offerExpiry: { + fontSize: typography.fontSize.xs, + color: colors.textSecondary, + }, + copyButton: { + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 8, + backgroundColor: colors.primary, + }, + copyButtonText: { + fontSize: typography.fontSize.sm, + fontWeight: typography.fontWeight.semibold, + color: '#FFFFFF', + }, +}); diff --git a/app/features/screens/offersScreen/offersScreen.tsx b/app/features/screens/offersScreen/offersScreen.tsx new file mode 100644 index 0000000..c72a682 --- /dev/null +++ b/app/features/screens/offersScreen/offersScreen.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { View, Text, FlatList, TouchableOpacity } from 'react-native'; +import { getStyles } from './offersScreen.styles'; +import { Header } from '@components'; +import { useAppTheme } from '@theme'; + +const OFFERS = [ + { id: '1', code: 'WELCOME50', description: '50% off on your first order', expiry: '30 Jul 2026' }, + { id: '2', code: 'FLAT20', description: 'Flat β‚Ή20 off on orders above β‚Ή199', expiry: '15 Aug 2026' }, + { id: '3', code: 'FREEDEL', description: 'Free delivery on orders above β‚Ή299', expiry: '31 Jul 2026' }, +]; + +export const OffersScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + +
+ item.id} + renderItem={({ item }) => ( + + + {item.code} + {item.description} + Expires: {item.expiry} + + {}} + activeOpacity={0.7} + > + Copy + + + )} + contentContainerStyle={styles.list} + /> + + ); +}; diff --git a/app/features/screens/onboardingCompleteScreen/index.ts b/app/features/screens/onboardingCompleteScreen/index.ts new file mode 100644 index 0000000..f1d2bfd --- /dev/null +++ b/app/features/screens/onboardingCompleteScreen/index.ts @@ -0,0 +1 @@ +export * from './onboardingCompleteScreen'; diff --git a/app/features/screens/onboardingCompleteScreen/onboardingCompleteScreen.styles.ts b/app/features/screens/onboardingCompleteScreen/onboardingCompleteScreen.styles.ts new file mode 100644 index 0000000..f7b2304 --- /dev/null +++ b/app/features/screens/onboardingCompleteScreen/onboardingCompleteScreen.styles.ts @@ -0,0 +1,35 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + paddingHorizontal: 24, + }, + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + icon: { + fontSize: 80, + marginBottom: 24, + }, + title: { + fontSize: typography.fontSize.xxl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 12, + }, + subtitle: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + textAlign: 'center', + lineHeight: 24, + paddingHorizontal: 16, + }, + footer: { + paddingBottom: 40, + }, +}); diff --git a/app/features/screens/onboardingCompleteScreen/onboardingCompleteScreen.tsx b/app/features/screens/onboardingCompleteScreen/onboardingCompleteScreen.tsx new file mode 100644 index 0000000..8046efd --- /dev/null +++ b/app/features/screens/onboardingCompleteScreen/onboardingCompleteScreen.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { getStyles } from './onboardingCompleteScreen.styles'; +import { PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; +import { useAppDispatch } from '../../../hooks/useAppDispatch'; +import { completeOnboarding } from '../../../store/authSlice'; + +export const OnboardingCompleteScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const dispatch = useAppDispatch(); + + const handleExplore = () => { + dispatch(completeOnboarding()); + }; + + return ( + + + πŸŽ‰ + All Set! + + You're ready to explore and order from the best providers near you. + + + + + + + ); +}; diff --git a/app/features/screens/orderConfirmedScreen/index.ts b/app/features/screens/orderConfirmedScreen/index.ts new file mode 100644 index 0000000..09707e7 --- /dev/null +++ b/app/features/screens/orderConfirmedScreen/index.ts @@ -0,0 +1 @@ +export * from './orderConfirmedScreen'; diff --git a/app/features/screens/orderConfirmedScreen/orderConfirmedScreen.styles.ts b/app/features/screens/orderConfirmedScreen/orderConfirmedScreen.styles.ts new file mode 100644 index 0000000..521e5a1 --- /dev/null +++ b/app/features/screens/orderConfirmedScreen/orderConfirmedScreen.styles.ts @@ -0,0 +1,55 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 24, + }, + icon: { + fontSize: 64, + marginBottom: 16, + }, + title: { + fontSize: typography.fontSize.xxl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 8, + }, + subtitle: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + textAlign: 'center', + marginBottom: 32, + }, + orderCard: { + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 20, + width: '100%', + borderWidth: 1, + borderColor: colors.border, + alignItems: 'center', + }, + orderId: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 4, + }, + orderEta: { + fontSize: typography.fontSize.sm, + color: colors.primary, + fontWeight: typography.fontWeight.medium, + }, + footer: { + padding: 24, + paddingBottom: 40, + }, +}); diff --git a/app/features/screens/orderConfirmedScreen/orderConfirmedScreen.tsx b/app/features/screens/orderConfirmedScreen/orderConfirmedScreen.tsx new file mode 100644 index 0000000..8a1d5e1 --- /dev/null +++ b/app/features/screens/orderConfirmedScreen/orderConfirmedScreen.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { getStyles } from './orderConfirmedScreen.styles'; +import { Header, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; + +export const OrderConfirmedScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + +
{}} /> + + βœ… + Order Confirmed! + + Your order has been placed successfully + + + Order ID: ORD-123456 + Estimated delivery: 25-30 min + + + + {}} /> + + + ); +}; diff --git a/app/features/screens/orderDeliveredScreen/index.ts b/app/features/screens/orderDeliveredScreen/index.ts new file mode 100644 index 0000000..11924b3 --- /dev/null +++ b/app/features/screens/orderDeliveredScreen/index.ts @@ -0,0 +1 @@ +export * from './orderDeliveredScreen'; diff --git a/app/features/screens/orderDeliveredScreen/orderDeliveredScreen.styles.ts b/app/features/screens/orderDeliveredScreen/orderDeliveredScreen.styles.ts new file mode 100644 index 0000000..5f05676 --- /dev/null +++ b/app/features/screens/orderDeliveredScreen/orderDeliveredScreen.styles.ts @@ -0,0 +1,31 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 24, + }, + icon: { + fontSize: 64, + marginBottom: 16, + }, + title: { + fontSize: typography.fontSize.xl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + textAlign: 'center', + marginBottom: 8, + }, + subtitle: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + marginBottom: 32, + }, +}); diff --git a/app/features/screens/orderDeliveredScreen/orderDeliveredScreen.tsx b/app/features/screens/orderDeliveredScreen/orderDeliveredScreen.tsx new file mode 100644 index 0000000..655de4e --- /dev/null +++ b/app/features/screens/orderDeliveredScreen/orderDeliveredScreen.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import { View, Text } from 'react-native'; +import { getStyles } from './orderDeliveredScreen.styles'; +import { Header, RatingStars, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; + +export const OrderDeliveredScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const [rating, setRating] = useState(0); + + return ( + +
{}} /> + + πŸŽ‰ + Your order has been delivered! + How was your experience? + + + + {}} + disabled={rating === 0} + style={{ marginTop: 32 }} + /> + + + ); +}; diff --git a/app/features/screens/orderTrackingScreen/index.ts b/app/features/screens/orderTrackingScreen/index.ts new file mode 100644 index 0000000..f51dbf1 --- /dev/null +++ b/app/features/screens/orderTrackingScreen/index.ts @@ -0,0 +1 @@ +export * from './orderTrackingScreen'; diff --git a/app/features/screens/orderTrackingScreen/orderTrackingScreen.styles.ts b/app/features/screens/orderTrackingScreen/orderTrackingScreen.styles.ts new file mode 100644 index 0000000..2ef7ccb --- /dev/null +++ b/app/features/screens/orderTrackingScreen/orderTrackingScreen.styles.ts @@ -0,0 +1,91 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + flex: 1, + padding: 24, + }, + timeline: { + flex: 1, + justifyContent: 'center', + paddingLeft: 8, + }, + milestoneRow: { + flexDirection: 'row', + alignItems: 'flex-start', + marginBottom: 8, + }, + milestoneIndicator: { + alignItems: 'center', + width: 24, + marginRight: 16, + }, + dot: { + width: 16, + height: 16, + borderRadius: 8, + backgroundColor: colors.border, + zIndex: 1, + }, + dotActive: { + backgroundColor: colors.primary, + }, + dotCurrent: { + width: 20, + height: 20, + borderRadius: 10, + backgroundColor: colors.primary, + borderWidth: 3, + borderColor: '#E8F5E9', + }, + line: { + width: 2, + flex: 1, + backgroundColor: colors.border, + minHeight: 30, + marginVertical: -2, + }, + lineActive: { + backgroundColor: colors.primary, + }, + milestoneInfo: { + paddingBottom: 20, + }, + milestoneLabel: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.medium, + color: colors.textSecondary, + marginBottom: 2, + }, + milestoneLabelActive: { + color: colors.text, + fontWeight: typography.fontWeight.semibold, + }, + milestoneTime: { + fontSize: typography.fontSize.sm, + color: colors.placeholder, + }, + supportCard: { + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 20, + borderWidth: 1, + borderColor: colors.border, + }, + supportTitle: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 4, + }, + supportSubtext: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + marginBottom: 12, + }, +}); diff --git a/app/features/screens/orderTrackingScreen/orderTrackingScreen.tsx b/app/features/screens/orderTrackingScreen/orderTrackingScreen.tsx new file mode 100644 index 0000000..5cabb87 --- /dev/null +++ b/app/features/screens/orderTrackingScreen/orderTrackingScreen.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { getStyles } from './orderTrackingScreen.styles'; +import { Header, PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; + +const MILESTONES = [ + { key: 'placed', label: 'Order Placed', time: '12:30 PM' }, + { key: 'confirmed', label: 'Confirmed', time: '12:32 PM' }, + { key: 'dispatched', label: 'Dispatched', time: '12:40 PM' }, + { key: 'delivered', label: 'Delivered', time: 'Pending' }, +]; + +export const OrderTrackingScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + return ( + +
{}} /> + + + {MILESTONES.map((milestone, index) => ( + + + + {index < MILESTONES.length - 1 && ( + + )} + + + + {milestone.label} + + {milestone.time} + + + ))} + + + Need help? + Contact support for assistance + {}} /> + + + + ); +}; diff --git a/app/features/screens/otpScreen/index.ts b/app/features/screens/otpScreen/index.ts new file mode 100644 index 0000000..ebdf118 --- /dev/null +++ b/app/features/screens/otpScreen/index.ts @@ -0,0 +1 @@ +export * from './otpScreen'; diff --git a/app/features/screens/otpScreen/otpScreen.styles.ts b/app/features/screens/otpScreen/otpScreen.styles.ts new file mode 100644 index 0000000..9db8d41 --- /dev/null +++ b/app/features/screens/otpScreen/otpScreen.styles.ts @@ -0,0 +1,85 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + flex: 1, + paddingHorizontal: 24, + justifyContent: 'center', + alignItems: 'center', + }, + title: { + fontSize: typography.fontSize.xxl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 8, + }, + subtitle: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + marginBottom: 40, + }, + otpContainer: { + flexDirection: 'row', + gap: 10, + marginBottom: 32, + }, + otpCell: { + width: 48, + height: 56, + borderWidth: 1.5, + borderColor: colors.border, + borderRadius: 12, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: colors.inputBg, + }, + otpCellActive: { + borderColor: colors.primary, + }, + otpCellFilled: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + otpCellText: { + fontSize: typography.fontSize.xl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + }, + keypad: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + gap: 12, + maxWidth: 300, + marginBottom: 24, + }, + key: { + width: 80, + height: 56, + justifyContent: 'center', + alignItems: 'center', + borderRadius: 12, + backgroundColor: colors.inputBg, + borderWidth: 1, + borderColor: colors.border, + }, + keyText: { + fontSize: typography.fontSize.xl, + fontWeight: typography.fontWeight.medium, + color: colors.text, + }, + timerText: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + resendText: { + fontSize: typography.fontSize.sm, + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, +}); diff --git a/app/features/screens/otpScreen/otpScreen.tsx b/app/features/screens/otpScreen/otpScreen.tsx new file mode 100644 index 0000000..9e83cdf --- /dev/null +++ b/app/features/screens/otpScreen/otpScreen.tsx @@ -0,0 +1,154 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + KeyboardAvoidingView, + Platform, +} from 'react-native'; +import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { getStyles } from './otpScreen.styles'; +import { PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; +import { useAppDispatch } from '../../../hooks/useAppDispatch'; +import { verifyOtp } from '../../../store/authSlice'; +import { AuthStackParamList } from '../../../navigation/authStack'; + +type OtpNavProp = StackNavigationProp; +type OtpRouteProp = RouteProp; + +const OTP_LENGTH = 6; +const RESEND_TIMER = 30; + +export const OtpScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const navigation = useNavigation(); + const route = useRoute(); + const dispatch = useAppDispatch(); + const { mobileNumber } = route.params; + + const [otp, setOtp] = useState(Array(OTP_LENGTH).fill('')); + const [activeIndex, setActiveIndex] = useState(0); + const [timer, setTimer] = useState(RESEND_TIMER); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (timer > 0) { + const interval = setInterval(() => setTimer((t) => t - 1), 1000); + return () => clearInterval(interval); + } + }, [timer]); + + const handleKeyPress = (key: string) => { + if (key === 'backspace') { + if (otp[activeIndex] || activeIndex > 0) { + const newOtp = [...otp]; + if (otp[activeIndex]) { + newOtp[activeIndex] = ''; + } else if (activeIndex > 0) { + newOtp[activeIndex - 1] = ''; + setActiveIndex(activeIndex - 1); + } + setOtp(newOtp); + } + } else if (key >= '0' && key <= '9' && activeIndex < OTP_LENGTH) { + const newOtp = [...otp]; + newOtp[activeIndex] = key; + setOtp(newOtp); + if (activeIndex < OTP_LENGTH - 1) { + setActiveIndex(activeIndex + 1); + } + } + }; + + const handleVerify = async () => { + const otpString = otp.join(''); + if (otpString.length !== OTP_LENGTH) return; + setIsLoading(true); + await dispatch(verifyOtp({ mobileNumber, otp: otpString })); + setIsLoading(false); + navigation.navigate('SetLocationScreen'); + }; + + return ( + + + Verify OTP + + Code sent to {mobileNumber} + + + + {otp.map((digit, index) => ( + + {digit} + + ))} + + + + {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => ( + handleKeyPress(String(num))} + activeOpacity={0.6} + > + {num} + + ))} + handleKeyPress('backspace')} + activeOpacity={0.6} + > + ⌫ + + handleKeyPress('0')} + activeOpacity={0.6} + > + 0 + + + + + {timer > 0 ? ( + Resend code in {timer}s + ) : ( + { + setTimer(RESEND_TIMER); + setOtp(Array(OTP_LENGTH).fill('')); + setActiveIndex(0); + }} + activeOpacity={0.7} + > + Resend OTP + + )} + + + + + ); +}; diff --git a/app/features/screens/preferencesScreen/index.ts b/app/features/screens/preferencesScreen/index.ts new file mode 100644 index 0000000..7dc5967 --- /dev/null +++ b/app/features/screens/preferencesScreen/index.ts @@ -0,0 +1 @@ +export * from './preferencesScreen'; diff --git a/app/features/screens/preferencesScreen/preferencesScreen.styles.ts b/app/features/screens/preferencesScreen/preferencesScreen.styles.ts new file mode 100644 index 0000000..6607804 --- /dev/null +++ b/app/features/screens/preferencesScreen/preferencesScreen.styles.ts @@ -0,0 +1,76 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + paddingHorizontal: 24, + }, + content: { + paddingTop: 60, + paddingBottom: 24, + }, + title: { + fontSize: typography.fontSize.xxl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 8, + }, + subtitle: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + marginBottom: 32, + }, + grid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 12, + marginBottom: 32, + }, + gridItem: { + width: '47%', + aspectRatio: 1.2, + borderRadius: 16, + borderWidth: 1.5, + borderColor: colors.border, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: colors.background, + }, + gridItemSelected: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + gridIcon: { + fontSize: 40, + marginBottom: 8, + }, + gridLabel: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.medium, + color: colors.text, + }, + gridLabelSelected: { + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, + toggleRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 16, + borderTopWidth: 1, + borderTopColor: colors.border, + }, + toggleLabel: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.medium, + color: colors.text, + }, + toggleSubtext: { + fontSize: typography.fontSize.xs, + color: colors.textSecondary, + marginTop: 2, + }, +}); diff --git a/app/features/screens/preferencesScreen/preferencesScreen.tsx b/app/features/screens/preferencesScreen/preferencesScreen.tsx new file mode 100644 index 0000000..eb515a9 --- /dev/null +++ b/app/features/screens/preferencesScreen/preferencesScreen.tsx @@ -0,0 +1,90 @@ +import React, { useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + Switch, + ScrollView, +} from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { getStyles } from './preferencesScreen.styles'; +import { PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; +import { AuthStackParamList } from '../../../navigation/authStack'; + +type NavProp = StackNavigationProp; + +const CATEGORIES = [ + { key: 'food', icon: 'πŸ”', label: 'Food' }, + { key: 'groceries', icon: 'πŸ›’', label: 'Groceries' }, + { key: 'pharmacy', icon: 'πŸ’Š', label: 'Pharmacy' }, + { key: 'others', icon: 'πŸ“¦', label: 'Others' }, +]; + +export const PreferencesScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const navigation = useNavigation(); + + const [selectedCategories, setSelectedCategories] = useState(['food']); + const [notificationsEnabled, setNotificationsEnabled] = useState(true); + + const toggleCategory = (key: string) => { + setSelectedCategories((prev) => + prev.includes(key) + ? prev.filter((c) => c !== key) + : [...prev, key], + ); + }; + + return ( + + Preferences + Select your interests + + + {CATEGORIES.map((cat) => ( + toggleCategory(cat.key)} + activeOpacity={0.7} + > + {cat.icon} + + {cat.label} + + + ))} + + + + + Enable Notifications + Get order updates and offers + + + + + navigation.navigate('OnboardingCompleteScreen')} + style={{ marginTop: 32 }} + /> + + ); +}; diff --git a/app/features/screens/providerDetailsScreen/index.ts b/app/features/screens/providerDetailsScreen/index.ts new file mode 100644 index 0000000..a976177 --- /dev/null +++ b/app/features/screens/providerDetailsScreen/index.ts @@ -0,0 +1 @@ +export * from './providerDetailsScreen'; diff --git a/app/features/screens/providerDetailsScreen/providerDetailsScreen.styles.ts b/app/features/screens/providerDetailsScreen/providerDetailsScreen.styles.ts new file mode 100644 index 0000000..9e80219 --- /dev/null +++ b/app/features/screens/providerDetailsScreen/providerDetailsScreen.styles.ts @@ -0,0 +1,95 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + content: { + flex: 1, + }, + hero: { + alignItems: 'center', + paddingVertical: 24, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + heroImage: { + width: 100, + height: 100, + borderRadius: 50, + backgroundColor: colors.inputBg, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 12, + }, + heroEmoji: { + fontSize: 44, + }, + heroName: { + fontSize: typography.fontSize.xl, + fontWeight: typography.fontWeight.bold, + color: colors.text, + marginBottom: 8, + }, + heroStats: { + flexDirection: 'row', + gap: 16, + }, + heroStat: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + categoryRow: { + paddingVertical: 12, + paddingHorizontal: 16, + flexGrow: 0, + }, + categoryTab: { + paddingHorizontal: 20, + paddingVertical: 8, + borderRadius: 20, + borderWidth: 1, + borderColor: colors.border, + marginRight: 8, + }, + categoryTabActive: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + categoryTabText: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + categoryTabTextActive: { + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, + cartStrip: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: colors.primary, + paddingHorizontal: 20, + paddingVertical: 14, + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + }, + cartStripText: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: '#FFFFFF', + }, + viewCartButton: { + backgroundColor: '#FFFFFF', + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 8, + }, + viewCartText: { + fontSize: typography.fontSize.sm, + fontWeight: typography.fontWeight.semibold, + color: colors.primary, + }, +}); diff --git a/app/features/screens/providerDetailsScreen/providerDetailsScreen.tsx b/app/features/screens/providerDetailsScreen/providerDetailsScreen.tsx new file mode 100644 index 0000000..24c55a0 --- /dev/null +++ b/app/features/screens/providerDetailsScreen/providerDetailsScreen.tsx @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + TouchableOpacity, + ScrollView, +} from 'react-native'; +import { getStyles } from './providerDetailsScreen.styles'; +import { Header, CatalogItemRow } from '@components'; +import { useAppTheme } from '@theme'; +import { useAppDispatch, useAppSelector } from '../../../hooks/useAppDispatch'; +import { addItem } from '../../../store/cartSlice'; +import { deliveryService } from '../../../services/deliveryService'; +import { CatalogItem } from '../../../interfaces'; + +const CATEGORIES = ['Pizza', 'Sides', 'Beverages']; + +export const ProviderDetailsScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const dispatch = useAppDispatch(); + const cartItems = useAppSelector((state) => state.cart.items); + + const [catalog, setCatalog] = useState([]); + const [activeCategory, setActiveCategory] = useState(CATEGORIES[0]); + + useEffect(() => { + deliveryService.getProviderCatalog('p1').then(setCatalog); + }, []); + + const filteredItems = catalog.filter((item) => item.category === activeCategory); + + const getItemQuantity = (itemId: string) => { + const item = cartItems.find((i) => i.item.id === itemId); + return item ? item.quantity : 0; + }; + + const totalItems = cartItems.reduce((sum, i) => sum + i.quantity, 0); + const totalPrice = cartItems.reduce( + (sum, i) => sum + i.item.price * i.quantity, + 0, + ); + + return ( + +
{}} /> + + + + πŸ• + + Pizza Planet + + ⭐ 4.5 + πŸ• 25 min + πŸ• Italian + + + + + {CATEGORIES.map((cat) => ( + setActiveCategory(cat)} + activeOpacity={0.7} + > + + {cat} + + + ))} + + + {filteredItems.map((item) => ( + + dispatch( + addItem({ + providerId: 'p1', + providerName: 'Pizza Planet', + item, + }), + ) + } + onRemove={() => {}} + /> + ))} + + + {totalItems > 0 && ( + + + {totalItems} Items | β‚Ή{totalPrice} + + + View Cart + + + )} + + ); +}; diff --git a/app/features/screens/providerListScreen/index.ts b/app/features/screens/providerListScreen/index.ts new file mode 100644 index 0000000..d5b538b --- /dev/null +++ b/app/features/screens/providerListScreen/index.ts @@ -0,0 +1 @@ +export * from './providerListScreen'; diff --git a/app/features/screens/providerListScreen/providerListScreen.styles.ts b/app/features/screens/providerListScreen/providerListScreen.styles.ts new file mode 100644 index 0000000..694a8ca --- /dev/null +++ b/app/features/screens/providerListScreen/providerListScreen.styles.ts @@ -0,0 +1,38 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + filterRow: { + paddingVertical: 8, + paddingHorizontal: 16, + flexGrow: 0, + }, + filterChip: { + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 20, + borderWidth: 1, + borderColor: colors.border, + marginRight: 8, + backgroundColor: colors.background, + }, + filterChipActive: { + borderColor: colors.primary, + backgroundColor: '#E8F5E9', + }, + filterChipText: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + }, + filterChipTextActive: { + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, + list: { + paddingBottom: 24, + }, +}); diff --git a/app/features/screens/providerListScreen/providerListScreen.tsx b/app/features/screens/providerListScreen/providerListScreen.tsx new file mode 100644 index 0000000..b0b314a --- /dev/null +++ b/app/features/screens/providerListScreen/providerListScreen.tsx @@ -0,0 +1,69 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + FlatList, + TouchableOpacity, + ScrollView, +} from 'react-native'; +import { getStyles } from './providerListScreen.styles'; +import { ProviderCard, Header } from '@components'; +import { useAppTheme } from '@theme'; +import { deliveryService } from '../../../services/deliveryService'; +import { Provider } from '../../../interfaces'; + +const FILTERS = ['Filters', 'Rating 4.0+', 'Fast Delivery']; + +export const ProviderListScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const [providers, setProviders] = useState([]); + const [activeFilter, setActiveFilter] = useState(null); + + useEffect(() => { + deliveryService.getProviders().then(setProviders); + }, []); + + return ( + +
{}} /> + + {FILTERS.map((filter) => ( + setActiveFilter(activeFilter === filter ? null : filter)} + activeOpacity={0.7} + > + + {filter} + + + ))} + + item.id} + renderItem={({ item }) => ( + + )} + contentContainerStyle={styles.list} + /> + + ); +}; diff --git a/app/features/screens/searchScreen/index.ts b/app/features/screens/searchScreen/index.ts new file mode 100644 index 0000000..9b0c7b3 --- /dev/null +++ b/app/features/screens/searchScreen/index.ts @@ -0,0 +1 @@ +export * from './searchScreen'; diff --git a/app/features/screens/searchScreen/searchScreen.styles.ts b/app/features/screens/searchScreen/searchScreen.styles.ts new file mode 100644 index 0000000..849ec49 --- /dev/null +++ b/app/features/screens/searchScreen/searchScreen.styles.ts @@ -0,0 +1,27 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + paddingTop: 8, + }, + list: { + paddingBottom: 24, + }, + empty: { + alignItems: 'center', + paddingTop: 60, + }, + emptyIcon: { + fontSize: 48, + marginBottom: 12, + }, + emptyText: { + fontSize: typography.fontSize.md, + color: colors.textSecondary, + textAlign: 'center', + paddingHorizontal: 40, + }, +}); diff --git a/app/features/screens/searchScreen/searchScreen.tsx b/app/features/screens/searchScreen/searchScreen.tsx new file mode 100644 index 0000000..3686f21 --- /dev/null +++ b/app/features/screens/searchScreen/searchScreen.tsx @@ -0,0 +1,64 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + FlatList, +} from 'react-native'; +import { getStyles } from './searchScreen.styles'; +import { SearchBar, ProviderCard } from '@components'; +import { useAppTheme } from '@theme'; +import { deliveryService } from '../../../services/deliveryService'; +import { Provider } from '../../../interfaces'; + +export const SearchScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + + const [query, setQuery] = useState(''); + const [results, setResults] = useState([]); + + useEffect(() => { + if (query.trim().length > 0) { + deliveryService.searchProviders(query).then(setResults); + } else { + setResults([]); + } + }, [query]); + + return ( + + + item.id} + renderItem={({ item }) => ( + + )} + ListEmptyComponent={ + query.trim().length > 0 ? ( + + No results found + + ) : ( + + πŸ” + Search for food, groceries, medicines... + + ) + } + contentContainerStyle={styles.list} + /> + + ); +}; diff --git a/app/features/screens/setLocationScreen/index.ts b/app/features/screens/setLocationScreen/index.ts new file mode 100644 index 0000000..1bd476b --- /dev/null +++ b/app/features/screens/setLocationScreen/index.ts @@ -0,0 +1 @@ +export * from './setLocationScreen'; diff --git a/app/features/screens/setLocationScreen/setLocationScreen.styles.ts b/app/features/screens/setLocationScreen/setLocationScreen.styles.ts new file mode 100644 index 0000000..f768f02 --- /dev/null +++ b/app/features/screens/setLocationScreen/setLocationScreen.styles.ts @@ -0,0 +1,75 @@ +import { StyleSheet } from 'react-native'; +import { typography } from '@theme'; + +export const getStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + scrollContainer: { + flexGrow: 1, + paddingTop: 60, + }, + mapPlaceholder: { + height: 240, + backgroundColor: colors.inputBg, + marginHorizontal: 24, + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 24, + borderWidth: 1, + borderColor: colors.border, + }, + mapIcon: { + fontSize: 48, + marginBottom: 8, + }, + mapText: { + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.semibold, + color: colors.textSecondary, + }, + mapSubtext: { + fontSize: typography.fontSize.xs, + color: colors.placeholder, + marginTop: 4, + }, + card: { + backgroundColor: colors.cardBg, + borderRadius: 12, + padding: 16, + marginHorizontal: 24, + marginBottom: 24, + borderWidth: 1, + borderColor: colors.border, + }, + cardTitle: { + fontSize: typography.fontSize.sm, + color: colors.textSecondary, + marginBottom: 4, + }, + cardAddress: { + fontSize: typography.fontSize.md, + fontWeight: typography.fontWeight.semibold, + color: colors.text, + marginBottom: 8, + }, + changeLink: { + alignSelf: 'flex-end', + }, + changeText: { + fontSize: typography.fontSize.sm, + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, + manualButton: { + alignItems: 'center', + paddingVertical: 16, + }, + manualButtonText: { + fontSize: typography.fontSize.sm, + color: colors.primary, + fontWeight: typography.fontWeight.semibold, + }, +}); diff --git a/app/features/screens/setLocationScreen/setLocationScreen.tsx b/app/features/screens/setLocationScreen/setLocationScreen.tsx new file mode 100644 index 0000000..4922c02 --- /dev/null +++ b/app/features/screens/setLocationScreen/setLocationScreen.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { + View, + Text, + TouchableOpacity, + KeyboardAvoidingView, + Platform, + ScrollView, +} from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { getStyles } from './setLocationScreen.styles'; +import { PrimaryButton } from '@components'; +import { useAppTheme } from '@theme'; +import { AuthStackParamList } from '../../../navigation/authStack'; + +type NavProp = StackNavigationProp; + +export const SetLocationScreen: React.FC = () => { + const { colors } = useAppTheme(); + const styles = getStyles(colors); + const navigation = useNavigation(); + + return ( + + + + πŸ—ΊοΈ + Map View + + (react-native-maps integration) + + + + + Delivery Location + + Koramangala 4th Block, Bengaluru, 560034 + + + Change + + + + navigation.navigate('CompleteProfileScreen')} + style={{ marginHorizontal: 24 }} + /> + + navigation.navigate('CompleteProfileScreen')} + activeOpacity={0.7} + > + Enter address manually + + + + ); +}; diff --git a/app/hooks/useAppDispatch.ts b/app/hooks/useAppDispatch.ts new file mode 100644 index 0000000..39f730f --- /dev/null +++ b/app/hooks/useAppDispatch.ts @@ -0,0 +1,5 @@ +import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; +import type { RootState, AppDispatch } from '../store'; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/app/interfaces/index.ts b/app/interfaces/index.ts new file mode 100644 index 0000000..2066c82 --- /dev/null +++ b/app/interfaces/index.ts @@ -0,0 +1,85 @@ +export interface User { + id: string; + mobileNumber: string; + name?: string; + email?: string; + locationLabels?: string[]; +} + +export interface Location { + latitude: number; + longitude: number; + address: string; + city: string; + pincode: string; + label?: string; +} + +export interface Provider { + id: string; + imageUrl: string; + name: string; + rating: number; + deliveryTime: string; + tag: string; + discountText?: string; + serviceType: string; +} + +export interface CatalogItem { + id: string; + imageUrl: string; + title: string; + description: string; + price: number; + category: string; +} + +export interface CartItem { + id: string; + providerId: string; + providerName: string; + item: CatalogItem; + quantity: number; +} + +export type OrderStatus = 'placed' | 'confirmed' | 'dispatched' | 'delivered' | 'cancelled'; + +export interface Order { + id: string; + providerId: string; + providerName: string; + providerImage: string; + items: CartItem[]; + status: OrderStatus; + total: number; + deliveryFee: number; + platformFee: number; + discount: number; + couponCode?: string; + createdAt: string; + estimatedDelivery: string; + deliveryAddress: Location; +} + +export interface TrackingStep { + key: OrderStatus; + label: string; + timestamp?: string; + isCompleted: boolean; + isActive: boolean; +} + +export interface DeliveryAgent { + name: string; + rating: number; + vehicleDetails: string; + phoneNumber: string; +} + +export interface PaymentMethod { + id: string; + type: 'UPI' | 'Card' | 'Wallet' | 'COD'; + label: string; + icon?: string; +} diff --git a/app/navigation/appStack.tsx b/app/navigation/appStack.tsx new file mode 100644 index 0000000..9fc5e43 --- /dev/null +++ b/app/navigation/appStack.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Text } from 'react-native'; +import { + HomeScreen, + SearchScreen, + MyOrdersScreen, + OffersScreen, + AccountScreen, +} from '@features/screens'; + +export type AppStackParamList = { + HomeScreen: undefined; + SearchScreen: undefined; + MyOrdersScreen: undefined; + OffersScreen: undefined; + AccountScreen: undefined; +}; + +const Tab = createBottomTabNavigator(); + +const tabIcons: Record = { + HomeScreen: '🏠', + SearchScreen: 'πŸ”', + MyOrdersScreen: 'πŸ“¦', + OffersScreen: '🏷️', + AccountScreen: 'πŸ‘€', +}; + +const renderTabIcon = (routeName: string, focused: boolean) => ( + + {tabIcons[routeName]} + +); + +export const AppStack: React.FC = () => { + return ( + ({ + headerShown: false, + tabBarIcon: ({ focused }) => renderTabIcon(route.name, focused), + tabBarActiveTintColor: '#05824C', + tabBarInactiveTintColor: '#666666', + tabBarStyle: { + paddingBottom: 8, + paddingTop: 8, + height: 60, + }, + tabBarLabelStyle: { + fontSize: 11, + fontWeight: '500', + }, + })} + > + + + + + + + ); +}; diff --git a/app/navigation/authStack.tsx b/app/navigation/authStack.tsx new file mode 100644 index 0000000..27c69d7 --- /dev/null +++ b/app/navigation/authStack.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; +import { + LoginScreen, + OtpScreen, + SetLocationScreen, + CompleteProfileScreen, + PreferencesScreen, + OnboardingCompleteScreen, +} from '@features/screens'; + +export type AuthStackParamList = { + LoginScreen: undefined; + OtpScreen: { mobileNumber: string }; + SetLocationScreen: undefined; + CompleteProfileScreen: undefined; + PreferencesScreen: undefined; + OnboardingCompleteScreen: undefined; +}; + +const Stack = createStackNavigator(); + +export const AuthStack: React.FC = () => { + return ( + + + + + + + + + ); +}; diff --git a/app/navigation/rootNavigator.tsx b/app/navigation/rootNavigator.tsx new file mode 100644 index 0000000..43127d0 --- /dev/null +++ b/app/navigation/rootNavigator.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '../store'; +import { AuthStack } from './authStack'; +import { AppStack } from './appStack'; + +export const RootNavigator: React.FC = () => { + const { isAuthenticated, onboardingComplete } = useSelector( + (state: RootState) => state.auth, + ); + + if (!isAuthenticated || !onboardingComplete) { + return ; + } + + return ; +}; diff --git a/app/services/apiClient.ts b/app/services/apiClient.ts new file mode 100644 index 0000000..5bbe5f4 --- /dev/null +++ b/app/services/apiClient.ts @@ -0,0 +1,18 @@ +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +export const apiClient = { + async get(url: string, mockData: T): Promise { + await delay(800); + return mockData; + }, + + async post(url: string, body: unknown, mockResponse: T): Promise { + await delay(1000); + return mockResponse; + }, + + async put(url: string, body: unknown, mockResponse: T): Promise { + await delay(800); + return mockResponse; + }, +}; diff --git a/app/services/authService.ts b/app/services/authService.ts new file mode 100644 index 0000000..8945b3c --- /dev/null +++ b/app/services/authService.ts @@ -0,0 +1,49 @@ +import { apiClient } from './apiClient'; +import { User } from '../interfaces'; + +const mockUser: User = { + id: 'user-001', + mobileNumber: '', + name: '', + email: '', + locationLabels: [], +}; + +export const authService = { + async login(mobileNumber: string) { + return apiClient.post( + '/auth/login', + { mobileNumber }, + { success: true, message: 'OTP sent successfully' }, + ); + }, + + async verifyOtp(mobileNumber: string, otp: string) { + return apiClient.post( + '/auth/verify-otp', + { mobileNumber, otp }, + { + success: true, + user: { + ...mockUser, + mobileNumber, + id: 'user-001', + }, + }, + ); + }, + + async updateProfile(data: { name: string; email: string; locationLabels: string[] }) { + return apiClient.put('/auth/profile', data, { + success: true, + user: { + ...mockUser, + ...data, + }, + }); + }, + + async logout() { + return apiClient.post('/auth/logout', {}, { success: true }); + }, +}; diff --git a/app/services/deliveryService.ts b/app/services/deliveryService.ts new file mode 100644 index 0000000..9c9d8b0 --- /dev/null +++ b/app/services/deliveryService.ts @@ -0,0 +1,71 @@ +import { apiClient } from './apiClient'; +import { Provider, CatalogItem, Order, DeliveryAgent, Location } from '../interfaces'; + +const mockProviders: Provider[] = [ + { id: 'p1', imageUrl: '', name: 'Pizza Planet', rating: 4.5, deliveryTime: '25 min', tag: 'Food', discountText: '50% OFF', serviceType: 'Italian' }, + { id: 'p2', imageUrl: '', name: 'Fresh Mart', rating: 4.2, deliveryTime: '30 min', tag: 'Groceries', discountText: '20% OFF', serviceType: 'Grocery' }, + { id: 'p3', imageUrl: '', name: 'MediQuick', rating: 4.7, deliveryTime: '15 min', tag: 'Pharmacy', discountText: undefined, serviceType: 'Pharmacy' }, + { id: 'p4', imageUrl: '', name: 'Burger Barn', rating: 4.3, deliveryTime: '20 min', tag: 'Food', discountText: 'Buy 1 Get 1', serviceType: 'Fast Food' }, + { id: 'p5', imageUrl: '', name: 'Green Basket', rating: 4.0, deliveryTime: '35 min', tag: 'Groceries', discountText: undefined, serviceType: 'Organic' }, +]; + +const mockCatalogItems: CatalogItem[] = [ + { id: 'i1', imageUrl: '', title: 'Margherita Pizza', description: 'Classic cheese and tomato', price: 299, category: 'Pizza' }, + { id: 'i2', imageUrl: '', title: 'Pepperoni Pizza', description: 'Loaded with pepperoni slices', price: 399, category: 'Pizza' }, + { id: 'i3', imageUrl: '', title: 'French Fries', description: 'Crispy golden fries', price: 99, category: 'Sides' }, + { id: 'i4', imageUrl: '', title: 'Garlic Bread', description: 'Toasted bread with garlic butter', price: 149, category: 'Sides' }, + { id: 'i5', imageUrl: '', title: 'Chocolate Shake', description: 'Rich chocolate milkshake', price: 199, category: 'Beverages' }, + { id: 'i6', imageUrl: '', title: 'Cola', description: 'Refreshing cold drink', price: 49, category: 'Beverages' }, +]; + +export const deliveryService = { + async getProviders() { + return apiClient.get('/providers', mockProviders); + }, + + async getProviderCatalog(providerId: string) { + return apiClient.get(`/providers/${providerId}/catalog`, mockCatalogItems); + }, + + async searchProviders(query: string) { + const results = mockProviders.filter( + (p) => + p.name.toLowerCase().includes(query.toLowerCase()) || + p.tag.toLowerCase().includes(query.toLowerCase()), + ); + return apiClient.get(`/search?q=${query}`, results); + }, + + async placeOrder(orderData: Partial) { + return apiClient.post('/orders', orderData, { + success: true, + order: { + id: `ORD-${Date.now()}`, + ...orderData, + status: 'placed' as const, + createdAt: new Date().toISOString(), + }, + }); + }, + + async getDeliveryAgent() { + const agent: DeliveryAgent = { + name: 'Rahul Sharma', + rating: 4.8, + vehicleDetails: 'KA-01-AB-1234 - Honda Activa', + phoneNumber: '+91 98765 43210', + }; + return apiClient.get('/delivery/agent', agent); + }, + + async getLiveLocation() { + const location: Location = { + latitude: 12.9352, + longitude: 77.6245, + address: 'Koramangala 4th Block', + city: 'Bengaluru', + pincode: '560034', + }; + return apiClient.get('/delivery/live-location', location); + }, +}; diff --git a/app/store/authSlice.ts b/app/store/authSlice.ts new file mode 100644 index 0000000..915498f --- /dev/null +++ b/app/store/authSlice.ts @@ -0,0 +1,102 @@ +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { User, Location } from '../interfaces'; +import { authService } from '../services/authService'; + +interface AuthState { + user: User | null; + isAuthenticated: boolean; + isLoading: boolean; + onboardingComplete: boolean; + error: string | null; +} + +const initialState: AuthState = { + user: null, + isAuthenticated: false, + isLoading: false, + onboardingComplete: false, + error: null, +}; + +export const loginWithPhone = createAsyncThunk( + 'auth/loginWithPhone', + async (mobileNumber: string) => { + const response = await authService.login(mobileNumber); + return response; + }, +); + +export const verifyOtp = createAsyncThunk( + 'auth/verifyOtp', + async ({ mobileNumber, otp }: { mobileNumber: string; otp: string }) => { + const response = await authService.verifyOtp(mobileNumber, otp); + return response; + }, +); + +const authSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + setUser(state, action: PayloadAction) { + state.user = action.payload; + }, + setLocation(state, _action: PayloadAction) { + if (state.user) { + state.user = { ...state.user }; + } + }, + completeProfile(state, action: PayloadAction<{ name: string; email: string; locationLabels: string[] }>) { + if (state.user) { + state.user = { + ...state.user, + name: action.payload.name, + email: action.payload.email, + locationLabels: action.payload.locationLabels, + }; + } + }, + completeOnboarding(state) { + state.onboardingComplete = true; + }, + logout(state) { + state.user = null; + state.isAuthenticated = false; + state.onboardingComplete = false; + state.error = null; + }, + clearError(state) { + state.error = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(loginWithPhone.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(loginWithPhone.fulfilled, (state) => { + state.isLoading = false; + }) + .addCase(loginWithPhone.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message || 'Login failed'; + }) + .addCase(verifyOtp.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(verifyOtp.fulfilled, (state, action) => { + state.isLoading = false; + state.isAuthenticated = true; + state.user = action.payload.user; + }) + .addCase(verifyOtp.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message || 'OTP verification failed'; + }); + }, +}); + +export const { setUser, setLocation, completeProfile, completeOnboarding, logout, clearError } = authSlice.actions; +export default authSlice.reducer; diff --git a/app/store/cartSlice.ts b/app/store/cartSlice.ts new file mode 100644 index 0000000..c8986d8 --- /dev/null +++ b/app/store/cartSlice.ts @@ -0,0 +1,96 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { CartItem, CatalogItem } from '../interfaces'; + +interface CartState { + items: CartItem[]; + couponCode: string | null; + discount: number; + providerId: string | null; + providerName: string | null; +} + +const initialState: CartState = { + items: [], + couponCode: null, + discount: 0, + providerId: null, + providerName: null, +}; + +const cartSlice = createSlice({ + name: 'cart', + initialState, + reducers: { + addItem(state, action: PayloadAction<{ providerId: string; providerName: string; item: CatalogItem }>) { + const { providerId, providerName, item } = action.payload; + if (state.providerId && state.providerId !== providerId) { + state.items = []; + } + state.providerId = providerId; + state.providerName = providerName; + const existing = state.items.find((i) => i.item.id === item.id); + if (existing) { + existing.quantity += 1; + } else { + state.items.push({ + id: `${providerId}-${item.id}`, + providerId, + providerName, + item, + quantity: 1, + }); + } + }, + removeItem(state, action: PayloadAction) { + const existing = state.items.find((i) => i.item.id === action.payload); + if (existing) { + if (existing.quantity > 1) { + existing.quantity -= 1; + } else { + state.items = state.items.filter((i) => i.item.id !== action.payload); + } + } + if (state.items.length === 0) { + state.providerId = null; + state.providerName = null; + } + }, + clearCart(state) { + state.items = []; + state.couponCode = null; + state.discount = 0; + state.providerId = null; + state.providerName = null; + }, + applyCoupon(state, action: PayloadAction) { + state.couponCode = action.payload; + state.discount = 50; + }, + removeCoupon(state) { + state.couponCode = null; + state.discount = 0; + }, + }, +}); + +export const { addItem, removeItem, clearCart, applyCoupon, removeCoupon } = cartSlice.actions; + +export const selectCartTotal = (state: { cart: CartState }) => { + const subtotal = state.cart.items.reduce( + (sum, item) => sum + item.item.price * item.quantity, + 0, + ); + const deliveryFee = 40; + const platformFee = 10; + const discount = state.cart.discount; + return { + subtotal, + deliveryFee, + platformFee, + discount, + total: Math.max(0, subtotal + deliveryFee + platformFee - discount), + itemCount: state.cart.items.reduce((sum, item) => sum + item.quantity, 0), + }; +}; + +export default cartSlice.reducer; diff --git a/app/store/index.ts b/app/store/index.ts new file mode 100644 index 0000000..4c0bcbd --- /dev/null +++ b/app/store/index.ts @@ -0,0 +1,15 @@ +import { configureStore } from '@reduxjs/toolkit'; +import authReducer from './authSlice'; +import cartReducer from './cartSlice'; +import orderReducer from './orderSlice'; + +export const store = configureStore({ + reducer: { + auth: authReducer, + cart: cartReducer, + order: orderReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/app/store/orderSlice.ts b/app/store/orderSlice.ts new file mode 100644 index 0000000..17ae137 --- /dev/null +++ b/app/store/orderSlice.ts @@ -0,0 +1,67 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Order, OrderStatus, TrackingStep, DeliveryAgent, Location } from '../interfaces'; + +interface OrderState { + currentOrder: Order | null; + orders: Order[]; + trackingSteps: TrackingStep[]; + deliveryAgent: DeliveryAgent | null; + liveLocation: Location | null; +} + +const buildTrackingSteps = (status: OrderStatus): TrackingStep[] => { + const steps: { key: OrderStatus; label: string }[] = [ + { key: 'placed', label: 'Order Placed' }, + { key: 'confirmed', label: 'Confirmed' }, + { key: 'dispatched', label: 'Dispatched' }, + { key: 'delivered', label: 'Delivered' }, + ]; + const currentIndex = steps.findIndex((s) => s.key === status); + return steps.map((step, index) => ({ + ...step, + isCompleted: index < currentIndex, + isActive: index === currentIndex, + })); +}; + +const initialState: OrderState = { + currentOrder: null, + orders: [], + trackingSteps: [], + deliveryAgent: null, + liveLocation: null, +}; + +const orderSlice = createSlice({ + name: 'order', + initialState, + reducers: { + placeOrder(state, action: PayloadAction) { + state.currentOrder = action.payload; + state.orders.unshift(action.payload); + state.trackingSteps = buildTrackingSteps('placed'); + }, + updateOrderStatus(state, action: PayloadAction) { + if (state.currentOrder) { + state.currentOrder.status = action.payload; + state.trackingSteps = buildTrackingSteps(action.payload); + const idx = state.orders.findIndex((o) => o.id === state.currentOrder!.id); + if (idx !== -1) { + state.orders[idx] = { ...state.orders[idx], status: action.payload }; + } + } + }, + setDeliveryAgent(state, action: PayloadAction) { + state.deliveryAgent = action.payload; + }, + updateLiveLocation(state, action: PayloadAction) { + state.liveLocation = action.payload; + }, + addOrder(state, action: PayloadAction) { + state.orders.unshift(action.payload); + }, + }, +}); + +export const { placeOrder, updateOrderStatus, setDeliveryAgent, updateLiveLocation, addOrder } = orderSlice.actions; +export default orderSlice.reducer; diff --git a/babel.config.js b/babel.config.js index 73c2cce..4bfa3f7 100644 --- a/babel.config.js +++ b/babel.config.js @@ -12,5 +12,6 @@ module.exports = { }, }, ], + 'react-native-worklets/plugin', ], }; diff --git a/implementation.md b/implementation.md new file mode 100644 index 0000000..6456fa4 --- /dev/null +++ b/implementation.md @@ -0,0 +1,253 @@ +Multi-Delivery Mobile App – Reusable Screen Flow Implementation Plan +Overview +This plan details the implementation of a generic Multi-Delivery Customer Mobile App screen flow (supporting Food, Groceries, Pharmacy, and other delivery services). + +To ensure the codebase is easily portable and reusable in other projects: + +Clean Architecture & Separation of Concerns: All components are presentation-only, fully driven by props, and decoupled from state. +Pluggable API Service Layer: The API and store layers are modular, allowing simple swapping of mock data with real APIs later. +Generic Terminology: Uses general naming (e.g., provider instead of restaurant, catalogItem instead of food/menu item) to support any delivery vertical. +camelCase Convention: All directory, file, and variable names use camelCase. +Decisions (Updated) +Design Element Updated Requirement +Login Flow 🚫 No password, Forgot Password?, or Sign Up links required. Simply request Mobile Number and navigate to OTP. +API Integration πŸ”Œ Structure with pluggable service clients. Currently returns mock data, easily swappable with real endpoints later. +Naming & Portability πŸ“¦ Modular, highly generic component names (e.g., providerCard, catalogItemRow). Built to be highly reusable in other projects. +User Review Required +WARNING + +Native libraries (react-native-maps, React Navigation, etc.) require building native binaries (yarn android / yarn ios) after installation. + +Naming & Folder Structure Conventions +camelCase for all file and folder names (e.g. otpScreen/, providerCard/). +Avoid domain-specific terminology: +Use provider instead of restaurant / shop. +Use catalogItem or product instead of menu / foodItem. +Use serviceType instead of cuisine. +Proposed Changes +Phase 1 – Foundation, Navigation & Dependencies +Install core dependencies: + +@react-navigation/native, @react-navigation/stack, @react-navigation/bottom-tabs +react-native-screens, react-native-safe-area-context +react-native-gesture-handler, react-native-reanimated +react-native-maps +@reduxjs/toolkit, react-redux +react-native-push-notification, @react-native-community/push-notification-ios +[NEW] app/navigation/rootNavigator.tsx +Root navigator that directs to either authStack or appStack based on Redux authentication state. + +[NEW] app/navigation/authStack.tsx +Stack navigator for the onboarding flow (screens 1.1–1.6): + + +loginScreen β†’ otpScreen β†’ setLocationScreen β†’ completeProfileScreen β†’ preferencesScreen β†’ onboardingCompleteScreen +[NEW] app/navigation/appStack.tsx +Bottom Tab Navigator with 5 tabs: + +Tab Icon Screen / Navigator +Home 🏠 homeScreen +Search πŸ” searchScreen +Orders πŸ“¦ myOrdersScreen +Offers 🏷️ offersScreen +Account πŸ‘€ accountScreen +Phase 2 – Reusable & Generic Components (app/components/) +All components are strictly presentation-only, driven by typed props, and decoupled from state. + +[NEW] app/components/header/ +Generic top action bar with back navigation, center title, and optional right component (e.g., Change link, edit icon). + +[NEW] app/components/searchBar/ +Clean search text input with icon prefix. + +[NEW] app/components/providerCard/ +(was RestaurantCard) β€” Represents any merchant/store/delivery provider. + +Props: imageUrl, name, rating, deliveryTime, tag (e.g. "Food", "Groceries"), discountText. +[NEW] app/components/catalogItemRow/ +(was MenuItem) β€” Individual product/item detail row. + +Props: imageUrl, title, description, price, quantity, onAdd(), onRemove(). +[NEW] app/components/stepProgress/ +Progress tracker indicator for checkout steps: Address β†’ Payment β†’ Confirm. + +[NEW] app/components/badge/ +Status/offer pills (e.g. 50% OFF, Express, Popular). + +[NEW] app/components/quantitySelector/ +A modular stepper button (βˆ’ / +) to increment/decrement item counts. + +[NEW] app/components/categoryChip/ +Tappable icon/text pill selector for vertical categories (e.g., Food, Groceries, Pharmacy). + +[NEW] app/components/ratingStars/ +1-to-5 star rating component for post-delivery reviews. + +[NEW] app/components/orderHistoryCard/ +General item list card for order history: provider name, order date, order status, total price, reorder and detail actions. + +[NEW] app/components/paymentOption/ +Selectable list item row representing payment methods. + +[MODIFY] app/components/index.ts +Export all new components. + +Phase 3 – Auth & Onboarding (1.1 β†’ 1.6) +[MODIFY] app/features/screens/loginScreen/ +1.1 Login + +Modify existing login layout to remove Password input, "Forgot Password?", and "Sign Up" links. +Only show Mobile Number input field and a "Login" CTA. +Clicking "Login" navigates directly to otpScreen. +[NEW] app/features/screens/otpScreen/ +1.2 OTP Verification + +Displays code delivery status text. +6-cell OTP input box grid (custom numeric keypad: 1–9, 0, ⌫). +Resend OTP counter. +Navigates to setLocationScreen upon verification. +[NEW] app/features/screens/setLocationScreen/ +1.3 Set Delivery Location + +Interactive Map View pinning current location. +Card displaying details: Koramangala 4th Block, Bengaluru, 560034. +"Use this location" primary button & "Enter address manually" secondary button. +[NEW] app/features/screens/completeProfileScreen/ +1.4 Complete Profile + +Form requesting Name and Email. +Location label tags selector (Home, Work, Other). +"Save & Continue" action. +[NEW] app/features/screens/preferencesScreen/ +1.5 Preferences + +Selection grid for category interests: Food, Groceries, Pharmacy, Others. +Notifications toggle control. +"Continue" button. +[NEW] app/features/screens/onboardingCompleteScreen/ +1.6 Onboarding Complete + +Welcome success illustration. +"Explore Now" primary action button to dismiss stack and launch appStack tabs. +Phase 4 – Discover, Cart & Checkout (2.1 β†’ 2.6) +[NEW] app/features/screens/homeScreen/ +2.1 Home + +Address bar header with quick selector dropdown. +Integrated search bar (tapping redirects focus to Search tab). +General promotional banner card (e.g., 50% OFF banner). +Category chip row: Food, Grocery, Pharmacy, More. +"Popular Providers" vertical list feed using generic providerCard components. +[NEW] app/features/screens/searchScreen/ +Search Tab + +Search field. +List of matching search results (providers or items). +[NEW] app/features/screens/providerListScreen/ +2.2 Provider List (was RestaurantListScreen) + +Shows categorized list of stores/merchants. +Filter chips: Filters, Rating 4.0+, Fast Delivery. +Dynamic list of providerCard components. +[NEW] app/features/screens/providerDetailsScreen/ +2.3 Provider Details & Catalog (was RestaurantDetailScreen) + +Large merchant image, name, rating, tag, and delivery statistics. +Category catalog tab selection. +List of products/items via catalogItemRow. +Bottom active cart action strip ("X Items | β‚ΉY View Cart"). +[NEW] app/features/screens/cartScreen/ +2.4 Cart + +List of current cart selections with quantity adjustments. +Fee line breakdown: Delivery Fee, Platform Fee, discounts. +Coupon input box. +Bottom action panel ("Total: β‚ΉY View Bill"). +[NEW] app/features/screens/checkoutAddressScreen/ +2.5 Checkout – Address + +Checkout progress steps indicator (Step 1). +Deliver-to details block. +Delivery options switcher: Standard (Free) vs. Express (Charge). +Action button. +[NEW] app/features/screens/checkoutPaymentScreen/ +2.6 Checkout – Payment + +Checkout progress steps indicator (Step 2). +Payment method list component: UPI, Card, Wallet, COD. +Primary Checkout action. +Phase 5 – Order & Tracking (3.1 β†’ 3.4) +[NEW] app/features/screens/orderConfirmedScreen/ +3.1 Order Confirmed + +Success confirmation banner. +Order details header (Order ID, time estimate). +"View Tracking" action routing to orderTrackingScreen. +[NEW] app/features/screens/orderTrackingScreen/ +3.2 Order Tracking + +General milestones tracking layout: Order Placed β†’ Confirmed β†’ Dispatched β†’ Delivered. +Active status highlighting. +Customer support entry point. +[NEW] app/features/screens/liveTrackingScreen/ +3.3 Live Tracking + +Real-time map displaying mock delivery partner marker coordinates (react-native-maps). +Blinking active tracking indicator badge. +Delivery agent detail sheet (Name, rating, vehicle details, contact shortcuts). +[NEW] app/features/screens/orderDeliveredScreen/ +3.4 Order Delivered + +Post-delivery review modal with ratingStars. +"Rate & Tip" action button. +Phase 6 – Orders, Support & Account (4.1 β†’ 4.3) +[NEW] app/features/screens/myOrdersScreen/ +4.1 My Orders + +Order tabs: All, Ongoing, Completed. +List of order cards using orderHistoryCard. +[NEW] app/features/screens/offersScreen/ +Offers Tab + +Available coupons list with copy/apply action triggers. +[NEW] app/features/screens/helpSupportScreen/ +4.2 Help & Support + +General FAQ directory categories. +Support hotlines call action block. +[NEW] app/features/screens/accountScreen/ +4.3 Account + +Profile details card. +User management panel options (Addresses, Payments, Notifications, Logout). +Phase 7 – Store, Clean Services & API Client Layer +To support real backend replacement easily, we decouple state and actions from components: + +[NEW] app/services/apiClient.ts +Base API config. Currently calls local mock delay functions, easily swapped for fetch or axios instances later. + +[NEW] app/services/authService.ts +Handles logic for authentication (e.g. login, verifyOtp, logout). + +[NEW] app/services/deliveryService.ts +API integration service for fetching providers, items, cart management, and active deliveries. + +[NEW] app/store/authSlice.ts +Redux slice managing session states. + +[NEW] app/store/cartSlice.ts +Redux slice managing cart items state, totals, and active coupon. + +[NEW] app/store/orderSlice.ts +Redux slice managing active order statuses, tracking steps, and live coordinates. + +[NEW] app/store/index.ts +Main Redux store entry point. + +Verification Plan +Automated Tests +Run npx tsc --noEmit validation compile. +Manual Verification +Login & Onboarding: Input phone β†’ OTP screen keypad input β†’ Map location β†’ User profile details. +Provider & Cart Flow: Home category select β†’ providerListScreen -> providerDetailsScreen -> Catalog item quantity add -> Cart page. +Order Tracking Map: Confirm simulated marker movement maps correctly on Android & iOS. \ No newline at end of file diff --git a/package.json b/package.json index 7d5e404..21c9129 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,20 @@ "dependencies": { "@react-native-community/checkbox": "^0.5.20", "@react-native/new-app-screen": "0.86.0", + "@react-navigation/bottom-tabs": "^7.18.3", + "@react-navigation/native": "^7.3.4", + "@react-navigation/stack": "^7.10.6", + "@reduxjs/toolkit": "^2.12.0", "react": "19.2.3", "react-native": "0.86.0", - "react-native-safe-area-context": "^5.5.2", - "react-native-svg": "^15.15.5" + "react-native-gesture-handler": "^3.0.2", + "react-native-maps": "^1.29.0", + "react-native-reanimated": "^4.5.0", + "react-native-safe-area-context": "^5.8.0", + "react-native-screens": "^4.25.2", + "react-native-svg": "^15.15.5", + "react-native-worklets": "^0.10.0", + "react-redux": "^9.3.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/yarn.lock b/yarn.lock index 7d7095a..99c2d71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -429,7 +429,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.29.7": +"@babel/plugin-transform-arrow-functions@^7.27.1", "@babel/plugin-transform-arrow-functions@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz#d651343f562c03f47951bd1802195d0e10605f27" integrity sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ== @@ -468,7 +468,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.29.7" -"@babel/plugin-transform-class-properties@^7.25.4", "@babel/plugin-transform-class-properties@^7.29.7": +"@babel/plugin-transform-class-properties@^7.25.4", "@babel/plugin-transform-class-properties@^7.28.6", "@babel/plugin-transform-class-properties@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz#034897b8a21beec163332fac2de235b14409abdf" integrity sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA== @@ -484,7 +484,7 @@ "@babel/helper-create-class-features-plugin" "^7.29.7" "@babel/helper-plugin-utils" "^7.29.7" -"@babel/plugin-transform-classes@^7.25.4", "@babel/plugin-transform-classes@^7.29.7": +"@babel/plugin-transform-classes@^7.25.4", "@babel/plugin-transform-classes@^7.28.6", "@babel/plugin-transform-classes@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz#61d3e5aaae0c838acc3204d9db7c8dc05c25815b" integrity sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g== @@ -666,7 +666,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.29.7" -"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator@^7.29.7": +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator@^7.28.6", "@babel/plugin-transform-nullish-coalescing-operator@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz#8a54cdf88c3f50433a6173117a286195b67714cc" integrity sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg== @@ -706,7 +706,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.29.7" -"@babel/plugin-transform-optional-chaining@^7.24.8", "@babel/plugin-transform-optional-chaining@^7.29.7": +"@babel/plugin-transform-optional-chaining@^7.24.8", "@babel/plugin-transform-optional-chaining@^7.28.6", "@babel/plugin-transform-optional-chaining@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz#b84a1b574b3c73001023092567e16c492b720e51" integrity sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ== @@ -811,7 +811,7 @@ babel-plugin-polyfill-regenerator "^0.6.5" semver "^6.3.1" -"@babel/plugin-transform-shorthand-properties@^7.29.7": +"@babel/plugin-transform-shorthand-properties@^7.27.1", "@babel/plugin-transform-shorthand-properties@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz#25c0436b98f4bd9ca4b98e1fbd662743bbaab9bf" integrity sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg== @@ -833,7 +833,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.29.7" -"@babel/plugin-transform-template-literals@^7.29.7": +"@babel/plugin-transform-template-literals@^7.27.1", "@babel/plugin-transform-template-literals@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz#ada97d8e0832bca8edb315888aa654b1570f3835" integrity sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA== @@ -847,7 +847,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.29.7" -"@babel/plugin-transform-typescript@^7.25.2": +"@babel/plugin-transform-typescript@^7.25.2", "@babel/plugin-transform-typescript@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz#f0449c3df7037bbe232043476851c38f5e4a7615" integrity sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw== @@ -873,7 +873,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.29.7" "@babel/helper-plugin-utils" "^7.29.7" -"@babel/plugin-transform-unicode-regex@^7.24.7", "@babel/plugin-transform-unicode-regex@^7.29.7": +"@babel/plugin-transform-unicode-regex@^7.24.7", "@babel/plugin-transform-unicode-regex@^7.27.1", "@babel/plugin-transform-unicode-regex@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz#c3064b293ff7f1794b71f7650eec8db9896d3e59" integrity sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA== @@ -975,6 +975,17 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/preset-typescript@^7.28.5": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz#de9be1f47b785c979ec7b3a71f4cd8bae5267b62" + integrity sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ== + dependencies: + "@babel/helper-plugin-utils" "^7.29.7" + "@babel/helper-validator-option" "^7.29.7" + "@babel/plugin-syntax-jsx" "^7.29.7" + "@babel/plugin-transform-modules-commonjs" "^7.29.7" + "@babel/plugin-transform-typescript" "^7.29.7" + "@babel/runtime@^7.25.0": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.7.tgz#12022450c45a4da6d8d8287b18a4ff2ddb23f768" @@ -1002,7 +1013,7 @@ "@babel/types" "^7.29.7" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== @@ -1704,6 +1715,78 @@ invariant "^2.2.4" nullthrows "^1.1.1" +"@react-navigation/bottom-tabs@^7.18.3": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.18.3.tgz#371c39aee03aa90979d64e8d97da78559fbbcc92" + integrity sha512-715guj97M1yklVkN9ikUl6KJL9wO6hNot7URyUm+A1r5ltEHVgahKTgR8w7e4dNwiMjUjAnOdSErR0LaX1h+kQ== + dependencies: + "@react-navigation/elements" "^2.9.26" + color "^4.2.3" + sf-symbols-typescript "^2.1.0" + +"@react-navigation/core@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.21.2.tgz#448591d27d6c1472491a8ea9b901954ea727a310" + integrity sha512-EsJhQnsL5NuIhXsWF7URijS5hd2AaJUREdq1fOIowlyNNQBA3jCnkGU0DkW7+eI40cQ+GXH8DQw+ci7TefLIxQ== + dependencies: + "@react-navigation/routers" "^7.6.0" + escape-string-regexp "^4.0.0" + fast-deep-equal "^3.1.3" + nanoid "^3.3.11" + query-string "^7.1.3" + react-is "^19.1.0" + use-latest-callback "^0.2.4" + use-sync-external-store "^1.5.0" + +"@react-navigation/elements@^2.9.26": + version "2.9.26" + resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.26.tgz#89cd09db890534c115031b1a6d99d916fdcda96c" + integrity sha512-EaHSJf42MjnH5VFL+KZfQIyqJvdQWK9Z27J7WUSuIVX5NXlR925sPmhWf540DX9gD1ny8BfUGPZAuw/PO4tK2w== + dependencies: + color "^4.2.3" + use-latest-callback "^0.2.4" + use-sync-external-store "^1.5.0" + +"@react-navigation/native@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.3.4.tgz#a9ba7b0fde595b019a16a87ab609ad5b618bfbef" + integrity sha512-zyAqFLeZuaakerMMnhYqBeGAzJ+WFQUTgezP3FxSlXNwFq5XxnjP28vi2XHGqm4zg4pGWls9Ay4JKshIHUOpwA== + dependencies: + "@react-navigation/core" "^7.21.2" + escape-string-regexp "^4.0.0" + fast-deep-equal "^3.1.3" + nanoid "^3.3.11" + standard-navigation "^0.0.7" + use-latest-callback "^0.2.4" + +"@react-navigation/routers@^7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-7.6.0.tgz#04ee630c4ecbdfa3c8ef65000909aa2dbf0ff78f" + integrity sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow== + dependencies: + nanoid "^3.3.11" + +"@react-navigation/stack@^7.10.6": + version "7.10.6" + resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-7.10.6.tgz#5adf039d106a097bcb5514f07c1afc9e3ec75483" + integrity sha512-LWtBSCPNi3Htnb3051wan5hVTsoORxUocBeuAjZ3VS1u6wTtfObeqOOhanz/L+uiAzvTWcAHhevQhtlGeFn/mg== + dependencies: + "@react-navigation/elements" "^2.9.26" + color "^4.2.3" + use-latest-callback "^0.2.4" + +"@reduxjs/toolkit@^2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.12.0.tgz#e62787503a38561e04bb8f39e29ca8db689590f9" + integrity sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw== + dependencies: + "@standard-schema/spec" "^1.0.0" + "@standard-schema/utils" "^0.3.0" + immer "^11.0.0" + redux "^5.0.1" + redux-thunk "^3.1.0" + reselect "^5.1.0" + "@sideway/address@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" @@ -1740,6 +1823,16 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@standard-schema/spec@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@standard-schema/utils@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" + integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== + "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -1773,6 +1866,11 @@ dependencies: "@babel/types" "^7.28.2" +"@types/geojson@^7946.0.13": + version "7946.0.16" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" + integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -1833,6 +1931,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -2458,9 +2561,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001799: - version "1.0.30001799" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz#5c909138c27f1a61219d3e092071c1cc7d32dc55" - integrity sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw== + version "1.0.30001800" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz#b896c773e1c39400809415162bb5320371291b36" + integrity sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA== chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" @@ -2575,11 +2678,27 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + colorette@^1.0.7: version "1.4.0" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" @@ -2769,6 +2888,11 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decode-uri-component@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + dedent@^1.0.0: version "1.7.2" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9" @@ -2888,9 +3012,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.5.376: - version "1.5.381" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz#037549001adc80e04834ede96dc52a1fbf6c9fde" - integrity sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg== + version "1.5.382" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.382.tgz#e76f05d3ec337524b9c61761ebbc16fe91ecf5b4" + integrity sha512-8ETaWbV6SZOrno+G93Ffd9ENsMtetqdnqj4nlfxFW90Sm5GgnuV28Kf62hqQVD6VUgzm7qFQKsTsAPmeUiU3Ug== emittery@^0.13.1: version "0.13.1" @@ -3422,6 +3546,11 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== + finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -3806,6 +3935,11 @@ image-size@^1.0.2: dependencies: queue "6.0.2" +immer@^11.0.0: + version "11.1.8" + resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.8.tgz#08a6426f7019dbce8d6dff8c4a43bb25c550a575" + integrity sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -3870,6 +4004,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.4" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" + integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== + is-async-function@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" @@ -5077,6 +5216,11 @@ ms@2.1.3, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +nanoid@^3.3.11: + version "3.3.15" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.15.tgz#36c490fad8c6e86c824c940dfdde999b69ed4316" + integrity sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -5494,6 +5638,16 @@ qs@~6.15.1: es-define-property "^1.0.1" side-channel "^1.1.1" +query-string@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== + dependencies: + decode-uri-component "^0.2.2" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5529,6 +5683,11 @@ react-devtools-core@^6.1.5: shell-quote "^1.6.1" ws "^7" +react-freeze@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.4.tgz#cbbea2762b0368b05cbe407ddc9d518c57c6f3ad" + integrity sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -5539,16 +5698,52 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react-is@^19.2.3: +react-is@^19.1.0, react-is@^19.2.3: version "19.2.7" resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.7.tgz#57668ee86a78574a542b0a539455212b2c086df2" integrity sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A== -react-native-safe-area-context@^5.5.2: +react-native-gesture-handler@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-3.0.2.tgz#ea80fd2424d08f8b624977221f0544a5201f6d83" + integrity sha512-XBTgmvr2jh/xrMwlHTV2Z1x6IFUl3zfLxyaCAnvj3GSXK5YlY3ZmiIs57hniPmh3I7RtjPFxtGdRr0i+PzyBrg== + dependencies: + "@types/react-test-renderer" "^19.1.0" + invariant "^2.2.4" + +react-native-is-edge-to-edge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz#feb9a6a8faf0874298947edd556e5af22044e139" + integrity sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA== + +react-native-maps@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/react-native-maps/-/react-native-maps-1.29.0.tgz#7ba5b9f70c9dcedeb53d31847809c56661341742" + integrity sha512-tXyYyyeZgiThHQvr/d22V7MMOxUfxdkXujFkGEuGP6FwBvVew/QmTS8VNiPbPce95uBIqE/SaOjai3Zwndpadg== + dependencies: + "@types/geojson" "^7946.0.13" + +react-native-reanimated@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-4.5.0.tgz#7029849859625a6247c7cf11161d5bef2cb99b12" + integrity sha512-+iPfvK34PKKYP/p/4TaBliFkbfvjGDIvXuiiaxvISP5ip7sWegvlacwU/uAV6zNDSSmX0tDyER7PurPMKGDipA== + dependencies: + react-native-is-edge-to-edge "^1.3.1" + semver "^7.7.3" + +react-native-safe-area-context@^5.8.0: version "5.8.0" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.8.0.tgz#d2fb9fc8e0f0583311e261dc484b95ceb80f8902" integrity sha512-t+ZsAVzY/wWzzx34vqGbo3/as9EEESJdbyZNL7Yg5EYX+toYMtMqFoDDCvqZUi35eeGVsXc6pAaEk4edMwbuCQ== +react-native-screens@^4.25.2: + version "4.25.2" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.25.2.tgz#f12c3d8d2cb9d69e1267af1bbba1b37d1d05452d" + integrity sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg== + dependencies: + react-freeze "^1.0.0" + warn-once "^0.1.0" + react-native-svg@^15.15.5: version "15.15.5" resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.15.5.tgz#822805c14481b8ec16d5fbac1e3ce2b27a5509d7" @@ -5557,6 +5752,24 @@ react-native-svg@^15.15.5: css-select "^5.1.0" css-tree "^1.1.3" +react-native-worklets@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.10.0.tgz#e2e8f8e1ba0eccc4e69f3a4e75894ecab4ba19b0" + integrity sha512-JhE6IxDf6iabC0qu3+TAKA4v9RlluXmoIngPQX7/QUByf75lfrsHZ6/dQhyjEWnp1EEQiwzz8Cpew140ZcewDw== + dependencies: + "@babel/plugin-transform-arrow-functions" "^7.27.1" + "@babel/plugin-transform-class-properties" "^7.28.6" + "@babel/plugin-transform-classes" "^7.28.6" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6" + "@babel/plugin-transform-optional-chaining" "^7.28.6" + "@babel/plugin-transform-shorthand-properties" "^7.27.1" + "@babel/plugin-transform-template-literals" "^7.27.1" + "@babel/plugin-transform-unicode-regex" "^7.27.1" + "@babel/preset-typescript" "^7.28.5" + "@babel/types" "^7.27.1" + convert-source-map "^2.0.0" + semver "^7.7.4" + react-native@0.86.0: version "0.86.0" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.86.0.tgz#a5d62adbf350010ad7a61617b9bc300b2ff12870" @@ -5595,6 +5808,14 @@ react-native@0.86.0: ws "^7.5.10" yargs "^17.6.2" +react-redux@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.3.0.tgz#a30113bb6d95c0a715d54dda4308d450fca6ce09" + integrity sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g== + dependencies: + "@types/use-sync-external-store" "^0.0.6" + use-sync-external-store "^1.4.0" + react-refresh@^0.14.0: version "0.14.2" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" @@ -5622,6 +5843,16 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== + +redux@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + reflect.getprototypeof@^1.0.10, reflect.getprototypeof@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" @@ -5704,6 +5935,11 @@ reselect@^4.1.7: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== +reselect@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.2.0.tgz#f380ef7664332d26ea06c1cba04bdbbdcaa955f1" + integrity sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -5823,7 +6059,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.1.3, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.7.3: +semver@^7.1.3, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.7.3, semver@^7.7.4: version "7.8.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== @@ -5903,6 +6139,11 @@ setprototypeof@~1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sf-symbols-typescript@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz#926d6e0715e3d8784cadf7658431e36581254208" + integrity sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -5965,6 +6206,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-swizzle@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" + integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== + dependencies: + is-arrayish "^0.3.1" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -6010,6 +6258,11 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6034,6 +6287,11 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" +standard-navigation@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/standard-navigation/-/standard-navigation-0.0.7.tgz#8c5265c4244446e0fb2b4bc9861a9979dd34f22f" + integrity sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ== + statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -6052,6 +6310,11 @@ stop-iteration-iterator@^1.1.0: es-errors "^1.3.0" internal-slot "^1.1.0" +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -6401,6 +6664,16 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-latest-callback@^0.2.4: + version "0.2.6" + resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.6.tgz#e5ea752808c86219acc179ace0ae3c1203255e77" + integrity sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg== + +use-sync-external-store@^1.4.0, use-sync-external-store@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -6437,6 +6710,11 @@ walker@^1.0.7, walker@^1.0.8: dependencies: makeerror "1.0.12" +warn-once@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" + integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q== + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"