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 React, { useState } from 'react';
|
||||||
import { View, Text, TextInput, TouchableOpacity } from 'react-native';
|
import { View, Text, TextInput, TouchableOpacity } from 'react-native';
|
||||||
import { CustomInputProps } from './customInput.props';
|
import { CustomInputProps } from './CustomInput.props';
|
||||||
import { getStyles } from './customInput.styles';
|
import { getStyles } from './CustomInput.styles';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
|
||||||
export const CustomInput: React.FC<CustomInputProps> = ({
|
export const CustomInput: React.FC<CustomInputProps> = ({
|
||||||
@ -25,7 +25,13 @@ export const CustomInput: React.FC<CustomInputProps> = ({
|
|||||||
<Text style={styles.label}>{label}</Text>
|
<Text style={styles.label}>{label}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View style={[styles.inputContainer, error ? { borderColor: colors.error } : null, style]}>
|
<View
|
||||||
|
style={[
|
||||||
|
styles.inputContainer,
|
||||||
|
error ? { borderColor: colors.error } : null,
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
placeholderTextColor={colors.placeholder}
|
placeholderTextColor={colors.placeholder}
|
||||||
@ -36,8 +42,13 @@ export const CustomInput: React.FC<CustomInputProps> = ({
|
|||||||
/>
|
/>
|
||||||
<View style={styles.actionsRow}>
|
<View style={styles.actionsRow}>
|
||||||
{isPassword && (
|
{isPassword && (
|
||||||
<TouchableOpacity onPress={() => setIsSecure(!isSecure)} activeOpacity={0.7}>
|
<TouchableOpacity
|
||||||
<Text style={styles.rightAction}>{isSecure ? 'Show' : 'Hide'}</Text>
|
onPress={() => setIsSecure(!isSecure)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={styles.rightAction}>
|
||||||
|
{isSecure ? 'Show' : 'Hide'}
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
{rightActionText && onRightActionPress && (
|
{rightActionText && onRightActionPress && (
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from './customInput';
|
export * from './CustomInput';
|
||||||
export * from './customInput.props';
|
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 {
|
export interface PrimaryButtonProps extends TouchableOpacityProps {
|
||||||
title: string;
|
title: string;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
textStyle?: StyleProp<TextStyle>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TouchableOpacity, Text, ActivityIndicator } from 'react-native';
|
import { TouchableOpacity, Text, ActivityIndicator } from 'react-native';
|
||||||
import { PrimaryButtonProps } from './primaryButton.props';
|
import { PrimaryButtonProps } from './PrimaryButton.props';
|
||||||
import { styles } from './primaryButton.styles';
|
import { styles } from './PrimaryButton.styles';
|
||||||
import { colors } from '@theme';
|
import { colors } from '@theme';
|
||||||
|
|
||||||
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
||||||
@ -9,6 +9,7 @@ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
|||||||
isLoading = false,
|
isLoading = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
style,
|
style,
|
||||||
|
textStyle,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const isDisabled = disabled || isLoading;
|
const isDisabled = disabled || isLoading;
|
||||||
@ -23,7 +24,7 @@ export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<ActivityIndicator color={colors.white} />
|
<ActivityIndicator color={colors.white} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.text}>{title}</Text>
|
<Text style={[styles.text, textStyle]}>{title}</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from './primaryButton';
|
export * from './PrimaryButton';
|
||||||
export * from './primaryButton.props';
|
export * from './PrimaryButton.props';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TouchableOpacity, Text } from 'react-native';
|
import { TouchableOpacity, Text } from 'react-native';
|
||||||
import { SocialButtonProps } from './socialButton.props';
|
import { SocialButtonProps } from './SocialButton.props';
|
||||||
import { getStyles } from './socialButton.styles';
|
import { getStyles } from './SocialButton.styles';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import Svg, { Path } from 'react-native-svg';
|
import Svg, { Path } from 'react-native-svg';
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ const GoogleIcon = ({ style }: { style: any }) => (
|
|||||||
</Svg>
|
</Svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AppleIcon = ({ style, color }: { style: any, color: string }) => (
|
const AppleIcon = ({ style, color }: { style: any; color: string }) => (
|
||||||
<Svg width={24} height={24} viewBox="0 0 24 24" style={style}>
|
<Svg width={24} height={24} viewBox="0 0 24 24" style={style}>
|
||||||
<Path
|
<Path
|
||||||
fill={color}
|
fill={color}
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from './socialButton';
|
export * from './SocialButton';
|
||||||
export * from './socialButton.props';
|
export * from './SocialButton.props';
|
||||||
|
|||||||
@ -1,3 +1,22 @@
|
|||||||
export * from './customInput';
|
export * from './CustomInput';
|
||||||
export * from './primaryButton';
|
export * from './PrimaryButton';
|
||||||
export * from './socialButton';
|
export * from './SocialButton';
|
||||||
|
export * from './badge/badge';
|
||||||
|
export * from './card/card';
|
||||||
|
export * from './chipSelector/chipSelector';
|
||||||
|
export * from './countdownTimer/countdownTimer';
|
||||||
|
export * from './divider/divider';
|
||||||
|
export * from './emptyState/emptyState';
|
||||||
|
export * from './jobStatusBanner/jobStatusBanner';
|
||||||
|
export * from './loadingOverlay/loadingOverlay';
|
||||||
|
export * from './locationRow/locationRow';
|
||||||
|
export * from './mapBottomSheet/mapBottomSheet';
|
||||||
|
export * from './mapView/mapView';
|
||||||
|
export * from './numericKeypad/numericKeypad';
|
||||||
|
export * from './onlineToggleButton/onlineToggleButton';
|
||||||
|
export * from './orderCard/orderCard';
|
||||||
|
export * from './otpInput/otpInput';
|
||||||
|
export * from './screenHeader/screenHeader';
|
||||||
|
export * from './sectionHeader/sectionHeader';
|
||||||
|
export * from './statusBadge/statusBadge';
|
||||||
|
export * from './toast/toast';
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import { typography, borderRadius } from '@theme';
|
|||||||
export const getStyles = (colors: any) =>
|
export const getStyles = (colors: any) =>
|
||||||
StyleSheet.create({
|
StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
...StyleSheet.absoluteFillObject,
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@ -11,7 +11,11 @@ export const getStyles = (colors: any) =>
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
map: {
|
map: {
|
||||||
...StyleSheet.absoluteFillObject,
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
},
|
},
|
||||||
mockOverlay: {
|
mockOverlay: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
export * from './toast';
|
export * from './toast';
|
||||||
export { default } from './toast';
|
|
||||||
|
|||||||
@ -8,29 +8,141 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Alert,
|
Alert,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { getStyles } from './loginScreen.styles';
|
import Svg, { Circle, Rect, Path, G } from 'react-native-svg';
|
||||||
import { CustomInput, PrimaryButton, SocialButton } from '@components';
|
import { getStyles } from './LoginScreen.styles';
|
||||||
|
import { CustomInput, PrimaryButton } from '@components';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
|
||||||
|
// Premium SVG Mascot: Delivery boy on scooter
|
||||||
|
const DeliveryScooterSvg = () => {
|
||||||
|
return (
|
||||||
|
<View style={{ alignItems: 'center', marginVertical: 20 }}>
|
||||||
|
<Svg width="160" height="130" viewBox="0 0 160 130" fill="none">
|
||||||
|
{/* Background Accent Circles */}
|
||||||
|
<Circle cx="80" cy="65" r="55" fill="#E8F5EE" />
|
||||||
|
<Circle cx="120" cy="35" r="15" fill="#C8E6C9" opacity="0.6" />
|
||||||
|
|
||||||
|
{/* Scooter Wheels */}
|
||||||
|
<Circle
|
||||||
|
cx="45"
|
||||||
|
cy="100"
|
||||||
|
r="14"
|
||||||
|
fill="#37474F"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
strokeWidth="3"
|
||||||
|
/>
|
||||||
|
<Circle cx="45" cy="100" r="6" fill="#CFD8DC" />
|
||||||
|
<Circle
|
||||||
|
cx="115"
|
||||||
|
cy="100"
|
||||||
|
r="14"
|
||||||
|
fill="#37474F"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
strokeWidth="3"
|
||||||
|
/>
|
||||||
|
<Circle cx="115" cy="100" r="6" fill="#CFD8DC" />
|
||||||
|
|
||||||
|
{/* Scooter Body */}
|
||||||
|
{/* Main deck */}
|
||||||
|
<Path
|
||||||
|
d="M 45 100 L 115 100"
|
||||||
|
stroke="#78909C"
|
||||||
|
strokeWidth="6"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<Path d="M 45 95 L 90 95 L 95 80 L 50 80 Z" fill="#05824C" />
|
||||||
|
{/* Front steering column */}
|
||||||
|
<Path
|
||||||
|
d="M 115 100 L 110 65 L 105 55"
|
||||||
|
stroke="#78909C"
|
||||||
|
strokeWidth="5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
{/* Handlebars */}
|
||||||
|
<Path
|
||||||
|
d="M 100 55 L 112 55"
|
||||||
|
stroke="#37474F"
|
||||||
|
strokeWidth="4"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
{/* Front Shield */}
|
||||||
|
<Path d="M 110 65 L 118 65 L 114 90 L 108 90 Z" fill="#05824C" />
|
||||||
|
|
||||||
|
{/* Delivery Box */}
|
||||||
|
<Rect x="30" y="52" width="28" height="28" rx="4" fill="#046B3E" />
|
||||||
|
<Rect x="34" y="56" width="20" height="20" rx="2" fill="#05824C" />
|
||||||
|
{/* Box Stripe */}
|
||||||
|
<Path d="M 30 66 L 58 66" stroke="#FFFFFF" strokeWidth="2" />
|
||||||
|
|
||||||
|
{/* Delivery Rider */}
|
||||||
|
{/* Torso/Jacket */}
|
||||||
|
<Path d="M 68 55 L 88 55 L 82 82 L 65 82 Z" fill="#05824C" />
|
||||||
|
{/* Backpack strap */}
|
||||||
|
<Path d="M 64 58 L 74 82" stroke="#37474F" strokeWidth="3" />
|
||||||
|
{/* Head / Helmet */}
|
||||||
|
<Circle cx="80" cy="38" r="9" fill="#FFCCBC" />
|
||||||
|
{/* Helmet */}
|
||||||
|
<Path d="M 70 36 C 70 26, 90 26, 90 36 Z" fill="#37474F" />
|
||||||
|
{/* Visor */}
|
||||||
|
<Path d="M 80 32 L 89 35 L 80 38 Z" fill="#00E5FF" />
|
||||||
|
{/* Arm stretching to handle */}
|
||||||
|
<Path
|
||||||
|
d="M 80 58 L 98 56"
|
||||||
|
stroke="#046B3E"
|
||||||
|
strokeWidth="4"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<Path
|
||||||
|
d="M 98 56 L 105 55"
|
||||||
|
stroke="#FFCCBC"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Motion lines */}
|
||||||
|
<Path
|
||||||
|
d="M 10 70 L 22 70"
|
||||||
|
stroke="#05824C"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
opacity="0.8"
|
||||||
|
/>
|
||||||
|
<Path
|
||||||
|
d="M 5 80 L 15 80"
|
||||||
|
stroke="#05824C"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
opacity="0.6"
|
||||||
|
/>
|
||||||
|
<Path
|
||||||
|
d="M 12 90 L 20 90"
|
||||||
|
stroke="#05824C"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
opacity="0.8"
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const LoginScreen: React.FC = () => {
|
export const LoginScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
const styles = getStyles(colors);
|
const styles = getStyles(colors);
|
||||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
const navigation =
|
||||||
|
useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||||
|
|
||||||
const [mobileNumber, setMobileNumber] = useState('');
|
const [mobileNumber, setMobileNumber] = useState('');
|
||||||
// const [password, setPassword] = useState('');
|
// const [password, setPassword] = useState('');
|
||||||
// const [rememberMe, setRememberMe] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [errors, setErrors] = useState<{ mobile?: string; password?: string }>(
|
const [errors, setErrors] = useState<{ mobile?: string }>({});
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
const newErrors: { mobile?: string; password?: string } = {};
|
const newErrors: { mobile?: string } = {};
|
||||||
if (!mobileNumber) {
|
if (!mobileNumber) {
|
||||||
newErrors.mobile = 'Mobile number is required';
|
newErrors.mobile = 'Mobile number is required';
|
||||||
} else if (mobileNumber.length < 9) {
|
} else if (mobileNumber.length < 9) {
|
||||||
@ -39,8 +151,8 @@ export const LoginScreen: React.FC = () => {
|
|||||||
|
|
||||||
// if (!password) {
|
// if (!password) {
|
||||||
// newErrors.password = 'Password is required';
|
// newErrors.password = 'Password is required';
|
||||||
// } else if (password.length < 6) {
|
// } else if (password.length < 4) {
|
||||||
// newErrors.password = 'Password must be at least 6 characters';
|
// newErrors.password = 'Password must be at least 4 characters';
|
||||||
// }
|
// }
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
@ -58,16 +170,12 @@ export const LoginScreen: React.FC = () => {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSocialLogin = (provider: 'google' | 'apple') => {
|
|
||||||
Alert.alert('Social Login', `Initiating ${provider} login...`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleForgotPassword = () => {
|
// const handleForgotPassword = () => {
|
||||||
// Alert.alert('Forgot Password', 'Redirecting to reset password...');
|
// Alert.alert('Forgot Password', 'OTP verification will be sent to recover password.');
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const handleSignUp = () => {
|
const handleSignUp = () => {
|
||||||
Alert.alert('Sign Up', 'Redirecting to registration...');
|
Alert.alert('Sign Up', 'Redirecting to register partner profile...');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,8 +187,10 @@ export const LoginScreen: React.FC = () => {
|
|||||||
contentContainerStyle={styles.scrollContainer}
|
contentContainerStyle={styles.scrollContainer}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
|
<DeliveryScooterSvg />
|
||||||
|
|
||||||
<View style={styles.headerContainer}>
|
<View style={styles.headerContainer}>
|
||||||
<Text style={styles.title}>Welcome back 👋</Text>
|
<Text style={styles.title}>Welcome Partner!</Text>
|
||||||
<Text style={styles.subtitle}>Login to continue</Text>
|
<Text style={styles.subtitle}>Login to continue</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -99,46 +209,18 @@ export const LoginScreen: React.FC = () => {
|
|||||||
|
|
||||||
{/* <CustomInput
|
{/* <CustomInput
|
||||||
label="Password"
|
label="Password"
|
||||||
placeholder="••••••••"
|
placeholder="Enter password"
|
||||||
isPassword
|
isPassword
|
||||||
rightActionText="Forgot?"
|
rightActionText="Forgot Password?"
|
||||||
onRightActionPress={handleForgotPassword}
|
onRightActionPress={handleForgotPassword}
|
||||||
value={password}
|
value={password}
|
||||||
onChangeText={(text) => {
|
onChangeText={text => {
|
||||||
setPassword(text);
|
setPassword(text);
|
||||||
if (errors.password) setErrors({ ...errors, password: undefined });
|
if (errors.password) setErrors({ ...errors, password: undefined });
|
||||||
}}
|
}}
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
/> */}
|
/> */}
|
||||||
|
|
||||||
{/* <View style={styles.row}>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.rememberMeContainer}
|
|
||||||
onPress={() => setRememberMe(!rememberMe)}
|
|
||||||
activeOpacity={0.8}
|
|
||||||
>
|
|
||||||
<CheckBox
|
|
||||||
value={rememberMe}
|
|
||||||
onValueChange={setRememberMe}
|
|
||||||
tintColors={{
|
|
||||||
true: colors.primary,
|
|
||||||
false: colors.textSecondary,
|
|
||||||
}}
|
|
||||||
tintColor={colors.textSecondary}
|
|
||||||
onTintColor={colors.primary}
|
|
||||||
onCheckColor={colors.white}
|
|
||||||
onFillColor={colors.primary}
|
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
/>
|
|
||||||
<Text style={styles.checkboxText}>Remember me</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View> */}
|
|
||||||
<View style={styles.otpWrapper}>
|
|
||||||
<Text style={styles.otpText}>
|
|
||||||
OTP will send on this number
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
title="Login"
|
title="Login"
|
||||||
onPress={handleLogin}
|
onPress={handleLogin}
|
||||||
@ -146,23 +228,6 @@ export const LoginScreen: React.FC = () => {
|
|||||||
style={styles.loginButton}
|
style={styles.loginButton}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={styles.dividerContainer}>
|
|
||||||
<View style={styles.line} />
|
|
||||||
<Text style={styles.dividerText}>or continue with</Text>
|
|
||||||
<View style={styles.line} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.socialContainer}>
|
|
||||||
<SocialButton
|
|
||||||
provider="google"
|
|
||||||
onPress={() => handleSocialLogin('google')}
|
|
||||||
/>
|
|
||||||
<SocialButton
|
|
||||||
provider="apple"
|
|
||||||
onPress={() => handleSocialLogin('apple')}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<TouchableOpacity onPress={handleSignUp} activeOpacity={0.8}>
|
<TouchableOpacity onPress={handleSignUp} activeOpacity={0.8}>
|
||||||
<Text style={styles.footerText}>
|
<Text style={styles.footerText}>
|
||||||
Don’t have an account?{' '}
|
Don’t have an account?{' '}
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
export { default } from './loginScreen';
|
export * from './LoginScreen';
|
||||||
export * from './loginScreen';
|
export * from './LoginScreen.styles';
|
||||||
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 React, { useState, useEffect } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { CustomInput, PrimaryButton } from '@components';
|
import { OtpInput, NumericKeypad, PrimaryButton } from '@components';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './OtpScreen.styles';
|
||||||
|
|
||||||
export const OtpScreen: React.FC<{ route: any }> = ({ route: _route }) => {
|
export const OtpScreen: React.FC<{ route: any }> = ({ route }) => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||||
|
|
||||||
|
const phone = route?.params?.phone || '+91 98765 43210';
|
||||||
const [otp, setOtp] = useState('');
|
const [otp, setOtp] = useState('');
|
||||||
const handleVerify = () => {
|
const [timer, setTimer] = useState(30);
|
||||||
navigation.navigate(RouteNames.SetLocation);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (timer <= 0) return;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setTimer((prev) => prev - 1);
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [timer]);
|
||||||
|
|
||||||
|
const handleKeyPress = (digit: string) => {
|
||||||
|
if (otp.length < 6) {
|
||||||
|
setError(false);
|
||||||
|
setOtp((prev) => prev + digit);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBackspace = () => {
|
||||||
|
setOtp((prev) => prev.slice(0, -1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVerify = () => {
|
||||||
|
if (otp.length < 6) {
|
||||||
|
setError(true);
|
||||||
|
Alert.alert('Invalid OTP', 'Please enter a 6-digit verification code.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
if (otp === '123456' || otp.length === 6) {
|
||||||
|
navigation.navigate(RouteNames.SetLocation);
|
||||||
|
} else {
|
||||||
|
setError(true);
|
||||||
|
Alert.alert('Verification Failed', 'The code you entered is incorrect. Try 123456.');
|
||||||
|
}
|
||||||
|
}, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResend = () => {
|
||||||
|
if (timer > 0) return;
|
||||||
|
setOtp('');
|
||||||
|
setTimer(30);
|
||||||
|
Alert.alert('OTP Resent', 'A new verification code has been sent to your mobile number.');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}
|
<View style={styles.container}>
|
||||||
>
|
<ScrollView contentContainerStyle={styles.scrollContainer} bounces={false}>
|
||||||
<Text style={styles.title}>Enter OTP</Text>
|
<View style={styles.headerContainer}>
|
||||||
<CustomInput label="OTP" value={otp} onChangeText={setOtp} keyboardType="numeric" />
|
<Text style={styles.title}>Verify Phone</Text>
|
||||||
<PrimaryButton title="Verify" onPress={handleVerify} />
|
<Text style={styles.subtitle}>
|
||||||
|
Enter the 6-digit OTP code sent to{' '}
|
||||||
|
<Text style={styles.phoneText}>{phone}</Text>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.contentWrapper}>
|
||||||
|
<View style={styles.otpWrapper}>
|
||||||
|
<OtpInput length={6} value={otp} error={error} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.resendContainer}>
|
||||||
|
{timer > 0 ? (
|
||||||
|
<Text style={styles.resendText}>
|
||||||
|
Resend code in <Text style={{ color: colors.primary, fontWeight: 'bold' }}>00:{timer < 10 ? `0${timer}` : timer}</Text>
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<TouchableOpacity onPress={handleResend} activeOpacity={0.7}>
|
||||||
|
<Text style={styles.resendLink}>Resend Code</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Verify & Continue"
|
||||||
|
onPress={handleVerify}
|
||||||
|
isLoading={isLoading}
|
||||||
|
style={styles.verifyButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.keypadContainer}>
|
||||||
|
<NumericKeypad
|
||||||
|
onPress={handleKeyPress}
|
||||||
|
onBackspace={handleBackspace}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', padding: 20 },
|
|
||||||
title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default OtpScreen;
|
export default OtpScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PrimaryButton } from '@components';
|
||||||
|
import { useAppSelector } from '@store';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './arrivedAtCustomerScreen.styles';
|
||||||
|
|
||||||
const ArrivedAtCustomerScreen: React.FC = () => {
|
export const ArrivedAtCustomerScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||||
|
|
||||||
|
const handleConfirmArrival = () => {
|
||||||
|
navigation.navigate(RouteNames.DeliverOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!activeJob) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Arrived At Customer Screen</Text>
|
{/* Map Background Placeholder */}
|
||||||
|
<View style={styles.mapPlaceholder}>
|
||||||
|
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Arrived Card */}
|
||||||
|
<View style={styles.bottomCard}>
|
||||||
|
<Text style={styles.cardTitle}>You've arrived</Text>
|
||||||
|
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
||||||
|
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Confirm Arrival"
|
||||||
|
onPress={handleConfirmArrival}
|
||||||
|
style={styles.arrivedButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ArrivedAtCustomerScreen;
|
export default ArrivedAtCustomerScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PrimaryButton } from '@components';
|
||||||
|
import { useAppSelector } from '@store';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './arrivedAtStoreScreen.styles';
|
||||||
|
|
||||||
const ArrivedAtStoreScreen: React.FC = () => {
|
export const ArrivedAtStoreScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||||
|
|
||||||
|
const handleConfirmArrival = () => {
|
||||||
|
navigation.navigate(RouteNames.ConfirmPickup);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!activeJob) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Arrived At Store Screen</Text>
|
{/* Map Background Placeholder */}
|
||||||
|
<View style={styles.mapPlaceholder}>
|
||||||
|
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Arrived Card */}
|
||||||
|
<View style={styles.bottomCard}>
|
||||||
|
<Text style={styles.cardTitle}>You've arrived</Text>
|
||||||
|
<Text style={styles.storeName}>{activeJob.pickupName}</Text>
|
||||||
|
<Text style={styles.storeAddress}>{activeJob.pickupAddress}</Text>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Confirm Arrival"
|
||||||
|
onPress={handleConfirmArrival}
|
||||||
|
style={styles.confirmButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ArrivedAtStoreScreen;
|
export default ArrivedAtStoreScreen;
|
||||||
|
|||||||
@ -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 React, { useState } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { PrimaryButton } from '@components';
|
import { CustomInput, PrimaryButton } from '@components';
|
||||||
|
import { ClipboardIcon, ChevronRightIcon, PersonIcon } from '@icons';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './completeProfileScreen.styles';
|
||||||
|
|
||||||
const CompleteProfileScreen: React.FC = () => {
|
export const CompleteProfileScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||||
|
|
||||||
|
const [fullName, setFullName] = useState('Rahul Kumar');
|
||||||
|
const [email, setEmail] = useState('rahul.kumar@gmail.com');
|
||||||
|
const [vehicleNo, setVehicleNo] = useState('KA-01-MJ-8845');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const documents = [
|
||||||
|
{ id: 'dl', title: 'Driving License', sub: 'Verified', status: 'verified' },
|
||||||
|
{ id: 'aadhaar', title: 'Aadhaar Card', sub: 'Verified', status: 'verified' },
|
||||||
|
{ id: 'rc', title: 'Vehicle RC', sub: 'Verified', status: 'verified' },
|
||||||
|
{ id: 'insurance', title: 'Insurance Policy', sub: 'Expires on 12 May 2026', status: 'expiring' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleDocumentPress = (docTitle: string) => {
|
||||||
|
Alert.alert('Document Details', `Viewing or uploading ${docTitle}...`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!fullName || !email || !vehicleNo) {
|
||||||
|
Alert.alert('Required Fields', 'Please fill out all profile information.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
navigation.navigate(RouteNames.OnboardingComplete);
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>Complete Profile Screen</Text>
|
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||||
<PrimaryButton
|
<View style={styles.headerContainer}>
|
||||||
title="Continue"
|
<Text style={styles.title}>Complete Profile</Text>
|
||||||
onPress={() => navigation.navigate(RouteNames.OnboardingComplete)}
|
<Text style={styles.subtitle}>Enter details & upload documents to get verified</Text>
|
||||||
style={{ marginTop: 20, width: '90%' }}
|
</View>
|
||||||
/>
|
|
||||||
|
{/* Profile Avatar Upload Mock */}
|
||||||
|
<View style={styles.profileImageContainer}>
|
||||||
|
<TouchableOpacity style={styles.avatarPlaceholder} activeOpacity={0.8}>
|
||||||
|
<PersonIcon size={40} color={colors.textSecondary} />
|
||||||
|
<Text style={styles.avatarText}>Add Photo</Text>
|
||||||
|
<View style={styles.cameraBadge}>
|
||||||
|
<Text style={{ color: colors.white, fontSize: 12, fontWeight: 'bold' }}>+</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Personal Details */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Personal Details</Text>
|
||||||
|
<CustomInput
|
||||||
|
label="Full Name"
|
||||||
|
placeholder="Rahul Kumar"
|
||||||
|
value={fullName}
|
||||||
|
onChangeText={setFullName}
|
||||||
|
/>
|
||||||
|
<CustomInput
|
||||||
|
label="Email Address"
|
||||||
|
placeholder="rahul.kumar@gmail.com"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
keyboardType="email-address"
|
||||||
|
/>
|
||||||
|
<CustomInput
|
||||||
|
label="Vehicle Number"
|
||||||
|
placeholder="KA-01-MJ-8845"
|
||||||
|
value={vehicleNo}
|
||||||
|
onChangeText={setVehicleNo}
|
||||||
|
autoCapitalize="characters"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Required Documents Checklist */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Required Documents</Text>
|
||||||
|
{documents.map((doc) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={doc.id}
|
||||||
|
style={styles.documentItem}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
onPress={() => handleDocumentPress(doc.title)}
|
||||||
|
>
|
||||||
|
<View style={styles.docLeft}>
|
||||||
|
<ClipboardIcon size={22} color={colors.primary} />
|
||||||
|
<View style={styles.docTextContainer}>
|
||||||
|
<Text style={styles.docTitle}>{doc.title}</Text>
|
||||||
|
<Text style={styles.docSub}>{doc.sub}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusBadge,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
doc.status === 'verified'
|
||||||
|
? colors.primaryLight
|
||||||
|
: colors.warningLight,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.statusText,
|
||||||
|
{
|
||||||
|
color:
|
||||||
|
doc.status === 'verified'
|
||||||
|
? colors.primary
|
||||||
|
: colors.warning,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{doc.status === 'verified' ? 'VERIFIED' : 'ACTIVE'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<ChevronRightIcon size={20} color={colors.textSecondary} />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Submit & Verify"
|
||||||
|
onPress={handleSubmit}
|
||||||
|
isLoading={isLoading}
|
||||||
|
style={styles.submitButton}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
title: { fontSize: 20 },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default CompleteProfileScreen;
|
export default CompleteProfileScreen;
|
||||||
|
|||||||
@ -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 React, { useState } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { ScreenHeader, PrimaryButton } from '@components';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { advanceJobStatus } from '@store/slices/jobSlice';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames, JobStatus } from '@utils/constants';
|
||||||
|
import { getStyles } from './confirmPickupScreen.styles';
|
||||||
|
|
||||||
const ConfirmPickupScreen: React.FC = () => {
|
export const ConfirmPickupScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||||
|
|
||||||
|
// Togglable checklist for food/merchandise items
|
||||||
|
const [checkedItems, setCheckedItems] = useState<{ [key: number]: boolean }>({
|
||||||
|
0: true,
|
||||||
|
1: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ id: 0, name: 'Cappuccino (Medium)', qty: 1 },
|
||||||
|
{ id: 1, name: 'Chocolate Croissant', qty: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleToggleCheck = (id: number) => {
|
||||||
|
setCheckedItems((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[id]: !prev[id],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePickedUp = () => {
|
||||||
|
// Ensure all items are checked before picking up
|
||||||
|
const allChecked = items.every((item) => checkedItems[item.id]);
|
||||||
|
if (!allChecked) {
|
||||||
|
Alert.alert('Incomplete Verification', 'Please confirm that you have checked all items before picking up.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(advanceJobStatus(JobStatus.PickedUp));
|
||||||
|
navigation.navigate(RouteNames.OrderPickedUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReportIssue = () => {
|
||||||
|
Alert.alert('Report Issue', 'Helpdesk notified. Support agent will contact you shortly.');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!activeJob) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Confirm Pickup Screen</Text>
|
<ScreenHeader
|
||||||
|
title="Confirm Pickup"
|
||||||
|
showBack={true}
|
||||||
|
onBack={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Order ID Banner */}
|
||||||
|
<View style={styles.orderHeader}>
|
||||||
|
<Text style={styles.orderIdText}>Order ID: {activeJob.orderId}</Text>
|
||||||
|
<View style={styles.paymentTag}>
|
||||||
|
<Text style={styles.paymentTagText}>ONLINE PAID</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Merchant Info */}
|
||||||
|
<View style={styles.merchantInfoCard}>
|
||||||
|
<Text style={styles.merchantLabel}>Pickup Store</Text>
|
||||||
|
<Text style={styles.merchantName}>{activeJob.pickupName}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Items Checklist */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Verify Items ({items.length})</Text>
|
||||||
|
{items.map((item) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={item.id}
|
||||||
|
style={styles.itemRow}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
onPress={() => handleToggleCheck(item.id)}
|
||||||
|
>
|
||||||
|
<View style={[styles.checkbox, checkedItems[item.id] && styles.checkboxChecked]}>
|
||||||
|
{checkedItems[item.id] && <Text style={styles.checkSign}>✓</Text>}
|
||||||
|
</View>
|
||||||
|
<Text style={styles.itemNameText}>{item.name}</Text>
|
||||||
|
<Text style={styles.itemQtyText}>x{item.qty}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<View style={styles.btnRow}>
|
||||||
|
<PrimaryButton
|
||||||
|
title="Issue"
|
||||||
|
onPress={handleReportIssue}
|
||||||
|
style={styles.issueBtn}
|
||||||
|
textStyle={{ color: colors.error }}
|
||||||
|
/>
|
||||||
|
<PrimaryButton
|
||||||
|
title="Picked Up"
|
||||||
|
onPress={handlePickedUp}
|
||||||
|
style={styles.pickedUpBtn}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: "center", alignItems: "center" },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ConfirmPickupScreen;
|
export default ConfirmPickupScreen;
|
||||||
|
|||||||
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 React, { useEffect, useRef } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, Alert, Switch } from 'react-native';
|
||||||
|
import { useAppTheme, borderRadius, spacing, shadow, typography } from '@theme';
|
||||||
|
import { PrimaryButton } from '@components';
|
||||||
|
import { BellIcon, PersonIcon, WalletIcon, ClipboardIcon } from '@icons';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { setOnlineStatus } from '@store/slices/authSlice';
|
||||||
|
import { setNewJobOffer } from '@store/slices/jobSlice';
|
||||||
|
import { mockJobOffer } from '@store/mockData/mockJobs';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames, JobStatus } from '@utils/constants';
|
||||||
|
import { formatCurrency } from '@utils/formatters';
|
||||||
|
import { getStyles } from './dashboardScreen.styles';
|
||||||
|
|
||||||
const DashboardScreen: React.FC = () => (
|
const DashboardScreen: React.FC = () => {
|
||||||
<View style={styles.container}>
|
const { colors } = useAppTheme();
|
||||||
<Text>Dashboard Screen</Text>
|
const styles = getStyles(colors);
|
||||||
</View>
|
const dispatch = useAppDispatch();
|
||||||
);
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const isOnline = useAppSelector((state) => state.auth.isOnline);
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
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}>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* 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;
|
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 React, { useState } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { ScreenHeader, PrimaryButton } from '@components';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { completeJob } from '@store/slices/jobSlice';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './deliverOrderScreen.styles';
|
||||||
|
|
||||||
const DeliverOrderScreen: React.FC = () => {
|
export const DeliverOrderScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ id: 0, name: 'Cappuccino (Medium)', qty: 1 },
|
||||||
|
{ id: 1, name: 'Chocolate Croissant', qty: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleCompleteDelivery = () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
dispatch(completeJob());
|
||||||
|
navigation.navigate(RouteNames.DeliveryCompleted);
|
||||||
|
}, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!activeJob) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Deliver Order Screen</Text>
|
<ScreenHeader
|
||||||
|
title="Deliver Order"
|
||||||
|
showBack={true}
|
||||||
|
onBack={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Customer Detail Card */}
|
||||||
|
<View style={styles.card}>
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text style={styles.label}>Customer Name</Text>
|
||||||
|
<Text style={styles.value}>{activeJob.dropName}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.row, { marginTop: 10 }]}>
|
||||||
|
<Text style={styles.label}>Order ID</Text>
|
||||||
|
<Text style={styles.value}>{activeJob.orderId}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Items Summary list */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Items Details</Text>
|
||||||
|
{items.map((item) => (
|
||||||
|
<View key={item.id} style={styles.itemRow}>
|
||||||
|
<Text style={styles.itemName}>{item.name}</Text>
|
||||||
|
<Text style={styles.itemQty}>x{item.qty}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Payment Collect Details */}
|
||||||
|
<View style={styles.paymentCard}>
|
||||||
|
<Text style={styles.paymentLabel}>Cash to Collect</Text>
|
||||||
|
<Text style={styles.paymentVal}>₹0.00 (Online Paid)</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Complete Delivery"
|
||||||
|
onPress={handleCompleteDelivery}
|
||||||
|
isLoading={isLoading}
|
||||||
|
style={styles.completeButton}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default DeliverOrderScreen;
|
export default DeliverOrderScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
|
import Svg, { Circle, Path } from 'react-native-svg';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PrimaryButton } from '@components';
|
||||||
|
import { useNavigation, CommonActions } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './deliveryCompletedScreen.styles';
|
||||||
|
|
||||||
const DeliveryCompletedScreen: React.FC = () => {
|
export const DeliveryCompletedScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const handleGoToDashboard = () => {
|
||||||
|
// Reset route stack and go back to Dashboard
|
||||||
|
navigation.dispatch(
|
||||||
|
CommonActions.reset({
|
||||||
|
index: 0,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'BottomTabs',
|
||||||
|
state: {
|
||||||
|
routes: [{ name: RouteNames.Dashboard }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Delivery Completed Screen</Text>
|
{/* Checkmark Vector */}
|
||||||
|
<View style={styles.illustrationContainer}>
|
||||||
|
<Svg width="120" height="120" viewBox="0 0 120 120" fill="none">
|
||||||
|
<Circle cx="60" cy="60" r="50" fill={colors.primaryLight} />
|
||||||
|
<Circle cx="60" cy="60" r="40" fill={colors.primary} />
|
||||||
|
<Path
|
||||||
|
d="M 44 62 L 54 72 L 76 46"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
strokeWidth="6"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.title}>Delivery Completed!</Text>
|
||||||
|
<Text style={styles.earnedText}>
|
||||||
|
You earned <Text style={styles.amount}>₹78.00</Text> on this delivery
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Summary Card */}
|
||||||
|
<View style={styles.summaryCard}>
|
||||||
|
<View style={styles.summaryRow}>
|
||||||
|
<Text style={styles.summaryLabel}>Order ID</Text>
|
||||||
|
<Text style={styles.summaryVal}>#ORD125487</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.summaryRow}>
|
||||||
|
<Text style={styles.summaryLabel}>Delivery Distance</Text>
|
||||||
|
<Text style={styles.summaryVal}>1.2 km</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.summaryRow}>
|
||||||
|
<Text style={styles.summaryLabel}>Time Taken</Text>
|
||||||
|
<Text style={styles.summaryVal}>15 mins</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.summaryRow}>
|
||||||
|
<Text style={styles.summaryLabel}>Payment Mode</Text>
|
||||||
|
<Text style={styles.summaryVal}>Online Paid</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Go to Dashboard"
|
||||||
|
onPress={handleGoToDashboard}
|
||||||
|
style={styles.button}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default DeliveryCompletedScreen;
|
export default DeliveryCompletedScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { ScreenHeader } from '@components';
|
||||||
|
import { ClipboardIcon, ChevronRightIcon } from '@icons';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { getStyles } from './documentsScreen.styles';
|
||||||
|
|
||||||
const DocumentsScreen: React.FC = () => {
|
export const DocumentsScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
|
const documentsList = [
|
||||||
|
{ id: 'dl', title: 'Driving License', sub: 'Verified', status: 'verified' },
|
||||||
|
{ id: 'aadhaar', title: 'Aadhaar Card', sub: 'Verified', status: 'verified' },
|
||||||
|
{ id: 'rc', title: 'Vehicle RC', sub: 'Verified', status: 'verified' },
|
||||||
|
{ id: 'insurance', title: 'Insurance', sub: 'Expires on 12 May 2026', status: 'expiring' },
|
||||||
|
{ id: 'vehicle_photo', title: 'Vehicle Photo', sub: 'Verified', status: 'verified' },
|
||||||
|
{ id: 'profile_photo', title: 'Profile Photo', sub: 'Verified', status: 'verified' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleDocClick = (title: string) => {
|
||||||
|
Alert.alert('Document Preview', `Opening document view for ${title}...`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Documents Screen</Text>
|
{/* Navigation Screen Header */}
|
||||||
|
<ScreenHeader
|
||||||
|
title="Documents"
|
||||||
|
showBack={true}
|
||||||
|
onBack={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
{documentsList.map((doc) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={doc.id}
|
||||||
|
style={styles.docCard}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
onPress={() => handleDocClick(doc.title)}
|
||||||
|
>
|
||||||
|
<View style={styles.docLeft}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
<ClipboardIcon size={20} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.docInfo}>
|
||||||
|
<Text style={styles.docTitle} numberOfLines={1}>{doc.title}</Text>
|
||||||
|
<Text style={styles.docSub}>{doc.sub}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.statusContainer}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusBadge,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
doc.status === 'verified'
|
||||||
|
? colors.primaryLight
|
||||||
|
: colors.warningLight,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.statusText,
|
||||||
|
{
|
||||||
|
color:
|
||||||
|
doc.status === 'verified'
|
||||||
|
? colors.primary
|
||||||
|
: colors.warning,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{doc.status === 'verified' ? 'VERIFIED' : 'ACTIVE'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<ChevronRightIcon size={20} color={colors.textSecondary} />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default DocumentsScreen;
|
export default DocumentsScreen;
|
||||||
|
|||||||
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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { WalletIcon } from '@icons';
|
||||||
|
import { useAppSelector } from '@store';
|
||||||
|
import { formatCurrency } from '@utils/formatters';
|
||||||
|
import { getStyles } from './earningsScreen.styles';
|
||||||
|
|
||||||
const EarningsScreen: React.FC = () => {
|
export const EarningsScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
|
||||||
|
const { todayEarnings, completedCount, onlineMinutes } = useAppSelector((state) => state.earnings);
|
||||||
|
const { jobHistory } = useAppSelector((state) => state.job);
|
||||||
|
|
||||||
|
// Default mock jobs history if none completed yet
|
||||||
|
const defaultHistory: { orderId: string; time: string; amount: number; paymentType: 'online' | 'cod'; }[] = [
|
||||||
|
{ orderId: '#ORD125487', time: '12:35 PM', amount: 78.0, paymentType: 'online' },
|
||||||
|
{ orderId: '#ORD125422', time: '11:15 AM', amount: 65.0, paymentType: 'cod' },
|
||||||
|
{ orderId: '#ORD125389', time: '09:40 AM', amount: 120.0, paymentType: 'online' },
|
||||||
|
{ orderId: '#ORD125310', time: 'Yesterday', amount: 85.0, paymentType: 'online' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayHistory = jobHistory.length > 0
|
||||||
|
? jobHistory.map(job => ({
|
||||||
|
orderId: job.orderId,
|
||||||
|
time: job.deliveredAt ? new Date(job.deliveredAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : 'Delivered',
|
||||||
|
amount: job.amount,
|
||||||
|
paymentType: job.paymentType,
|
||||||
|
})).concat(defaultHistory)
|
||||||
|
: defaultHistory;
|
||||||
|
|
||||||
|
const formatOnlineTime = (mins: number) => {
|
||||||
|
const hours = Math.floor(mins / 60);
|
||||||
|
const remainingMins = mins % 60;
|
||||||
|
return `${hours}h ${remainingMins < 10 ? `0${remainingMins}` : remainingMins}m`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Earnings Screen</Text>
|
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
<View style={styles.headerContainer}>
|
||||||
|
<Text style={styles.title}>Earnings</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Total Earnings Card */}
|
||||||
|
<View style={styles.summaryCard}>
|
||||||
|
<Text style={styles.summaryTitle}>Today's Earnings</Text>
|
||||||
|
<Text style={styles.totalAmount}>{formatCurrency(todayEarnings)}</Text>
|
||||||
|
|
||||||
|
<View style={styles.statsRow}>
|
||||||
|
<View style={styles.statBox}>
|
||||||
|
<Text style={styles.statVal}>{completedCount}</Text>
|
||||||
|
<Text style={styles.statLabel}>Deliveries</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.statDivider} />
|
||||||
|
|
||||||
|
<View style={styles.statBox}>
|
||||||
|
<Text style={styles.statVal}>{formatOnlineTime(onlineMinutes)}</Text>
|
||||||
|
<Text style={styles.statLabel}>Online Time</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.statDivider} />
|
||||||
|
|
||||||
|
<View style={styles.statBox}>
|
||||||
|
<Text style={styles.statVal}>₹78.50</Text>
|
||||||
|
<Text style={styles.statLabel}>Avg / Order</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Recent Deliveries List */}
|
||||||
|
<View style={styles.listSection}>
|
||||||
|
<Text style={styles.sectionTitle}>Recent Deliveries</Text>
|
||||||
|
|
||||||
|
{displayHistory.map((item, index) => (
|
||||||
|
<View key={index} style={styles.earningItem}>
|
||||||
|
<View style={styles.itemLeft}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
<WalletIcon size={20} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.orderIdText}>{item.orderId}</Text>
|
||||||
|
<Text style={styles.timeText}>{item.time}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.itemRight}>
|
||||||
|
<Text style={styles.itemAmount}>+{formatCurrency(item.amount)}</Text>
|
||||||
|
<View style={styles.paymentBadge}>
|
||||||
|
<Text style={styles.paymentText}>{item.paymentType.toUpperCase()}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default EarningsScreen;
|
export default EarningsScreen;
|
||||||
|
|||||||
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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PersonIcon } from '@icons';
|
||||||
|
import { getStyles } from './inboxScreen.styles';
|
||||||
|
|
||||||
const InboxScreen: React.FC = () => {
|
export const InboxScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
|
||||||
|
const chats = [
|
||||||
|
{
|
||||||
|
id: 'support',
|
||||||
|
sender: 'SG Partner Support',
|
||||||
|
snippet: 'Hi Rahul, your document verification is complete. Let us know if you need assistance.',
|
||||||
|
time: '10 mins ago',
|
||||||
|
unreadCount: 1,
|
||||||
|
isSystem: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rohit',
|
||||||
|
sender: 'Rohit Sharma (Customer)',
|
||||||
|
snippet: 'Please leave the order near the gate, thank you.',
|
||||||
|
time: '1 hour ago',
|
||||||
|
unreadCount: 0,
|
||||||
|
isSystem: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ccd',
|
||||||
|
sender: 'Cafe Coffee Day (Merchant)',
|
||||||
|
snippet: 'Hi, the order #ORD125487 is ready for pickup. Please arrive.',
|
||||||
|
time: '2 hours ago',
|
||||||
|
unreadCount: 0,
|
||||||
|
isSystem: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleChatPress = (sender: string) => {
|
||||||
|
Alert.alert('Chat Thread', `Opening conversation with ${sender}...`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }] }>
|
<View style={styles.container}>
|
||||||
<Text style={styles.screenTitle}>Inbox Screen</Text>
|
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
<View style={styles.headerContainer}>
|
||||||
|
<Text style={styles.title}>Inbox</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{chats.map((chat) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={chat.id}
|
||||||
|
style={styles.chatItem}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
onPress={() => handleChatPress(chat.sender)}
|
||||||
|
>
|
||||||
|
{/* Sender Avatar */}
|
||||||
|
<View style={styles.avatar}>
|
||||||
|
<PersonIcon size={24} color={chat.isSystem ? colors.primary : colors.textSecondary} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Chat Content Info */}
|
||||||
|
<View style={styles.chatInfo}>
|
||||||
|
<View style={styles.chatHeader}>
|
||||||
|
<Text style={styles.chatTitleText} numberOfLines={1}>{chat.sender}</Text>
|
||||||
|
<Text style={styles.timeText}>{chat.time}</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.chatSnippetText} numberOfLines={1}>
|
||||||
|
{chat.snippet}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Unread badge info */}
|
||||||
|
{chat.unreadCount > 0 && (
|
||||||
|
<View style={styles.rightMeta}>
|
||||||
|
<View style={styles.unreadBadge}>
|
||||||
|
<Text style={styles.unreadBadgeText}>{chat.unreadCount}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
screenTitle: {
|
|
||||||
fontSize: 18,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default InboxScreen;
|
export default InboxScreen;
|
||||||
|
|||||||
@ -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 React, { useState } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { OrderCard, EmptyState } from '@components';
|
||||||
|
import { useAppSelector } from '@store';
|
||||||
|
import { ClipboardIcon } from '@icons';
|
||||||
|
import { getStyles } from './jobsScreen.styles';
|
||||||
|
|
||||||
const JobsScreen: React.FC = () => {
|
export const JobsScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState<'active' | 'history'>('active');
|
||||||
|
const { activeJob, jobHistory } = useAppSelector((state) => state.job);
|
||||||
|
|
||||||
|
// Default history mock jobs
|
||||||
|
const defaultHistory = [
|
||||||
|
{
|
||||||
|
pickupName: 'Cafe Coffee Day',
|
||||||
|
pickupAddress: 'MG Road, Bengaluru',
|
||||||
|
dropName: 'Rohit Sharma',
|
||||||
|
dropAddress: 'Koramangala 4th Block, Bengaluru',
|
||||||
|
amount: 78.0,
|
||||||
|
distance: 1.2,
|
||||||
|
itemCount: 2,
|
||||||
|
paymentType: 'online',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pickupName: 'Pizza Hut',
|
||||||
|
pickupAddress: 'Indiranagar, Bengaluru',
|
||||||
|
dropName: 'Aishwarya Sen',
|
||||||
|
dropAddress: 'Domlur Stage 2, Bengaluru',
|
||||||
|
amount: 95.0,
|
||||||
|
distance: 3.8,
|
||||||
|
itemCount: 3,
|
||||||
|
paymentType: 'cod',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pickupName: 'Burgers & Co',
|
||||||
|
pickupAddress: 'Koramangala 5th Block',
|
||||||
|
dropName: 'Sameer Khan',
|
||||||
|
dropAddress: 'HSR Layout Sector 3, Bengaluru',
|
||||||
|
amount: 110.0,
|
||||||
|
distance: 4.5,
|
||||||
|
itemCount: 1,
|
||||||
|
paymentType: 'online',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>Jobs Screen</Text>
|
<View style={styles.headerContainer}>
|
||||||
|
<Text style={styles.title}>Jobs</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Tabs Selector */}
|
||||||
|
<View style={styles.tabBar}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.tab, activeTab === 'active' && styles.tabActive]}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
onPress={() => setActiveTab('active')}
|
||||||
|
>
|
||||||
|
<Text style={[styles.tabText, activeTab === 'active' && styles.tabTextActive]}>
|
||||||
|
Active / Upcoming
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.tab, activeTab === 'history' && styles.tabActive]}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
onPress={() => setActiveTab('history')}
|
||||||
|
>
|
||||||
|
<Text style={[styles.tabText, activeTab === 'history' && styles.tabTextActive]}>
|
||||||
|
History
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView contentContainerStyle={styles.listContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
{activeTab === 'active' ? (
|
||||||
|
// Active job tab
|
||||||
|
activeJob ? (
|
||||||
|
<View style={styles.cardWrapper}>
|
||||||
|
<OrderCard
|
||||||
|
pickupName={activeJob.pickupName}
|
||||||
|
pickupAddress={activeJob.pickupAddress}
|
||||||
|
dropName={activeJob.dropName}
|
||||||
|
dropAddress={activeJob.dropAddress}
|
||||||
|
amount={activeJob.amount}
|
||||||
|
distance={activeJob.distance}
|
||||||
|
itemCount={activeJob.itemCount}
|
||||||
|
paymentType={activeJob.paymentType}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={styles.emptyStateContainer}>
|
||||||
|
<ClipboardIcon size={48} color={colors.placeholder} />
|
||||||
|
<Text style={styles.emptyStateText}>No active deliveries right now</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
// History tab
|
||||||
|
<View>
|
||||||
|
{jobHistory.length > 0
|
||||||
|
? jobHistory.map((job, idx) => (
|
||||||
|
<View key={idx} style={styles.cardWrapper}>
|
||||||
|
<OrderCard
|
||||||
|
pickupName={job.pickupName}
|
||||||
|
pickupAddress={job.pickupAddress}
|
||||||
|
dropName={job.dropName}
|
||||||
|
dropAddress={job.dropAddress}
|
||||||
|
amount={job.amount}
|
||||||
|
distance={job.distance}
|
||||||
|
itemCount={job.itemCount}
|
||||||
|
paymentType={job.paymentType}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
: defaultHistory.map((job, idx) => (
|
||||||
|
<View key={idx} style={styles.cardWrapper}>
|
||||||
|
<OrderCard
|
||||||
|
pickupName={job.pickupName}
|
||||||
|
pickupAddress={job.pickupAddress}
|
||||||
|
dropName={job.dropName}
|
||||||
|
dropAddress={job.dropAddress}
|
||||||
|
amount={job.amount}
|
||||||
|
distance={job.distance}
|
||||||
|
itemCount={job.itemCount}
|
||||||
|
paymentType={job.paymentType}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
title: { fontSize: 20 },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default JobsScreen;
|
export default JobsScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PrimaryButton } from '@components';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { advanceJobStatus } from '@store/slices/jobSlice';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames, JobStatus } from '@utils/constants';
|
||||||
|
import { getStyles } from './liveTrackingScreen.styles';
|
||||||
|
|
||||||
const LiveTrackingScreen: React.FC = () => {
|
export const LiveTrackingScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
return (
|
const styles = getStyles(colors);
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
const dispatch = useAppDispatch();
|
||||||
<Text>Live Tracking Screen</Text>
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
</View>
|
|
||||||
);
|
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}>
|
||||||
|
{/* 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;
|
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 React, { useState, useEffect } from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PrimaryButton, LocationRow } from '@components';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { acceptJob, rejectJob } from '@store/slices/jobSlice';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { formatCurrency } from '@utils/formatters';
|
||||||
|
import { getStyles } from './newJobRequestScreen.styles';
|
||||||
|
|
||||||
const NewJobRequestScreen: React.FC = () => {
|
export const NewJobRequestScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const newJobOffer = useAppSelector((state) => state.job.newJobOffer);
|
||||||
|
const [secondsLeft, setSecondsLeft] = useState(15);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (secondsLeft <= 0) {
|
||||||
|
handleReject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setSecondsLeft((prev) => prev - 1);
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [secondsLeft]);
|
||||||
|
|
||||||
|
const handleAccept = () => {
|
||||||
|
dispatch(acceptJob());
|
||||||
|
Alert.alert('Job Accepted', 'Heading to Cafe Coffee Day pickup location.', [
|
||||||
|
{
|
||||||
|
text: 'OK',
|
||||||
|
onPress: () => navigation.navigate(RouteNames.OrderAccepted),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReject = () => {
|
||||||
|
dispatch(rejectJob());
|
||||||
|
navigation.navigate(RouteNames.BottomTabs, { screen: RouteNames.Dashboard });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!newJobOffer) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>New Job Request Screen</Text>
|
<View style={styles.modalContent}>
|
||||||
|
{/* Header Row with Title and Countdown */}
|
||||||
|
<View style={styles.headerRow}>
|
||||||
|
<Text style={styles.modalTitle}>New Delivery Request</Text>
|
||||||
|
<View style={styles.timerContainer}>
|
||||||
|
<Text style={styles.timerText}>{secondsLeft}s</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Route Details Card */}
|
||||||
|
<View style={styles.routeContainer}>
|
||||||
|
<LocationRow
|
||||||
|
type="pickup"
|
||||||
|
name="Cafe Coffee Day"
|
||||||
|
address={newJobOffer.pickupAddress}
|
||||||
|
/>
|
||||||
|
<View style={styles.spacer} />
|
||||||
|
<LocationRow
|
||||||
|
type="drop"
|
||||||
|
name="Rohit Sharma"
|
||||||
|
address={newJobOffer.dropAddress}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Earnings & Distance Details */}
|
||||||
|
<View style={styles.detailsRow}>
|
||||||
|
<View style={styles.detailBox}>
|
||||||
|
<Text style={styles.detailLabel}>Estimated Earnings</Text>
|
||||||
|
<Text style={styles.detailValue}>{formatCurrency(newJobOffer.amount)}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.divider} />
|
||||||
|
|
||||||
|
<View style={styles.detailBox}>
|
||||||
|
<Text style={styles.detailLabel}>Distance</Text>
|
||||||
|
<Text style={styles.detailValue}>{newJobOffer.distance} km</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Button Actions */}
|
||||||
|
<View style={styles.btnRow}>
|
||||||
|
<PrimaryButton
|
||||||
|
title="Reject"
|
||||||
|
onPress={handleReject}
|
||||||
|
style={styles.rejectBtn}
|
||||||
|
textStyle={{ color: colors.text }}
|
||||||
|
/>
|
||||||
|
<PrimaryButton
|
||||||
|
title="Accept"
|
||||||
|
onPress={handleAccept}
|
||||||
|
style={styles.acceptBtn}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default NewJobRequestScreen;
|
export default NewJobRequestScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
|
import Svg, { Circle, Path } from 'react-native-svg';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { PrimaryButton } from '@components';
|
import { PrimaryButton } from '@components';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { RootStackParamList } from '@navigation/navigationTypes';
|
import { RootStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './onboardingCompleteScreen.styles';
|
||||||
|
|
||||||
const OnboardingCompleteScreen: React.FC = () => {
|
const OnboardingCompleteScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>Onboarding Complete Screen</Text>
|
{/* Premium Success Checkmark Vector */}
|
||||||
|
<View style={styles.illustrationContainer}>
|
||||||
|
<Svg width="140" height="140" viewBox="0 0 140 140" fill="none">
|
||||||
|
<Circle cx="70" cy="70" r="60" fill={colors.primaryLight} />
|
||||||
|
<Circle cx="70" cy="70" r="48" fill={colors.primary} />
|
||||||
|
{/* Checkmark icon */}
|
||||||
|
<Path
|
||||||
|
d="M 50 72 L 64 86 L 90 54"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
strokeWidth="8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.successTitle}>Onboarding Completed!</Text>
|
||||||
|
<Text style={styles.successDescription}>
|
||||||
|
Welcome to the SG Delivery family! Your account has been successfully verified. You are now ready to go online and start earning.
|
||||||
|
</Text>
|
||||||
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
title="Get Started"
|
title="Get Started"
|
||||||
onPress={() => navigation.navigate('App', { screen: 'BottomTabs' })}
|
onPress={() =>
|
||||||
style={{ marginTop: 20, width: '90%' }}
|
navigation.navigate('App', {
|
||||||
|
screen: RouteNames.BottomTabs,
|
||||||
|
params: { screen: RouteNames.Dashboard },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
style={styles.startButton}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
title: { fontSize: 20 },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default OnboardingCompleteScreen;
|
export default OnboardingCompleteScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PrimaryButton } from '@components';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { advanceJobStatus } from '@store/slices/jobSlice';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames, JobStatus } from '@utils/constants';
|
||||||
|
import { getStyles } from './orderAcceptedScreen.styles';
|
||||||
|
|
||||||
const OrderAcceptedScreen: React.FC = () => {
|
export const OrderAcceptedScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||||
|
|
||||||
|
const handleNavigate = () => {
|
||||||
|
dispatch(advanceJobStatus(JobStatus.ArrivedAtStore));
|
||||||
|
navigation.navigate(RouteNames.ArrivedAtStore);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!activeJob) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Order Accepted Screen</Text>
|
{/* Top Banner Alert */}
|
||||||
|
<View style={styles.headerBanner}>
|
||||||
|
<Text style={styles.headerText}>Order Accepted • Go to pickup location</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Blank Map Placeholder Background */}
|
||||||
|
<View style={styles.mapPlaceholder}>
|
||||||
|
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Merchant Bottom Card */}
|
||||||
|
<View style={styles.bottomCard}>
|
||||||
|
<Text style={styles.cardTitle}>Pickup Merchant</Text>
|
||||||
|
<Text style={styles.storeName}>{activeJob.pickupName}</Text>
|
||||||
|
<Text style={styles.storeAddress}>{activeJob.pickupAddress}</Text>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Navigate"
|
||||||
|
onPress={handleNavigate}
|
||||||
|
style={styles.navigateButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default OrderAcceptedScreen;
|
export default OrderAcceptedScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PrimaryButton } from '@components';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { advanceJobStatus } from '@store/slices/jobSlice';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames, JobStatus } from '@utils/constants';
|
||||||
|
import { getStyles } from './orderPickedUpScreen.styles';
|
||||||
|
|
||||||
const OrderPickedUpScreen: React.FC = () => {
|
export const OrderPickedUpScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const activeJob = useAppSelector((state) => state.job.activeJob);
|
||||||
|
|
||||||
|
const handleStartDelivery = () => {
|
||||||
|
dispatch(advanceJobStatus(JobStatus.EnRoute));
|
||||||
|
navigation.navigate(RouteNames.LiveTracking);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!activeJob) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Text>Order Picked Up Screen</Text>
|
{/* Map Background Placeholder */}
|
||||||
|
<View style={styles.mapPlaceholder}>
|
||||||
|
<Text style={styles.placeholderText}>[ Map Placeholder ]</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Deliver Bottom Card */}
|
||||||
|
<View style={styles.bottomCard}>
|
||||||
|
<Text style={styles.cardTitle}>Deliver to Customer</Text>
|
||||||
|
<Text style={styles.customerName}>{activeJob.dropName}</Text>
|
||||||
|
<Text style={styles.customerAddress}>{activeJob.dropAddress}</Text>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
title="Start Delivery"
|
||||||
|
onPress={handleStartDelivery}
|
||||||
|
style={styles.startButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: "center", alignItems: "center" },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default OrderPickedUpScreen;
|
export default OrderPickedUpScreen;
|
||||||
|
|||||||
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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
|
import { PersonIcon, ChevronRightIcon, ClipboardIcon, StarIcon } from '@icons';
|
||||||
|
import { useAppDispatch } from '@store';
|
||||||
|
import { logout } from '@store/slices/authSlice';
|
||||||
|
import { useNavigation, CommonActions } from '@react-navigation/native';
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './profileScreen.styles';
|
||||||
|
|
||||||
const ProfileScreen: React.FC = () => {
|
export const ProfileScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ id: 'personal', title: 'Personal Info', icon: <PersonIcon size={20} color={colors.primary} /> },
|
||||||
|
{ id: 'vehicle', title: 'Vehicle Info', icon: <ClipboardIcon size={20} color={colors.primary} /> },
|
||||||
|
{ id: 'documents', title: 'Documents', icon: <ClipboardIcon size={20} color={colors.primary} />, route: RouteNames.Documents },
|
||||||
|
{ id: 'bank', title: 'Bank Details', icon: <ClipboardIcon size={20} color={colors.primary} /> },
|
||||||
|
{ id: 'emergency', title: 'Emergency Contact', icon: <PersonIcon size={20} color={colors.primary} /> },
|
||||||
|
{ id: 'settings', title: 'App Settings', icon: <PersonIcon size={20} color={colors.primary} /> },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleMenuPress = (item: any) => {
|
||||||
|
if (item.route) {
|
||||||
|
navigation.navigate(item.route);
|
||||||
|
} else {
|
||||||
|
Alert.alert('Menu Option', `Viewing ${item.title} section...`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
Alert.alert(
|
||||||
|
'Logout',
|
||||||
|
'Are you sure you want to log out of SG Delivery Partner?',
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Logout',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => {
|
||||||
|
dispatch(logout());
|
||||||
|
// Reset navigation stack to the Auth Stack
|
||||||
|
navigation.dispatch(
|
||||||
|
CommonActions.reset({
|
||||||
|
index: 0,
|
||||||
|
routes: [{ name: 'Auth' }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }] }>
|
<View style={styles.container}>
|
||||||
<Text style={styles.screenTitle}>Profile Screen</Text>
|
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
{/* Header User Card */}
|
||||||
|
<View style={styles.headerCard}>
|
||||||
|
<View style={styles.avatar}>
|
||||||
|
<PersonIcon size={44} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.name}>Rahul Kumar</Text>
|
||||||
|
|
||||||
|
<View style={styles.ratingRow}>
|
||||||
|
<StarIcon size={16} color={colors.warning} />
|
||||||
|
<Text style={styles.ratingText}>4.8</Text>
|
||||||
|
<Text style={styles.tripsText}>(128 Trips)</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.idText}>ID: DP126478</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Profile Menu options */}
|
||||||
|
<View style={styles.menuSection}>
|
||||||
|
{menuItems.map((item, idx) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={item.id}
|
||||||
|
style={[styles.menuItem, idx === menuItems.length - 1 && { borderBottomWidth: 0 }]}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
onPress={() => handleMenuPress(item)}
|
||||||
|
>
|
||||||
|
<View style={styles.menuItemLeft}>
|
||||||
|
<View style={styles.menuIconContainer}>{item.icon}</View>
|
||||||
|
<Text style={styles.menuText}>{item.title}</Text>
|
||||||
|
</View>
|
||||||
|
<ChevronRightIcon size={20} color={colors.textSecondary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Logout Button */}
|
||||||
|
<TouchableOpacity style={styles.logoutBtn} activeOpacity={0.8} onPress={handleLogout}>
|
||||||
|
<Text style={styles.logoutText}>Log Out</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
screenTitle: {
|
|
||||||
fontSize: 24,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ProfileScreen;
|
export default ProfileScreen;
|
||||||
|
|||||||
@ -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 React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { PrimaryButton } from '@components';
|
import { PrimaryButton } from '@components';
|
||||||
|
import { LocationPinIcon } from '@icons';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
import { getStyles } from './setLocationScreen.styles';
|
||||||
|
|
||||||
const SetLocationScreen: React.FC = () => {
|
const SetLocationScreen: React.FC = () => {
|
||||||
const { colors } = useAppTheme();
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }] }>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>Set Location</Text>
|
{/* Blank Map Placeholder Area */}
|
||||||
<PrimaryButton
|
<View style={styles.mapContainer}>
|
||||||
title="Continue"
|
<View style={styles.placeholderMap}>
|
||||||
onPress={() => navigation.navigate(RouteNames.CompleteProfile)}
|
{/* Pulsing marker simulation */}
|
||||||
style={{ marginTop: 20, width: '90%' }}
|
<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="Confirm Location"
|
||||||
|
onPress={() => navigation.navigate(RouteNames.CompleteProfile)}
|
||||||
|
style={styles.confirmButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
||||||
title: { fontSize: 24 }
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SetLocationScreen;
|
export default SetLocationScreen;
|
||||||
|
|||||||
@ -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 { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { AppStackParamList } from '@navigation/navigationTypes';
|
import { AppStackParamList } from '@navigation/navigationTypes';
|
||||||
import BottomTabNavigator from './bottomTabNavigator';
|
import BottomTabNavigator from './bottomTabNavigator';
|
||||||
import NewJobRequestScreen from '@features/screens/newJobRequestScreen/newJobRequestScreen';
|
import {
|
||||||
import OrderAcceptedScreen from '@features/screens/orderAcceptedScreen/orderAcceptedScreen';
|
NewJobRequestScreen,
|
||||||
import ArrivedAtStoreScreen from '@features/screens/arrivedAtStoreScreen/arrivedAtStoreScreen';
|
OrderAcceptedScreen,
|
||||||
import ConfirmPickupScreen from '@features/screens/confirmPickupScreen/confirmPickupScreen';
|
ArrivedAtStoreScreen,
|
||||||
import OrderPickedUpScreen from '@features/screens/orderPickedUpScreen/orderPickedUpScreen';
|
ConfirmPickupScreen,
|
||||||
import LiveTrackingScreen from '@features/screens/liveTrackingScreen/liveTrackingScreen';
|
OrderPickedUpScreen,
|
||||||
import ArrivedAtCustomerScreen from '@features/screens/arrivedAtCustomerScreen/arrivedAtCustomerScreen';
|
LiveTrackingScreen,
|
||||||
import DeliverOrderScreen from '@features/screens/deliverOrderScreen/deliverOrderScreen';
|
ArrivedAtCustomerScreen,
|
||||||
import DeliveryCompletedScreen from '@features/screens/deliveryCompletedScreen/deliveryCompletedScreen';
|
DeliverOrderScreen,
|
||||||
import DocumentsScreen from '@features/screens/documentsScreen/documentsScreen';
|
DeliveryCompletedScreen,
|
||||||
|
DocumentsScreen,
|
||||||
|
} from '@features/screens';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<AppStackParamList>();
|
const Stack = createNativeStackNavigator<AppStackParamList>();
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { AuthStackParamList } from '@navigation/navigationTypes';
|
import { AuthStackParamList } from '@navigation/navigationTypes';
|
||||||
import LoginScreen from '@features/screens/loginScreen';
|
import {
|
||||||
import OtpScreen from '@features/screens/otpScreen';
|
LoginScreen,
|
||||||
import SetLocationScreen from '@features/screens/setLocationScreen';
|
OtpScreen,
|
||||||
import CompleteProfileScreen from '@features/screens/completeProfileScreen';
|
SetLocationScreen,
|
||||||
import OnboardingCompleteScreen from '@features/screens/onboardingCompleteScreen';
|
CompleteProfileScreen,
|
||||||
|
OnboardingCompleteScreen,
|
||||||
|
} from '@features/screens';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
||||||
@ -15,9 +17,18 @@ const AuthStack: React.FC = () => {
|
|||||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||||
<Stack.Screen name={RouteNames.Login} component={LoginScreen} />
|
<Stack.Screen name={RouteNames.Login} component={LoginScreen} />
|
||||||
<Stack.Screen name={RouteNames.Otp} component={OtpScreen} />
|
<Stack.Screen name={RouteNames.Otp} component={OtpScreen} />
|
||||||
<Stack.Screen name={RouteNames.SetLocation} component={SetLocationScreen} />
|
<Stack.Screen
|
||||||
<Stack.Screen name={RouteNames.CompleteProfile} component={CompleteProfileScreen} />
|
name={RouteNames.SetLocation}
|
||||||
<Stack.Screen name={RouteNames.OnboardingComplete} component={OnboardingCompleteScreen} />
|
component={SetLocationScreen}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name={RouteNames.CompleteProfile}
|
||||||
|
component={CompleteProfileScreen}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name={RouteNames.OnboardingComplete}
|
||||||
|
component={OnboardingCompleteScreen}
|
||||||
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import { BottomTabParamList } from '@navigation/navigationTypes';
|
import { BottomTabParamList } from '@navigation/navigationTypes';
|
||||||
import DashboardScreen from '@features/screens/dashboardScreen';
|
import {
|
||||||
import EarningsScreen from '@features/screens/earningsScreen';
|
DashboardScreen,
|
||||||
import JobsScreen from '@features/screens/jobsScreen';
|
EarningsScreen,
|
||||||
import InboxScreen from '@features/screens/inboxScreen/inboxScreen';
|
JobsScreen,
|
||||||
import ProfileScreen from '@features/screens/profileScreen/profileScreen';
|
InboxScreen,
|
||||||
|
ProfileScreen,
|
||||||
|
} from '@features/screens';
|
||||||
import { useAppTheme } from '@theme';
|
import { useAppTheme } from '@theme';
|
||||||
import { RouteNames } from '@utils/constants';
|
import { RouteNames } from '@utils/constants';
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class LocationService {
|
|||||||
// Mocked location resolver
|
// Mocked location resolver
|
||||||
async getCurrentLocation(): Promise<{ coordinates: Coordinates; address: string; city: string; state: string; pincode: string }> {
|
async getCurrentLocation(): Promise<{ coordinates: Coordinates; address: string; city: string; state: string; pincode: string }> {
|
||||||
// Return mock coordinates and address after a delay
|
// Return mock coordinates and address after a delay
|
||||||
await new Promise(resolve => setTimeout(resolve, 800));
|
await new Promise<void>(resolve => setTimeout(resolve, 800));
|
||||||
return {
|
return {
|
||||||
coordinates: {
|
coordinates: {
|
||||||
latitude: 12.9716,
|
latitude: 12.9716,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ type Callback = (data: any) => void;
|
|||||||
class MockSocketService {
|
class MockSocketService {
|
||||||
private isConnected = false;
|
private isConnected = false;
|
||||||
private listeners: Record<string, Callback[]> = {};
|
private listeners: Record<string, Callback[]> = {};
|
||||||
private mockJobInterval: NodeJS.Timeout | null = null;
|
private mockJobInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
private token: string | null = null;
|
private token: string | null = null;
|
||||||
|
|
||||||
connect(token: string) {
|
connect(token: string) {
|
||||||
|
|||||||
@ -8,21 +8,19 @@ const KEYS = {
|
|||||||
|
|
||||||
class StorageService {
|
class StorageService {
|
||||||
async saveToken(token: string, partnerId: string): Promise<void> {
|
async saveToken(token: string, partnerId: string): Promise<void> {
|
||||||
await AsyncStorage.multiSet([
|
await AsyncStorage.setItem(KEYS.TOKEN, token);
|
||||||
[KEYS.TOKEN, token],
|
await AsyncStorage.setItem(KEYS.PARTNER_ID, partnerId);
|
||||||
[KEYS.PARTNER_ID, partnerId],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getToken(): Promise<{ token: string | null; partnerId: string | null }> {
|
async getToken(): Promise<{ token: string | null; partnerId: string | null }> {
|
||||||
const values = await AsyncStorage.multiGet([KEYS.TOKEN, KEYS.PARTNER_ID]);
|
const token = await AsyncStorage.getItem(KEYS.TOKEN);
|
||||||
const token = values[0][1];
|
const partnerId = await AsyncStorage.getItem(KEYS.PARTNER_ID);
|
||||||
const partnerId = values[1][1];
|
|
||||||
return { token, partnerId };
|
return { token, partnerId };
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeToken(): Promise<void> {
|
async removeToken(): Promise<void> {
|
||||||
await AsyncStorage.multiRemove([KEYS.TOKEN, KEYS.PARTNER_ID]);
|
await AsyncStorage.removeItem(KEYS.TOKEN);
|
||||||
|
await AsyncStorage.removeItem(KEYS.PARTNER_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setOnboardingComplete(complete: boolean): Promise<void> {
|
async setOnboardingComplete(complete: boolean): Promise<void> {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const authApi = createApi({
|
|||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
sendOtp: builder.mutation<{ requestId: string }, { phone: string }>({
|
sendOtp: builder.mutation<{ requestId: string }, { phone: string }>({
|
||||||
queryFn: async ({ phone: _phone }) => {
|
queryFn: async ({ phone: _phone }) => {
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
await new Promise<void>((r) => setTimeout(r, 1000));
|
||||||
return { data: mockSendOtpResponse };
|
return { data: mockSendOtpResponse };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -40,7 +40,7 @@ export const authApi = createApi({
|
|||||||
{ requestId: string; otp: string }
|
{ requestId: string; otp: string }
|
||||||
>({
|
>({
|
||||||
queryFn: async ({ otp }) => {
|
queryFn: async ({ otp }) => {
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
await new Promise<void>((r) => setTimeout(r, 1000));
|
||||||
if (otp === '000000') {
|
if (otp === '000000') {
|
||||||
return { error: { status: 'CUSTOM_ERROR', error: 'Invalid OTP' } };
|
return { error: { status: 'CUSTOM_ERROR', error: 'Invalid OTP' } };
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ export const authApi = createApi({
|
|||||||
}),
|
}),
|
||||||
getProfile: builder.query<PartnerProfile, string>({
|
getProfile: builder.query<PartnerProfile, string>({
|
||||||
queryFn: async (_partnerId) => {
|
queryFn: async (_partnerId) => {
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
await new Promise<void>((r) => setTimeout(r, 500));
|
||||||
return { data: mockProfile };
|
return { data: mockProfile };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -58,7 +58,7 @@ export const authApi = createApi({
|
|||||||
Partial<PartnerProfile>
|
Partial<PartnerProfile>
|
||||||
>({
|
>({
|
||||||
queryFn: async (updates) => {
|
queryFn: async (updates) => {
|
||||||
await new Promise((r) => setTimeout(r, 800));
|
await new Promise<void>((r) => setTimeout(r, 800));
|
||||||
return { data: { ...mockProfile, ...updates } };
|
return { data: { ...mockProfile, ...updates } };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -8,13 +8,13 @@ export const earningsApi = createApi({
|
|||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getTodayEarnings: builder.query<typeof mockTodayEarnings, void>({
|
getTodayEarnings: builder.query<typeof mockTodayEarnings, void>({
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
await new Promise<void>((r) => setTimeout(r, 500));
|
||||||
return { data: mockTodayEarnings };
|
return { data: mockTodayEarnings };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getWeeklyEarnings: builder.query<DayEarning[], void>({
|
getWeeklyEarnings: builder.query<DayEarning[], void>({
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
await new Promise<void>((r) => setTimeout(r, 500));
|
||||||
return { data: mockWeeklyEarnings };
|
return { data: mockWeeklyEarnings };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export const jobApi = createApi({
|
|||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
acceptJob: builder.mutation<Job, string>({
|
acceptJob: builder.mutation<Job, string>({
|
||||||
queryFn: async (jobId) => {
|
queryFn: async (jobId) => {
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
await new Promise<void>((r) => setTimeout(r, 500));
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
...mockJobOffer,
|
...mockJobOffer,
|
||||||
@ -24,13 +24,13 @@ export const jobApi = createApi({
|
|||||||
}),
|
}),
|
||||||
rejectJob: builder.mutation<void, string>({
|
rejectJob: builder.mutation<void, string>({
|
||||||
queryFn: async (_jobId) => {
|
queryFn: async (_jobId) => {
|
||||||
await new Promise((r) => setTimeout(r, 300));
|
await new Promise<void>((r) => setTimeout(r, 300));
|
||||||
return { data: undefined };
|
return { data: undefined };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
confirmPickup: builder.mutation<void, string>({
|
confirmPickup: builder.mutation<void, string>({
|
||||||
queryFn: async (_jobId) => {
|
queryFn: async (_jobId) => {
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
await new Promise<void>((r) => setTimeout(r, 500));
|
||||||
return { data: undefined };
|
return { data: undefined };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -39,13 +39,13 @@ export const jobApi = createApi({
|
|||||||
{ jobId: string; rating: number }
|
{ jobId: string; rating: number }
|
||||||
>({
|
>({
|
||||||
queryFn: async ({ jobId: _jobId }) => {
|
queryFn: async ({ jobId: _jobId }) => {
|
||||||
await new Promise((r) => setTimeout(r, 800));
|
await new Promise<void>((r) => setTimeout(r, 800));
|
||||||
return { data: { earnings: 78 } };
|
return { data: { earnings: 78 } };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
reportIssue: builder.mutation<void, { jobId: string; reason: string }>({
|
reportIssue: builder.mutation<void, { jobId: string; reason: string }>({
|
||||||
queryFn: async (_args) => {
|
queryFn: async (_args) => {
|
||||||
await new Promise((r) => setTimeout(r, 500));
|
await new Promise<void>((r) => setTimeout(r, 500));
|
||||||
return { data: undefined };
|
return { data: undefined };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -11,10 +11,12 @@
|
|||||||
"@theme": ["./app/theme"],
|
"@theme": ["./app/theme"],
|
||||||
"@theme/*": ["./app/theme/*"],
|
"@theme/*": ["./app/theme/*"],
|
||||||
"@navigation/*": ["./app/navigation/*"],
|
"@navigation/*": ["./app/navigation/*"],
|
||||||
|
"@store": ["./app/store"],
|
||||||
"@store/*": ["./app/store/*"],
|
"@store/*": ["./app/store/*"],
|
||||||
"@services/*": ["./app/services/*"],
|
"@services/*": ["./app/services/*"],
|
||||||
"@hooks/*": ["./app/hooks/*"],
|
"@hooks/*": ["./app/hooks/*"],
|
||||||
"@utils/*": ["./app/utils/*"],
|
"@utils/*": ["./app/utils/*"],
|
||||||
|
"@icons": ["./app/components/icons"],
|
||||||
"@icons/*": ["./app/components/icons/*"],
|
"@icons/*": ["./app/components/icons/*"],
|
||||||
"@app-types/*": ["./app/types/*"]
|
"@app-types/*": ["./app/types/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user