feat: implement onboarding stack and various feature screens for OTP, bank details, and profile management.

This commit is contained in:
Tamojit Biswas 2026-07-21 18:27:32 +05:30
parent 65f685d5cf
commit 525ed45b23
7 changed files with 88 additions and 38 deletions

View File

@ -56,7 +56,9 @@ export const OtpScreen: React.FC<{ route: any }> = ({ route }) => {
await dispatch( await dispatch(
verifyOtp({ phone, code: otp, role: 'DELIVERY' }), verifyOtp({ phone, code: otp, role: 'DELIVERY' }),
).unwrap(); ).unwrap();
navigation.navigate('OnBoarding', { screen: RouteNames.CompleteProfile }); // Navigation is handled automatically by rootNavigator's conditional
// rendering: once accessToken is set, the Auth stack unmounts and
// OnBoarding or App stack mounts based on user.status.
} catch (error) { } catch (error) {
Alert.alert('Error', error as string); Alert.alert('Error', error as string);
} }

View File

@ -12,10 +12,8 @@ import { WalletIcon, CheckCircleIcon, ClipboardIcon } 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 { AppStackParamList } from '@navigation/navigationTypes'; import { AppStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import { BankAccount } from '@interfaces';
import { getStyles } from './bankDetailsScreen.styles'; import { getStyles } from './bankDetailsScreen.styles';
import { formatDate, maskAccountNumber } from '@utils'; import { formatDate, maskAccountNumber, RouteNames } from '@utils';
import { fetchBankAccounts } from '../addBankDetailsScreen'; import { fetchBankAccounts } from '../addBankDetailsScreen';
import { useAppDispatch, useAppSelector } from '@store'; import { useAppDispatch, useAppSelector } from '@store';

View File

@ -8,6 +8,7 @@ export interface completeProfileState {
error: string | null; error: string | null;
submitError: string | null; submitError: string | null;
userProfile: UserProfile | null; userProfile: UserProfile | null;
alreadyNavigate: boolean;
} }
const initialState: completeProfileState = { const initialState: completeProfileState = {
@ -15,6 +16,7 @@ const initialState: completeProfileState = {
error: null, error: null,
submitError: null, submitError: null,
userProfile: null, userProfile: null,
alreadyNavigate: false,
}; };
export const completeProfileReducer = createReducer(initialState, builder => { export const completeProfileReducer = createReducer(initialState, builder => {
@ -26,6 +28,7 @@ export const completeProfileReducer = createReducer(initialState, builder => {
.addCase(completeOnboard.fulfilled, (state, action) => { .addCase(completeOnboard.fulfilled, (state, action) => {
state.isLoading = false; state.isLoading = false;
state.userProfile = action.payload; state.userProfile = action.payload;
state.alreadyNavigate = true;
}) })
.addCase(completeOnboard.rejected, (state, action) => { .addCase(completeOnboard.rejected, (state, action) => {
state.isLoading = false; state.isLoading = false;

View File

@ -198,7 +198,7 @@ export const DocumentsScreen: React.FC = () => {
statusBarTranslucent statusBarTranslucent
onRequestClose={() => setSelectedDoc(null)} onRequestClose={() => setSelectedDoc(null)}
> >
<SafeAreaView style={styles.modalContainer}> <View style={styles.modalContainer}>
{/* Modal Header */} {/* Modal Header */}
<View style={styles.modalHeader}> <View style={styles.modalHeader}>
<View style={styles.modalHeaderInfo}> <View style={styles.modalHeaderInfo}>
@ -259,7 +259,7 @@ export const DocumentsScreen: React.FC = () => {
) )
)} )}
</View> </View>
</SafeAreaView> </View>
</Modal> </Modal>
</View> </View>
); );

View File

@ -10,35 +10,75 @@ export const EarningsScreen: React.FC = () => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
const styles = getStyles(colors); const styles = getStyles(colors);
const { todayEarnings, completedCount, onlineMinutes } = useAppSelector((state) => state.earnings); const { todayEarnings, completedCount, onlineMinutes } = useAppSelector(
const { jobHistory } = useAppSelector((state) => state.job); state => state.earnings,
);
const { jobHistory } = useAppSelector(state => state.job);
// Default mock jobs history if none completed yet // Default mock jobs history if none completed yet
const defaultHistory: { orderId: string; time: string; amount: number; paymentType: 'online' | 'cod'; }[] = [ const defaultHistory: {
{ orderId: '#ORD125487', time: '12:35 PM', amount: 78.0, paymentType: 'online' }, orderId: string;
{ orderId: '#ORD125422', time: '11:15 AM', amount: 65.0, paymentType: 'cod' }, time: string;
{ orderId: '#ORD125389', time: '09:40 AM', amount: 120.0, paymentType: 'online' }, amount: string;
{ orderId: '#ORD125310', time: 'Yesterday', amount: 85.0, paymentType: 'online' }, 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 const displayHistory =
? jobHistory.map(job => ({ jobHistory.length > 0
orderId: job.orderId, ? jobHistory
time: job.deliveredAt ? new Date(job.deliveredAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : 'Delivered', .map(job => ({
amount: job.amount, orderId: job.orderId,
paymentType: job.paymentType, time: job.deliveredAt
})).concat(defaultHistory) ? new Date(job.deliveredAt).toLocaleTimeString([], {
: defaultHistory; hour: '2-digit',
minute: '2-digit',
})
: 'Delivered',
amount: job.amount,
paymentType: job.paymentType,
}))
.concat(defaultHistory)
: defaultHistory;
const formatOnlineTime = (mins: number) => { const formatOnlineTime = (mins: number) => {
const hours = Math.floor(mins / 60); const hours = Math.floor(mins / 60);
const remainingMins = mins % 60; const remainingMins = mins % 60;
return `${hours}h ${remainingMins < 10 ? `0${remainingMins}` : remainingMins}m`; return `${hours}h ${
remainingMins < 10 ? `0${remainingMins}` : remainingMins
}m`;
}; };
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ScrollView contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}> <ScrollView
contentContainerStyle={styles.scrollContainer}
showsVerticalScrollIndicator={false}
>
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
<Text style={styles.title}>Earnings</Text> <Text style={styles.title}>Earnings</Text>
</View> </View>
@ -46,8 +86,10 @@ export const EarningsScreen: React.FC = () => {
{/* Total Earnings Card */} {/* Total Earnings Card */}
<View style={styles.summaryCard}> <View style={styles.summaryCard}>
<Text style={styles.summaryTitle}>Today's Earnings</Text> <Text style={styles.summaryTitle}>Today's Earnings</Text>
<Text style={styles.totalAmount}>{formatCurrency(todayEarnings)}</Text> <Text style={styles.totalAmount}>
{formatCurrency(todayEarnings)}
</Text>
<View style={styles.statsRow}> <View style={styles.statsRow}>
<View style={styles.statBox}> <View style={styles.statBox}>
<Text style={styles.statVal}>{completedCount}</Text> <Text style={styles.statVal}>{completedCount}</Text>
@ -57,7 +99,9 @@ export const EarningsScreen: React.FC = () => {
<View style={styles.statDivider} /> <View style={styles.statDivider} />
<View style={styles.statBox}> <View style={styles.statBox}>
<Text style={styles.statVal}>{formatOnlineTime(onlineMinutes)}</Text> <Text style={styles.statVal}>
{formatOnlineTime(onlineMinutes)}
</Text>
<Text style={styles.statLabel}>Online Time</Text> <Text style={styles.statLabel}>Online Time</Text>
</View> </View>
@ -73,7 +117,7 @@ export const EarningsScreen: React.FC = () => {
{/* Recent Deliveries List */} {/* Recent Deliveries List */}
<View style={styles.listSection}> <View style={styles.listSection}>
<Text style={styles.sectionTitle}>Recent Deliveries</Text> <Text style={styles.sectionTitle}>Recent Deliveries</Text>
{displayHistory.map((item, index) => ( {displayHistory.map((item, index) => (
<View key={index} style={styles.earningItem}> <View key={index} style={styles.earningItem}>
<View style={styles.itemLeft}> <View style={styles.itemLeft}>
@ -87,9 +131,13 @@ export const EarningsScreen: React.FC = () => {
</View> </View>
<View style={styles.itemRight}> <View style={styles.itemRight}>
<Text style={styles.itemAmount}>+{formatCurrency(item.amount)}</Text> <Text style={styles.itemAmount}>
+{formatCurrency(Number(item.amount))}
</Text>
<View style={styles.paymentBadge}> <View style={styles.paymentBadge}>
<Text style={styles.paymentText}>{item.paymentType.toUpperCase()}</Text> <Text style={styles.paymentText}>
{item.paymentType.toUpperCase()}
</Text>
</View> </View>
</View> </View>
</View> </View>

View File

@ -19,11 +19,6 @@ export const ProfileScreen: React.FC = () => {
const { profile } = useAppSelector(state => state.accountInfo); const { profile } = useAppSelector(state => state.accountInfo);
const menuItems = [ const menuItems = [
{
id: 'personal',
title: 'Personal Info',
icon: <PersonIcon size={20} color={colors.primary} />,
},
{ {
id: 'vehicle', id: 'vehicle',
title: 'Vehicle Info', title: 'Vehicle Info',

View File

@ -8,20 +8,24 @@ import {
KycScreen, KycScreen,
} from '@features/screens'; } from '@features/screens';
import { RouteNames } from '@utils/constants'; import { RouteNames } from '@utils/constants';
import { useAppSelector } from '@store';
const Stack = createNativeStackNavigator<OnBoardingParamList>(); const Stack = createNativeStackNavigator<OnBoardingParamList>();
const onBoardingStack: React.FC = () => { const onBoardingStack: React.FC = () => {
const { alreadyNavigate } = useAppSelector(state => state.onboard);
return ( return (
<Stack.Navigator screenOptions={{ headerShown: false }}> <Stack.Navigator screenOptions={{ headerShown: false }}>
{/* <Stack.Screen {/* <Stack.Screen
name={RouteNames.SetLocation} name={RouteNames.SetLocation}
component={SetLocationScreen} component={SetLocationScreen}
/> */} /> */}
<Stack.Screen {!alreadyNavigate && (
name={RouteNames.CompleteProfile} <Stack.Screen
component={CompleteProfileScreen} name={RouteNames.CompleteProfile}
/> component={CompleteProfileScreen}
/>
)}
<Stack.Screen name={RouteNames.Kyc} component={KycScreen} /> <Stack.Screen name={RouteNames.Kyc} component={KycScreen} />
<Stack.Screen <Stack.Screen
name={RouteNames.OnboardingComplete} name={RouteNames.OnboardingComplete}