feat: implement core feature screens, navigation structure, and reusable UI components for delivery partner app

This commit is contained in:
Tamojit Biswas 2026-07-06 10:13:04 +05:30
parent a8a04a02f6
commit 72f294d700
65 changed files with 4679 additions and 1342 deletions

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity } from 'react-native'; import { View, Text, TextInput, TouchableOpacity } from 'react-native';
import { CustomInputProps } from './customInput.props'; import { CustomInputProps } from './CustomInput.props';
import { getStyles } from './customInput.styles'; import { getStyles } from './CustomInput.styles';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
export const CustomInput: React.FC<CustomInputProps> = ({ export const CustomInput: React.FC<CustomInputProps> = ({
@ -25,7 +25,13 @@ export const CustomInput: React.FC<CustomInputProps> = ({
<Text style={styles.label}>{label}</Text> <Text style={styles.label}>{label}</Text>
</View> </View>
)} )}
<View style={[styles.inputContainer, error ? { borderColor: colors.error } : null, style]}> <View
style={[
styles.inputContainer,
error ? { borderColor: colors.error } : null,
style,
]}
>
<TextInput <TextInput
style={styles.input} style={styles.input}
placeholderTextColor={colors.placeholder} placeholderTextColor={colors.placeholder}
@ -36,8 +42,13 @@ export const CustomInput: React.FC<CustomInputProps> = ({
/> />
<View style={styles.actionsRow}> <View style={styles.actionsRow}>
{isPassword && ( {isPassword && (
<TouchableOpacity onPress={() => setIsSecure(!isSecure)} activeOpacity={0.7}> <TouchableOpacity
<Text style={styles.rightAction}>{isSecure ? 'Show' : 'Hide'}</Text> onPress={() => setIsSecure(!isSecure)}
activeOpacity={0.7}
>
<Text style={styles.rightAction}>
{isSecure ? 'Show' : 'Hide'}
</Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
{rightActionText && onRightActionPress && ( {rightActionText && onRightActionPress && (

View File

@ -1,2 +1,2 @@
export * from './customInput'; export * from './CustomInput';
export * from './customInput.props'; export * from './CustomInput.props';

View File

@ -1,7 +1,8 @@
import { TouchableOpacityProps } from 'react-native'; import { TouchableOpacityProps, StyleProp, TextStyle } from 'react-native';
export interface PrimaryButtonProps extends TouchableOpacityProps { export interface PrimaryButtonProps extends TouchableOpacityProps {
title: string; title: string;
isLoading?: boolean; isLoading?: boolean;
disabled?: boolean; disabled?: boolean;
textStyle?: StyleProp<TextStyle>;
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { TouchableOpacity, Text, ActivityIndicator } from 'react-native'; import { TouchableOpacity, Text, ActivityIndicator } from 'react-native';
import { PrimaryButtonProps } from './primaryButton.props'; import { PrimaryButtonProps } from './PrimaryButton.props';
import { styles } from './primaryButton.styles'; import { styles } from './PrimaryButton.styles';
import { colors } from '@theme'; import { colors } from '@theme';
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
@ -9,6 +9,7 @@ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
isLoading = false, isLoading = false,
disabled = false, disabled = false,
style, style,
textStyle,
...rest ...rest
}) => { }) => {
const isDisabled = disabled || isLoading; const isDisabled = disabled || isLoading;
@ -23,7 +24,7 @@ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
{isLoading ? ( {isLoading ? (
<ActivityIndicator color={colors.white} /> <ActivityIndicator color={colors.white} />
) : ( ) : (
<Text style={styles.text}>{title}</Text> <Text style={[styles.text, textStyle]}>{title}</Text>
)} )}
</TouchableOpacity> </TouchableOpacity>
); );

View File

@ -1,2 +1,2 @@
export * from './primaryButton'; export * from './PrimaryButton';
export * from './primaryButton.props'; export * from './PrimaryButton.props';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { TouchableOpacity, Text } from 'react-native'; import { TouchableOpacity, Text } from 'react-native';
import { SocialButtonProps } from './socialButton.props'; import { SocialButtonProps } from './SocialButton.props';
import { getStyles } from './socialButton.styles'; import { getStyles } from './SocialButton.styles';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import Svg, { Path } from 'react-native-svg'; import Svg, { Path } from 'react-native-svg';
@ -26,7 +26,7 @@ const GoogleIcon = ({ style }: { style: any }) => (
</Svg> </Svg>
); );
const AppleIcon = ({ style, color }: { style: any, color: string }) => ( const AppleIcon = ({ style, color }: { style: any; color: string }) => (
<Svg width={24} height={24} viewBox="0 0 24 24" style={style}> <Svg width={24} height={24} viewBox="0 0 24 24" style={style}>
<Path <Path
fill={color} fill={color}

View File

@ -1,2 +1,2 @@
export * from './socialButton'; export * from './SocialButton';
export * from './socialButton.props'; export * from './SocialButton.props';

View File

@ -1,3 +1,22 @@
export * from './customInput'; export * from './CustomInput';
export * from './primaryButton'; export * from './PrimaryButton';
export * from './socialButton'; export * from './SocialButton';
export * from './badge/badge';
export * from './card/card';
export * from './chipSelector/chipSelector';
export * from './countdownTimer/countdownTimer';
export * from './divider/divider';
export * from './emptyState/emptyState';
export * from './jobStatusBanner/jobStatusBanner';
export * from './loadingOverlay/loadingOverlay';
export * from './locationRow/locationRow';
export * from './mapBottomSheet/mapBottomSheet';
export * from './mapView/mapView';
export * from './numericKeypad/numericKeypad';
export * from './onlineToggleButton/onlineToggleButton';
export * from './orderCard/orderCard';
export * from './otpInput/otpInput';
export * from './screenHeader/screenHeader';
export * from './sectionHeader/sectionHeader';
export * from './statusBadge/statusBadge';
export * from './toast/toast';

View File

@ -4,7 +4,11 @@ import { typography, borderRadius } from '@theme';
export const getStyles = (colors: any) => export const getStyles = (colors: any) =>
StyleSheet.create({ StyleSheet.create({
container: { container: {
...StyleSheet.absoluteFillObject, position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)', backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',

View File

@ -11,7 +11,11 @@ export const getStyles = (colors: any) =>
alignItems: 'center', alignItems: 'center',
}, },
map: { map: {
...StyleSheet.absoluteFillObject, position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}, },
mockOverlay: { mockOverlay: {
position: 'absolute', position: 'absolute',

View File

@ -1,2 +1 @@
export * from './toast'; export * from './toast';
export { default } from './toast';

View File

@ -8,29 +8,141 @@ import {
TouchableOpacity, TouchableOpacity,
Alert, Alert,
} from 'react-native'; } from 'react-native';
import { getStyles } from './loginScreen.styles'; import Svg, { Circle, Rect, Path, G } from 'react-native-svg';
import { CustomInput, PrimaryButton, SocialButton } from '@components'; import { getStyles } from './LoginScreen.styles';
import { CustomInput, PrimaryButton } from '@components';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes'; import { AuthStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';
// Premium SVG Mascot: Delivery boy on scooter
const DeliveryScooterSvg = () => {
return (
<View style={{ alignItems: 'center', marginVertical: 20 }}>
<Svg width="160" height="130" viewBox="0 0 160 130" fill="none">
{/* Background Accent Circles */}
<Circle cx="80" cy="65" r="55" fill="#E8F5EE" />
<Circle cx="120" cy="35" r="15" fill="#C8E6C9" opacity="0.6" />
{/* Scooter Wheels */}
<Circle
cx="45"
cy="100"
r="14"
fill="#37474F"
stroke="#FFFFFF"
strokeWidth="3"
/>
<Circle cx="45" cy="100" r="6" fill="#CFD8DC" />
<Circle
cx="115"
cy="100"
r="14"
fill="#37474F"
stroke="#FFFFFF"
strokeWidth="3"
/>
<Circle cx="115" cy="100" r="6" fill="#CFD8DC" />
{/* Scooter Body */}
{/* Main deck */}
<Path
d="M 45 100 L 115 100"
stroke="#78909C"
strokeWidth="6"
strokeLinecap="round"
/>
<Path d="M 45 95 L 90 95 L 95 80 L 50 80 Z" fill="#05824C" />
{/* Front steering column */}
<Path
d="M 115 100 L 110 65 L 105 55"
stroke="#78909C"
strokeWidth="5"
strokeLinecap="round"
/>
{/* Handlebars */}
<Path
d="M 100 55 L 112 55"
stroke="#37474F"
strokeWidth="4"
strokeLinecap="round"
/>
{/* Front Shield */}
<Path d="M 110 65 L 118 65 L 114 90 L 108 90 Z" fill="#05824C" />
{/* Delivery Box */}
<Rect x="30" y="52" width="28" height="28" rx="4" fill="#046B3E" />
<Rect x="34" y="56" width="20" height="20" rx="2" fill="#05824C" />
{/* Box Stripe */}
<Path d="M 30 66 L 58 66" stroke="#FFFFFF" strokeWidth="2" />
{/* Delivery Rider */}
{/* Torso/Jacket */}
<Path d="M 68 55 L 88 55 L 82 82 L 65 82 Z" fill="#05824C" />
{/* Backpack strap */}
<Path d="M 64 58 L 74 82" stroke="#37474F" strokeWidth="3" />
{/* Head / Helmet */}
<Circle cx="80" cy="38" r="9" fill="#FFCCBC" />
{/* Helmet */}
<Path d="M 70 36 C 70 26, 90 26, 90 36 Z" fill="#37474F" />
{/* Visor */}
<Path d="M 80 32 L 89 35 L 80 38 Z" fill="#00E5FF" />
{/* Arm stretching to handle */}
<Path
d="M 80 58 L 98 56"
stroke="#046B3E"
strokeWidth="4"
strokeLinecap="round"
/>
<Path
d="M 98 56 L 105 55"
stroke="#FFCCBC"
strokeWidth="3"
strokeLinecap="round"
/>
{/* Motion lines */}
<Path
d="M 10 70 L 22 70"
stroke="#05824C"
strokeWidth="2"
strokeLinecap="round"
opacity="0.8"
/>
<Path
d="M 5 80 L 15 80"
stroke="#05824C"
strokeWidth="2"
strokeLinecap="round"
opacity="0.6"
/>
<Path
d="M 12 90 L 20 90"
stroke="#05824C"
strokeWidth="2"
strokeLinecap="round"
opacity="0.8"
/>
</Svg>
</View>
);
};
export const LoginScreen: React.FC = () => { export const LoginScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors); const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>(); const navigation =
useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
const [mobileNumber, setMobileNumber] = useState(''); const [mobileNumber, setMobileNumber] = useState('');
// const [password, setPassword] = useState(''); // const [password, setPassword] = useState('');
// const [rememberMe, setRememberMe] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [errors, setErrors] = useState<{ mobile?: string; password?: string }>( const [errors, setErrors] = useState<{ mobile?: string }>({});
{},
);
const validate = () => { const validate = () => {
const newErrors: { mobile?: string; password?: string } = {}; const newErrors: { mobile?: string } = {};
if (!mobileNumber) { if (!mobileNumber) {
newErrors.mobile = 'Mobile number is required'; newErrors.mobile = 'Mobile number is required';
} else if (mobileNumber.length < 9) { } else if (mobileNumber.length < 9) {
@ -39,8 +151,8 @@ export const LoginScreen: React.FC = () => {
// if (!password) { // if (!password) {
// newErrors.password = 'Password is required'; // newErrors.password = 'Password is required';
// } else if (password.length < 6) { // } else if (password.length < 4) {
// newErrors.password = 'Password must be at least 6 characters'; // newErrors.password = 'Password must be at least 4 characters';
// } // }
setErrors(newErrors); setErrors(newErrors);
@ -58,16 +170,12 @@ export const LoginScreen: React.FC = () => {
}, 1500); }, 1500);
}; };
const handleSocialLogin = (provider: 'google' | 'apple') => {
Alert.alert('Social Login', `Initiating ${provider} login...`);
};
// const handleForgotPassword = () => { // const handleForgotPassword = () => {
// Alert.alert('Forgot Password', 'Redirecting to reset password...'); // Alert.alert('Forgot Password', 'OTP verification will be sent to recover password.');
// }; // };
const handleSignUp = () => { const handleSignUp = () => {
Alert.alert('Sign Up', 'Redirecting to registration...'); Alert.alert('Sign Up', 'Redirecting to register partner profile...');
}; };
return ( return (
@ -79,8 +187,10 @@ export const LoginScreen: React.FC = () => {
contentContainerStyle={styles.scrollContainer} contentContainerStyle={styles.scrollContainer}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
> >
<DeliveryScooterSvg />
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
<Text style={styles.title}>Welcome back 👋</Text> <Text style={styles.title}>Welcome Partner!</Text>
<Text style={styles.subtitle}>Login to continue</Text> <Text style={styles.subtitle}>Login to continue</Text>
</View> </View>
@ -99,46 +209,18 @@ export const LoginScreen: React.FC = () => {
{/* <CustomInput {/* <CustomInput
label="Password" label="Password"
placeholder="••••••••" placeholder="Enter password"
isPassword isPassword
rightActionText="Forgot?" rightActionText="Forgot Password?"
onRightActionPress={handleForgotPassword} onRightActionPress={handleForgotPassword}
value={password} value={password}
onChangeText={(text) => { onChangeText={text => {
setPassword(text); setPassword(text);
if (errors.password) setErrors({ ...errors, password: undefined }); if (errors.password) setErrors({ ...errors, password: undefined });
}} }}
error={errors.password} 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> */}
<View style={styles.otpWrapper}>
<Text style={styles.otpText}>
OTP will send on this number
</Text>
</View>
<PrimaryButton <PrimaryButton
title="Login" title="Login"
onPress={handleLogin} onPress={handleLogin}
@ -146,23 +228,6 @@ export const LoginScreen: React.FC = () => {
style={styles.loginButton} style={styles.loginButton}
/> />
<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}> <TouchableOpacity onPress={handleSignUp} activeOpacity={0.8}>
<Text style={styles.footerText}> <Text style={styles.footerText}>
Dont have an account?{' '} Dont have an account?{' '}

View File

@ -1,3 +1,2 @@
export { default } from './loginScreen'; export * from './LoginScreen';
export * from './loginScreen'; export * from './LoginScreen.styles';
export * from './loginScreen.styles';

View File

@ -0,0 +1,69 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
flexGrow: 1,
justifyContent: 'space-between',
paddingHorizontal: spacing.xxl,
paddingBottom: spacing.xxl,
},
headerContainer: {
marginTop: 40,
marginBottom: 24,
},
title: {
fontSize: typography.fontSize.xxl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
},
subtitle: {
fontSize: typography.fontSize.md,
color: colors.textSecondary,
lineHeight: 22,
},
phoneText: {
color: colors.text,
fontWeight: typography.fontWeight.semibold,
},
contentWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
otpWrapper: {
width: '100%',
alignItems: 'center',
marginVertical: spacing.xl,
},
resendContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginTop: spacing.md,
marginBottom: spacing.xl,
},
resendText: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
},
resendLink: {
fontSize: typography.fontSize.sm,
color: colors.primary,
fontWeight: typography.fontWeight.bold,
},
verifyButton: {
width: '100%',
marginTop: spacing.lg,
},
keypadContainer: {
width: '100%',
marginTop: spacing.xl,
},
});

View File

@ -1,32 +1,115 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { CustomInput, PrimaryButton } from '@components'; import { OtpInput, NumericKeypad, PrimaryButton } from '@components';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes'; import { AuthStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';
import { getStyles } from './OtpScreen.styles';
export const OtpScreen: React.FC<{ route: any }> = ({ route: _route }) => { export const OtpScreen: React.FC<{ route: any }> = ({ route }) => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>(); const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
const phone = route?.params?.phone || '+91 98765 43210';
const [otp, setOtp] = useState(''); const [otp, setOtp] = useState('');
const handleVerify = () => { const [timer, setTimer] = useState(30);
navigation.navigate(RouteNames.SetLocation); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
if (timer <= 0) return;
const interval = setInterval(() => {
setTimer((prev) => prev - 1);
}, 1000);
return () => clearInterval(interval);
}, [timer]);
const handleKeyPress = (digit: string) => {
if (otp.length < 6) {
setError(false);
setOtp((prev) => prev + digit);
}
}; };
const handleBackspace = () => {
setOtp((prev) => prev.slice(0, -1));
};
const handleVerify = () => {
if (otp.length < 6) {
setError(true);
Alert.alert('Invalid OTP', 'Please enter a 6-digit verification code.');
return;
}
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
if (otp === '123456' || otp.length === 6) {
navigation.navigate(RouteNames.SetLocation);
} else {
setError(true);
Alert.alert('Verification Failed', 'The code you entered is incorrect. Try 123456.');
}
}, 1200);
};
const handleResend = () => {
if (timer > 0) return;
setOtp('');
setTimer(30);
Alert.alert('OTP Resent', 'A new verification code has been sent to your mobile number.');
};
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]} <View style={styles.container}>
> <ScrollView contentContainerStyle={styles.scrollContainer} bounces={false}>
<Text style={styles.title}>Enter OTP</Text> <View style={styles.headerContainer}>
<CustomInput label="OTP" value={otp} onChangeText={setOtp} keyboardType="numeric" /> <Text style={styles.title}>Verify Phone</Text>
<PrimaryButton title="Verify" onPress={handleVerify} /> <Text style={styles.subtitle}>
Enter the 6-digit OTP code sent to{' '}
<Text style={styles.phoneText}>{phone}</Text>
</Text>
</View>
<View style={styles.contentWrapper}>
<View style={styles.otpWrapper}>
<OtpInput length={6} value={otp} error={error} />
</View>
<View style={styles.resendContainer}>
{timer > 0 ? (
<Text style={styles.resendText}>
Resend code in <Text style={{ color: colors.primary, fontWeight: 'bold' }}>00:{timer < 10 ? `0${timer}` : timer}</Text>
</Text>
) : (
<TouchableOpacity onPress={handleResend} activeOpacity={0.7}>
<Text style={styles.resendLink}>Resend Code</Text>
</TouchableOpacity>
)}
</View>
<PrimaryButton
title="Verify & Continue"
onPress={handleVerify}
isLoading={isLoading}
style={styles.verifyButton}
/>
</View>
<View style={styles.keypadContainer}>
<NumericKeypad
onPress={handleKeyPress}
onBackspace={handleBackspace}
disabled={isLoading}
/>
</View>
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 20 },
title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
});
export default OtpScreen; export default OtpScreen;

View File

@ -1,2 +1 @@
export { default } from './otpScreen'; export * from './OtpScreen';
export * from './otpScreen';

View File

@ -0,0 +1,55 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } 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',
},
placeholderText: {
fontSize: typography.fontSize.sm,
color: colors.placeholder,
},
bottomCard: {
position: 'absolute',
bottom: spacing.xxl,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
...shadow.lg,
borderWidth: 1,
borderColor: colors.border,
zIndex: 10,
},
cardTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
textTransform: 'uppercase',
marginBottom: spacing.xs,
},
customerName: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
},
customerAddress: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
lineHeight: 20,
marginBottom: spacing.lg,
},
arrivedButton: {
width: '100%',
},
});

View File

@ -1,18 +1,48 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components';
import { useAppSelector } from '@store';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { getStyles } from './arrivedAtCustomerScreen.styles';
const ArrivedAtCustomerScreen: React.FC = () => { export const ArrivedAtCustomerScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const activeJob = useAppSelector((state) => state.job.activeJob);
const handleConfirmArrival = () => {
navigation.navigate(RouteNames.DeliverOrder);
};
if (!activeJob) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Arrived At Customer Screen</Text> {/* Map Background Placeholder */}
<View style={styles.mapPlaceholder}>
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
</View>
{/* Arrived Card */}
<View style={styles.bottomCard}>
<Text style={styles.cardTitle}>You've arrived</Text>
<Text style={styles.customerName}>{activeJob.dropName}</Text>
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
<PrimaryButton
title="Confirm Arrival"
onPress={handleConfirmArrival}
style={styles.arrivedButton}
/>
</View>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default ArrivedAtCustomerScreen; export default ArrivedAtCustomerScreen;

View File

@ -0,0 +1,55 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } 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',
},
placeholderText: {
fontSize: typography.fontSize.sm,
color: colors.placeholder,
},
bottomCard: {
position: 'absolute',
bottom: spacing.xxl,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
...shadow.lg,
borderWidth: 1,
borderColor: colors.border,
zIndex: 10,
},
cardTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
textTransform: 'uppercase',
marginBottom: spacing.xs,
},
storeName: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
},
storeAddress: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
lineHeight: 20,
marginBottom: spacing.lg,
},
confirmButton: {
width: '100%',
},
});

View File

@ -1,18 +1,48 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components';
import { useAppSelector } from '@store';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { getStyles } from './arrivedAtStoreScreen.styles';
const ArrivedAtStoreScreen: React.FC = () => { export const ArrivedAtStoreScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const activeJob = useAppSelector((state) => state.job.activeJob);
const handleConfirmArrival = () => {
navigation.navigate(RouteNames.ConfirmPickup);
};
if (!activeJob) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Arrived At Store Screen</Text> {/* Map Background Placeholder */}
<View style={styles.mapPlaceholder}>
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
</View>
{/* Arrived Card */}
<View style={styles.bottomCard}>
<Text style={styles.cardTitle}>You've arrived</Text>
<Text style={styles.storeName}>{activeJob.pickupName}</Text>
<Text style={styles.storeAddress}>{activeJob.pickupAddress}</Text>
<PrimaryButton
title="Confirm Arrival"
onPress={handleConfirmArrival}
style={styles.confirmButton}
/>
</View>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default ArrivedAtStoreScreen; export default ArrivedAtStoreScreen;

View File

@ -0,0 +1,114 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.xl,
paddingBottom: spacing.xxxl,
},
headerContainer: {
marginTop: 20,
marginBottom: spacing.xl,
},
title: {
fontSize: typography.fontSize.xxl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
},
subtitle: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
},
section: {
marginBottom: spacing.xl,
},
sectionTitle: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.md,
},
profileImageContainer: {
alignItems: 'center',
marginBottom: spacing.xl,
},
avatarPlaceholder: {
width: 90,
height: 90,
borderRadius: 45,
backgroundColor: colors.inputBg,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderColor: colors.border,
position: 'relative',
},
avatarText: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
fontWeight: typography.fontWeight.bold,
marginTop: spacing.xs,
},
cameraBadge: {
position: 'absolute',
bottom: 0,
right: 0,
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: colors.primary,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: colors.background,
},
documentItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
backgroundColor: colors.inputBg,
borderRadius: borderRadius.md,
marginBottom: spacing.sm,
borderWidth: 1,
borderColor: colors.border,
},
docLeft: {
flexDirection: 'row',
alignItems: 'center',
},
docTextContainer: {
marginLeft: spacing.sm,
},
docTitle: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
},
docSub: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
marginTop: 2,
},
statusBadge: {
paddingHorizontal: spacing.sm,
paddingVertical: 4,
borderRadius: borderRadius.sm,
marginRight: spacing.sm,
},
statusText: {
fontSize: 10,
fontWeight: 'bold',
},
submitButton: {
marginTop: spacing.xl,
width: '100%',
},
});

View File

@ -1,31 +1,151 @@
import React from 'react'; import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components'; import { CustomInput, PrimaryButton } from '@components';
import { ClipboardIcon, ChevronRightIcon, PersonIcon } from '@icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes'; import { AuthStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';
import { getStyles } from './completeProfileScreen.styles';
const CompleteProfileScreen: React.FC = () => { export const CompleteProfileScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>(); const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
const [fullName, setFullName] = useState('Rahul Kumar');
const [email, setEmail] = useState('rahul.kumar@gmail.com');
const [vehicleNo, setVehicleNo] = useState('KA-01-MJ-8845');
const [isLoading, setIsLoading] = useState(false);
const documents = [
{ id: 'dl', title: 'Driving License', sub: 'Verified', status: 'verified' },
{ id: 'aadhaar', title: 'Aadhaar Card', sub: 'Verified', status: 'verified' },
{ id: 'rc', title: 'Vehicle RC', sub: 'Verified', status: 'verified' },
{ id: 'insurance', title: 'Insurance Policy', sub: 'Expires on 12 May 2026', status: 'expiring' },
];
const handleDocumentPress = (docTitle: string) => {
Alert.alert('Document Details', `Viewing or uploading ${docTitle}...`);
};
const handleSubmit = () => {
if (!fullName || !email || !vehicleNo) {
Alert.alert('Required Fields', 'Please fill out all profile information.');
return;
}
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
navigation.navigate(RouteNames.OnboardingComplete);
}, 1500);
};
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text style={styles.title}>Complete Profile Screen</Text> <ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
<PrimaryButton <View style={styles.headerContainer}>
title="Continue" <Text style={styles.title}>Complete Profile</Text>
onPress={() => navigation.navigate(RouteNames.OnboardingComplete)} <Text style={styles.subtitle}>Enter details & upload documents to get verified</Text>
style={{ marginTop: 20, width: '90%' }} </View>
{/* Profile Avatar Upload Mock */}
<View style={styles.profileImageContainer}>
<TouchableOpacity style={styles.avatarPlaceholder} activeOpacity={0.8}>
<PersonIcon size={40} color={colors.textSecondary} />
<Text style={styles.avatarText}>Add Photo</Text>
<View style={styles.cameraBadge}>
<Text style={{ color: colors.white, fontSize: 12, fontWeight: 'bold' }}>+</Text>
</View>
</TouchableOpacity>
</View>
{/* Personal Details */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Personal Details</Text>
<CustomInput
label="Full Name"
placeholder="Rahul Kumar"
value={fullName}
onChangeText={setFullName}
/> />
<CustomInput
label="Email Address"
placeholder="rahul.kumar@gmail.com"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
/>
<CustomInput
label="Vehicle Number"
placeholder="KA-01-MJ-8845"
value={vehicleNo}
onChangeText={setVehicleNo}
autoCapitalize="characters"
/>
</View>
{/* Required Documents Checklist */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Required Documents</Text>
{documents.map((doc) => (
<TouchableOpacity
key={doc.id}
style={styles.documentItem}
activeOpacity={0.7}
onPress={() => handleDocumentPress(doc.title)}
>
<View style={styles.docLeft}>
<ClipboardIcon size={22} color={colors.primary} />
<View style={styles.docTextContainer}>
<Text style={styles.docTitle}>{doc.title}</Text>
<Text style={styles.docSub}>{doc.sub}</Text>
</View>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View
style={[
styles.statusBadge,
{
backgroundColor:
doc.status === 'verified'
? colors.primaryLight
: colors.warningLight,
},
]}
>
<Text
style={[
styles.statusText,
{
color:
doc.status === 'verified'
? colors.primary
: colors.warning,
},
]}
>
{doc.status === 'verified' ? 'VERIFIED' : 'ACTIVE'}
</Text>
</View>
<ChevronRightIcon size={20} color={colors.textSecondary} />
</View>
</TouchableOpacity>
))}
</View>
<PrimaryButton
title="Submit & Verify"
onPress={handleSubmit}
isLoading={isLoading}
style={styles.submitButton}
/>
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 20 },
});
export default CompleteProfileScreen; export default CompleteProfileScreen;

View File

@ -0,0 +1,116 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxl,
},
orderHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 20,
marginBottom: spacing.md,
},
orderIdText: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
paymentTag: {
backgroundColor: colors.primaryLight,
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: borderRadius.sm,
},
paymentTagText: {
fontSize: 10,
fontWeight: 'bold',
color: colors.primary,
},
merchantInfoCard: {
backgroundColor: colors.inputBg,
borderRadius: borderRadius.md,
padding: spacing.md,
marginBottom: spacing.lg,
borderWidth: 1,
borderColor: colors.border,
},
merchantLabel: {
fontSize: 10,
color: colors.textSecondary,
fontWeight: 'bold',
textTransform: 'uppercase',
marginBottom: 4,
},
merchantName: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
section: {
marginBottom: spacing.xl,
},
sectionTitle: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.md,
},
itemRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
checkbox: {
width: 20,
height: 20,
borderRadius: 4,
borderWidth: 2,
borderColor: colors.primary,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
},
checkboxChecked: {
backgroundColor: colors.primary,
},
checkSign: {
color: colors.white,
fontSize: 10,
fontWeight: 'bold',
},
itemNameText: {
fontSize: typography.fontSize.md,
color: colors.text,
flex: 1,
},
itemQtyText: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
btnRow: {
flexDirection: 'row',
gap: 12,
marginTop: spacing.md,
},
issueBtn: {
flex: 1,
backgroundColor: colors.errorLight,
borderColor: colors.error,
borderWidth: 1,
},
pickedUpBtn: {
flex: 2,
backgroundColor: colors.primary,
},
});

View File

@ -1,18 +1,118 @@
import React from 'react'; import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { ScreenHeader, PrimaryButton } from '@components';
import { useAppDispatch, useAppSelector } from '@store';
import { advanceJobStatus } from '@store/slices/jobSlice';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames, JobStatus } from '@utils/constants';
import { getStyles } from './confirmPickupScreen.styles';
const ConfirmPickupScreen: React.FC = () => { export const ConfirmPickupScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const activeJob = useAppSelector((state) => state.job.activeJob);
// Togglable checklist for food/merchandise items
const [checkedItems, setCheckedItems] = useState<{ [key: number]: boolean }>({
0: true,
1: true,
});
const items = [
{ id: 0, name: 'Cappuccino (Medium)', qty: 1 },
{ id: 1, name: 'Chocolate Croissant', qty: 1 },
];
const handleToggleCheck = (id: number) => {
setCheckedItems((prev) => ({
...prev,
[id]: !prev[id],
}));
};
const handlePickedUp = () => {
// Ensure all items are checked before picking up
const allChecked = items.every((item) => checkedItems[item.id]);
if (!allChecked) {
Alert.alert('Incomplete Verification', 'Please confirm that you have checked all items before picking up.');
return;
}
dispatch(advanceJobStatus(JobStatus.PickedUp));
navigation.navigate(RouteNames.OrderPickedUp);
};
const handleReportIssue = () => {
Alert.alert('Report Issue', 'Helpdesk notified. Support agent will contact you shortly.');
};
if (!activeJob) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Confirm Pickup Screen</Text> <ScreenHeader
title="Confirm Pickup"
showBack={true}
onBack={() => navigation.goBack()}
/>
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
{/* Order ID Banner */}
<View style={styles.orderHeader}>
<Text style={styles.orderIdText}>Order ID: {activeJob.orderId}</Text>
<View style={styles.paymentTag}>
<Text style={styles.paymentTagText}>ONLINE PAID</Text>
</View>
</View>
{/* Merchant Info */}
<View style={styles.merchantInfoCard}>
<Text style={styles.merchantLabel}>Pickup Store</Text>
<Text style={styles.merchantName}>{activeJob.pickupName}</Text>
</View>
{/* Items Checklist */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Verify Items ({items.length})</Text>
{items.map((item) => (
<TouchableOpacity
key={item.id}
style={styles.itemRow}
activeOpacity={0.8}
onPress={() => handleToggleCheck(item.id)}
>
<View style={[styles.checkbox, checkedItems[item.id] && styles.checkboxChecked]}>
{checkedItems[item.id] && <Text style={styles.checkSign}></Text>}
</View>
<Text style={styles.itemNameText}>{item.name}</Text>
<Text style={styles.itemQtyText}>x{item.qty}</Text>
</TouchableOpacity>
))}
</View>
{/* Action Buttons */}
<View style={styles.btnRow}>
<PrimaryButton
title="Issue"
onPress={handleReportIssue}
style={styles.issueBtn}
textStyle={{ color: colors.error }}
/>
<PrimaryButton
title="Picked Up"
onPress={handlePickedUp}
style={styles.pickedUpBtn}
/>
</View>
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default ConfirmPickupScreen; export default ConfirmPickupScreen;

View File

@ -0,0 +1,266 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxxl,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 20,
marginBottom: spacing.lg,
},
greetingText: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
},
nameText: {
fontSize: typography.fontSize.xl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginTop: 2,
},
toggleWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.inputBg,
paddingHorizontal: spacing.sm,
paddingVertical: 6,
borderRadius: borderRadius.full,
borderWidth: 1,
borderColor: colors.border,
},
statusDot: {
width: 8,
height: 8,
borderRadius: 4,
marginRight: 6,
},
statusText: {
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.bold,
},
offlineBanner: {
backgroundColor: colors.errorLight,
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
borderRadius: borderRadius.md,
marginBottom: spacing.lg,
alignItems: 'center',
borderWidth: 1,
borderColor: colors.error,
},
offlineBannerText: {
fontSize: typography.fontSize.sm,
color: colors.error,
fontWeight: typography.fontWeight.medium,
},
summaryCard: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
marginBottom: spacing.lg,
...shadow.md,
borderWidth: 1,
borderColor: colors.border,
},
summaryTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
marginBottom: spacing.lg,
},
statsRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
statBox: {
flex: 1,
alignItems: 'center',
},
statVal: {
fontSize: typography.fontSize.xl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: 4,
},
statLabel: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
},
statDivider: {
width: 1,
height: 32,
backgroundColor: colors.border,
},
promptCard: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
alignItems: 'center',
marginBottom: spacing.lg,
...shadow.md,
borderWidth: 1,
borderColor: colors.border,
},
promptTitle: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
textAlign: 'center',
marginBottom: spacing.sm,
},
promptDesc: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
textAlign: 'center',
marginBottom: spacing.xl,
lineHeight: 20,
},
onlineBtn: {
width: '100%',
},
performanceCard: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
marginBottom: spacing.lg,
...shadow.md,
borderWidth: 1,
borderColor: colors.border,
},
perfTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
marginBottom: spacing.md,
},
perfRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
perfBox: {
alignItems: 'center',
flex: 1,
},
perfVal: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: 2,
},
perfLabel: {
fontSize: 10,
color: colors.textSecondary,
},
chartCard: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
marginBottom: spacing.lg,
...shadow.md,
borderWidth: 1,
borderColor: colors.border,
},
chartTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
marginBottom: spacing.lg,
},
chartRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-end',
height: 120,
paddingBottom: 8,
},
barContainer: {
alignItems: 'center',
flex: 1,
},
bar: {
width: 14,
borderRadius: borderRadius.sm,
backgroundColor: colors.primary,
},
barLabel: {
fontSize: 10,
color: colors.textSecondary,
marginTop: 8,
},
barVal: {
fontSize: 8,
color: colors.text,
marginBottom: 4,
fontWeight: 'bold',
},
offlineBtn: {
width: '100%',
marginBottom: spacing.xl,
},
simulateBtn: {
marginVertical: spacing.sm,
backgroundColor: colors.primaryLight,
borderColor: colors.primary,
borderWidth: 1,
paddingVertical: 8,
borderRadius: borderRadius.sm,
alignItems: 'center',
},
simulateBtnText: {
color: colors.primary,
fontWeight: 'bold',
fontSize: typography.fontSize.sm,
},
helpSection: {
marginTop: spacing.sm,
},
helpTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
marginBottom: spacing.md,
},
helpRow: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 10,
},
helpItem: {
flex: 1,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.md,
padding: spacing.md,
alignItems: 'center',
borderWidth: 1,
borderColor: colors.border,
...shadow.sm,
},
helpLabel: {
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginTop: spacing.sm,
textAlign: 'center',
},
helpSub: {
fontSize: 9,
color: colors.textSecondary,
marginTop: 2,
textAlign: 'center',
},
});

View File

@ -1,14 +1,332 @@
import React from 'react'; import React, { useEffect, useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert, Switch } from 'react-native';
import { useAppTheme, borderRadius, spacing, shadow, typography } from '@theme';
import { PrimaryButton } from '@components';
import { BellIcon, PersonIcon, WalletIcon, ClipboardIcon } from '@icons';
import { useAppDispatch, useAppSelector } from '@store';
import { setOnlineStatus } from '@store/slices/authSlice';
import { setNewJobOffer } from '@store/slices/jobSlice';
import { mockJobOffer } from '@store/mockData/mockJobs';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames, JobStatus } from '@utils/constants';
import { formatCurrency } from '@utils/formatters';
import { getStyles } from './dashboardScreen.styles';
const DashboardScreen: React.FC = () => ( const DashboardScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const isOnline = useAppSelector((state) => state.auth.isOnline);
const { todayEarnings, completedCount, onlineMinutes, weeklyData } = useAppSelector(
(state) => state.earnings
);
const { jobStatus, activeJob } = useAppSelector((state) => state.job);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Background simulation: when online and idle, trigger new order offer after 12 seconds
useEffect(() => {
if (isOnline && jobStatus === JobStatus.Idle) {
timerRef.current = setTimeout(() => {
dispatch(setNewJobOffer(mockJobOffer));
navigation.navigate(RouteNames.NewJobRequest);
}, 12000);
} else {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, [isOnline, jobStatus, dispatch, navigation]);
const handleToggleOnline = () => {
dispatch(setOnlineStatus(!isOnline));
};
const handleSimulateOffer = () => {
if (!isOnline) {
Alert.alert('Go Online', 'Please go online to receive order offers.');
return;
}
if (jobStatus !== JobStatus.Idle) {
Alert.alert('Active Delivery', 'You already have an active job offer or order.');
return;
}
dispatch(setNewJobOffer(mockJobOffer));
navigation.navigate(RouteNames.NewJobRequest);
};
const handleActiveJobBannerPress = () => {
// Navigate back to the screen corresponding to the current job step
switch (jobStatus) {
case JobStatus.NewRequest:
navigation.navigate(RouteNames.NewJobRequest);
break;
case JobStatus.Accepted:
navigation.navigate(RouteNames.OrderAccepted);
break;
case JobStatus.ArrivedAtStore:
navigation.navigate(RouteNames.ConfirmPickup);
break;
case JobStatus.PickedUp:
navigation.navigate(RouteNames.OrderPickedUp);
break;
case JobStatus.EnRoute:
navigation.navigate(RouteNames.LiveTracking);
break;
case JobStatus.ArrivedAtCustomer:
navigation.navigate(RouteNames.DeliverOrder);
break;
default:
Alert.alert('Status Error', 'Unknown active delivery state.');
}
};
const formatOnlineTime = (mins: number) => {
const hours = Math.floor(mins / 60);
const remainingMins = mins % 60;
return `${hours}h ${remainingMins < 10 ? `0${remainingMins}` : remainingMins}m`;
};
// Helper to render chart bar heights dynamically based on amounts
const maxWeeklyAmount = Math.max(...weeklyData.map((d) => d.amount), 1);
return (
<View style={styles.container}> <View style={styles.container}>
<Text>Dashboard Screen</Text> <ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
{/* Header Section */}
<View style={styles.header}>
<View>
<Text style={styles.greetingText}>Good Morning </Text>
<Text style={styles.nameText}>Rahul Kumar</Text>
</View>
<View style={styles.toggleWrapper}>
<View
style={[
styles.statusDot,
{ backgroundColor: isOnline ? colors.statusOnline : colors.statusOffline },
]}
/>
<Text
style={[
styles.statusText,
{ color: isOnline ? colors.statusOnline : colors.statusOffline },
]}
>
{isOnline ? 'Online' : 'Offline'}
</Text>
<Switch
value={isOnline}
onValueChange={handleToggleOnline}
trackColor={{ false: colors.border, true: colors.primaryLight }}
thumbColor={isOnline ? colors.primary : colors.placeholder}
style={{ marginLeft: 8 }}
/>
</View>
</View>
{/* Offline Warning Banner */}
{!isOnline && (
<View style={styles.offlineBanner}>
<Text style={styles.offlineBannerText}>
You are offline. Go online to start receiving orders.
</Text>
</View>
)}
{/* Today's Summary Card */}
<View style={styles.summaryCard}>
<Text style={styles.summaryTitle}>Today's Summary</Text>
<View style={styles.statsRow}>
<View style={styles.statBox}>
<Text style={styles.statVal}>
{formatCurrency(isOnline ? todayEarnings : 0)}
</Text>
<Text style={styles.statLabel}>Earnings</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statVal}>{isOnline ? completedCount : 0}</Text>
<Text style={styles.statLabel}>Completed</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statVal}>
{formatOnlineTime(isOnline ? onlineMinutes : 0)}
</Text>
<Text style={styles.statLabel}>Online Time</Text>
</View>
</View>
</View>
{/* Conditional Layout for Online vs Offline */}
{isOnline ? (
<>
{/* Performance Stats */}
<View style={styles.performanceCard}>
<Text style={styles.perfTitle}>Your Performance</Text>
<View style={styles.perfRow}>
<View style={styles.perfBox}>
<Text style={[styles.perfVal, { color: colors.warning }]}>4.8 </Text>
<Text style={styles.perfLabel}>Rating</Text>
</View>
<View style={styles.perfBox}>
<Text style={styles.perfVal}>95%</Text>
<Text style={styles.perfLabel}>Acceptance</Text>
</View>
<View style={styles.perfBox}>
<Text style={styles.perfVal}>98%</Text>
<Text style={styles.perfLabel}>Completion</Text>
</View>
</View>
</View>
{/* Weekly Earnings Chart (Styled SVG/Flex columns) */}
<View style={styles.chartCard}>
<Text style={styles.chartTitle}>Weekly Overview</Text>
<View style={styles.chartRow}>
{weeklyData.map((data, index) => {
// Bar height percent
const heightPercent = `${(data.amount / maxWeeklyAmount) * 85}%`;
return (
<View key={index} style={styles.barContainer}>
<Text style={styles.barVal}>{data.amount}</Text>
<View
style={[
styles.bar,
{
height: heightPercent as any,
backgroundColor: index === weeklyData.length - 1 ? colors.primary : colors.placeholder,
},
]}
/>
<Text style={styles.barLabel}>{data.day}</Text>
</View> </View>
); );
})}
</View>
</View>
const styles = StyleSheet.create({ {/* Developer Simulation trigger */}
container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, <TouchableOpacity
}); style={styles.simulateBtn}
activeOpacity={0.8}
onPress={handleSimulateOffer}
>
<Text style={styles.simulateBtnText}> Simulate Immediate Job Offer</Text>
</TouchableOpacity>
<PrimaryButton
title="Go Offline"
onPress={handleToggleOnline}
style={[styles.offlineBtn, { backgroundColor: colors.error }]}
/>
</>
) : (
<View style={styles.promptCard}>
<Text style={styles.promptTitle}>Go Online to start receiving delivery requests</Text>
<Text style={styles.promptDesc}>
Make sure you are in a high-demand delivery zone to maximize your earnings.
</Text>
<PrimaryButton
title="Go Online"
onPress={handleToggleOnline}
style={styles.onlineBtn}
/>
</View>
)}
{/* Help & Support Cards at bottom */}
<View style={styles.helpSection}>
<Text style={styles.helpTitle}>Help & Support</Text>
<View style={styles.helpRow}>
<TouchableOpacity
style={styles.helpItem}
onPress={() => Alert.alert('Emergency SOS', 'Calling nearest security/emergency dispatch...')}
>
<BellIcon size={24} color={colors.error} />
<Text style={styles.helpLabel}>Emergency SOS</Text>
<Text style={styles.helpSub}>Immediate Assistance</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.helpItem}
onPress={() => navigation.navigate(RouteNames.BottomTabs, { screen: RouteNames.Profile })}
>
<ClipboardIcon size={24} color={colors.primary} />
<Text style={styles.helpLabel}>FAQ Center</Text>
<Text style={styles.helpSub}>Help & Guides</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.helpItem}
onPress={() => Alert.alert('Support Chat', 'Initiating live helpdesk agent chat...')}
>
<PersonIcon size={24} color={colors.statusOnDelivery} />
<Text style={styles.helpLabel}>Live Support</Text>
<Text style={styles.helpSub}>24/7 Chat Help</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
{/* Active Job Floating Bottom Banner */}
{jobStatus !== JobStatus.Idle && jobStatus !== JobStatus.NewRequest && activeJob && (
<TouchableOpacity
style={{
position: 'absolute',
bottom: spacing.xs,
left: spacing.md,
right: spacing.md,
backgroundColor: colors.primary,
borderRadius: borderRadius.md,
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
...shadow.lg,
}}
activeOpacity={0.9}
onPress={handleActiveJobBannerPress}
>
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
<ClipboardIcon size={20} color={colors.white} />
<View style={{ marginLeft: spacing.sm, flex: 1 }}>
<Text style={{ color: colors.white, fontWeight: 'bold', fontSize: typography.fontSize.sm }}>
Order {activeJob.orderId}
</Text>
<Text style={{ color: colors.primaryLight, fontSize: typography.fontSize.xs }} numberOfLines={1}>
{jobStatus === JobStatus.Accepted && 'Heading to pickup store...'}
{jobStatus === JobStatus.ArrivedAtStore && 'At pickup store, check items...'}
{jobStatus === JobStatus.PickedUp && 'Order picked up, routing to customer...'}
{jobStatus === JobStatus.EnRoute && 'On the way to customer...'}
{jobStatus === JobStatus.ArrivedAtCustomer && 'Arrived at customer location...'}
</Text>
</View>
</View>
<View style={{ backgroundColor: colors.white, paddingHorizontal: spacing.md, paddingVertical: 6, borderRadius: borderRadius.sm }}>
<Text style={{ color: colors.primary, fontWeight: 'bold', fontSize: typography.fontSize.xs }}>View</Text>
</View>
</TouchableOpacity>
)}
</View>
);
};
export default DashboardScreen; export default DashboardScreen;

View File

@ -0,0 +1,91 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxl,
},
card: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.md,
padding: spacing.md,
marginTop: 20,
marginBottom: spacing.lg,
borderWidth: 1,
borderColor: colors.border,
...shadow.sm,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.xs,
},
label: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
textTransform: 'uppercase',
fontWeight: 'bold',
},
value: {
fontSize: typography.fontSize.md,
color: colors.text,
fontWeight: typography.fontWeight.semibold,
},
section: {
marginBottom: spacing.xl,
},
sectionTitle: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.md,
},
itemRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
itemName: {
fontSize: typography.fontSize.md,
color: colors.text,
},
itemQty: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
paymentCard: {
backgroundColor: colors.primaryLight,
borderRadius: borderRadius.md,
padding: spacing.md,
marginBottom: spacing.xl,
borderWidth: 1,
borderColor: colors.primary,
alignItems: 'center',
},
paymentLabel: {
fontSize: typography.fontSize.xs,
color: colors.primary,
fontWeight: 'bold',
textTransform: 'uppercase',
marginBottom: 4,
},
paymentVal: {
fontSize: typography.fontSize.xl,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
},
completeButton: {
width: '100%',
},
});

View File

@ -1,18 +1,87 @@
import React from 'react'; import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { ScreenHeader, PrimaryButton } from '@components';
import { useAppDispatch, useAppSelector } from '@store';
import { completeJob } from '@store/slices/jobSlice';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { getStyles } from './deliverOrderScreen.styles';
const DeliverOrderScreen: React.FC = () => { export const DeliverOrderScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const activeJob = useAppSelector((state) => state.job.activeJob);
const [isLoading, setIsLoading] = useState(false);
const items = [
{ id: 0, name: 'Cappuccino (Medium)', qty: 1 },
{ id: 1, name: 'Chocolate Croissant', qty: 1 },
];
const handleCompleteDelivery = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
dispatch(completeJob());
navigation.navigate(RouteNames.DeliveryCompleted);
}, 1200);
};
if (!activeJob) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Deliver Order Screen</Text> <ScreenHeader
title="Deliver Order"
showBack={true}
onBack={() => navigation.goBack()}
/>
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
{/* Customer Detail Card */}
<View style={styles.card}>
<View style={styles.row}>
<Text style={styles.label}>Customer Name</Text>
<Text style={styles.value}>{activeJob.dropName}</Text>
</View>
<View style={[styles.row, { marginTop: 10 }]}>
<Text style={styles.label}>Order ID</Text>
<Text style={styles.value}>{activeJob.orderId}</Text>
</View>
</View>
{/* Items Summary list */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Items Details</Text>
{items.map((item) => (
<View key={item.id} style={styles.itemRow}>
<Text style={styles.itemName}>{item.name}</Text>
<Text style={styles.itemQty}>x{item.qty}</Text>
</View>
))}
</View>
{/* Payment Collect Details */}
<View style={styles.paymentCard}>
<Text style={styles.paymentLabel}>Cash to Collect</Text>
<Text style={styles.paymentVal}>0.00 (Online Paid)</Text>
</View>
<PrimaryButton
title="Complete Delivery"
onPress={handleCompleteDelivery}
isLoading={isLoading}
style={styles.completeButton}
/>
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default DeliverOrderScreen; export default DeliverOrderScreen;

View File

@ -0,0 +1,63 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: spacing.xxl,
},
illustrationContainer: {
marginBottom: spacing.xxl,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: typography.fontSize.xxl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
textAlign: 'center',
marginBottom: spacing.sm,
},
earnedText: {
fontSize: typography.fontSize.md,
color: colors.textSecondary,
textAlign: 'center',
marginBottom: spacing.xl,
},
amount: {
fontSize: typography.fontSize.xl,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
},
summaryCard: {
width: '100%',
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
marginBottom: spacing.huge,
borderWidth: 1,
borderColor: colors.border,
...shadow.sm,
},
summaryRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginVertical: spacing.xs,
},
summaryLabel: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
},
summaryVal: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
button: {
width: '100%',
},
});

View File

@ -1,18 +1,88 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components';
import { useNavigation, CommonActions } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { getStyles } from './deliveryCompletedScreen.styles';
const DeliveryCompletedScreen: React.FC = () => { export const DeliveryCompletedScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const handleGoToDashboard = () => {
// Reset route stack and go back to Dashboard
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'BottomTabs',
state: {
routes: [{ name: RouteNames.Dashboard }],
},
},
],
})
);
};
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Delivery Completed Screen</Text> {/* Checkmark Vector */}
<View style={styles.illustrationContainer}>
<Svg width="120" height="120" viewBox="0 0 120 120" fill="none">
<Circle cx="60" cy="60" r="50" fill={colors.primaryLight} />
<Circle cx="60" cy="60" r="40" fill={colors.primary} />
<Path
d="M 44 62 L 54 72 L 76 46"
stroke="#FFFFFF"
strokeWidth="6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
</View>
<Text style={styles.title}>Delivery Completed!</Text>
<Text style={styles.earnedText}>
You earned <Text style={styles.amount}>78.00</Text> on this delivery
</Text>
{/* Summary Card */}
<View style={styles.summaryCard}>
<View style={styles.summaryRow}>
<Text style={styles.summaryLabel}>Order ID</Text>
<Text style={styles.summaryVal}>#ORD125487</Text>
</View>
<View style={styles.summaryRow}>
<Text style={styles.summaryLabel}>Delivery Distance</Text>
<Text style={styles.summaryVal}>1.2 km</Text>
</View>
<View style={styles.summaryRow}>
<Text style={styles.summaryLabel}>Time Taken</Text>
<Text style={styles.summaryVal}>15 mins</Text>
</View>
<View style={styles.summaryRow}>
<Text style={styles.summaryLabel}>Payment Mode</Text>
<Text style={styles.summaryVal}>Online Paid</Text>
</View>
</View>
<PrimaryButton
title="Go to Dashboard"
onPress={handleGoToDashboard}
style={styles.button}
/>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default DeliveryCompletedScreen; export default DeliveryCompletedScreen;

View File

@ -0,0 +1,67 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxl,
},
docCard: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: colors.cardBg,
borderRadius: borderRadius.md,
padding: spacing.md,
marginBottom: spacing.sm,
borderWidth: 1,
borderColor: colors.border,
...shadow.sm,
},
docLeft: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
iconContainer: {
width: 40,
height: 40,
borderRadius: borderRadius.full,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
},
docInfo: {
flex: 1,
},
docTitle: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: 2,
},
docSub: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
},
statusContainer: {
flexDirection: 'row',
alignItems: 'center',
},
statusBadge: {
paddingHorizontal: spacing.sm,
paddingVertical: 4,
borderRadius: borderRadius.sm,
marginRight: spacing.sm,
},
statusText: {
fontSize: 10,
fontWeight: 'bold',
},
});

View File

@ -1,18 +1,89 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { ScreenHeader } from '@components';
import { ClipboardIcon, ChevronRightIcon } from '@icons';
import { useNavigation } from '@react-navigation/native';
import { getStyles } from './documentsScreen.styles';
const DocumentsScreen: React.FC = () => { export const DocumentsScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation();
const documentsList = [
{ id: 'dl', title: 'Driving License', sub: 'Verified', status: 'verified' },
{ id: 'aadhaar', title: 'Aadhaar Card', sub: 'Verified', status: 'verified' },
{ id: 'rc', title: 'Vehicle RC', sub: 'Verified', status: 'verified' },
{ id: 'insurance', title: 'Insurance', sub: 'Expires on 12 May 2026', status: 'expiring' },
{ id: 'vehicle_photo', title: 'Vehicle Photo', sub: 'Verified', status: 'verified' },
{ id: 'profile_photo', title: 'Profile Photo', sub: 'Verified', status: 'verified' },
];
const handleDocClick = (title: string) => {
Alert.alert('Document Preview', `Opening document view for ${title}...`);
};
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Documents Screen</Text> {/* Navigation Screen Header */}
<ScreenHeader
title="Documents"
showBack={true}
onBack={() => navigation.goBack()}
/>
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
{documentsList.map((doc) => (
<TouchableOpacity
key={doc.id}
style={styles.docCard}
activeOpacity={0.7}
onPress={() => handleDocClick(doc.title)}
>
<View style={styles.docLeft}>
<View style={styles.iconContainer}>
<ClipboardIcon size={20} color={colors.primary} />
</View>
<View style={styles.docInfo}>
<Text style={styles.docTitle} numberOfLines={1}>{doc.title}</Text>
<Text style={styles.docSub}>{doc.sub}</Text>
</View>
</View>
<View style={styles.statusContainer}>
<View
style={[
styles.statusBadge,
{
backgroundColor:
doc.status === 'verified'
? colors.primaryLight
: colors.warningLight,
},
]}
>
<Text
style={[
styles.statusText,
{
color:
doc.status === 'verified'
? colors.primary
: colors.warning,
},
]}
>
{doc.status === 'verified' ? 'VERIFIED' : 'ACTIVE'}
</Text>
</View>
<ChevronRightIcon size={20} color={colors.textSecondary} />
</View>
</TouchableOpacity>
))}
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default DocumentsScreen; export default DocumentsScreen;

View File

@ -0,0 +1,133 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxxl,
},
headerContainer: {
marginTop: 20,
marginBottom: spacing.lg,
},
title: {
fontSize: typography.fontSize.xxl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
summaryCard: {
backgroundColor: colors.primary,
borderRadius: borderRadius.lg,
padding: spacing.xl,
marginBottom: spacing.lg,
...shadow.md,
},
summaryTitle: {
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.bold,
color: colors.primaryLight,
textTransform: 'uppercase',
marginBottom: spacing.xs,
},
totalAmount: {
fontSize: 32,
fontWeight: typography.fontWeight.bold,
color: colors.white,
marginBottom: spacing.lg,
},
statsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
borderTopWidth: 1,
borderTopColor: 'rgba(255,255,255,0.15)',
paddingTop: spacing.md,
},
statBox: {
flex: 1,
alignItems: 'center',
},
statVal: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.white,
marginBottom: 2,
},
statLabel: {
fontSize: 10,
color: colors.primaryLight,
},
statDivider: {
width: 1,
height: 24,
backgroundColor: 'rgba(255,255,255,0.15)',
},
listSection: {
marginTop: spacing.md,
},
sectionTitle: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.md,
},
earningItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: colors.cardBg,
borderRadius: borderRadius.md,
padding: spacing.md,
marginBottom: spacing.sm,
borderWidth: 1,
borderColor: colors.border,
...shadow.sm,
},
itemLeft: {
flexDirection: 'row',
alignItems: 'center',
},
iconContainer: {
width: 40,
height: 40,
borderRadius: borderRadius.full,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
},
orderIdText: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: 2,
},
timeText: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
},
itemRight: {
alignItems: 'flex-end',
},
itemAmount: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
marginBottom: 4,
},
paymentBadge: {
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: borderRadius.sm,
backgroundColor: colors.inputBg,
},
paymentText: {
fontSize: 9,
fontWeight: 'bold',
color: colors.textSecondary,
},
});

View File

@ -1,18 +1,103 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { WalletIcon } from '@icons';
import { useAppSelector } from '@store';
import { formatCurrency } from '@utils/formatters';
import { getStyles } from './earningsScreen.styles';
const EarningsScreen: React.FC = () => { export const EarningsScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const { todayEarnings, completedCount, onlineMinutes } = useAppSelector((state) => state.earnings);
const { jobHistory } = useAppSelector((state) => state.job);
// Default mock jobs history if none completed yet
const defaultHistory: { orderId: string; time: string; amount: number; paymentType: 'online' | 'cod'; }[] = [
{ orderId: '#ORD125487', time: '12:35 PM', amount: 78.0, paymentType: 'online' },
{ orderId: '#ORD125422', time: '11:15 AM', amount: 65.0, paymentType: 'cod' },
{ orderId: '#ORD125389', time: '09:40 AM', amount: 120.0, paymentType: 'online' },
{ orderId: '#ORD125310', time: 'Yesterday', amount: 85.0, paymentType: 'online' },
];
const displayHistory = jobHistory.length > 0
? jobHistory.map(job => ({
orderId: job.orderId,
time: job.deliveredAt ? new Date(job.deliveredAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : 'Delivered',
amount: job.amount,
paymentType: job.paymentType,
})).concat(defaultHistory)
: defaultHistory;
const formatOnlineTime = (mins: number) => {
const hours = Math.floor(mins / 60);
const remainingMins = mins % 60;
return `${hours}h ${remainingMins < 10 ? `0${remainingMins}` : remainingMins}m`;
};
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Earnings Screen</Text> <ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
<View style={styles.headerContainer}>
<Text style={styles.title}>Earnings</Text>
</View>
{/* Total Earnings Card */}
<View style={styles.summaryCard}>
<Text style={styles.summaryTitle}>Today's Earnings</Text>
<Text style={styles.totalAmount}>{formatCurrency(todayEarnings)}</Text>
<View style={styles.statsRow}>
<View style={styles.statBox}>
<Text style={styles.statVal}>{completedCount}</Text>
<Text style={styles.statLabel}>Deliveries</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statVal}>{formatOnlineTime(onlineMinutes)}</Text>
<Text style={styles.statLabel}>Online Time</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statBox}>
<Text style={styles.statVal}>78.50</Text>
<Text style={styles.statLabel}>Avg / Order</Text>
</View>
</View>
</View>
{/* Recent Deliveries List */}
<View style={styles.listSection}>
<Text style={styles.sectionTitle}>Recent Deliveries</Text>
{displayHistory.map((item, index) => (
<View key={index} style={styles.earningItem}>
<View style={styles.itemLeft}>
<View style={styles.iconContainer}>
<WalletIcon size={20} color={colors.primary} />
</View>
<View>
<Text style={styles.orderIdText}>{item.orderId}</Text>
<Text style={styles.timeText}>{item.time}</Text>
</View>
</View>
<View style={styles.itemRight}>
<Text style={styles.itemAmount}>+{formatCurrency(item.amount)}</Text>
<View style={styles.paymentBadge}>
<Text style={styles.paymentText}>{item.paymentType.toUpperCase()}</Text>
</View>
</View>
</View>
))}
</View>
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default EarningsScreen; export default EarningsScreen;

View File

@ -0,0 +1,81 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxxl,
},
headerContainer: {
marginTop: 20,
marginBottom: spacing.lg,
},
title: {
fontSize: typography.fontSize.xxl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
chatItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: spacing.md,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
avatar: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
},
chatInfo: {
flex: 1,
},
chatHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
chatTitleText: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
timeText: {
fontSize: 10,
color: colors.textSecondary,
},
chatSnippetText: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
},
rightMeta: {
alignItems: 'flex-end',
justifyContent: 'center',
marginLeft: spacing.sm,
},
unreadBadge: {
width: 18,
height: 18,
borderRadius: 9,
backgroundColor: colors.primary,
justifyContent: 'center',
alignItems: 'center',
marginTop: 6,
},
unreadBadgeText: {
color: colors.white,
fontSize: 10,
fontWeight: 'bold',
},
});

View File

@ -1,25 +1,87 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PersonIcon } from '@icons';
import { getStyles } from './inboxScreen.styles';
const InboxScreen: React.FC = () => { export const InboxScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const chats = [
{
id: 'support',
sender: 'SG Partner Support',
snippet: 'Hi Rahul, your document verification is complete. Let us know if you need assistance.',
time: '10 mins ago',
unreadCount: 1,
isSystem: true,
},
{
id: 'rohit',
sender: 'Rohit Sharma (Customer)',
snippet: 'Please leave the order near the gate, thank you.',
time: '1 hour ago',
unreadCount: 0,
isSystem: false,
},
{
id: 'ccd',
sender: 'Cafe Coffee Day (Merchant)',
snippet: 'Hi, the order #ORD125487 is ready for pickup. Please arrive.',
time: '2 hours ago',
unreadCount: 0,
isSystem: false,
},
];
const handleChatPress = (sender: string) => {
Alert.alert('Chat Thread', `Opening conversation with ${sender}...`);
};
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }] }> <View style={styles.container}>
<Text style={styles.screenTitle}>Inbox Screen</Text> <ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
<View style={styles.headerContainer}>
<Text style={styles.title}>Inbox</Text>
</View>
{chats.map((chat) => (
<TouchableOpacity
key={chat.id}
style={styles.chatItem}
activeOpacity={0.7}
onPress={() => handleChatPress(chat.sender)}
>
{/* Sender Avatar */}
<View style={styles.avatar}>
<PersonIcon size={24} color={chat.isSystem ? colors.primary : colors.textSecondary} />
</View>
{/* Chat Content Info */}
<View style={styles.chatInfo}>
<View style={styles.chatHeader}>
<Text style={styles.chatTitleText} numberOfLines={1}>{chat.sender}</Text>
<Text style={styles.timeText}>{chat.time}</Text>
</View>
<Text style={styles.chatSnippetText} numberOfLines={1}>
{chat.snippet}
</Text>
</View>
{/* Unread badge info */}
{chat.unreadCount > 0 && (
<View style={styles.rightMeta}>
<View style={styles.unreadBadge}>
<Text style={styles.unreadBadgeText}>{chat.unreadCount}</Text>
</View>
</View>
)}
</TouchableOpacity>
))}
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
screenTitle: {
fontSize: 18,
},
});
export default InboxScreen; export default InboxScreen;

View File

@ -1 +1,20 @@
export * from './loginScreen'; export { default as LoginScreen } from './LoginScreen/LoginScreen';
export { default as OtpScreen } from './OtpScreen/OtpScreen';
export { default as SetLocationScreen } from './setLocationScreen/setLocationScreen';
export { default as CompleteProfileScreen } from './completeProfileScreen/completeProfileScreen';
export { default as OnboardingCompleteScreen } from './onboardingCompleteScreen/onboardingCompleteScreen';
export { default as DashboardScreen } from './dashboardScreen/dashboardScreen';
export { default as EarningsScreen } from './earningsScreen/earningsScreen';
export { default as JobsScreen } from './jobsScreen/jobsScreen';
export { default as InboxScreen } from './inboxScreen/inboxScreen';
export { default as ProfileScreen } from './profileScreen/profileScreen';
export { default as DocumentsScreen } from './documentsScreen/documentsScreen';
export { default as NewJobRequestScreen } from './newJobRequestScreen/newJobRequestScreen';
export { default as OrderAcceptedScreen } from './orderAcceptedScreen/orderAcceptedScreen';
export { default as ArrivedAtStoreScreen } from './arrivedAtStoreScreen/arrivedAtStoreScreen';
export { default as ConfirmPickupScreen } from './confirmPickupScreen/confirmPickupScreen';
export { default as OrderPickedUpScreen } from './orderPickedUpScreen/orderPickedUpScreen';
export { default as LiveTrackingScreen } from './liveTrackingScreen/liveTrackingScreen';
export { default as ArrivedAtCustomerScreen } from './arrivedAtCustomerScreen/arrivedAtCustomerScreen';
export { default as DeliverOrderScreen } from './deliverOrderScreen/deliverOrderScreen';
export { default as DeliveryCompletedScreen } from './deliveryCompletedScreen/deliveryCompletedScreen';

View File

@ -0,0 +1,67 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
headerContainer: {
marginTop: 20,
paddingHorizontal: spacing.lg,
marginBottom: spacing.md,
},
title: {
fontSize: typography.fontSize.xxl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
tabBar: {
flexDirection: 'row',
marginHorizontal: spacing.lg,
backgroundColor: colors.inputBg,
borderRadius: borderRadius.md,
padding: 4,
marginBottom: spacing.md,
borderWidth: 1,
borderColor: colors.border,
},
tab: {
flex: 1,
paddingVertical: spacing.sm,
alignItems: 'center',
borderRadius: borderRadius.sm,
},
tabActive: {
backgroundColor: colors.cardBg,
...shadow.sm,
},
tabText: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.medium,
color: colors.textSecondary,
},
tabTextActive: {
color: colors.primary,
fontWeight: typography.fontWeight.bold,
},
listContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxxl,
},
cardWrapper: {
marginBottom: spacing.md,
},
emptyStateContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: spacing.huge,
},
emptyStateText: {
fontSize: typography.fontSize.md,
color: colors.textSecondary,
marginTop: spacing.md,
},
});

View File

@ -1,19 +1,140 @@
import React from 'react'; import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { OrderCard, EmptyState } from '@components';
import { useAppSelector } from '@store';
import { ClipboardIcon } from '@icons';
import { getStyles } from './jobsScreen.styles';
const JobsScreen: React.FC = () => { export const JobsScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const [activeTab, setActiveTab] = useState<'active' | 'history'>('active');
const { activeJob, jobHistory } = useAppSelector((state) => state.job);
// Default history mock jobs
const defaultHistory = [
{
pickupName: 'Cafe Coffee Day',
pickupAddress: 'MG Road, Bengaluru',
dropName: 'Rohit Sharma',
dropAddress: 'Koramangala 4th Block, Bengaluru',
amount: 78.0,
distance: 1.2,
itemCount: 2,
paymentType: 'online',
},
{
pickupName: 'Pizza Hut',
pickupAddress: 'Indiranagar, Bengaluru',
dropName: 'Aishwarya Sen',
dropAddress: 'Domlur Stage 2, Bengaluru',
amount: 95.0,
distance: 3.8,
itemCount: 3,
paymentType: 'cod',
},
{
pickupName: 'Burgers & Co',
pickupAddress: 'Koramangala 5th Block',
dropName: 'Sameer Khan',
dropAddress: 'HSR Layout Sector 3, Bengaluru',
amount: 110.0,
distance: 4.5,
itemCount: 1,
paymentType: 'online',
},
];
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text style={styles.title}>Jobs Screen</Text> <View style={styles.headerContainer}>
<Text style={styles.title}>Jobs</Text>
</View>
{/* Tabs Selector */}
<View style={styles.tabBar}>
<TouchableOpacity
style={[styles.tab, activeTab === 'active' && styles.tabActive]}
activeOpacity={0.8}
onPress={() => setActiveTab('active')}
>
<Text style={[styles.tabText, activeTab === 'active' && styles.tabTextActive]}>
Active / Upcoming
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'history' && styles.tabActive]}
activeOpacity={0.8}
onPress={() => setActiveTab('history')}
>
<Text style={[styles.tabText, activeTab === 'history' && styles.tabTextActive]}>
History
</Text>
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={styles.listContainer} showsVerticalScrollIndicator={false}>
{activeTab === 'active' ? (
// Active job tab
activeJob ? (
<View style={styles.cardWrapper}>
<OrderCard
pickupName={activeJob.pickupName}
pickupAddress={activeJob.pickupAddress}
dropName={activeJob.dropName}
dropAddress={activeJob.dropAddress}
amount={activeJob.amount}
distance={activeJob.distance}
itemCount={activeJob.itemCount}
paymentType={activeJob.paymentType}
/>
</View>
) : (
<View style={styles.emptyStateContainer}>
<ClipboardIcon size={48} color={colors.placeholder} />
<Text style={styles.emptyStateText}>No active deliveries right now</Text>
</View>
)
) : (
// History tab
<View>
{jobHistory.length > 0
? jobHistory.map((job, idx) => (
<View key={idx} style={styles.cardWrapper}>
<OrderCard
pickupName={job.pickupName}
pickupAddress={job.pickupAddress}
dropName={job.dropName}
dropAddress={job.dropAddress}
amount={job.amount}
distance={job.distance}
itemCount={job.itemCount}
paymentType={job.paymentType}
/>
</View>
))
: defaultHistory.map((job, idx) => (
<View key={idx} style={styles.cardWrapper}>
<OrderCard
pickupName={job.pickupName}
pickupAddress={job.pickupAddress}
dropName={job.dropName}
dropAddress={job.dropAddress}
amount={job.amount}
distance={job.distance}
itemCount={job.itemCount}
paymentType={job.paymentType}
/>
</View>
))}
</View>
)}
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 20 },
});
export default JobsScreen; export default JobsScreen;

View File

@ -0,0 +1,91 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } 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',
},
placeholderText: {
fontSize: typography.fontSize.sm,
color: colors.placeholder,
},
etaCard: {
position: 'absolute',
top: spacing.lg,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.md,
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
alignItems: 'center',
...shadow.md,
borderWidth: 1,
borderColor: colors.border,
zIndex: 10,
},
etaTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
marginBottom: 2,
},
etaSub: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
},
bottomCard: {
position: 'absolute',
bottom: spacing.xxl,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
...shadow.lg,
borderWidth: 1,
borderColor: colors.border,
zIndex: 10,
},
cardTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
marginBottom: spacing.xs,
},
customerName: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
},
customerAddress: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
lineHeight: 20,
marginBottom: spacing.lg,
},
btnRow: {
flexDirection: 'row',
gap: 12,
},
contactBtn: {
flex: 1,
backgroundColor: colors.inputBg,
borderWidth: 1,
borderColor: colors.border,
},
arrivedBtn: {
flex: 2,
backgroundColor: colors.primary,
},
});

View File

@ -1,18 +1,69 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components';
import { useAppDispatch, useAppSelector } from '@store';
import { advanceJobStatus } from '@store/slices/jobSlice';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames, JobStatus } from '@utils/constants';
import { getStyles } from './liveTrackingScreen.styles';
const LiveTrackingScreen: React.FC = () => { export const LiveTrackingScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const activeJob = useAppSelector((state) => state.job.activeJob);
const handleArrived = () => {
dispatch(advanceJobStatus(JobStatus.ArrivedAtCustomer));
navigation.navigate(RouteNames.ArrivedAtCustomer);
};
const handleContact = () => {
Alert.alert('Contact Customer', `Calling ${activeJob?.dropName || 'Customer'} (+91 99887 76655)...`);
};
if (!activeJob) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Live Tracking Screen</Text> {/* Top Floating ETA Card */}
<View style={styles.etaCard}>
<Text style={styles.etaTitle}>Arriving in 8 mins</Text>
<Text style={styles.etaSub}>Distance: {activeJob.distance} km Speed: 24 km/h</Text>
</View>
{/* Map Background Placeholder */}
<View style={styles.mapPlaceholder}>
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
</View>
{/* Customer Location Bottom Card */}
<View style={styles.bottomCard}>
<Text style={styles.cardTitle}>Deliver to</Text>
<Text style={styles.customerName}>{activeJob.dropName}</Text>
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
<View style={styles.btnRow}>
<PrimaryButton
title="Call"
onPress={handleContact}
style={styles.contactBtn}
textStyle={{ color: colors.text }}
/>
<PrimaryButton
title="Arrived at Location"
onPress={handleArrived}
style={styles.arrivedBtn}
/>
</View>
</View>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default LiveTrackingScreen; export default LiveTrackingScreen;

View File

@ -0,0 +1,99 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.65)',
justifyContent: 'flex-end',
},
modalContent: {
backgroundColor: colors.cardBg,
borderTopLeftRadius: borderRadius.xl,
borderTopRightRadius: borderRadius.xl,
padding: spacing.xl,
paddingBottom: spacing.huge,
...shadow.lg,
},
headerRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: spacing.lg,
},
modalTitle: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
timerContainer: {
width: 44,
height: 44,
borderRadius: 22,
borderWidth: 3,
borderColor: colors.primary,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.primaryLight,
},
timerText: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
},
routeContainer: {
backgroundColor: colors.inputBg,
borderRadius: borderRadius.md,
padding: spacing.md,
marginBottom: spacing.lg,
borderWidth: 1,
borderColor: colors.border,
},
spacer: {
height: spacing.md,
},
detailsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: spacing.xl,
backgroundColor: colors.inputBg,
padding: spacing.md,
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: colors.border,
},
detailBox: {
flex: 1,
alignItems: 'center',
},
detailLabel: {
fontSize: 10,
color: colors.textSecondary,
fontWeight: 'bold',
textTransform: 'uppercase',
marginBottom: 4,
},
detailValue: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
divider: {
width: 1,
height: 28,
backgroundColor: colors.border,
},
btnRow: {
flexDirection: 'row',
gap: 12,
},
rejectBtn: {
flex: 1,
backgroundColor: colors.border,
},
acceptBtn: {
flex: 2,
backgroundColor: colors.primary,
},
});

View File

@ -1,18 +1,111 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton, LocationRow } from '@components';
import { useAppDispatch, useAppSelector } from '@store';
import { acceptJob, rejectJob } from '@store/slices/jobSlice';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { formatCurrency } from '@utils/formatters';
import { getStyles } from './newJobRequestScreen.styles';
const NewJobRequestScreen: React.FC = () => { export const NewJobRequestScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const newJobOffer = useAppSelector((state) => state.job.newJobOffer);
const [secondsLeft, setSecondsLeft] = useState(15);
useEffect(() => {
if (secondsLeft <= 0) {
handleReject();
return;
}
const timer = setInterval(() => {
setSecondsLeft((prev) => prev - 1);
}, 1000);
return () => clearInterval(timer);
}, [secondsLeft]);
const handleAccept = () => {
dispatch(acceptJob());
Alert.alert('Job Accepted', 'Heading to Cafe Coffee Day pickup location.', [
{
text: 'OK',
onPress: () => navigation.navigate(RouteNames.OrderAccepted),
},
]);
};
const handleReject = () => {
dispatch(rejectJob());
navigation.navigate(RouteNames.BottomTabs, { screen: RouteNames.Dashboard });
};
if (!newJobOffer) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>New Job Request Screen</Text> <View style={styles.modalContent}>
{/* Header Row with Title and Countdown */}
<View style={styles.headerRow}>
<Text style={styles.modalTitle}>New Delivery Request</Text>
<View style={styles.timerContainer}>
<Text style={styles.timerText}>{secondsLeft}s</Text>
</View>
</View>
{/* Route Details Card */}
<View style={styles.routeContainer}>
<LocationRow
type="pickup"
name="Cafe Coffee Day"
address={newJobOffer.pickupAddress}
/>
<View style={styles.spacer} />
<LocationRow
type="drop"
name="Rohit Sharma"
address={newJobOffer.dropAddress}
/>
</View>
{/* Earnings & Distance Details */}
<View style={styles.detailsRow}>
<View style={styles.detailBox}>
<Text style={styles.detailLabel}>Estimated Earnings</Text>
<Text style={styles.detailValue}>{formatCurrency(newJobOffer.amount)}</Text>
</View>
<View style={styles.divider} />
<View style={styles.detailBox}>
<Text style={styles.detailLabel}>Distance</Text>
<Text style={styles.detailValue}>{newJobOffer.distance} km</Text>
</View>
</View>
{/* Button Actions */}
<View style={styles.btnRow}>
<PrimaryButton
title="Reject"
onPress={handleReject}
style={styles.rejectBtn}
textStyle={{ color: colors.text }}
/>
<PrimaryButton
title="Accept"
onPress={handleAccept}
style={styles.acceptBtn}
/>
</View>
</View>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default NewJobRequestScreen; export default NewJobRequestScreen;

View File

@ -0,0 +1,35 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: spacing.xxl,
},
illustrationContainer: {
marginBottom: spacing.xxl,
alignItems: 'center',
justifyContent: 'center',
},
successTitle: {
fontSize: typography.fontSize.xxl,
fontWeight: typography.fontWeight.bold,
color: colors.text,
textAlign: 'center',
marginBottom: spacing.md,
},
successDescription: {
fontSize: typography.fontSize.md,
color: colors.textSecondary,
textAlign: 'center',
lineHeight: 24,
marginBottom: spacing.huge,
},
startButton: {
width: '100%',
},
});

View File

@ -1,30 +1,54 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components'; import { PrimaryButton } from '@components';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '@navigation/navigationTypes'; import { RootStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { getStyles } from './onboardingCompleteScreen.styles';
const OnboardingCompleteScreen: React.FC = () => { const OnboardingCompleteScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>(); const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text style={styles.title}>Onboarding Complete Screen</Text> {/* Premium Success Checkmark Vector */}
<View style={styles.illustrationContainer}>
<Svg width="140" height="140" viewBox="0 0 140 140" fill="none">
<Circle cx="70" cy="70" r="60" fill={colors.primaryLight} />
<Circle cx="70" cy="70" r="48" fill={colors.primary} />
{/* Checkmark icon */}
<Path
d="M 50 72 L 64 86 L 90 54"
stroke="#FFFFFF"
strokeWidth="8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
</View>
<Text style={styles.successTitle}>Onboarding Completed!</Text>
<Text style={styles.successDescription}>
Welcome to the SG Delivery family! Your account has been successfully verified. You are now ready to go online and start earning.
</Text>
<PrimaryButton <PrimaryButton
title="Get Started" title="Get Started"
onPress={() => navigation.navigate('App', { screen: 'BottomTabs' })} onPress={() =>
style={{ marginTop: 20, width: '90%' }} navigation.navigate('App', {
screen: RouteNames.BottomTabs,
params: { screen: RouteNames.Dashboard },
})
}
style={styles.startButton}
/> />
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 20 },
});
export default OnboardingCompleteScreen; export default OnboardingCompleteScreen;

View File

@ -0,0 +1,74 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } 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',
},
placeholderText: {
fontSize: typography.fontSize.sm,
color: colors.placeholder,
},
headerBanner: {
position: 'absolute',
top: spacing.lg,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.primary,
borderRadius: borderRadius.md,
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
alignItems: 'center',
...shadow.md,
zIndex: 10,
},
headerText: {
color: colors.white,
fontWeight: typography.fontWeight.bold,
fontSize: typography.fontSize.md,
textAlign: 'center',
},
bottomCard: {
position: 'absolute',
bottom: spacing.xxl,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
...shadow.lg,
borderWidth: 1,
borderColor: colors.border,
zIndex: 10,
},
cardTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
marginBottom: spacing.xs,
},
storeName: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
},
storeAddress: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
lineHeight: 20,
marginBottom: spacing.lg,
},
navigateButton: {
width: '100%',
},
});

View File

@ -1,18 +1,56 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components';
import { useAppDispatch, useAppSelector } from '@store';
import { advanceJobStatus } from '@store/slices/jobSlice';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames, JobStatus } from '@utils/constants';
import { getStyles } from './orderAcceptedScreen.styles';
const OrderAcceptedScreen: React.FC = () => { export const OrderAcceptedScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const activeJob = useAppSelector((state) => state.job.activeJob);
const handleNavigate = () => {
dispatch(advanceJobStatus(JobStatus.ArrivedAtStore));
navigation.navigate(RouteNames.ArrivedAtStore);
};
if (!activeJob) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Order Accepted Screen</Text> {/* Top Banner Alert */}
<View style={styles.headerBanner}>
<Text style={styles.headerText}>Order Accepted Go to pickup location</Text>
</View>
{/* Blank Map Placeholder Background */}
<View style={styles.mapPlaceholder}>
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
</View>
{/* Merchant Bottom Card */}
<View style={styles.bottomCard}>
<Text style={styles.cardTitle}>Pickup Merchant</Text>
<Text style={styles.storeName}>{activeJob.pickupName}</Text>
<Text style={styles.storeAddress}>{activeJob.pickupAddress}</Text>
<PrimaryButton
title="Navigate"
onPress={handleNavigate}
style={styles.navigateButton}
/>
</View>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default OrderAcceptedScreen; export default OrderAcceptedScreen;

View File

@ -0,0 +1,55 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } 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',
},
placeholderText: {
fontSize: typography.fontSize.sm,
color: colors.placeholder,
},
bottomCard: {
position: 'absolute',
bottom: spacing.xxl,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
...shadow.lg,
borderWidth: 1,
borderColor: colors.border,
zIndex: 10,
},
cardTitle: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
textTransform: 'uppercase',
marginBottom: spacing.xs,
},
customerName: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.sm,
},
customerAddress: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
lineHeight: 20,
marginBottom: spacing.lg,
},
startButton: {
width: '100%',
},
});

View File

@ -1,18 +1,51 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components';
import { useAppDispatch, useAppSelector } from '@store';
import { advanceJobStatus } from '@store/slices/jobSlice';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames, JobStatus } from '@utils/constants';
import { getStyles } from './orderPickedUpScreen.styles';
const OrderPickedUpScreen: React.FC = () => { export const OrderPickedUpScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const activeJob = useAppSelector((state) => state.job.activeJob);
const handleStartDelivery = () => {
dispatch(advanceJobStatus(JobStatus.EnRoute));
navigation.navigate(RouteNames.LiveTracking);
};
if (!activeJob) return null;
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }]}> <View style={styles.container}>
<Text>Order Picked Up Screen</Text> {/* Map Background Placeholder */}
<View style={styles.mapPlaceholder}>
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
</View>
{/* Deliver Bottom Card */}
<View style={styles.bottomCard}>
<Text style={styles.cardTitle}>Deliver to Customer</Text>
<Text style={styles.customerName}>{activeJob.dropName}</Text>
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
<PrimaryButton
title="Start Delivery"
onPress={handleStartDelivery}
style={styles.startButton}
/>
</View>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default OrderPickedUpScreen; export default OrderPickedUpScreen;

View File

@ -0,0 +1,106 @@
import { StyleSheet } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
scrollContainer: {
paddingHorizontal: spacing.lg,
paddingBottom: spacing.xxxl,
},
headerCard: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
alignItems: 'center',
marginTop: 20,
marginBottom: spacing.xl,
...shadow.md,
borderWidth: 1,
borderColor: colors.border,
},
avatar: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
marginBottom: spacing.md,
},
name: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: 6,
},
ratingRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 4,
},
ratingText: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.warning,
marginLeft: 4,
},
tripsText: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
marginLeft: 4,
},
idText: {
fontSize: typography.fontSize.xs,
color: colors.placeholder,
marginTop: 4,
},
menuSection: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
borderWidth: 1,
borderColor: colors.border,
overflow: 'hidden',
marginBottom: spacing.xl,
...shadow.sm,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
menuItemLeft: {
flexDirection: 'row',
alignItems: 'center',
},
menuIconContainer: {
marginRight: spacing.md,
},
menuText: {
fontSize: typography.fontSize.md,
color: colors.text,
fontWeight: typography.fontWeight.medium,
},
logoutBtn: {
backgroundColor: colors.errorLight,
borderColor: colors.error,
borderWidth: 1,
paddingVertical: spacing.md,
borderRadius: borderRadius.md,
alignItems: 'center',
marginBottom: spacing.xl,
},
logoutText: {
color: colors.error,
fontWeight: 'bold',
fontSize: typography.fontSize.md,
},
});

View File

@ -1,26 +1,107 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PersonIcon, ChevronRightIcon, ClipboardIcon, StarIcon } from '@icons';
import { useAppDispatch } from '@store';
import { logout } from '@store/slices/authSlice';
import { useNavigation, CommonActions } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { getStyles } from './profileScreen.styles';
const ProfileScreen: React.FC = () => { export const ProfileScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
const menuItems = [
{ id: 'personal', title: 'Personal Info', icon: <PersonIcon size={20} color={colors.primary} /> },
{ id: 'vehicle', title: 'Vehicle Info', icon: <ClipboardIcon size={20} color={colors.primary} /> },
{ id: 'documents', title: 'Documents', icon: <ClipboardIcon size={20} color={colors.primary} />, route: RouteNames.Documents },
{ id: 'bank', title: 'Bank Details', icon: <ClipboardIcon size={20} color={colors.primary} /> },
{ id: 'emergency', title: 'Emergency Contact', icon: <PersonIcon size={20} color={colors.primary} /> },
{ id: 'settings', title: 'App Settings', icon: <PersonIcon size={20} color={colors.primary} /> },
];
const handleMenuPress = (item: any) => {
if (item.route) {
navigation.navigate(item.route);
} else {
Alert.alert('Menu Option', `Viewing ${item.title} section...`);
}
};
const handleLogout = () => {
Alert.alert(
'Logout',
'Are you sure you want to log out of SG Delivery Partner?',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Logout',
style: 'destructive',
onPress: () => {
dispatch(logout());
// Reset navigation stack to the Auth Stack
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'Auth' }],
})
);
},
},
]
);
};
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }] }> <View style={styles.container}>
<Text style={styles.screenTitle}>Profile Screen</Text> <ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
{/* Header User Card */}
<View style={styles.headerCard}>
<View style={styles.avatar}>
<PersonIcon size={44} color={colors.primary} />
</View>
<Text style={styles.name}>Rahul Kumar</Text>
<View style={styles.ratingRow}>
<StarIcon size={16} color={colors.warning} />
<Text style={styles.ratingText}>4.8</Text>
<Text style={styles.tripsText}>(128 Trips)</Text>
</View>
<Text style={styles.idText}>ID: DP126478</Text>
</View>
{/* Profile Menu options */}
<View style={styles.menuSection}>
{menuItems.map((item, idx) => (
<TouchableOpacity
key={item.id}
style={[styles.menuItem, idx === menuItems.length - 1 && { borderBottomWidth: 0 }]}
activeOpacity={0.7}
onPress={() => handleMenuPress(item)}
>
<View style={styles.menuItemLeft}>
<View style={styles.menuIconContainer}>{item.icon}</View>
<Text style={styles.menuText}>{item.title}</Text>
</View>
<ChevronRightIcon size={20} color={colors.textSecondary} />
</TouchableOpacity>
))}
</View>
{/* Logout Button */}
<TouchableOpacity style={styles.logoutBtn} activeOpacity={0.8} onPress={handleLogout}>
<Text style={styles.logoutText}>Log Out</Text>
</TouchableOpacity>
</ScrollView>
</View> </View>
); );
}; };
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 16,
},
screenTitle: {
fontSize: 24,
},
});
export default ProfileScreen; export default ProfileScreen;

View File

@ -0,0 +1,95 @@
import { StyleSheet, Dimensions } from 'react-native';
import { typography, spacing, borderRadius, shadow } from '@theme';
const { width } = Dimensions.get('window');
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
},
mapContainer: {
flex: 1,
backgroundColor: colors.inputBg,
justifyContent: 'center',
alignItems: 'center',
},
placeholderMap: {
alignItems: 'center',
justifyContent: 'center',
},
mapText: {
fontSize: typography.fontSize.lg,
color: colors.textSecondary,
fontWeight: typography.fontWeight.semibold,
marginTop: spacing.md,
},
mapSubText: {
fontSize: typography.fontSize.sm,
color: colors.placeholder,
marginTop: spacing.xs,
},
markerOverlay: {
position: 'absolute',
justifyContent: 'center',
alignItems: 'center',
},
pulseCircle: {
position: 'absolute',
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: colors.primary,
opacity: 0.15,
},
bottomCard: {
position: 'absolute',
bottom: spacing.xxl,
left: spacing.lg,
right: spacing.lg,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.lg,
padding: spacing.xl,
...shadow.lg,
borderWidth: 1,
borderColor: colors.border,
},
cardHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: spacing.md,
},
pinIconContainer: {
width: 36,
height: 36,
borderRadius: borderRadius.full,
backgroundColor: colors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.sm,
},
cardTitle: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
addressContainer: {
marginBottom: spacing.xl,
},
addressLabel: {
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
marginBottom: spacing.xs,
},
addressText: {
fontSize: typography.fontSize.md,
color: colors.text,
lineHeight: 22,
},
confirmButton: {
width: '100%',
},
});

View File

@ -1,31 +1,57 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text } from 'react-native';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components'; import { PrimaryButton } from '@components';
import { LocationPinIcon } from '@icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes'; import { AuthStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';
import { getStyles } from './setLocationScreen.styles';
const SetLocationScreen: React.FC = () => { const SetLocationScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>(); const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
return ( return (
<View style={[styles.container, { backgroundColor: colors.background }] }> <View style={styles.container}>
<Text style={styles.title}>Set Location</Text> {/* Blank Map Placeholder Area */}
<View style={styles.mapContainer}>
<View style={styles.placeholderMap}>
{/* Pulsing marker simulation */}
<View style={styles.pulseCircle} />
<LocationPinIcon size={40} color={colors.primary} />
<Text style={styles.mapText}>Select Location</Text>
<Text style={styles.mapSubText}>Drag the map to pinpoint your location</Text>
</View>
</View>
{/* Bottom Sheet Location Selector Card */}
<View style={styles.bottomCard}>
<View style={styles.cardHeader}>
<View style={styles.pinIconContainer}>
<LocationPinIcon size={20} color={colors.primary} />
</View>
<Text style={styles.cardTitle}>Choose Delivery Area</Text>
</View>
<View style={styles.addressContainer}>
<Text style={styles.addressLabel}>Current Address</Text>
<Text style={styles.addressText}>
MG Road, Ashok Nagar, Bengaluru, Karnataka 560001
</Text>
</View>
<PrimaryButton <PrimaryButton
title="Continue" title="Confirm Location"
onPress={() => navigation.navigate(RouteNames.CompleteProfile)} onPress={() => navigation.navigate(RouteNames.CompleteProfile)}
style={{ marginTop: 20, width: '90%' }} style={styles.confirmButton}
/> />
</View> </View>
</View>
); );
}; };
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 24 }
});
export default SetLocationScreen; export default SetLocationScreen;

View File

@ -1 +1 @@
export { useToast } from '@components/toast/toast'; export { useToast } from '@components';

View File

@ -2,16 +2,18 @@ import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { AppStackParamList } from '@navigation/navigationTypes'; import { AppStackParamList } from '@navigation/navigationTypes';
import BottomTabNavigator from './bottomTabNavigator'; import BottomTabNavigator from './bottomTabNavigator';
import NewJobRequestScreen from '@features/screens/newJobRequestScreen/newJobRequestScreen'; import {
import OrderAcceptedScreen from '@features/screens/orderAcceptedScreen/orderAcceptedScreen'; NewJobRequestScreen,
import ArrivedAtStoreScreen from '@features/screens/arrivedAtStoreScreen/arrivedAtStoreScreen'; OrderAcceptedScreen,
import ConfirmPickupScreen from '@features/screens/confirmPickupScreen/confirmPickupScreen'; ArrivedAtStoreScreen,
import OrderPickedUpScreen from '@features/screens/orderPickedUpScreen/orderPickedUpScreen'; ConfirmPickupScreen,
import LiveTrackingScreen from '@features/screens/liveTrackingScreen/liveTrackingScreen'; OrderPickedUpScreen,
import ArrivedAtCustomerScreen from '@features/screens/arrivedAtCustomerScreen/arrivedAtCustomerScreen'; LiveTrackingScreen,
import DeliverOrderScreen from '@features/screens/deliverOrderScreen/deliverOrderScreen'; ArrivedAtCustomerScreen,
import DeliveryCompletedScreen from '@features/screens/deliveryCompletedScreen/deliveryCompletedScreen'; DeliverOrderScreen,
import DocumentsScreen from '@features/screens/documentsScreen/documentsScreen'; DeliveryCompletedScreen,
DocumentsScreen,
} from '@features/screens';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';
const Stack = createNativeStackNavigator<AppStackParamList>(); const Stack = createNativeStackNavigator<AppStackParamList>();

View File

@ -1,11 +1,13 @@
import React from 'react'; import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes'; import { AuthStackParamList } from '@navigation/navigationTypes';
import LoginScreen from '@features/screens/loginScreen'; import {
import OtpScreen from '@features/screens/otpScreen'; LoginScreen,
import SetLocationScreen from '@features/screens/setLocationScreen'; OtpScreen,
import CompleteProfileScreen from '@features/screens/completeProfileScreen'; SetLocationScreen,
import OnboardingCompleteScreen from '@features/screens/onboardingCompleteScreen'; CompleteProfileScreen,
OnboardingCompleteScreen,
} from '@features/screens';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';
const Stack = createNativeStackNavigator<AuthStackParamList>(); const Stack = createNativeStackNavigator<AuthStackParamList>();
@ -15,9 +17,18 @@ const AuthStack: React.FC = () => {
<Stack.Navigator screenOptions={{ headerShown: false }}> <Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name={RouteNames.Login} component={LoginScreen} /> <Stack.Screen name={RouteNames.Login} component={LoginScreen} />
<Stack.Screen name={RouteNames.Otp} component={OtpScreen} /> <Stack.Screen name={RouteNames.Otp} component={OtpScreen} />
<Stack.Screen name={RouteNames.SetLocation} component={SetLocationScreen} /> <Stack.Screen
<Stack.Screen name={RouteNames.CompleteProfile} component={CompleteProfileScreen} /> name={RouteNames.SetLocation}
<Stack.Screen name={RouteNames.OnboardingComplete} component={OnboardingCompleteScreen} /> component={SetLocationScreen}
/>
<Stack.Screen
name={RouteNames.CompleteProfile}
component={CompleteProfileScreen}
/>
<Stack.Screen
name={RouteNames.OnboardingComplete}
component={OnboardingCompleteScreen}
/>
</Stack.Navigator> </Stack.Navigator>
); );
}; };

View File

@ -1,11 +1,13 @@
import React from 'react'; import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { BottomTabParamList } from '@navigation/navigationTypes'; import { BottomTabParamList } from '@navigation/navigationTypes';
import DashboardScreen from '@features/screens/dashboardScreen'; import {
import EarningsScreen from '@features/screens/earningsScreen'; DashboardScreen,
import JobsScreen from '@features/screens/jobsScreen'; EarningsScreen,
import InboxScreen from '@features/screens/inboxScreen/inboxScreen'; JobsScreen,
import ProfileScreen from '@features/screens/profileScreen/profileScreen'; InboxScreen,
ProfileScreen,
} from '@features/screens';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';

View File

@ -25,7 +25,7 @@ class LocationService {
// Mocked location resolver // Mocked location resolver
async getCurrentLocation(): Promise<{ coordinates: Coordinates; address: string; city: string; state: string; pincode: string }> { async getCurrentLocation(): Promise<{ coordinates: Coordinates; address: string; city: string; state: string; pincode: string }> {
// Return mock coordinates and address after a delay // Return mock coordinates and address after a delay
await new Promise(resolve => setTimeout(resolve, 800)); await new Promise<void>(resolve => setTimeout(resolve, 800));
return { return {
coordinates: { coordinates: {
latitude: 12.9716, latitude: 12.9716,

View File

@ -7,7 +7,7 @@ type Callback = (data: any) => void;
class MockSocketService { class MockSocketService {
private isConnected = false; private isConnected = false;
private listeners: Record<string, Callback[]> = {}; private listeners: Record<string, Callback[]> = {};
private mockJobInterval: NodeJS.Timeout | null = null; private mockJobInterval: ReturnType<typeof setInterval> | null = null;
private token: string | null = null; private token: string | null = null;
connect(token: string) { connect(token: string) {

View File

@ -8,21 +8,19 @@ const KEYS = {
class StorageService { class StorageService {
async saveToken(token: string, partnerId: string): Promise<void> { async saveToken(token: string, partnerId: string): Promise<void> {
await AsyncStorage.multiSet([ await AsyncStorage.setItem(KEYS.TOKEN, token);
[KEYS.TOKEN, token], await AsyncStorage.setItem(KEYS.PARTNER_ID, partnerId);
[KEYS.PARTNER_ID, partnerId],
]);
} }
async getToken(): Promise<{ token: string | null; partnerId: string | null }> { async getToken(): Promise<{ token: string | null; partnerId: string | null }> {
const values = await AsyncStorage.multiGet([KEYS.TOKEN, KEYS.PARTNER_ID]); const token = await AsyncStorage.getItem(KEYS.TOKEN);
const token = values[0][1]; const partnerId = await AsyncStorage.getItem(KEYS.PARTNER_ID);
const partnerId = values[1][1];
return { token, partnerId }; return { token, partnerId };
} }
async removeToken(): Promise<void> { async removeToken(): Promise<void> {
await AsyncStorage.multiRemove([KEYS.TOKEN, KEYS.PARTNER_ID]); await AsyncStorage.removeItem(KEYS.TOKEN);
await AsyncStorage.removeItem(KEYS.PARTNER_ID);
} }
async setOnboardingComplete(complete: boolean): Promise<void> { async setOnboardingComplete(complete: boolean): Promise<void> {

View File

@ -31,7 +31,7 @@ export const authApi = createApi({
endpoints: (builder) => ({ endpoints: (builder) => ({
sendOtp: builder.mutation<{ requestId: string }, { phone: string }>({ sendOtp: builder.mutation<{ requestId: string }, { phone: string }>({
queryFn: async ({ phone: _phone }) => { queryFn: async ({ phone: _phone }) => {
await new Promise((r) => setTimeout(r, 1000)); await new Promise<void>((r) => setTimeout(r, 1000));
return { data: mockSendOtpResponse }; return { data: mockSendOtpResponse };
}, },
}), }),
@ -40,7 +40,7 @@ export const authApi = createApi({
{ requestId: string; otp: string } { requestId: string; otp: string }
>({ >({
queryFn: async ({ otp }) => { queryFn: async ({ otp }) => {
await new Promise((r) => setTimeout(r, 1000)); await new Promise<void>((r) => setTimeout(r, 1000));
if (otp === '000000') { if (otp === '000000') {
return { error: { status: 'CUSTOM_ERROR', error: 'Invalid OTP' } }; return { error: { status: 'CUSTOM_ERROR', error: 'Invalid OTP' } };
} }
@ -49,7 +49,7 @@ export const authApi = createApi({
}), }),
getProfile: builder.query<PartnerProfile, string>({ getProfile: builder.query<PartnerProfile, string>({
queryFn: async (_partnerId) => { queryFn: async (_partnerId) => {
await new Promise((r) => setTimeout(r, 500)); await new Promise<void>((r) => setTimeout(r, 500));
return { data: mockProfile }; return { data: mockProfile };
}, },
}), }),
@ -58,7 +58,7 @@ export const authApi = createApi({
Partial<PartnerProfile> Partial<PartnerProfile>
>({ >({
queryFn: async (updates) => { queryFn: async (updates) => {
await new Promise((r) => setTimeout(r, 800)); await new Promise<void>((r) => setTimeout(r, 800));
return { data: { ...mockProfile, ...updates } }; return { data: { ...mockProfile, ...updates } };
}, },
}), }),

View File

@ -8,13 +8,13 @@ export const earningsApi = createApi({
endpoints: (builder) => ({ endpoints: (builder) => ({
getTodayEarnings: builder.query<typeof mockTodayEarnings, void>({ getTodayEarnings: builder.query<typeof mockTodayEarnings, void>({
queryFn: async () => { queryFn: async () => {
await new Promise((r) => setTimeout(r, 500)); await new Promise<void>((r) => setTimeout(r, 500));
return { data: mockTodayEarnings }; return { data: mockTodayEarnings };
}, },
}), }),
getWeeklyEarnings: builder.query<DayEarning[], void>({ getWeeklyEarnings: builder.query<DayEarning[], void>({
queryFn: async () => { queryFn: async () => {
await new Promise((r) => setTimeout(r, 500)); await new Promise<void>((r) => setTimeout(r, 500));
return { data: mockWeeklyEarnings }; return { data: mockWeeklyEarnings };
}, },
}), }),

View File

@ -9,7 +9,7 @@ export const jobApi = createApi({
endpoints: (builder) => ({ endpoints: (builder) => ({
acceptJob: builder.mutation<Job, string>({ acceptJob: builder.mutation<Job, string>({
queryFn: async (jobId) => { queryFn: async (jobId) => {
await new Promise((r) => setTimeout(r, 500)); await new Promise<void>((r) => setTimeout(r, 500));
return { return {
data: { data: {
...mockJobOffer, ...mockJobOffer,
@ -24,13 +24,13 @@ export const jobApi = createApi({
}), }),
rejectJob: builder.mutation<void, string>({ rejectJob: builder.mutation<void, string>({
queryFn: async (_jobId) => { queryFn: async (_jobId) => {
await new Promise((r) => setTimeout(r, 300)); await new Promise<void>((r) => setTimeout(r, 300));
return { data: undefined }; return { data: undefined };
}, },
}), }),
confirmPickup: builder.mutation<void, string>({ confirmPickup: builder.mutation<void, string>({
queryFn: async (_jobId) => { queryFn: async (_jobId) => {
await new Promise((r) => setTimeout(r, 500)); await new Promise<void>((r) => setTimeout(r, 500));
return { data: undefined }; return { data: undefined };
}, },
}), }),
@ -39,13 +39,13 @@ export const jobApi = createApi({
{ jobId: string; rating: number } { jobId: string; rating: number }
>({ >({
queryFn: async ({ jobId: _jobId }) => { queryFn: async ({ jobId: _jobId }) => {
await new Promise((r) => setTimeout(r, 800)); await new Promise<void>((r) => setTimeout(r, 800));
return { data: { earnings: 78 } }; return { data: { earnings: 78 } };
}, },
}), }),
reportIssue: builder.mutation<void, { jobId: string; reason: string }>({ reportIssue: builder.mutation<void, { jobId: string; reason: string }>({
queryFn: async (_args) => { queryFn: async (_args) => {
await new Promise((r) => setTimeout(r, 500)); await new Promise<void>((r) => setTimeout(r, 500));
return { data: undefined }; return { data: undefined };
}, },
}), }),

View File

@ -11,10 +11,12 @@
"@theme": ["./app/theme"], "@theme": ["./app/theme"],
"@theme/*": ["./app/theme/*"], "@theme/*": ["./app/theme/*"],
"@navigation/*": ["./app/navigation/*"], "@navigation/*": ["./app/navigation/*"],
"@store": ["./app/store"],
"@store/*": ["./app/store/*"], "@store/*": ["./app/store/*"],
"@services/*": ["./app/services/*"], "@services/*": ["./app/services/*"],
"@hooks/*": ["./app/hooks/*"], "@hooks/*": ["./app/hooks/*"],
"@utils/*": ["./app/utils/*"], "@utils/*": ["./app/utils/*"],
"@icons": ["./app/components/icons"],
"@icons/*": ["./app/components/icons/*"], "@icons/*": ["./app/components/icons/*"],
"@app-types/*": ["./app/types/*"] "@app-types/*": ["./app/types/*"]
} }

1966
yarn.lock

File diff suppressed because it is too large Load Diff