feat(app): initial data flow
This commit is contained in:
parent
2500962320
commit
8f6e162e5e
@ -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
|
||||
|
||||
44
app/App.tsx
44
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 (
|
||||
<SafeAreaProvider>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<AppContent />
|
||||
</SafeAreaProvider>
|
||||
<Provider store={store}>
|
||||
<SafeAreaProvider>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<NavigationContainer>
|
||||
<RootNavigator />
|
||||
</NavigationContainer>
|
||||
</SafeAreaProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function AppContent() {
|
||||
const safeAreaInsets = useSafeAreaInsets();
|
||||
const { colors } = useAppTheme();
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: safeAreaInsets.top, paddingBottom: safeAreaInsets.bottom, backgroundColor: colors.background }]}>
|
||||
<LoginScreen />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
||||
@ -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<CustomInputProps> = ({
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './CustomInput';
|
||||
export * from './CustomInput.props';
|
||||
export * from './customInput';
|
||||
export * from './customInput.props';
|
||||
|
||||
@ -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<PrimaryButtonProps> = ({
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './PrimaryButton';
|
||||
export * from './PrimaryButton.props';
|
||||
export * from './primaryButton';
|
||||
export * from './primaryButton.props';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './SocialButton';
|
||||
export * from './SocialButton.props';
|
||||
export * from './socialButton';
|
||||
export * from './socialButton.props';
|
||||
|
||||
4
app/components/badge/badge.props.ts
Normal file
4
app/components/badge/badge.props.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface BadgeProps {
|
||||
text: string;
|
||||
variant?: 'primary' | 'success' | 'warning' | 'danger';
|
||||
}
|
||||
30
app/components/badge/badge.styles.ts
Normal file
30
app/components/badge/badge.styles.ts
Normal file
@ -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,
|
||||
};
|
||||
};
|
||||
17
app/components/badge/badge.tsx
Normal file
17
app/components/badge/badge.tsx
Normal file
@ -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<BadgeProps> = ({ text, variant = 'primary' }) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles();
|
||||
const variantStyle = getVariantStyle(colors, variant);
|
||||
|
||||
return (
|
||||
<View style={[styles.badge, { backgroundColor: variantStyle.backgroundColor }]}>
|
||||
<Text style={[styles.text, { color: variantStyle.color }]}>{text}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
2
app/components/badge/index.ts
Normal file
2
app/components/badge/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './badge';
|
||||
export * from './badge.props';
|
||||
9
app/components/catalogItemRow/catalogItemRow.props.ts
Normal file
9
app/components/catalogItemRow/catalogItemRow.props.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface CatalogItemRowProps {
|
||||
imageUrl: string;
|
||||
title: string;
|
||||
description: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
onAdd: () => void;
|
||||
onRemove: () => void;
|
||||
}
|
||||
75
app/components/catalogItemRow/catalogItemRow.styles.ts
Normal file
75
app/components/catalogItemRow/catalogItemRow.styles.ts
Normal file
@ -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',
|
||||
},
|
||||
});
|
||||
60
app/components/catalogItemRow/catalogItemRow.tsx
Normal file
60
app/components/catalogItemRow/catalogItemRow.tsx
Normal file
@ -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<CatalogItemRowProps> = ({
|
||||
imageUrl,
|
||||
title,
|
||||
description,
|
||||
price,
|
||||
quantity,
|
||||
onAdd,
|
||||
onRemove,
|
||||
}) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.image}>
|
||||
<Text style={styles.imagePlaceholder}>{imageUrl || '🍕'}</Text>
|
||||
</View>
|
||||
<View style={styles.info}>
|
||||
<Text style={styles.title} numberOfLines={1}>{title}</Text>
|
||||
<Text style={styles.description} numberOfLines={2}>{description}</Text>
|
||||
<Text style={styles.price}>₹{price}</Text>
|
||||
</View>
|
||||
<View style={styles.actions}>
|
||||
{quantity > 0 ? (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={[styles.stepperButton, quantity === 0 && styles.stepperButtonDisabled]}
|
||||
onPress={onRemove}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.stepperButtonText}>-</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.quantity}>{quantity}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.stepperButton}
|
||||
onPress={onAdd}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.stepperButtonText}>+</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
style={styles.stepperButton}
|
||||
onPress={onAdd}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.stepperButtonText}>+</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
2
app/components/catalogItemRow/index.ts
Normal file
2
app/components/catalogItemRow/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './catalogItemRow';
|
||||
export * from './catalogItemRow.props';
|
||||
6
app/components/categoryChip/categoryChip.props.ts
Normal file
6
app/components/categoryChip/categoryChip.props.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface CategoryChipProps {
|
||||
icon: string;
|
||||
label: string;
|
||||
isSelected: boolean;
|
||||
onPress: () => void;
|
||||
}
|
||||
33
app/components/categoryChip/categoryChip.styles.ts
Normal file
33
app/components/categoryChip/categoryChip.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
28
app/components/categoryChip/categoryChip.tsx
Normal file
28
app/components/categoryChip/categoryChip.tsx
Normal file
@ -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<CategoryChipProps> = ({
|
||||
icon,
|
||||
label,
|
||||
isSelected,
|
||||
onPress,
|
||||
}) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, isSelected && styles.selected]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.icon}>{icon}</Text>
|
||||
<Text style={[styles.label, isSelected && styles.labelSelected]}>
|
||||
{label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
2
app/components/categoryChip/index.ts
Normal file
2
app/components/categoryChip/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './categoryChip';
|
||||
export * from './categoryChip.props';
|
||||
7
app/components/header/header.props.ts
Normal file
7
app/components/header/header.props.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface HeaderProps {
|
||||
title: string;
|
||||
onBack?: () => void;
|
||||
rightComponent?: ReactNode;
|
||||
}
|
||||
36
app/components/header/header.styles.ts
Normal file
36
app/components/header/header.styles.ts
Normal file
@ -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',
|
||||
},
|
||||
});
|
||||
28
app/components/header/header.tsx
Normal file
28
app/components/header/header.tsx
Normal file
@ -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<HeaderProps> = ({ title, onBack, rightComponent }) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.backButton}>
|
||||
{onBack && (
|
||||
<TouchableOpacity onPress={onBack} activeOpacity={0.7}>
|
||||
<Text style={styles.backText}>{'<'}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title} numberOfLines={1}>{title}</Text>
|
||||
</View>
|
||||
<View style={styles.rightContainer}>
|
||||
{rightComponent}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
2
app/components/header/index.ts
Normal file
2
app/components/header/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './header';
|
||||
export * from './header.props';
|
||||
@ -1,3 +1,14 @@
|
||||
export * from './CustomInput';
|
||||
export * from './PrimaryButton';
|
||||
export * from './SocialButton';
|
||||
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';
|
||||
|
||||
2
app/components/orderHistoryCard/index.ts
Normal file
2
app/components/orderHistoryCard/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './orderHistoryCard';
|
||||
export * from './orderHistoryCard.props';
|
||||
@ -0,0 +1,9 @@
|
||||
export interface OrderHistoryCardProps {
|
||||
providerName: string;
|
||||
providerImage: string;
|
||||
orderDate: string;
|
||||
status: string;
|
||||
total: number;
|
||||
onReorder?: () => void;
|
||||
onDetails?: () => void;
|
||||
}
|
||||
86
app/components/orderHistoryCard/orderHistoryCard.styles.ts
Normal file
86
app/components/orderHistoryCard/orderHistoryCard.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
58
app/components/orderHistoryCard/orderHistoryCard.tsx
Normal file
58
app/components/orderHistoryCard/orderHistoryCard.tsx
Normal file
@ -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<OrderHistoryCardProps> = ({
|
||||
providerName,
|
||||
providerImage,
|
||||
orderDate,
|
||||
status,
|
||||
total,
|
||||
onReorder,
|
||||
onDetails,
|
||||
}) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.image}>
|
||||
<Text style={styles.imagePlaceholder}>{providerImage || '🏪'}</Text>
|
||||
</View>
|
||||
<View style={styles.headerInfo}>
|
||||
<Text style={styles.providerName}>{providerName}</Text>
|
||||
<Text style={styles.orderDate}>{orderDate}</Text>
|
||||
</View>
|
||||
<View style={styles.statusBadge}>
|
||||
<Text style={styles.statusText}>{status}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.footer}>
|
||||
<Text style={styles.total}>₹{total}</Text>
|
||||
<View style={styles.actions}>
|
||||
{onReorder && (
|
||||
<TouchableOpacity
|
||||
style={styles.actionButton}
|
||||
onPress={onReorder}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.actionButtonText}>Reorder</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{onDetails && (
|
||||
<TouchableOpacity
|
||||
style={styles.actionButton}
|
||||
onPress={onDetails}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.actionButtonText}>Details</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
2
app/components/paymentOption/index.ts
Normal file
2
app/components/paymentOption/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './paymentOption';
|
||||
export * from './paymentOption.props';
|
||||
6
app/components/paymentOption/paymentOption.props.ts
Normal file
6
app/components/paymentOption/paymentOption.props.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface PaymentOptionProps {
|
||||
type: string;
|
||||
label: string;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
48
app/components/paymentOption/paymentOption.styles.ts
Normal file
48
app/components/paymentOption/paymentOption.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
29
app/components/paymentOption/paymentOption.tsx
Normal file
29
app/components/paymentOption/paymentOption.tsx
Normal file
@ -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<PaymentOptionProps> = ({
|
||||
type,
|
||||
label,
|
||||
isSelected,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, isSelected && styles.selected]}
|
||||
onPress={onSelect}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.icon}>{type === 'UPI' ? '📱' : type === 'Card' ? '💳' : type === 'Wallet' ? '👛' : '💵'}</Text>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
<View style={[styles.radio, isSelected && styles.radioSelected]}>
|
||||
{isSelected && <View style={styles.radioInner} />}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
2
app/components/providerCard/index.ts
Normal file
2
app/components/providerCard/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './providerCard';
|
||||
export * from './providerCard.props';
|
||||
9
app/components/providerCard/providerCard.props.ts
Normal file
9
app/components/providerCard/providerCard.props.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface ProviderCardProps {
|
||||
imageUrl: string;
|
||||
name: string;
|
||||
rating: number;
|
||||
deliveryTime: string;
|
||||
tag: string;
|
||||
discountText?: string;
|
||||
onPress?: () => void;
|
||||
}
|
||||
69
app/components/providerCard/providerCard.styles.ts
Normal file
69
app/components/providerCard/providerCard.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
43
app/components/providerCard/providerCard.tsx
Normal file
43
app/components/providerCard/providerCard.tsx
Normal file
@ -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<ProviderCardProps> = ({
|
||||
imageUrl,
|
||||
name,
|
||||
rating,
|
||||
deliveryTime,
|
||||
tag,
|
||||
discountText,
|
||||
onPress,
|
||||
}) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.container}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<View style={styles.image}>
|
||||
<Text style={styles.imagePlaceholder}>{imageUrl || '🍽️'}</Text>
|
||||
</View>
|
||||
<View style={styles.info}>
|
||||
<Text style={styles.name} numberOfLines={1}>{name}</Text>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.rating}>⭐ {rating.toFixed(1)}</Text>
|
||||
<Text style={styles.deliveryTime}>🕐 {deliveryTime}</Text>
|
||||
</View>
|
||||
<Text style={styles.tag}>{tag}</Text>
|
||||
{discountText && (
|
||||
<View style={styles.discountBadge}>
|
||||
<Text style={styles.discountText}>{discountText}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
2
app/components/quantitySelector/index.ts
Normal file
2
app/components/quantitySelector/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './quantitySelector';
|
||||
export * from './quantitySelector.props';
|
||||
@ -0,0 +1,7 @@
|
||||
export interface QuantitySelectorProps {
|
||||
value: number;
|
||||
onIncrement: () => void;
|
||||
onDecrement: () => void;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
33
app/components/quantitySelector/quantitySelector.styles.ts
Normal file
33
app/components/quantitySelector/quantitySelector.styles.ts
Normal file
@ -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',
|
||||
},
|
||||
});
|
||||
38
app/components/quantitySelector/quantitySelector.tsx
Normal file
38
app/components/quantitySelector/quantitySelector.tsx
Normal file
@ -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<QuantitySelectorProps> = ({
|
||||
value,
|
||||
onIncrement,
|
||||
onDecrement,
|
||||
min = 0,
|
||||
max = 99,
|
||||
}) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, value <= min && styles.buttonDisabled]}
|
||||
onPress={onDecrement}
|
||||
disabled={value <= min}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.buttonText}>-</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.value}>{value}</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, value >= max && styles.buttonDisabled]}
|
||||
onPress={onIncrement}
|
||||
disabled={value >= max}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.buttonText}>+</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
2
app/components/ratingStars/index.ts
Normal file
2
app/components/ratingStars/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './ratingStars';
|
||||
export * from './ratingStars.props';
|
||||
5
app/components/ratingStars/ratingStars.props.ts
Normal file
5
app/components/ratingStars/ratingStars.props.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface RatingStarsProps {
|
||||
rating: number;
|
||||
onRate?: (rating: number) => void;
|
||||
size?: number;
|
||||
}
|
||||
12
app/components/ratingStars/ratingStars.styles.ts
Normal file
12
app/components/ratingStars/ratingStars.styles.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export const getStyles = () => StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
star: {
|
||||
marginHorizontal: 4,
|
||||
},
|
||||
});
|
||||
29
app/components/ratingStars/ratingStars.tsx
Normal file
29
app/components/ratingStars/ratingStars.tsx
Normal file
@ -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<RatingStarsProps> = ({
|
||||
rating,
|
||||
onRate,
|
||||
size = 32,
|
||||
}) => {
|
||||
const styles = getStyles();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<TouchableOpacity
|
||||
key={star}
|
||||
onPress={() => onRate?.(star)}
|
||||
disabled={!onRate}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[styles.star, { fontSize: size }]}>
|
||||
{star <= rating ? '⭐' : '☆'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
2
app/components/searchBar/index.ts
Normal file
2
app/components/searchBar/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './searchBar';
|
||||
export * from './searchBar.props';
|
||||
7
app/components/searchBar/searchBar.props.ts
Normal file
7
app/components/searchBar/searchBar.props.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface SearchBarProps {
|
||||
value: string;
|
||||
onChangeText: (text: string) => void;
|
||||
placeholder?: string;
|
||||
onFocus?: () => void;
|
||||
onSubmit?: () => void;
|
||||
}
|
||||
28
app/components/searchBar/searchBar.styles.ts
Normal file
28
app/components/searchBar/searchBar.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
34
app/components/searchBar/searchBar.tsx
Normal file
34
app/components/searchBar/searchBar.tsx
Normal file
@ -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<SearchBarProps> = ({
|
||||
value,
|
||||
onChangeText,
|
||||
placeholder = 'Search...',
|
||||
onFocus,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.icon}>🔍</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={value}
|
||||
onChangeText={onChangeText}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={colors.placeholder}
|
||||
onFocus={onFocus}
|
||||
onSubmitEditing={onSubmit}
|
||||
returnKeyType="search"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
2
app/components/stepProgress/index.ts
Normal file
2
app/components/stepProgress/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './stepProgress';
|
||||
export * from './stepProgress.props';
|
||||
4
app/components/stepProgress/stepProgress.props.ts
Normal file
4
app/components/stepProgress/stepProgress.props.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface StepProgressProps {
|
||||
steps: { key: string; label: string }[];
|
||||
activeStep: number;
|
||||
}
|
||||
55
app/components/stepProgress/stepProgress.styles.ts
Normal file
55
app/components/stepProgress/stepProgress.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
45
app/components/stepProgress/stepProgress.tsx
Normal file
45
app/components/stepProgress/stepProgress.tsx
Normal file
@ -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<StepProgressProps> = ({ steps, activeStep }) => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{steps.map((step, index) => (
|
||||
<React.Fragment key={step.key}>
|
||||
<View style={styles.stepContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.circle,
|
||||
index <= activeStep && styles.circleActive,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.circleText}>{index + 1}</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
styles.label,
|
||||
index <= activeStep && styles.labelActive,
|
||||
]}
|
||||
>
|
||||
{step.label}
|
||||
</Text>
|
||||
</View>
|
||||
{index < steps.length - 1 && (
|
||||
<View
|
||||
style={[
|
||||
styles.connector,
|
||||
index < activeStep && styles.connectorActive,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@ -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<AuthStackParamList, 'LoginScreen'>;
|
||||
|
||||
export const LoginScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const navigation = useNavigation<LoginNavProp>();
|
||||
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<string | undefined>(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}
|
||||
>
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.title}>Welcome back 👋</Text>
|
||||
<Text style={styles.title}>Welcome back</Text>
|
||||
<Text style={styles.subtitle}>Login to continue</Text>
|
||||
</View>
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
label="Password"
|
||||
placeholder="••••••••"
|
||||
isPassword
|
||||
rightActionText="Forgot?"
|
||||
onRightActionPress={handleForgotPassword}
|
||||
value={password}
|
||||
onChangeText={(text) => {
|
||||
setPassword(text);
|
||||
if (errors.password) setErrors({ ...errors, password: undefined });
|
||||
}}
|
||||
error={errors.password}
|
||||
/>
|
||||
|
||||
<View style={styles.row}>
|
||||
<TouchableOpacity
|
||||
style={styles.rememberMeContainer}
|
||||
onPress={() => setRememberMe(!rememberMe)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<CheckBox
|
||||
value={rememberMe}
|
||||
onValueChange={setRememberMe}
|
||||
tintColors={{ true: colors.primary, false: colors.textSecondary }}
|
||||
tintColor={colors.textSecondary}
|
||||
onTintColor={colors.primary}
|
||||
onCheckColor={colors.white}
|
||||
onFillColor={colors.primary}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={styles.checkboxText}>Remember me</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<PrimaryButton
|
||||
title="Login"
|
||||
onPress={handleLogin}
|
||||
isLoading={isLoading}
|
||||
style={{ marginBottom: 12 }}
|
||||
style={{ marginTop: 12 }}
|
||||
/>
|
||||
|
||||
<View style={styles.dividerContainer}>
|
||||
<View style={styles.line} />
|
||||
<Text style={styles.dividerText}>or continue with</Text>
|
||||
<View style={styles.line} />
|
||||
</View>
|
||||
|
||||
<View style={styles.socialContainer}>
|
||||
<SocialButton provider="google" onPress={() => handleSocialLogin('google')} />
|
||||
<SocialButton provider="apple" onPress={() => handleSocialLogin('apple')} />
|
||||
</View>
|
||||
|
||||
<TouchableOpacity onPress={handleSignUp} activeOpacity={0.8}>
|
||||
<Text style={styles.footerText}>
|
||||
Don’t have an account? <Text style={styles.signUpLink}>Sign up</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './LoginScreen';
|
||||
export * from './LoginScreen.styles';
|
||||
export * from './loginScreen';
|
||||
export * from './loginScreen.styles';
|
||||
|
||||
68
app/features/screens/accountScreen/accountScreen.styles.ts
Normal file
68
app/features/screens/accountScreen/accountScreen.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
60
app/features/screens/accountScreen/accountScreen.tsx
Normal file
60
app/features/screens/accountScreen/accountScreen.tsx
Normal file
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Account" />
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
<View style={styles.profileCard}>
|
||||
<View style={styles.avatar}>
|
||||
<Text style={styles.avatarText}>
|
||||
{user?.name?.charAt(0)?.toUpperCase() || 'U'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.profileName}>{user?.name || 'User'}</Text>
|
||||
<Text style={styles.profilePhone}>{user?.mobileNumber || '+91 98765 43210'}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.menuSection}>
|
||||
{MENU_ITEMS.map((item, index) => (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
style={styles.menuItem}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.menuIcon}>{item.icon}</Text>
|
||||
<Text style={styles.menuLabel}>{item.label}</Text>
|
||||
<Text style={styles.menuArrow}>{'>'}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<PrimaryButton
|
||||
title="Logout"
|
||||
onPress={() => dispatch(logout())}
|
||||
style={{ marginTop: 24 }}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/accountScreen/index.ts
Normal file
1
app/features/screens/accountScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './accountScreen';
|
||||
109
app/features/screens/cartScreen/cartScreen.styles.ts
Normal file
109
app/features/screens/cartScreen/cartScreen.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
110
app/features/screens/cartScreen/cartScreen.tsx
Normal file
110
app/features/screens/cartScreen/cartScreen.tsx
Normal file
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Cart" onBack={() => {}} />
|
||||
<FlatList
|
||||
data={items}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.cartItem}>
|
||||
<View style={styles.itemInfo}>
|
||||
<Text style={styles.itemTitle}>{item.item.title}</Text>
|
||||
<Text style={styles.itemPrice}>₹{item.item.price}</Text>
|
||||
</View>
|
||||
<QuantitySelector
|
||||
value={item.quantity}
|
||||
onIncrement={() => {}}
|
||||
onDecrement={() => dispatch(removeItem(item.item.id))}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
ListFooterComponent={
|
||||
<View style={styles.footer}>
|
||||
<View style={styles.couponRow}>
|
||||
<TextInput
|
||||
style={styles.couponInput}
|
||||
placeholder="Enter coupon code"
|
||||
placeholderTextColor={colors.placeholder}
|
||||
value={couponInput}
|
||||
onChangeText={setCouponInput}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={styles.applyButton}
|
||||
onPress={() => {
|
||||
if (couponCode) {
|
||||
dispatch(removeCoupon());
|
||||
setCouponInput('');
|
||||
} else if (couponInput) {
|
||||
dispatch(applyCoupon(couponInput));
|
||||
}
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.applyButtonText}>
|
||||
{couponCode ? 'Remove' : 'Apply'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View style={styles.feeRow}>
|
||||
<Text style={styles.feeLabel}>Subtotal</Text>
|
||||
<Text style={styles.feeValue}>₹{totals.subtotal}</Text>
|
||||
</View>
|
||||
<View style={styles.feeRow}>
|
||||
<Text style={styles.feeLabel}>Delivery Fee</Text>
|
||||
<Text style={styles.feeValue}>₹{totals.deliveryFee}</Text>
|
||||
</View>
|
||||
<View style={styles.feeRow}>
|
||||
<Text style={styles.feeLabel}>Platform Fee</Text>
|
||||
<Text style={styles.feeValue}>₹{totals.platformFee}</Text>
|
||||
</View>
|
||||
{totals.discount > 0 && (
|
||||
<View style={styles.feeRow}>
|
||||
<Text style={[styles.feeLabel, { color: colors.primary }]}>
|
||||
Discount
|
||||
</Text>
|
||||
<Text style={[styles.feeValue, { color: colors.primary }]}>
|
||||
-₹{totals.discount}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={[styles.feeRow, styles.totalRow]}>
|
||||
<Text style={styles.totalLabel}>Total</Text>
|
||||
<Text style={styles.totalValue}>₹{totals.total}</Text>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
contentContainerStyle={styles.list}
|
||||
/>
|
||||
<View style={styles.bottomPanel}>
|
||||
<Text style={styles.bottomTotal}>Total: ₹{totals.total}</Text>
|
||||
<PrimaryButton
|
||||
title="View Bill"
|
||||
onPress={() => {}}
|
||||
style={{ flex: 1, marginLeft: 12 }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/cartScreen/index.ts
Normal file
1
app/features/screens/cartScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './cartScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Checkout" onBack={() => {}} />
|
||||
<StepProgress steps={CHECKOUT_STEPS} activeStep={0} />
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
<View style={styles.addressCard}>
|
||||
<Text style={styles.addressLabel}>Deliver to</Text>
|
||||
<Text style={styles.addressText}>
|
||||
Koramangala 4th Block, Bengaluru, 560034
|
||||
</Text>
|
||||
<Text style={styles.addressSubtext}>Home</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.sectionTitle}>Delivery Option</Text>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.optionCard,
|
||||
deliveryOption === 'standard' && styles.optionCardSelected,
|
||||
]}
|
||||
onPress={() => setDeliveryOption('standard')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.optionInfo}>
|
||||
<Text style={styles.optionTitle}>Standard Delivery</Text>
|
||||
<Text style={styles.optionSubtext}>Free • 25-30 min</Text>
|
||||
</View>
|
||||
<View style={[styles.radio, deliveryOption === 'standard' && styles.radioSelected]}>
|
||||
{deliveryOption === 'standard' && <View style={styles.radioInner} />}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.optionCard,
|
||||
deliveryOption === 'express' && styles.optionCardSelected,
|
||||
]}
|
||||
onPress={() => setDeliveryOption('express')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.optionInfo}>
|
||||
<Text style={styles.optionTitle}>Express Delivery</Text>
|
||||
<Text style={styles.optionSubtext}>₹40 • 10-15 min</Text>
|
||||
</View>
|
||||
<View style={[styles.radio, deliveryOption === 'express' && styles.radioSelected]}>
|
||||
{deliveryOption === 'express' && <View style={styles.radioInner} />}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
<View style={styles.footer}>
|
||||
<PrimaryButton title="Continue to Payment" onPress={() => {}} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/checkoutAddressScreen/index.ts
Normal file
1
app/features/screens/checkoutAddressScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './checkoutAddressScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Checkout" onBack={() => {}} />
|
||||
<StepProgress steps={CHECKOUT_STEPS} activeStep={1} />
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
<Text style={styles.sectionTitle}>Select Payment Method</Text>
|
||||
{PAYMENT_METHODS.map((method) => (
|
||||
<PaymentOption
|
||||
key={method.id}
|
||||
type={method.type}
|
||||
label={method.label}
|
||||
isSelected={selectedMethod === method.id}
|
||||
onSelect={() => setSelectedMethod(method.id)}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
<View style={styles.footer}>
|
||||
<PrimaryButton title="Place Order" onPress={() => {}} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/checkoutPaymentScreen/index.ts
Normal file
1
app/features/screens/checkoutPaymentScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './checkoutPaymentScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -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<AuthStackParamList, 'CompleteProfileScreen'>;
|
||||
|
||||
const LOCATION_LABELS = ['Home', 'Work', 'Other'];
|
||||
|
||||
export const CompleteProfileScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const navigation = useNavigation<NavProp>();
|
||||
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 (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
||||
<Text style={styles.title}>Complete Profile</Text>
|
||||
<Text style={styles.subtitle}>Tell us about yourself</Text>
|
||||
|
||||
<View style={styles.form}>
|
||||
<CustomInput
|
||||
label="Name"
|
||||
placeholder="John Doe"
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
/>
|
||||
<CustomInput
|
||||
label="Email"
|
||||
placeholder="john@example.com"
|
||||
keyboardType="email-address"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
/>
|
||||
|
||||
<Text style={styles.labelText}>Location Label</Text>
|
||||
<View style={styles.labelRow}>
|
||||
{LOCATION_LABELS.map((label) => (
|
||||
<TouchableOpacity
|
||||
key={label}
|
||||
style={[
|
||||
styles.labelChip,
|
||||
selectedLabel === label && styles.labelChipSelected,
|
||||
]}
|
||||
onPress={() => setSelectedLabel(label)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.labelChipText,
|
||||
selectedLabel === label && styles.labelChipTextSelected,
|
||||
]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<PrimaryButton
|
||||
title="Save & Continue"
|
||||
onPress={handleSave}
|
||||
disabled={!name || !email}
|
||||
/>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/completeProfileScreen/index.ts
Normal file
1
app/features/screens/completeProfileScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './completeProfileScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
48
app/features/screens/helpSupportScreen/helpSupportScreen.tsx
Normal file
48
app/features/screens/helpSupportScreen/helpSupportScreen.tsx
Normal file
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Help & Support" />
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
<Text style={styles.sectionTitle}>Frequently Asked Questions</Text>
|
||||
{FAQS.map((faq, index) => (
|
||||
<View key={index} style={styles.faqItem}>
|
||||
<Text style={styles.faqQuestion}>{faq.q}</Text>
|
||||
<Text style={styles.faqAnswer}>{faq.a}</Text>
|
||||
</View>
|
||||
))}
|
||||
|
||||
<Text style={styles.sectionTitle}>Contact Support</Text>
|
||||
<TouchableOpacity style={styles.supportCard} activeOpacity={0.7}>
|
||||
<Text style={styles.supportIcon}>📞</Text>
|
||||
<View>
|
||||
<Text style={styles.supportLabel}>Call us</Text>
|
||||
<Text style={styles.supportValue}>+91 1800 123 4567</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.supportCard} activeOpacity={0.7}>
|
||||
<Text style={styles.supportIcon}>✉️</Text>
|
||||
<View>
|
||||
<Text style={styles.supportLabel}>Email us</Text>
|
||||
<Text style={styles.supportValue}>support@sgdelivery.com</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/helpSupportScreen/index.ts
Normal file
1
app/features/screens/helpSupportScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './helpSupportScreen';
|
||||
64
app/features/screens/homeScreen/homeScreen.styles.ts
Normal file
64
app/features/screens/homeScreen/homeScreen.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
106
app/features/screens/homeScreen/homeScreen.tsx
Normal file
106
app/features/screens/homeScreen/homeScreen.tsx
Normal file
@ -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<AppStackParamList, 'HomeScreen'>;
|
||||
|
||||
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<NavProp>();
|
||||
|
||||
const [providers, setProviders] = useState<Provider[]>([]);
|
||||
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 (
|
||||
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.addressBar}>
|
||||
<Text style={styles.addressLabel}>Deliver to</Text>
|
||||
<TouchableOpacity style={styles.addressRow} activeOpacity={0.7}>
|
||||
<Text style={styles.addressText}>Koramangala 4th Block, B... </Text>
|
||||
<Text style={styles.dropdownArrow}>▼</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<SearchBar
|
||||
value=""
|
||||
onChangeText={() => {}}
|
||||
placeholder="Search providers or items..."
|
||||
onFocus={handleSearchFocus}
|
||||
/>
|
||||
|
||||
<View style={styles.banner}>
|
||||
<Text style={styles.bannerText}>50% OFF</Text>
|
||||
<Text style={styles.bannerSubtext}>on your first order</Text>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
horizontal
|
||||
data={CATEGORIES}
|
||||
keyExtractor={(item) => item.key}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.chipRow}
|
||||
renderItem={({ item }) => (
|
||||
<CategoryChip
|
||||
icon={item.icon}
|
||||
label={item.label}
|
||||
isSelected={selectedCategory === item.key}
|
||||
onPress={() => setSelectedCategory(item.key)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Text style={styles.sectionTitle}>
|
||||
{selectedCategory === 'all' ? 'Popular Providers' : selectedCategory}
|
||||
</Text>
|
||||
|
||||
{filteredProviders.map((provider) => (
|
||||
<ProviderCard
|
||||
key={provider.id}
|
||||
imageUrl={provider.imageUrl}
|
||||
name={provider.name}
|
||||
rating={provider.rating}
|
||||
deliveryTime={provider.deliveryTime}
|
||||
tag={provider.tag}
|
||||
discountText={provider.discountText}
|
||||
onPress={() => navigation.navigate('SearchScreen')}
|
||||
/>
|
||||
))}
|
||||
|
||||
<View style={{ height: 24 }} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/homeScreen/index.ts
Normal file
1
app/features/screens/homeScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './homeScreen';
|
||||
@ -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';
|
||||
|
||||
1
app/features/screens/liveTrackingScreen/index.ts
Normal file
1
app/features/screens/liveTrackingScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './liveTrackingScreen';
|
||||
@ -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',
|
||||
},
|
||||
});
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Live Tracking" onBack={() => {}} />
|
||||
<View style={styles.mapPlaceholder}>
|
||||
<Text style={styles.mapIcon}>🗺️</Text>
|
||||
<Text style={styles.mapText}>Live Map</Text>
|
||||
<Text style={styles.mapSubtext}>
|
||||
Delivery partner location tracking
|
||||
</Text>
|
||||
<View style={styles.blinkingBadge}>
|
||||
<Text style={styles.blinkingText}>● Live</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.agentSheet}>
|
||||
<Text style={styles.agentName}>Rahul Sharma</Text>
|
||||
<Text style={styles.agentRating}>⭐ 4.8</Text>
|
||||
<Text style={styles.agentVehicle}>KA-01-AB-1234 • Honda Activa</Text>
|
||||
<View style={styles.agentActions}>
|
||||
<PrimaryButton
|
||||
title="Call"
|
||||
onPress={() => {}}
|
||||
style={{ flex: 1, marginRight: 8 }}
|
||||
/>
|
||||
<PrimaryButton
|
||||
title="Message"
|
||||
onPress={() => {}}
|
||||
style={{ flex: 1, marginLeft: 8 }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/myOrdersScreen/index.ts
Normal file
1
app/features/screens/myOrdersScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './myOrdersScreen';
|
||||
46
app/features/screens/myOrdersScreen/myOrdersScreen.styles.ts
Normal file
46
app/features/screens/myOrdersScreen/myOrdersScreen.styles.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
78
app/features/screens/myOrdersScreen/myOrdersScreen.tsx
Normal file
78
app/features/screens/myOrdersScreen/myOrdersScreen.tsx
Normal file
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="My Orders" />
|
||||
<View style={styles.tabRow}>
|
||||
{TABS.map((tab) => (
|
||||
<TouchableOpacity
|
||||
key={tab}
|
||||
style={[
|
||||
styles.tab,
|
||||
activeTab === tab && styles.tabActive,
|
||||
]}
|
||||
onPress={() => setActiveTab(tab)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.tabText,
|
||||
activeTab === tab && styles.tabTextActive,
|
||||
]}
|
||||
>
|
||||
{tab}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
<FlatList
|
||||
data={filteredOrders}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<OrderHistoryCard
|
||||
providerName={item.providerName}
|
||||
providerImage={item.providerImage}
|
||||
orderDate={item.orderDate}
|
||||
status={item.status}
|
||||
total={item.total}
|
||||
onReorder={() => {}}
|
||||
onDetails={() => {}}
|
||||
/>
|
||||
)}
|
||||
contentContainerStyle={styles.list}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.empty}>
|
||||
<Text style={styles.emptyText}>No orders found</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/offersScreen/index.ts
Normal file
1
app/features/screens/offersScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './offersScreen';
|
||||
51
app/features/screens/offersScreen/offersScreen.styles.ts
Normal file
51
app/features/screens/offersScreen/offersScreen.styles.ts
Normal file
@ -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',
|
||||
},
|
||||
});
|
||||
43
app/features/screens/offersScreen/offersScreen.tsx
Normal file
43
app/features/screens/offersScreen/offersScreen.tsx
Normal file
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Offers" />
|
||||
<FlatList
|
||||
data={OFFERS}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.offerCard}>
|
||||
<View style={styles.offerInfo}>
|
||||
<Text style={styles.offerCode}>{item.code}</Text>
|
||||
<Text style={styles.offerDescription}>{item.description}</Text>
|
||||
<Text style={styles.offerExpiry}>Expires: {item.expiry}</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.copyButton}
|
||||
onPress={() => {}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.copyButtonText}>Copy</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
contentContainerStyle={styles.list}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/onboardingCompleteScreen/index.ts
Normal file
1
app/features/screens/onboardingCompleteScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './onboardingCompleteScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.icon}>🎉</Text>
|
||||
<Text style={styles.title}>All Set!</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
You're ready to explore and order from the best providers near you.
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.footer}>
|
||||
<PrimaryButton
|
||||
title="Explore Now"
|
||||
onPress={handleExplore}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/orderConfirmedScreen/index.ts
Normal file
1
app/features/screens/orderConfirmedScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './orderConfirmedScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Order Confirmed" onBack={() => {}} />
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.icon}>✅</Text>
|
||||
<Text style={styles.title}>Order Confirmed!</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Your order has been placed successfully
|
||||
</Text>
|
||||
<View style={styles.orderCard}>
|
||||
<Text style={styles.orderId}>Order ID: ORD-123456</Text>
|
||||
<Text style={styles.orderEta}>Estimated delivery: 25-30 min</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.footer}>
|
||||
<PrimaryButton title="View Tracking" onPress={() => {}} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/orderDeliveredScreen/index.ts
Normal file
1
app/features/screens/orderDeliveredScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './orderDeliveredScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Order Delivered" onBack={() => {}} />
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.icon}>🎉</Text>
|
||||
<Text style={styles.title}>Your order has been delivered!</Text>
|
||||
<Text style={styles.subtitle}>How was your experience?</Text>
|
||||
|
||||
<RatingStars rating={rating} onRate={setRating} size={40} />
|
||||
|
||||
<PrimaryButton
|
||||
title="Rate & Tip"
|
||||
onPress={() => {}}
|
||||
disabled={rating === 0}
|
||||
style={{ marginTop: 32 }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/orderTrackingScreen/index.ts
Normal file
1
app/features/screens/orderTrackingScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './orderTrackingScreen';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title="Track Order" onBack={() => {}} />
|
||||
<View style={styles.content}>
|
||||
<View style={styles.timeline}>
|
||||
{MILESTONES.map((milestone, index) => (
|
||||
<View key={milestone.key} style={styles.milestoneRow}>
|
||||
<View style={styles.milestoneIndicator}>
|
||||
<View
|
||||
style={[
|
||||
styles.dot,
|
||||
index <= 2 && styles.dotActive,
|
||||
index === 2 && styles.dotCurrent,
|
||||
]}
|
||||
/>
|
||||
{index < MILESTONES.length - 1 && (
|
||||
<View
|
||||
style={[
|
||||
styles.line,
|
||||
index < 2 && styles.lineActive,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.milestoneInfo}>
|
||||
<Text
|
||||
style={[
|
||||
styles.milestoneLabel,
|
||||
index <= 2 && styles.milestoneLabelActive,
|
||||
]}
|
||||
>
|
||||
{milestone.label}
|
||||
</Text>
|
||||
<Text style={styles.milestoneTime}>{milestone.time}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.supportCard}>
|
||||
<Text style={styles.supportTitle}>Need help?</Text>
|
||||
<Text style={styles.supportSubtext}>Contact support for assistance</Text>
|
||||
<PrimaryButton title="Contact Support" onPress={() => {}} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
1
app/features/screens/otpScreen/index.ts
Normal file
1
app/features/screens/otpScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './otpScreen';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user