feat: implement core feature screens, navigation structure, and reusable UI components for delivery partner app
This commit is contained in:
parent
a8a04a02f6
commit
72f294d700
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity } from 'react-native';
|
||||
import { CustomInputProps } from './customInput.props';
|
||||
import { getStyles } from './customInput.styles';
|
||||
import { CustomInputProps } from './CustomInput.props';
|
||||
import { getStyles } from './CustomInput.styles';
|
||||
import { useAppTheme } from '@theme';
|
||||
|
||||
export const CustomInput: React.FC<CustomInputProps> = ({
|
||||
@ -25,7 +25,13 @@ export const CustomInput: React.FC<CustomInputProps> = ({
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={[styles.inputContainer, error ? { borderColor: colors.error } : null, style]}>
|
||||
<View
|
||||
style={[
|
||||
styles.inputContainer,
|
||||
error ? { borderColor: colors.error } : null,
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholderTextColor={colors.placeholder}
|
||||
@ -36,8 +42,13 @@ export const CustomInput: React.FC<CustomInputProps> = ({
|
||||
/>
|
||||
<View style={styles.actionsRow}>
|
||||
{isPassword && (
|
||||
<TouchableOpacity onPress={() => setIsSecure(!isSecure)} activeOpacity={0.7}>
|
||||
<Text style={styles.rightAction}>{isSecure ? 'Show' : 'Hide'}</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => setIsSecure(!isSecure)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.rightAction}>
|
||||
{isSecure ? 'Show' : 'Hide'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{rightActionText && onRightActionPress && (
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './customInput';
|
||||
export * from './customInput.props';
|
||||
export * from './CustomInput';
|
||||
export * from './CustomInput.props';
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { TouchableOpacityProps } from 'react-native';
|
||||
import { TouchableOpacityProps, StyleProp, TextStyle } from 'react-native';
|
||||
|
||||
export interface PrimaryButtonProps extends TouchableOpacityProps {
|
||||
title: string;
|
||||
isLoading?: boolean;
|
||||
disabled?: boolean;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { TouchableOpacity, Text, ActivityIndicator } from 'react-native';
|
||||
import { PrimaryButtonProps } from './primaryButton.props';
|
||||
import { styles } from './primaryButton.styles';
|
||||
import { PrimaryButtonProps } from './PrimaryButton.props';
|
||||
import { styles } from './PrimaryButton.styles';
|
||||
import { colors } from '@theme';
|
||||
|
||||
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
||||
@ -9,6 +9,7 @@ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
||||
isLoading = false,
|
||||
disabled = false,
|
||||
style,
|
||||
textStyle,
|
||||
...rest
|
||||
}) => {
|
||||
const isDisabled = disabled || isLoading;
|
||||
@ -23,7 +24,7 @@ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
||||
{isLoading ? (
|
||||
<ActivityIndicator color={colors.white} />
|
||||
) : (
|
||||
<Text style={styles.text}>{title}</Text>
|
||||
<Text style={[styles.text, textStyle]}>{title}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './primaryButton';
|
||||
export * from './primaryButton.props';
|
||||
export * from './PrimaryButton';
|
||||
export * from './PrimaryButton.props';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { TouchableOpacity, Text } from 'react-native';
|
||||
import { SocialButtonProps } from './socialButton.props';
|
||||
import { getStyles } from './socialButton.styles';
|
||||
import { SocialButtonProps } from './SocialButton.props';
|
||||
import { getStyles } from './SocialButton.styles';
|
||||
import { useAppTheme } from '@theme';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
|
||||
@ -26,7 +26,7 @@ const GoogleIcon = ({ style }: { style: any }) => (
|
||||
</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}>
|
||||
<Path
|
||||
fill={color}
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './socialButton';
|
||||
export * from './socialButton.props';
|
||||
export * from './SocialButton';
|
||||
export * from './SocialButton.props';
|
||||
|
||||
@ -1,3 +1,22 @@
|
||||
export * from './customInput';
|
||||
export * from './primaryButton';
|
||||
export * from './socialButton';
|
||||
export * from './CustomInput';
|
||||
export * from './PrimaryButton';
|
||||
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';
|
||||
|
||||
@ -4,7 +4,11 @@ import { typography, borderRadius } from '@theme';
|
||||
export const getStyles = (colors: any) =>
|
||||
StyleSheet.create({
|
||||
container: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
||||
@ -11,7 +11,11 @@ export const getStyles = (colors: any) =>
|
||||
alignItems: 'center',
|
||||
},
|
||||
map: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
mockOverlay: {
|
||||
position: 'absolute',
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export * from './toast';
|
||||
export { default } from './toast';
|
||||
|
||||
@ -8,29 +8,141 @@ import {
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { getStyles } from './loginScreen.styles';
|
||||
import { CustomInput, PrimaryButton, SocialButton } from '@components';
|
||||
import Svg, { Circle, Rect, Path, G } from 'react-native-svg';
|
||||
import { getStyles } from './LoginScreen.styles';
|
||||
import { CustomInput, PrimaryButton } from '@components';
|
||||
import { useAppTheme } from '@theme';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||
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 = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||
|
||||
const [mobileNumber, setMobileNumber] = useState('');
|
||||
// const [password, setPassword] = useState('');
|
||||
// const [rememberMe, setRememberMe] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errors, setErrors] = useState<{ mobile?: string; password?: string }>(
|
||||
{},
|
||||
);
|
||||
const [errors, setErrors] = useState<{ mobile?: string }>({});
|
||||
|
||||
const validate = () => {
|
||||
const newErrors: { mobile?: string; password?: string } = {};
|
||||
const newErrors: { mobile?: string } = {};
|
||||
if (!mobileNumber) {
|
||||
newErrors.mobile = 'Mobile number is required';
|
||||
} else if (mobileNumber.length < 9) {
|
||||
@ -39,8 +151,8 @@ export const LoginScreen: React.FC = () => {
|
||||
|
||||
// if (!password) {
|
||||
// newErrors.password = 'Password is required';
|
||||
// } else if (password.length < 6) {
|
||||
// newErrors.password = 'Password must be at least 6 characters';
|
||||
// } else if (password.length < 4) {
|
||||
// newErrors.password = 'Password must be at least 4 characters';
|
||||
// }
|
||||
|
||||
setErrors(newErrors);
|
||||
@ -58,16 +170,12 @@ export const LoginScreen: React.FC = () => {
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleSocialLogin = (provider: 'google' | 'apple') => {
|
||||
Alert.alert('Social Login', `Initiating ${provider} login...`);
|
||||
};
|
||||
|
||||
// const handleForgotPassword = () => {
|
||||
// Alert.alert('Forgot Password', 'Redirecting to reset password...');
|
||||
// Alert.alert('Forgot Password', 'OTP verification will be sent to recover password.');
|
||||
// };
|
||||
|
||||
const handleSignUp = () => {
|
||||
Alert.alert('Sign Up', 'Redirecting to registration...');
|
||||
Alert.alert('Sign Up', 'Redirecting to register partner profile...');
|
||||
};
|
||||
|
||||
return (
|
||||
@ -79,8 +187,10 @@ export const LoginScreen: React.FC = () => {
|
||||
contentContainerStyle={styles.scrollContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<DeliveryScooterSvg />
|
||||
|
||||
<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>
|
||||
</View>
|
||||
|
||||
@ -99,46 +209,18 @@ export const LoginScreen: React.FC = () => {
|
||||
|
||||
{/* <CustomInput
|
||||
label="Password"
|
||||
placeholder="••••••••"
|
||||
placeholder="Enter password"
|
||||
isPassword
|
||||
rightActionText="Forgot?"
|
||||
rightActionText="Forgot Password?"
|
||||
onRightActionPress={handleForgotPassword}
|
||||
value={password}
|
||||
onChangeText={(text) => {
|
||||
onChangeText={text => {
|
||||
setPassword(text);
|
||||
if (errors.password) setErrors({ ...errors, password: undefined });
|
||||
}}
|
||||
error={errors.password}
|
||||
/> */}
|
||||
|
||||
{/* <View style={styles.row}>
|
||||
<TouchableOpacity
|
||||
style={styles.rememberMeContainer}
|
||||
onPress={() => setRememberMe(!rememberMe)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<CheckBox
|
||||
value={rememberMe}
|
||||
onValueChange={setRememberMe}
|
||||
tintColors={{
|
||||
true: colors.primary,
|
||||
false: colors.textSecondary,
|
||||
}}
|
||||
tintColor={colors.textSecondary}
|
||||
onTintColor={colors.primary}
|
||||
onCheckColor={colors.white}
|
||||
onFillColor={colors.primary}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={styles.checkboxText}>Remember me</Text>
|
||||
</TouchableOpacity>
|
||||
</View> */}
|
||||
<View style={styles.otpWrapper}>
|
||||
<Text style={styles.otpText}>
|
||||
OTP will send on this number
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<PrimaryButton
|
||||
title="Login"
|
||||
onPress={handleLogin}
|
||||
@ -146,23 +228,6 @@ export const LoginScreen: React.FC = () => {
|
||||
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}>
|
||||
<Text style={styles.footerText}>
|
||||
Don’t have an account?{' '}
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
export { default } from './loginScreen';
|
||||
export * from './loginScreen';
|
||||
export * from './loginScreen.styles';
|
||||
export * from './LoginScreen';
|
||||
export * from './LoginScreen.styles';
|
||||
|
||||
69
app/features/screens/OtpScreen/OtpScreen.styles.ts
Normal file
69
app/features/screens/OtpScreen/OtpScreen.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
@ -1,32 +1,115 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { CustomInput, PrimaryButton } from '@components';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||
import { OtpInput, NumericKeypad, PrimaryButton } from '@components';
|
||||
import { useAppTheme } from '@theme';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||
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 styles = getStyles(colors);
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||
|
||||
const phone = route?.params?.phone || '+91 98765 43210';
|
||||
const [otp, setOtp] = useState('');
|
||||
const handleVerify = () => {
|
||||
navigation.navigate(RouteNames.SetLocation);
|
||||
const [timer, setTimer] = useState(30);
|
||||
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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}
|
||||
>
|
||||
<Text style={styles.title}>Enter OTP</Text>
|
||||
<CustomInput label="OTP" value={otp} onChangeText={setOtp} keyboardType="numeric" />
|
||||
<PrimaryButton title="Verify" onPress={handleVerify} />
|
||||
<View style={styles.container}>
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer} bounces={false}>
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.title}>Verify Phone</Text>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', padding: 20 },
|
||||
title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
|
||||
});
|
||||
|
||||
export default OtpScreen;
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export { default } from './otpScreen';
|
||||
export * from './otpScreen';
|
||||
export * from './OtpScreen';
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,48 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Arrived At Customer Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default ArrivedAtCustomerScreen;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,48 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Arrived At Store Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default ArrivedAtStoreScreen;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,31 +1,151 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||
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 { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
import { getStyles } from './completeProfileScreen.styles';
|
||||
|
||||
const CompleteProfileScreen: React.FC = () => {
|
||||
export const CompleteProfileScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text style={styles.title}>Complete Profile Screen</Text>
|
||||
<PrimaryButton
|
||||
title="Continue"
|
||||
onPress={() => navigation.navigate(RouteNames.OnboardingComplete)}
|
||||
style={{ marginTop: 20, width: '90%' }}
|
||||
<View style={styles.container}>
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.title}>Complete Profile</Text>
|
||||
<Text style={styles.subtitle}>Enter details & upload documents to get verified</Text>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
title: { fontSize: 20 },
|
||||
});
|
||||
|
||||
export default CompleteProfileScreen;
|
||||
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,118 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Confirm Pickup Screen</Text>
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: "center", alignItems: "center" },
|
||||
});
|
||||
|
||||
export default ConfirmPickupScreen;
|
||||
|
||||
266
app/features/screens/dashboardScreen/dashboardScreen.styles.ts
Normal file
266
app/features/screens/dashboardScreen/dashboardScreen.styles.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
@ -1,14 +1,332 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
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}>
|
||||
<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>
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
{/* Developer Simulation trigger */}
|
||||
<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;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,87 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, ScrollView, Alert } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Deliver Order Screen</Text>
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default DeliverOrderScreen;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,88 @@
|
||||
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 { 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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Delivery Completed Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default DeliveryCompletedScreen;
|
||||
|
||||
@ -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',
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,89 @@
|
||||
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 { 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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Documents Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default DocumentsScreen;
|
||||
|
||||
133
app/features/screens/earningsScreen/earningsScreen.styles.ts
Normal file
133
app/features/screens/earningsScreen/earningsScreen.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,103 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text, ScrollView } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Earnings Screen</Text>
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default EarningsScreen;
|
||||
|
||||
81
app/features/screens/inboxScreen/inboxScreen.styles.ts
Normal file
81
app/features/screens/inboxScreen/inboxScreen.styles.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
@ -1,25 +1,87 @@
|
||||
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 { PersonIcon } from '@icons';
|
||||
import { getStyles } from './inboxScreen.styles';
|
||||
|
||||
const InboxScreen: React.FC = () => {
|
||||
export const InboxScreen: React.FC = () => {
|
||||
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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }] }>
|
||||
<Text style={styles.screenTitle}>Inbox Screen</Text>
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
screenTitle: {
|
||||
fontSize: 18,
|
||||
},
|
||||
});
|
||||
|
||||
export default InboxScreen;
|
||||
|
||||
@ -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';
|
||||
|
||||
67
app/features/screens/jobsScreen/jobsScreen.styles.ts
Normal file
67
app/features/screens/jobsScreen/jobsScreen.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
@ -1,19 +1,140 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, ScrollView, TouchableOpacity } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text style={styles.title}>Jobs Screen</Text>
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
title: { fontSize: 20 },
|
||||
});
|
||||
|
||||
export default JobsScreen;
|
||||
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,69 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text, Alert } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Live Tracking Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default LiveTrackingScreen;
|
||||
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,111 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Alert } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>New Job Request Screen</Text>
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default NewJobRequestScreen;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,30 +1,54 @@
|
||||
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 { PrimaryButton } from '@components';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { RootStackParamList } from '@navigation/navigationTypes';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
import { getStyles } from './onboardingCompleteScreen.styles';
|
||||
|
||||
const OnboardingCompleteScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text style={styles.title}>Onboarding Complete Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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
|
||||
title="Get Started"
|
||||
onPress={() => navigation.navigate('App', { screen: 'BottomTabs' })}
|
||||
style={{ marginTop: 20, width: '90%' }}
|
||||
onPress={() =>
|
||||
navigation.navigate('App', {
|
||||
screen: RouteNames.BottomTabs,
|
||||
params: { screen: RouteNames.Dashboard },
|
||||
})
|
||||
}
|
||||
style={styles.startButton}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
title: { fontSize: 20 },
|
||||
});
|
||||
|
||||
export default OnboardingCompleteScreen;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,56 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Order Accepted Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
});
|
||||
|
||||
export default OrderAcceptedScreen;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,18 +1,51 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text } from 'react-native';
|
||||
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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Text>Order Picked Up Screen</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: "center", alignItems: "center" },
|
||||
});
|
||||
|
||||
export default OrderPickedUpScreen;
|
||||
|
||||
106
app/features/screens/profileScreen/profileScreen.styles.ts
Normal file
106
app/features/screens/profileScreen/profileScreen.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
@ -1,26 +1,107 @@
|
||||
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 { 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 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 (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }] }>
|
||||
<Text style={styles.screenTitle}>Profile Screen</Text>
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
},
|
||||
screenTitle: {
|
||||
fontSize: 24,
|
||||
},
|
||||
});
|
||||
|
||||
export default ProfileScreen;
|
||||
|
||||
@ -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%',
|
||||
},
|
||||
});
|
||||
@ -1,31 +1,57 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { View, Text } from 'react-native';
|
||||
import { useAppTheme } from '@theme';
|
||||
import { PrimaryButton } from '@components';
|
||||
import { LocationPinIcon } from '@icons';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
import { getStyles } from './setLocationScreen.styles';
|
||||
|
||||
const SetLocationScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: colors.background }] }>
|
||||
<Text style={styles.title}>Set Location</Text>
|
||||
<View style={styles.container}>
|
||||
{/* 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
|
||||
title="Continue"
|
||||
title="Confirm Location"
|
||||
onPress={() => navigation.navigate(RouteNames.CompleteProfile)}
|
||||
style={{ marginTop: 20, width: '90%' }}
|
||||
style={styles.confirmButton}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
title: { fontSize: 24 }
|
||||
});
|
||||
|
||||
export default SetLocationScreen;
|
||||
|
||||
@ -1 +1 @@
|
||||
export { useToast } from '@components/toast/toast';
|
||||
export { useToast } from '@components';
|
||||
|
||||
@ -2,16 +2,18 @@ import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||
import BottomTabNavigator from './bottomTabNavigator';
|
||||
import NewJobRequestScreen from '@features/screens/newJobRequestScreen/newJobRequestScreen';
|
||||
import OrderAcceptedScreen from '@features/screens/orderAcceptedScreen/orderAcceptedScreen';
|
||||
import ArrivedAtStoreScreen from '@features/screens/arrivedAtStoreScreen/arrivedAtStoreScreen';
|
||||
import ConfirmPickupScreen from '@features/screens/confirmPickupScreen/confirmPickupScreen';
|
||||
import OrderPickedUpScreen from '@features/screens/orderPickedUpScreen/orderPickedUpScreen';
|
||||
import LiveTrackingScreen from '@features/screens/liveTrackingScreen/liveTrackingScreen';
|
||||
import ArrivedAtCustomerScreen from '@features/screens/arrivedAtCustomerScreen/arrivedAtCustomerScreen';
|
||||
import DeliverOrderScreen from '@features/screens/deliverOrderScreen/deliverOrderScreen';
|
||||
import DeliveryCompletedScreen from '@features/screens/deliveryCompletedScreen/deliveryCompletedScreen';
|
||||
import DocumentsScreen from '@features/screens/documentsScreen/documentsScreen';
|
||||
import {
|
||||
NewJobRequestScreen,
|
||||
OrderAcceptedScreen,
|
||||
ArrivedAtStoreScreen,
|
||||
ConfirmPickupScreen,
|
||||
OrderPickedUpScreen,
|
||||
LiveTrackingScreen,
|
||||
ArrivedAtCustomerScreen,
|
||||
DeliverOrderScreen,
|
||||
DeliveryCompletedScreen,
|
||||
DocumentsScreen,
|
||||
} from '@features/screens';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
|
||||
const Stack = createNativeStackNavigator<AppStackParamList>();
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||
import LoginScreen from '@features/screens/loginScreen';
|
||||
import OtpScreen from '@features/screens/otpScreen';
|
||||
import SetLocationScreen from '@features/screens/setLocationScreen';
|
||||
import CompleteProfileScreen from '@features/screens/completeProfileScreen';
|
||||
import OnboardingCompleteScreen from '@features/screens/onboardingCompleteScreen';
|
||||
import {
|
||||
LoginScreen,
|
||||
OtpScreen,
|
||||
SetLocationScreen,
|
||||
CompleteProfileScreen,
|
||||
OnboardingCompleteScreen,
|
||||
} from '@features/screens';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
|
||||
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
||||
@ -15,9 +17,18 @@ const AuthStack: React.FC = () => {
|
||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name={RouteNames.Login} component={LoginScreen} />
|
||||
<Stack.Screen name={RouteNames.Otp} component={OtpScreen} />
|
||||
<Stack.Screen name={RouteNames.SetLocation} component={SetLocationScreen} />
|
||||
<Stack.Screen name={RouteNames.CompleteProfile} component={CompleteProfileScreen} />
|
||||
<Stack.Screen name={RouteNames.OnboardingComplete} component={OnboardingCompleteScreen} />
|
||||
<Stack.Screen
|
||||
name={RouteNames.SetLocation}
|
||||
component={SetLocationScreen}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name={RouteNames.CompleteProfile}
|
||||
component={CompleteProfileScreen}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name={RouteNames.OnboardingComplete}
|
||||
component={OnboardingCompleteScreen}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { BottomTabParamList } from '@navigation/navigationTypes';
|
||||
import DashboardScreen from '@features/screens/dashboardScreen';
|
||||
import EarningsScreen from '@features/screens/earningsScreen';
|
||||
import JobsScreen from '@features/screens/jobsScreen';
|
||||
import InboxScreen from '@features/screens/inboxScreen/inboxScreen';
|
||||
import ProfileScreen from '@features/screens/profileScreen/profileScreen';
|
||||
import {
|
||||
DashboardScreen,
|
||||
EarningsScreen,
|
||||
JobsScreen,
|
||||
InboxScreen,
|
||||
ProfileScreen,
|
||||
} from '@features/screens';
|
||||
import { useAppTheme } from '@theme';
|
||||
import { RouteNames } from '@utils/constants';
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ class LocationService {
|
||||
// Mocked location resolver
|
||||
async getCurrentLocation(): Promise<{ coordinates: Coordinates; address: string; city: string; state: string; pincode: string }> {
|
||||
// Return mock coordinates and address after a delay
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 800));
|
||||
return {
|
||||
coordinates: {
|
||||
latitude: 12.9716,
|
||||
|
||||
@ -7,7 +7,7 @@ type Callback = (data: any) => void;
|
||||
class MockSocketService {
|
||||
private isConnected = false;
|
||||
private listeners: Record<string, Callback[]> = {};
|
||||
private mockJobInterval: NodeJS.Timeout | null = null;
|
||||
private mockJobInterval: ReturnType<typeof setInterval> | null = null;
|
||||
private token: string | null = null;
|
||||
|
||||
connect(token: string) {
|
||||
|
||||
@ -8,21 +8,19 @@ const KEYS = {
|
||||
|
||||
class StorageService {
|
||||
async saveToken(token: string, partnerId: string): Promise<void> {
|
||||
await AsyncStorage.multiSet([
|
||||
[KEYS.TOKEN, token],
|
||||
[KEYS.PARTNER_ID, partnerId],
|
||||
]);
|
||||
await AsyncStorage.setItem(KEYS.TOKEN, token);
|
||||
await AsyncStorage.setItem(KEYS.PARTNER_ID, partnerId);
|
||||
}
|
||||
|
||||
async getToken(): Promise<{ token: string | null; partnerId: string | null }> {
|
||||
const values = await AsyncStorage.multiGet([KEYS.TOKEN, KEYS.PARTNER_ID]);
|
||||
const token = values[0][1];
|
||||
const partnerId = values[1][1];
|
||||
const token = await AsyncStorage.getItem(KEYS.TOKEN);
|
||||
const partnerId = await AsyncStorage.getItem(KEYS.PARTNER_ID);
|
||||
return { token, partnerId };
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
@ -31,7 +31,7 @@ export const authApi = createApi({
|
||||
endpoints: (builder) => ({
|
||||
sendOtp: builder.mutation<{ requestId: string }, { phone: string }>({
|
||||
queryFn: async ({ phone: _phone }) => {
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
await new Promise<void>((r) => setTimeout(r, 1000));
|
||||
return { data: mockSendOtpResponse };
|
||||
},
|
||||
}),
|
||||
@ -40,7 +40,7 @@ export const authApi = createApi({
|
||||
{ requestId: string; otp: string }
|
||||
>({
|
||||
queryFn: async ({ otp }) => {
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
await new Promise<void>((r) => setTimeout(r, 1000));
|
||||
if (otp === '000000') {
|
||||
return { error: { status: 'CUSTOM_ERROR', error: 'Invalid OTP' } };
|
||||
}
|
||||
@ -49,7 +49,7 @@ export const authApi = createApi({
|
||||
}),
|
||||
getProfile: builder.query<PartnerProfile, string>({
|
||||
queryFn: async (_partnerId) => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
await new Promise<void>((r) => setTimeout(r, 500));
|
||||
return { data: mockProfile };
|
||||
},
|
||||
}),
|
||||
@ -58,7 +58,7 @@ export const authApi = createApi({
|
||||
Partial<PartnerProfile>
|
||||
>({
|
||||
queryFn: async (updates) => {
|
||||
await new Promise((r) => setTimeout(r, 800));
|
||||
await new Promise<void>((r) => setTimeout(r, 800));
|
||||
return { data: { ...mockProfile, ...updates } };
|
||||
},
|
||||
}),
|
||||
|
||||
@ -8,13 +8,13 @@ export const earningsApi = createApi({
|
||||
endpoints: (builder) => ({
|
||||
getTodayEarnings: builder.query<typeof mockTodayEarnings, void>({
|
||||
queryFn: async () => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
await new Promise<void>((r) => setTimeout(r, 500));
|
||||
return { data: mockTodayEarnings };
|
||||
},
|
||||
}),
|
||||
getWeeklyEarnings: builder.query<DayEarning[], void>({
|
||||
queryFn: async () => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
await new Promise<void>((r) => setTimeout(r, 500));
|
||||
return { data: mockWeeklyEarnings };
|
||||
},
|
||||
}),
|
||||
|
||||
@ -9,7 +9,7 @@ export const jobApi = createApi({
|
||||
endpoints: (builder) => ({
|
||||
acceptJob: builder.mutation<Job, string>({
|
||||
queryFn: async (jobId) => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
await new Promise<void>((r) => setTimeout(r, 500));
|
||||
return {
|
||||
data: {
|
||||
...mockJobOffer,
|
||||
@ -24,13 +24,13 @@ export const jobApi = createApi({
|
||||
}),
|
||||
rejectJob: builder.mutation<void, string>({
|
||||
queryFn: async (_jobId) => {
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
await new Promise<void>((r) => setTimeout(r, 300));
|
||||
return { data: undefined };
|
||||
},
|
||||
}),
|
||||
confirmPickup: builder.mutation<void, string>({
|
||||
queryFn: async (_jobId) => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
await new Promise<void>((r) => setTimeout(r, 500));
|
||||
return { data: undefined };
|
||||
},
|
||||
}),
|
||||
@ -39,13 +39,13 @@ export const jobApi = createApi({
|
||||
{ jobId: string; rating: number }
|
||||
>({
|
||||
queryFn: async ({ jobId: _jobId }) => {
|
||||
await new Promise((r) => setTimeout(r, 800));
|
||||
await new Promise<void>((r) => setTimeout(r, 800));
|
||||
return { data: { earnings: 78 } };
|
||||
},
|
||||
}),
|
||||
reportIssue: builder.mutation<void, { jobId: string; reason: string }>({
|
||||
queryFn: async (_args) => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
await new Promise<void>((r) => setTimeout(r, 500));
|
||||
return { data: undefined };
|
||||
},
|
||||
}),
|
||||
|
||||
@ -11,10 +11,12 @@
|
||||
"@theme": ["./app/theme"],
|
||||
"@theme/*": ["./app/theme/*"],
|
||||
"@navigation/*": ["./app/navigation/*"],
|
||||
"@store": ["./app/store"],
|
||||
"@store/*": ["./app/store/*"],
|
||||
"@services/*": ["./app/services/*"],
|
||||
"@hooks/*": ["./app/hooks/*"],
|
||||
"@utils/*": ["./app/utils/*"],
|
||||
"@icons": ["./app/components/icons"],
|
||||
"@icons/*": ["./app/components/icons/*"],
|
||||
"@app-types/*": ["./app/types/*"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user