feat(app): initial commit

This commit is contained in:
uttam 2026-07-02 14:34:19 +05:30
parent bdd81eef31
commit a8a04a02f6
148 changed files with 17227 additions and 958 deletions

View File

@ -3,17 +3,21 @@ import {
SafeAreaProvider, SafeAreaProvider,
useSafeAreaInsets, useSafeAreaInsets,
} from 'react-native-safe-area-context'; } from 'react-native-safe-area-context';
import { LoginScreen } from '@features/screens'; import RootNavigator from '@navigation/rootNavigator';
import { Provider } from 'react-redux';
import { store } from '@store';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
function App() { function App() {
const isDarkMode = useColorScheme() === 'dark'; const isDarkMode = useColorScheme() === 'dark';
return ( return (
<SafeAreaProvider> <Provider store={store}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} /> <SafeAreaProvider>
<AppContent /> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
</SafeAreaProvider> <AppContent />
</SafeAreaProvider>
</Provider>
); );
} }
@ -32,7 +36,7 @@ function AppContent() {
}, },
]} ]}
> >
<LoginScreen /> <RootNavigator />
</View> </View>
); );
} }

View File

@ -46,4 +46,9 @@ export const getStyles = (colors: any) => StyleSheet.create({
marginTop: 4, marginTop: 4,
marginLeft: 4, marginLeft: 4,
}, },
actionsRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
}); });

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity } from 'react-native'; import { View, Text, TextInput, TouchableOpacity } from 'react-native';
import { CustomInputProps } from './CustomInput.props'; import { CustomInputProps } from './customInput.props';
import { getStyles } from './CustomInput.styles'; import { getStyles } from './customInput.styles';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
export const CustomInput: React.FC<CustomInputProps> = ({ export const CustomInput: React.FC<CustomInputProps> = ({
@ -34,7 +34,7 @@ export const CustomInput: React.FC<CustomInputProps> = ({
autoCorrect={false} autoCorrect={false}
{...rest} {...rest}
/> />
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}> <View style={styles.actionsRow}>
{isPassword && ( {isPassword && (
<TouchableOpacity onPress={() => setIsSecure(!isSecure)} activeOpacity={0.7}> <TouchableOpacity onPress={() => setIsSecure(!isSecure)} activeOpacity={0.7}>
<Text style={styles.rightAction}>{isSecure ? 'Show' : 'Hide'}</Text> <Text style={styles.rightAction}>{isSecure ? 'Show' : 'Hide'}</Text>

View File

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

View File

@ -1,7 +1,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> = ({

View File

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

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { TouchableOpacity, Text } from 'react-native'; import { TouchableOpacity, Text } from 'react-native';
import { SocialButtonProps } from './SocialButton.props'; import { SocialButtonProps } from './socialButton.props';
import { getStyles } from './SocialButton.styles'; import { getStyles } from './socialButton.styles';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import Svg, { Path } from 'react-native-svg'; import Svg, { Path } from 'react-native-svg';

View File

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

View File

@ -0,0 +1,21 @@
import { StyleSheet } from 'react-native';
import { typography, borderRadius } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
minWidth: 18,
height: 18,
borderRadius: borderRadius.full,
backgroundColor: colors.error,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 4,
},
text: {
color: colors.white,
fontSize: typography.fontSize.xs - 2,
fontWeight: typography.fontWeight.bold,
textAlign: 'center',
},
});

View File

@ -0,0 +1,30 @@
import React from 'react';
import { View, Text, ViewStyle } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './badge.styles';
interface BadgeProps {
count: number;
color?: string;
style?: ViewStyle;
}
export const Badge: React.FC<BadgeProps> = ({ count, color, style }) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
if (count <= 0) return null;
return (
<View
style={[
styles.container,
color ? { backgroundColor: color } : null,
style,
]}
>
<Text style={styles.text}>{count > 99 ? '99+' : count}</Text>
</View>
);
};
export default Badge;

View File

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

View File

@ -0,0 +1,15 @@
import { StyleSheet } from 'react-native';
import { borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
card: {
backgroundColor: colors.cardBg,
borderRadius: borderRadius.md,
padding: 16,
marginVertical: 8,
borderWidth: 1,
borderColor: colors.border,
...shadow.sm,
},
});

View File

@ -0,0 +1,30 @@
import React from 'react';
import { View, ViewStyle, TouchableOpacity } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './card.styles';
interface CardProps {
children: React.ReactNode;
style?: ViewStyle;
onPress?: () => void;
}
export const Card: React.FC<CardProps> = ({ children, style, onPress }) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
if (onPress) {
return (
<TouchableOpacity
style={[styles.card, style]}
activeOpacity={0.8}
onPress={onPress}
>
{children}
</TouchableOpacity>
);
}
return <View style={[styles.card, style]}>{children}</View>;
};
export default Card;

View File

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

View File

@ -0,0 +1,34 @@
import { StyleSheet } from 'react-native';
import { typography, borderRadius } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flexDirection: 'row',
marginVertical: 12,
gap: 12,
},
chip: {
flex: 1,
paddingVertical: 12,
borderWidth: 1.5,
borderColor: colors.border,
borderRadius: borderRadius.md,
backgroundColor: colors.chipInactive,
alignItems: 'center',
justifyContent: 'center',
},
chipActive: {
backgroundColor: colors.chipActive,
borderColor: colors.chipBorder,
},
label: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.medium,
color: colors.textSecondary,
},
labelActive: {
color: colors.primary,
fontWeight: typography.fontWeight.semibold,
},
});

View File

@ -0,0 +1,46 @@
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './chipSelector.styles';
interface Option {
label: string;
value: string;
}
interface ChipSelectorProps {
options: Option[];
selected: string;
onChange: (value: any) => void;
}
export const ChipSelector: React.FC<ChipSelectorProps> = ({
options,
selected,
onChange,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<View style={styles.container}>
{options.map((option) => {
const isActive = option.value === selected;
return (
<TouchableOpacity
key={option.value}
style={[styles.chip, isActive && styles.chipActive]}
activeOpacity={0.7}
onPress={() => onChange(option.value)}
>
<Text style={[styles.label, isActive && styles.labelActive]}>
{option.label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
};
export default ChipSelector;

View File

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

View File

@ -0,0 +1,25 @@
import { StyleSheet } from 'react-native';
import { typography } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 10,
},
outerCircle: {
width: 70,
height: 70,
borderRadius: 35,
borderWidth: 3,
borderColor: colors.border,
justifyContent: 'center',
alignItems: 'center',
},
timerText: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
},
});

View File

@ -0,0 +1,44 @@
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './countdownTimer.styles';
interface CountdownTimerProps {
durationSeconds: number;
onExpire: () => void;
color?: string;
}
export const CountdownTimer: React.FC<CountdownTimerProps> = ({
durationSeconds,
onExpire,
color,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const [timeLeft, setTimeLeft] = useState(durationSeconds);
useEffect(() => {
if (timeLeft <= 0) {
onExpire();
return;
}
const timer = setInterval(() => {
setTimeLeft((prev) => prev - 1);
}, 1000);
return () => clearInterval(timer);
}, [timeLeft, onExpire]);
const progressColor = color || colors.primary;
return (
<View style={styles.container}>
<View style={[styles.outerCircle, { borderColor: progressColor }]}>
<Text style={[styles.timerText, { color: progressColor }]}>{timeLeft}s</Text>
</View>
</View>
);
};
export default CountdownTimer;

View File

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

View File

@ -0,0 +1,10 @@
import { StyleSheet } from 'react-native';
export const getStyles = (colors: any) =>
StyleSheet.create({
divider: {
width: '100%',
backgroundColor: colors.divider,
marginVertical: 12,
},
});

View File

@ -0,0 +1,31 @@
import React from 'react';
import { View, ViewStyle } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './divider.styles';
interface DividerProps {
color?: string;
thickness?: number;
style?: ViewStyle;
}
export const Divider: React.FC<DividerProps> = ({
color,
thickness = 1,
style,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<View
style={[
styles.divider,
{ height: thickness },
color ? { backgroundColor: color } : null,
style,
]}
/>
);
};
export default Divider;

View File

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

View File

@ -0,0 +1,41 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: spacing.xxl,
backgroundColor: colors.background,
},
iconContainer: {
marginBottom: spacing.lg,
opacity: 0.8,
},
title: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
textAlign: 'center',
marginBottom: spacing.xs,
},
subtitle: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
textAlign: 'center',
marginBottom: spacing.xl,
},
button: {
paddingVertical: spacing.md,
paddingHorizontal: spacing.xl,
backgroundColor: colors.primary,
borderRadius: 8,
},
buttonText: {
color: colors.white,
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
},
});

View File

@ -0,0 +1,37 @@
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './emptyState.styles';
interface EmptyStateProps {
icon?: React.ReactNode;
title: string;
subtitle?: string;
actionLabel?: string;
onAction?: () => void;
}
export const EmptyState: React.FC<EmptyStateProps> = ({
icon,
title,
subtitle,
actionLabel,
onAction,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<View style={styles.container}>
{icon && <View style={styles.iconContainer}>{icon}</View>}
<Text style={styles.title}>{title}</Text>
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : null}
{actionLabel && onAction ? (
<TouchableOpacity style={styles.button} activeOpacity={0.8} onPress={onAction}>
<Text style={styles.buttonText}>{actionLabel}</Text>
</TouchableOpacity>
) : null}
</View>
);
};
export default EmptyState;

View File

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

View File

@ -0,0 +1,24 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const BackIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M19 12H5M12 19l-7-7 7-7"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default BackIcon;

View File

@ -0,0 +1,24 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const BellIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 01-3.46 0"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default BellIcon;

View File

@ -0,0 +1,24 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const CallIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default CallIcon;

View File

@ -0,0 +1,31 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const CheckCircleIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M22 11.08V12a10 10 0 11-5.93-9.14"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M22 4L12 14.01l-3-3"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default CheckCircleIcon;

View File

@ -0,0 +1,24 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const ChevronRightIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M9 18l6-6-6-6"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default ChevronRightIcon;

View File

@ -0,0 +1,35 @@
import React from 'react';
import Svg, { Path, Rect } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const ClipboardIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<Rect
x="8"
y="2"
width="8"
height="4"
rx="1"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default ClipboardIcon;

View File

@ -0,0 +1,24 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const CloseIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M18 6L6 18M6 6l12 12"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default CloseIcon;

View File

@ -0,0 +1,31 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const HomeIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M9 22V12h6v10"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default HomeIcon;

View File

@ -0,0 +1,12 @@
export * from './homeIcon';
export * from './walletIcon';
export * from './clipboardIcon';
export * from './bellIcon';
export * from './personIcon';
export * from './chevronRightIcon';
export * from './callIcon';
export * from './locationPinIcon';
export * from './checkCircleIcon';
export * from './starIcon';
export * from './closeIcon';
export * from './backIcon';

View File

@ -0,0 +1,33 @@
import React from 'react';
import Svg, { Path, Circle } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const LocationPinIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<Circle
cx="12"
cy="10"
r="3"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default LocationPinIcon;

View File

@ -0,0 +1,33 @@
import React from 'react';
import Svg, { Path, Circle } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const PersonIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<Circle
cx="12"
cy="7"
r="4"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default PersonIcon;

View File

@ -0,0 +1,26 @@
import React from 'react';
import Svg, { Polygon } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
fill?: string;
style?: ViewStyle;
}
export const StarIcon: React.FC<IconProps> = ({ size = 24, color = '#666', fill = 'none', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
stroke={color}
strokeWidth="2"
fill={fill}
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default StarIcon;

View File

@ -0,0 +1,31 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { ViewStyle } from 'react-native';
interface IconProps {
size?: number;
color?: string;
style?: ViewStyle;
}
export const WalletIcon: React.FC<IconProps> = ({ size = 24, color = '#666', style }) => {
return (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={style}>
<Path
d="M20 12V8H6a2 2 0 01-2-2 2 2 0 012-2h12v1m2 11h-4a2 2 0 000 4h4v-4z"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M4 6v12a2 2 0 002 2h14a2 2 0 002-2v-4"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
};
export default WalletIcon;

View File

@ -1,3 +1,3 @@
export * from './CustomInput'; export * from './customInput';
export * from './PrimaryButton'; export * from './primaryButton';
export * from './SocialButton'; export * from './socialButton';

View File

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

View File

@ -0,0 +1,27 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
width: '100%',
paddingVertical: spacing.md,
paddingHorizontal: spacing.lg,
backgroundColor: colors.primaryLight,
borderBottomWidth: 1,
borderBottomColor: colors.border,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
label: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
},
detail: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
fontWeight: typography.fontWeight.medium,
},
});

View File

@ -0,0 +1,27 @@
import React from 'react';
import { View, Text } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './jobStatusBanner.styles';
interface JobStatusBannerProps {
stepLabel: string;
stepDetail: string;
backgroundColor?: string;
}
export const JobStatusBanner: React.FC<JobStatusBannerProps> = ({
stepLabel,
stepDetail,
backgroundColor,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<View style={[styles.container, backgroundColor ? { backgroundColor } : null]}>
<Text style={styles.label}>{stepLabel}</Text>
<Text style={styles.detail}>{stepDetail}</Text>
</View>
);
};
export default JobStatusBanner;

View File

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

View File

@ -0,0 +1,31 @@
import { StyleSheet } from 'react-native';
import { typography, borderRadius } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 9999,
},
box: {
padding: 24,
backgroundColor: colors.cardBg,
borderRadius: borderRadius.md,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 10,
elevation: 6,
},
message: {
marginTop: 16,
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.medium,
color: colors.text,
},
});

View File

@ -0,0 +1,29 @@
import React from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './loadingOverlay.styles';
interface LoadingOverlayProps {
visible: boolean;
message?: string;
}
export const LoadingOverlay: React.FC<LoadingOverlayProps> = ({
visible,
message = 'Loading...',
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
if (!visible) return null;
return (
<View style={styles.container}>
<View style={styles.box}>
<ActivityIndicator size="large" color={colors.primary} />
{message ? <Text style={styles.message}>{message}</Text> : null}
</View>
</View>
);
};
export default LoadingOverlay;

View File

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

View File

@ -0,0 +1,38 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'flex-start',
},
iconWrapper: {
width: 24,
height: 24,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
marginTop: 2,
},
pickupBg: {
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
dropBg: {
backgroundColor: 'rgba(46, 204, 113, 0.1)',
},
textContainer: {
flex: 1,
},
name: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
},
address: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
marginTop: 2,
},
});

View File

@ -0,0 +1,33 @@
import React from 'react';
import { View, Text } from 'react-native';
import { useAppTheme } from '@theme';
import { LocationPinIcon } from '@icons/locationPinIcon';
import { getStyles } from './locationRow.styles';
interface LocationRowProps {
type: 'pickup' | 'drop';
name: string;
address: string;
}
export const LocationRow: React.FC<LocationRowProps> = ({ type, name, address }) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const isPickup = type === 'pickup';
const iconColor = isPickup ? colors.statusOffline : colors.statusOnline;
const bgStyle = isPickup ? styles.pickupBg : styles.dropBg;
return (
<View style={styles.container}>
<View style={[styles.iconWrapper, bgStyle]}>
<LocationPinIcon size={16} color={iconColor} />
</View>
<View style={styles.textContainer}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.address}>{address}</Text>
</View>
</View>
);
};
export default LocationRow;

View File

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

View File

@ -0,0 +1,30 @@
import { StyleSheet } from 'react-native';
import { borderRadius, shadow } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
sheet: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: colors.cardBg,
borderTopLeftRadius: borderRadius.xl,
borderTopRightRadius: borderRadius.xl,
paddingHorizontal: 20,
paddingBottom: 30,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: colors.border,
...shadow.lg,
zIndex: 100,
},
handle: {
width: 40,
height: 5,
borderRadius: 3,
backgroundColor: colors.border,
alignSelf: 'center',
marginBottom: 16,
},
});

View File

@ -0,0 +1,21 @@
import React from 'react';
import { View } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './mapBottomSheet.styles';
interface MapBottomSheetProps {
children: React.ReactNode;
}
export const MapBottomSheet: React.FC<MapBottomSheetProps> = ({ children }) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<View style={styles.sheet}>
<View style={styles.handle} />
{children}
</View>
);
};
export default MapBottomSheet;

View File

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

View File

@ -0,0 +1,28 @@
import { StyleSheet } from 'react-native';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flex: 1,
width: '100%',
height: '100%',
backgroundColor: colors.inputBg,
justifyContent: 'center',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
mockOverlay: {
position: 'absolute',
top: 40,
padding: 10,
borderRadius: 20,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
alignSelf: 'center',
},
mockText: {
color: colors.primary,
fontWeight: 'bold',
},
});

View File

@ -0,0 +1,77 @@
import React from 'react';
import { View, Text } from 'react-native';
import MapView, { Marker, Polyline, PROVIDER_DEFAULT } from 'react-native-maps';
import { useAppTheme } from '@theme';
import { Coordinates } from '@app-types/index';
import { getStyles } from './mapView.styles';
interface MapViewComponentProps {
origin?: Coordinates;
destination?: Coordinates;
currentLocation?: Coordinates;
showRoute?: boolean;
style?: any;
}
export const MapViewComponent: React.FC<MapViewComponentProps> = ({
origin,
destination,
currentLocation,
showRoute = false,
style,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const defaultRegion = {
latitude: currentLocation?.latitude || origin?.latitude || 12.9716,
longitude: currentLocation?.longitude || origin?.longitude || 77.5946,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
};
return (
<View style={[styles.container, style]}>
<MapView
provider={PROVIDER_DEFAULT}
style={styles.map}
initialRegion={defaultRegion}
showsUserLocation={true}
loadingEnabled={true}
>
{currentLocation && (
<Marker
coordinate={currentLocation}
title="Your Location"
pinColor={colors.primary}
/>
)}
{origin && (
<Marker
coordinate={origin}
title="Pickup Location"
pinColor={colors.statusOffline}
/>
)}
{destination && (
<Marker
coordinate={destination}
title="Drop Location"
pinColor={colors.statusOnline}
/>
)}
{showRoute && origin && destination && (
<Polyline
coordinates={[origin, destination]}
strokeColor={colors.primary}
strokeWidth={4}
/>
)}
</MapView>
<View style={styles.mockOverlay}>
<Text style={styles.mockText}>📍 Live Navigation Map Active</Text>
</View>
</View>
);
};
export default MapViewComponent;

View File

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

View File

@ -0,0 +1,34 @@
import { StyleSheet } from 'react-native';
import { typography } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
width: '100%',
paddingVertical: 10,
backgroundColor: colors.background,
},
row: {
flexDirection: 'row',
justifyContent: 'space-around',
marginVertical: 8,
},
keyButton: {
width: 70,
height: 70,
borderRadius: 35,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.inputBg,
},
keyText: {
fontSize: typography.fontSize.xl,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
},
emptyKey: {
width: 70,
height: 70,
backgroundColor: 'transparent',
},
});

View File

@ -0,0 +1,62 @@
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './numericKeypad.styles';
interface NumericKeypadProps {
onPress: (digit: string) => void;
onBackspace: () => void;
disabled?: boolean;
}
export const NumericKeypad: React.FC<NumericKeypadProps> = ({
onPress,
onBackspace,
disabled = false,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const keys = [
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
['', '0', '⌫'],
];
const handlePress = (val: string) => {
if (disabled) return;
if (val === '⌫') {
onBackspace();
} else if (val !== '') {
onPress(val);
}
};
return (
<View style={styles.container}>
{keys.map((row, rIndex) => (
<View key={rIndex} style={styles.row}>
{row.map((val, cIndex) => {
if (val === '') {
return <View key={cIndex} style={styles.emptyKey} />;
}
return (
<TouchableOpacity
key={cIndex}
style={styles.keyButton}
activeOpacity={0.7}
onPress={() => handlePress(val)}
disabled={disabled}
>
<Text style={styles.keyText}>{val}</Text>
</TouchableOpacity>
);
})}
</View>
))}
</View>
);
};
export default NumericKeypad;

View File

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

View File

@ -0,0 +1,29 @@
import { StyleSheet } from 'react-native';
import { typography, borderRadius } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
button: {
width: '100%',
paddingVertical: 16,
borderRadius: borderRadius.md,
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
onlineBg: {
backgroundColor: colors.primary,
},
offlineBg: {
backgroundColor: colors.statusOffline,
},
text: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.white,
},
});

View File

@ -0,0 +1,38 @@
import React from 'react';
import { Text, TouchableOpacity, ActivityIndicator } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './onlineToggleButton.styles';
interface OnlineToggleButtonProps {
isOnline: boolean;
onToggle: () => void;
isLoading?: boolean;
}
export const OnlineToggleButton: React.FC<OnlineToggleButtonProps> = ({
isOnline,
onToggle,
isLoading = false,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const buttonStyle = isOnline ? styles.offlineBg : styles.onlineBg;
const label = isOnline ? 'Go Offline' : 'Go Online';
return (
<TouchableOpacity
style={[styles.button, buttonStyle]}
activeOpacity={0.8}
onPress={onToggle}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color={colors.white} />
) : (
<Text style={styles.text}>{label}</Text>
)}
</TouchableOpacity>
);
};
export default OnlineToggleButton;

View File

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

View File

@ -0,0 +1,49 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
amount: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
},
distance: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
fontWeight: typography.fontWeight.medium,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: spacing.sm,
},
detailsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: spacing.md,
paddingTop: spacing.md,
borderTopWidth: 1,
borderTopColor: colors.border,
},
spacer: {
height: 12,
},
centerCol: {
alignItems: 'center',
},
endCol: {
alignItems: 'flex-end',
},
label: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
},
value: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
marginTop: 2,
},
});

View File

@ -0,0 +1,63 @@
import React from 'react';
import { View, Text } from 'react-native';
import { useAppTheme } from '@theme';
import { Card } from '../card/card';
import { LocationRow } from '../locationRow/locationRow';
import { formatCurrency, formatDistance } from '@utils/formatters';
import { getStyles } from './orderCard.styles';
interface OrderCardProps {
pickupName: string;
pickupAddress: string;
dropName: string;
dropAddress: string;
amount: number;
distance: number;
itemCount?: number;
paymentType?: string;
onPress?: () => void;
}
export const OrderCard: React.FC<OrderCardProps> = ({
pickupName,
pickupAddress,
dropName,
dropAddress,
amount,
distance,
itemCount,
paymentType,
onPress,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<Card onPress={onPress}>
<LocationRow type="pickup" name={pickupName} address={pickupAddress} />
<View style={styles.spacer} />
<LocationRow type="drop" name={dropName} address={dropAddress} />
<View style={styles.detailsRow}>
<View>
<Text style={styles.label}>ESTIMATED EARNINGS</Text>
<Text style={styles.amount}>{formatCurrency(amount)}</Text>
</View>
<View style={styles.centerCol}>
<Text style={styles.label}>DISTANCE</Text>
<Text style={styles.distance}>{formatDistance(distance)}</Text>
</View>
{(itemCount !== undefined || paymentType !== undefined) && (
<View style={styles.endCol}>
<Text style={styles.label}>DETAILS</Text>
<Text style={styles.value}>
{itemCount ? `${itemCount} items` : ''}
{paymentType ? `${paymentType.toUpperCase()}` : ''}
</Text>
</View>
)}
</View>
</Card>
);
};
export default OrderCard;

View File

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

View File

@ -0,0 +1,36 @@
import { StyleSheet } from 'react-native';
import { typography, borderRadius } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
paddingHorizontal: 10,
marginVertical: 20,
},
inputContainer: {
width: 45,
height: 50,
borderWidth: 1.5,
borderColor: colors.border,
borderRadius: borderRadius.sm,
backgroundColor: colors.inputBg,
justifyContent: 'center',
alignItems: 'center',
},
inputActive: {
borderColor: colors.primary,
backgroundColor: colors.background,
},
inputError: {
borderColor: colors.error,
},
inputText: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
textAlign: 'center',
},
});

View File

@ -0,0 +1,40 @@
import React from 'react';
import { View, Text } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './otpInput.styles';
interface OtpInputProps {
length?: number;
value: string;
error?: boolean;
}
export const OtpInput: React.FC<OtpInputProps> = ({ length = 6, value, error }) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const boxes = Array(length).fill(0);
return (
<View style={styles.container}>
{boxes.map((_, index) => {
const char = value[index] || '';
const isFocused = index === value.length;
return (
<View
key={index}
style={[
styles.inputContainer,
isFocused && styles.inputActive,
error && styles.inputError,
]}
>
<Text style={styles.inputText}>{char}</Text>
</View>
);
})}
</View>
);
};
export default OtpInput;

View File

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

View File

@ -0,0 +1,34 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.lg,
paddingVertical: spacing.md,
backgroundColor: colors.background,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
leftContainer: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
backButton: {
marginRight: spacing.md,
padding: spacing.xs,
},
title: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
},
rightContainer: {
justifyContent: 'center',
alignItems: 'flex-end',
},
});

View File

@ -0,0 +1,39 @@
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useAppTheme } from '@theme';
import { BackIcon } from '@icons/backIcon';
import { getStyles } from './screenHeader.styles';
interface ScreenHeaderProps {
title: string;
onBack?: () => void;
showBack?: boolean;
rightElement?: React.ReactNode;
}
export const ScreenHeader: React.FC<ScreenHeaderProps> = ({
title,
onBack,
showBack = true,
rightElement,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<View style={styles.container}>
<View style={styles.leftContainer}>
{showBack && onBack && (
<TouchableOpacity style={styles.backButton} onPress={onBack} activeOpacity={0.7}>
<BackIcon size={24} color={colors.text} />
</TouchableOpacity>
)}
<Text style={styles.title} numberOfLines={1}>
{title}
</Text>
</View>
{rightElement && <View style={styles.rightContainer}>{rightElement}</View>}
</View>
);
};
export default ScreenHeader;

View File

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

View File

@ -0,0 +1,24 @@
import { StyleSheet } from 'react-native';
import { typography, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: spacing.sm,
marginVertical: spacing.xs,
width: '100%',
},
title: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
},
actionText: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.medium,
color: colors.primary,
},
});

View File

@ -0,0 +1,31 @@
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './sectionHeader.styles';
interface SectionHeaderProps {
title: string;
actionText?: string;
onActionPress?: () => void;
}
export const SectionHeader: React.FC<SectionHeaderProps> = ({
title,
actionText,
onActionPress,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
return (
<View style={styles.container}>
<Text style={styles.title}>{title}</Text>
{actionText && onActionPress && (
<TouchableOpacity activeOpacity={0.7} onPress={onActionPress}>
<Text style={styles.actionText}>{actionText}</Text>
</TouchableOpacity>
)}
</View>
);
};
export default SectionHeader;

View File

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

View File

@ -0,0 +1,49 @@
import { StyleSheet } from 'react-native';
import { typography, borderRadius } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: borderRadius.full,
alignSelf: 'flex-start',
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
marginRight: 6,
},
label: {
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.semibold,
textTransform: 'capitalize',
},
onlineBg: {
backgroundColor: '#E8F8F0',
},
offlineBg: {
backgroundColor: '#FEF0E6',
},
busyBg: {
backgroundColor: '#FEF9E7',
},
onDeliveryBg: {
backgroundColor: '#EBF5FB',
},
onlineText: {
color: colors.statusOnline,
},
offlineText: {
color: colors.statusOffline,
},
busyText: {
color: colors.statusBusy,
},
onDeliveryText: {
color: colors.statusOnDelivery,
},
});

View File

@ -0,0 +1,60 @@
import React from 'react';
import { View, Text } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './statusBadge.styles';
export type BadgeStatusType = 'online' | 'offline' | 'busy' | 'onDelivery';
interface StatusBadgeProps {
status: BadgeStatusType;
}
export const StatusBadge: React.FC<StatusBadgeProps> = ({ status }) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const getStyleConfig = () => {
switch (status) {
case 'online':
return {
bg: styles.onlineBg,
text: styles.onlineText,
dotColor: colors.statusOnline,
};
case 'offline':
return {
bg: styles.offlineBg,
text: styles.offlineText,
dotColor: colors.statusOffline,
};
case 'busy':
return {
bg: styles.busyBg,
text: styles.busyText,
dotColor: colors.statusBusy,
};
case 'onDelivery':
return {
bg: styles.onDeliveryBg,
text: styles.onDeliveryText,
dotColor: colors.statusOnDelivery,
};
default:
return {
bg: styles.offlineBg,
text: styles.offlineText,
dotColor: colors.statusOffline,
};
}
};
const config = getStyleConfig();
return (
<View style={[styles.container, config.bg]}>
<View style={[styles.dot, { backgroundColor: config.dotColor }]} />
<Text style={[styles.label, config.text]}>{status}</Text>
</View>
);
};
export default StatusBadge;

View File

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

View File

@ -0,0 +1,51 @@
import { StyleSheet } from 'react-native';
import { typography, borderRadius, spacing } from '@theme';
export const getStyles = (colors: any) =>
StyleSheet.create({
container: {
position: 'absolute',
bottom: spacing.xxl + 20,
left: spacing.lg,
right: spacing.lg,
padding: spacing.md,
borderRadius: borderRadius.md,
flexDirection: 'row',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
zIndex: 99999,
},
successBg: {
backgroundColor: colors.successLight,
borderColor: colors.success,
borderWidth: 1,
},
errorBg: {
backgroundColor: colors.errorLight,
borderColor: colors.error,
borderWidth: 1,
},
infoBg: {
backgroundColor: colors.primaryLight,
borderColor: colors.primary,
borderWidth: 1,
},
message: {
flex: 1,
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.medium,
},
successText: {
color: colors.success,
},
errorText: {
color: colors.error,
},
infoText: {
color: colors.primary,
},
});

View File

@ -0,0 +1,82 @@
import React, { createContext, useContext, useState, useCallback } from 'react';
import { Animated, Text } from 'react-native';
import { useAppTheme } from '@theme';
import { getStyles } from './toast.styles';
export type ToastType = 'success' | 'error' | 'info';
interface ToastOptions {
message: string;
type?: ToastType;
duration?: number;
}
interface ToastContextType {
showToast: (options: ToastOptions) => void;
}
const ToastContext = createContext<ToastContextType | undefined>(undefined);
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const [toast, setToast] = useState<{ message: string; type: ToastType } | null>(null);
const [fadeAnim] = useState(new Animated.Value(0));
const showToast = useCallback(
({ message, type = 'info', duration = 3000 }: ToastOptions) => {
setToast({ message, type });
// Animate In
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
// Dismiss after duration
setTimeout(() => {
Animated.timing(fadeAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start(() => setToast(null));
}, duration);
},
[fadeAnim]
);
const getStyleConfig = () => {
if (!toast) return { bg: null, text: null };
switch (toast.type) {
case 'success':
return { bg: styles.successBg, text: styles.successText };
case 'error':
return { bg: styles.errorBg, text: styles.errorText };
case 'info':
return { bg: styles.infoBg, text: styles.infoText };
}
};
const config = getStyleConfig();
return (
<ToastContext.Provider value={{ showToast }}>
{children}
{toast ? (
<Animated.View style={[styles.container, config.bg, { opacity: fadeAnim }]}>
<Text style={[styles.message, config.text]}>{toast.message}</Text>
</Animated.View>
) : null}
</ToastContext.Provider>
);
};
export const useToast = () => {
const context = useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within a ToastProvider');
}
return context;
};

View File

@ -1 +0,0 @@
export const DEFAULT_OTP = '111111';

View File

@ -76,4 +76,14 @@ export const getStyles = (colors: any) => StyleSheet.create({
color: colors.primary, color: colors.primary,
fontWeight: typography.fontWeight.bold, fontWeight: typography.fontWeight.bold,
}, },
otpWrapper: {
marginTop: 10,
},
otpText: {
flex: 1,
marginVertical: 10,
},
loginButton: {
marginBottom: 12,
},
}); });

View File

@ -8,13 +8,18 @@ import {
TouchableOpacity, TouchableOpacity,
Alert, Alert,
} from 'react-native'; } from 'react-native';
import { getStyles } from './LoginScreen.styles'; import { getStyles } from './loginScreen.styles';
import { CustomInput, PrimaryButton, SocialButton } from '@components'; import { CustomInput, PrimaryButton, SocialButton } from '@components';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
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 [mobileNumber, setMobileNumber] = useState(''); const [mobileNumber, setMobileNumber] = useState('');
// const [password, setPassword] = useState(''); // const [password, setPassword] = useState('');
@ -49,7 +54,7 @@ export const LoginScreen: React.FC = () => {
// Simulate API request // Simulate API request
setTimeout(() => { setTimeout(() => {
setIsLoading(false); setIsLoading(false);
Alert.alert('Success', 'Logged in successfully!'); navigation.navigate(RouteNames.Otp, { phone: mobileNumber });
}, 1500); }, 1500);
}; };
@ -128,8 +133,8 @@ export const LoginScreen: React.FC = () => {
<Text style={styles.checkboxText}>Remember me</Text> <Text style={styles.checkboxText}>Remember me</Text>
</TouchableOpacity> </TouchableOpacity>
</View> */} </View> */}
<View style={{ marginTop: 10 }}> <View style={styles.otpWrapper}>
<Text style={{ flex: 1, marginVertical: 10 }}> <Text style={styles.otpText}>
OTP will send on this number OTP will send on this number
</Text> </Text>
</View> </View>
@ -138,7 +143,7 @@ export const LoginScreen: React.FC = () => {
title="Login" title="Login"
onPress={handleLogin} onPress={handleLogin}
isLoading={isLoading} isLoading={isLoading}
style={{ marginBottom: 12 }} style={styles.loginButton}
/> />
<View style={styles.dividerContainer}> <View style={styles.dividerContainer}>
@ -169,3 +174,5 @@ export const LoginScreen: React.FC = () => {
</KeyboardAvoidingView> </KeyboardAvoidingView>
); );
}; };
export default LoginScreen;

View File

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

View File

@ -0,0 +1,32 @@
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { CustomInput, PrimaryButton } from '@components';
import { useAppTheme } from '@theme';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
export const OtpScreen: React.FC<{ route: any }> = ({ route: _route }) => {
const { colors } = useAppTheme();
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
const [otp, setOtp] = useState('');
const handleVerify = () => {
navigation.navigate(RouteNames.SetLocation);
};
return (
<View style={[styles.container, { backgroundColor: colors.background }]}
>
<Text style={styles.title}>Enter OTP</Text>
<CustomInput label="OTP" value={otp} onChangeText={setOtp} keyboardType="numeric" />
<PrimaryButton title="Verify" onPress={handleVerify} />
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 20 },
title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
});
export default OtpScreen;

View File

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

View File

@ -0,0 +1,18 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useAppTheme } from '@theme';
const ArrivedAtCustomerScreen: React.FC = () => {
const { colors } = useAppTheme();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<Text>Arrived At Customer Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default ArrivedAtCustomerScreen;

View File

@ -0,0 +1 @@
export { default } from "./arrivedAtCustomerScreen";

View File

@ -0,0 +1,18 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useAppTheme } from '@theme';
const ArrivedAtStoreScreen: React.FC = () => {
const { colors } = useAppTheme();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<Text>Arrived At Store Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default ArrivedAtStoreScreen;

View File

@ -0,0 +1 @@
export { default } from "./arrivedAtStoreScreen";

View File

@ -0,0 +1,31 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useAppTheme } from '@theme';
import { PrimaryButton } from '@components';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AuthStackParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
const CompleteProfileScreen: React.FC = () => {
const { colors } = useAppTheme();
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<Text style={styles.title}>Complete Profile Screen</Text>
<PrimaryButton
title="Continue"
onPress={() => navigation.navigate(RouteNames.OnboardingComplete)}
style={{ marginTop: 20, width: '90%' }}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 20 },
});
export default CompleteProfileScreen;

View File

@ -0,0 +1 @@
export { default } from './completeProfileScreen';

View File

@ -0,0 +1,18 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useAppTheme } from '@theme';
const ConfirmPickupScreen: React.FC = () => {
const { colors } = useAppTheme();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<Text>Confirm Pickup Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default ConfirmPickupScreen;

View File

@ -0,0 +1 @@
export { default } from "./confirmPickupScreen";

View File

@ -0,0 +1,14 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const DashboardScreen: React.FC = () => (
<View style={styles.container}>
<Text>Dashboard Screen</Text>
</View>
);
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default DashboardScreen;

View File

@ -0,0 +1 @@
export { default } from './dashboardScreen';

View File

@ -0,0 +1,18 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useAppTheme } from '@theme';
const DeliverOrderScreen: React.FC = () => {
const { colors } = useAppTheme();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<Text>Deliver Order Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default DeliverOrderScreen;

View File

@ -0,0 +1 @@
export { default } from "./deliverOrderScreen";

View File

@ -0,0 +1,18 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useAppTheme } from '@theme';
const DeliveryCompletedScreen: React.FC = () => {
const { colors } = useAppTheme();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<Text>Delivery Completed Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default DeliveryCompletedScreen;

View File

@ -0,0 +1 @@
export { default } from "./deliveryCompletedScreen";

View File

@ -0,0 +1,18 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useAppTheme } from '@theme';
const DocumentsScreen: React.FC = () => {
const { colors } = useAppTheme();
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<Text>Documents Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});
export default DocumentsScreen;

Some files were not shown because too many files have changed in this diff Show More