feat(app): initial commit
This commit is contained in:
parent
bdd81eef31
commit
a8a04a02f6
16
app/App.tsx
16
app/App.tsx
@ -3,17 +3,21 @@ import {
|
||||
SafeAreaProvider,
|
||||
useSafeAreaInsets,
|
||||
} 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';
|
||||
|
||||
function App() {
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<AppContent />
|
||||
</SafeAreaProvider>
|
||||
<Provider store={store}>
|
||||
<SafeAreaProvider>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<AppContent />
|
||||
</SafeAreaProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -32,7 +36,7 @@ function AppContent() {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<LoginScreen />
|
||||
<RootNavigator />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,4 +46,9 @@ export const getStyles = (colors: any) => StyleSheet.create({
|
||||
marginTop: 4,
|
||||
marginLeft: 4,
|
||||
},
|
||||
actionsRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity } from 'react-native';
|
||||
import { CustomInputProps } from './CustomInput.props';
|
||||
import { getStyles } from './CustomInput.styles';
|
||||
import { CustomInputProps } from './customInput.props';
|
||||
import { getStyles } from './customInput.styles';
|
||||
import { useAppTheme } from '@theme';
|
||||
|
||||
export const CustomInput: React.FC<CustomInputProps> = ({
|
||||
@ -34,7 +34,7 @@ export const CustomInput: React.FC<CustomInputProps> = ({
|
||||
autoCorrect={false}
|
||||
{...rest}
|
||||
/>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
|
||||
<View style={styles.actionsRow}>
|
||||
{isPassword && (
|
||||
<TouchableOpacity onPress={() => setIsSecure(!isSecure)} activeOpacity={0.7}>
|
||||
<Text style={styles.rightAction}>{isSecure ? 'Show' : 'Hide'}</Text>
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './CustomInput';
|
||||
export * from './CustomInput.props';
|
||||
export * from './customInput';
|
||||
export * from './customInput.props';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { TouchableOpacity, Text, ActivityIndicator } from 'react-native';
|
||||
import { PrimaryButtonProps } from './PrimaryButton.props';
|
||||
import { styles } from './PrimaryButton.styles';
|
||||
import { PrimaryButtonProps } from './primaryButton.props';
|
||||
import { styles } from './primaryButton.styles';
|
||||
import { colors } from '@theme';
|
||||
|
||||
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './PrimaryButton';
|
||||
export * from './PrimaryButton.props';
|
||||
export * from './primaryButton';
|
||||
export * from './primaryButton.props';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { TouchableOpacity, Text } from 'react-native';
|
||||
import { SocialButtonProps } from './SocialButton.props';
|
||||
import { getStyles } from './SocialButton.styles';
|
||||
import { SocialButtonProps } from './socialButton.props';
|
||||
import { getStyles } from './socialButton.styles';
|
||||
import { useAppTheme } from '@theme';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './SocialButton';
|
||||
export * from './SocialButton.props';
|
||||
export * from './socialButton';
|
||||
export * from './socialButton.props';
|
||||
|
||||
21
app/components/badge/badge.styles.ts
Normal file
21
app/components/badge/badge.styles.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
30
app/components/badge/badge.tsx
Normal file
30
app/components/badge/badge.tsx
Normal 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;
|
||||
2
app/components/badge/index.ts
Normal file
2
app/components/badge/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './badge';
|
||||
export { default } from './badge';
|
||||
15
app/components/card/card.styles.ts
Normal file
15
app/components/card/card.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
30
app/components/card/card.tsx
Normal file
30
app/components/card/card.tsx
Normal 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;
|
||||
2
app/components/card/index.ts
Normal file
2
app/components/card/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './card';
|
||||
export { default } from './card';
|
||||
34
app/components/chipSelector/chipSelector.styles.ts
Normal file
34
app/components/chipSelector/chipSelector.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
46
app/components/chipSelector/chipSelector.tsx
Normal file
46
app/components/chipSelector/chipSelector.tsx
Normal 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;
|
||||
2
app/components/chipSelector/index.ts
Normal file
2
app/components/chipSelector/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './chipSelector';
|
||||
export { default } from './chipSelector';
|
||||
25
app/components/countdownTimer/countdownTimer.styles.ts
Normal file
25
app/components/countdownTimer/countdownTimer.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
44
app/components/countdownTimer/countdownTimer.tsx
Normal file
44
app/components/countdownTimer/countdownTimer.tsx
Normal 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;
|
||||
2
app/components/countdownTimer/index.ts
Normal file
2
app/components/countdownTimer/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './countdownTimer';
|
||||
export { default } from './countdownTimer';
|
||||
10
app/components/divider/divider.styles.ts
Normal file
10
app/components/divider/divider.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
31
app/components/divider/divider.tsx
Normal file
31
app/components/divider/divider.tsx
Normal 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;
|
||||
2
app/components/divider/index.ts
Normal file
2
app/components/divider/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './divider';
|
||||
export { default } from './divider';
|
||||
41
app/components/emptyState/emptyState.styles.ts
Normal file
41
app/components/emptyState/emptyState.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
37
app/components/emptyState/emptyState.tsx
Normal file
37
app/components/emptyState/emptyState.tsx
Normal 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;
|
||||
2
app/components/emptyState/index.ts
Normal file
2
app/components/emptyState/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './emptyState';
|
||||
export { default } from './emptyState';
|
||||
24
app/components/icons/backIcon.tsx
Normal file
24
app/components/icons/backIcon.tsx
Normal 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;
|
||||
24
app/components/icons/bellIcon.tsx
Normal file
24
app/components/icons/bellIcon.tsx
Normal 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;
|
||||
24
app/components/icons/callIcon.tsx
Normal file
24
app/components/icons/callIcon.tsx
Normal 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;
|
||||
31
app/components/icons/checkCircleIcon.tsx
Normal file
31
app/components/icons/checkCircleIcon.tsx
Normal 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;
|
||||
24
app/components/icons/chevronRightIcon.tsx
Normal file
24
app/components/icons/chevronRightIcon.tsx
Normal 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;
|
||||
35
app/components/icons/clipboardIcon.tsx
Normal file
35
app/components/icons/clipboardIcon.tsx
Normal 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;
|
||||
24
app/components/icons/closeIcon.tsx
Normal file
24
app/components/icons/closeIcon.tsx
Normal 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;
|
||||
31
app/components/icons/homeIcon.tsx
Normal file
31
app/components/icons/homeIcon.tsx
Normal 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;
|
||||
12
app/components/icons/index.ts
Normal file
12
app/components/icons/index.ts
Normal 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';
|
||||
33
app/components/icons/locationPinIcon.tsx
Normal file
33
app/components/icons/locationPinIcon.tsx
Normal 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;
|
||||
33
app/components/icons/personIcon.tsx
Normal file
33
app/components/icons/personIcon.tsx
Normal 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;
|
||||
26
app/components/icons/starIcon.tsx
Normal file
26
app/components/icons/starIcon.tsx
Normal 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;
|
||||
31
app/components/icons/walletIcon.tsx
Normal file
31
app/components/icons/walletIcon.tsx
Normal 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;
|
||||
@ -1,3 +1,3 @@
|
||||
export * from './CustomInput';
|
||||
export * from './PrimaryButton';
|
||||
export * from './SocialButton';
|
||||
export * from './customInput';
|
||||
export * from './primaryButton';
|
||||
export * from './socialButton';
|
||||
2
app/components/jobStatusBanner/index.ts
Normal file
2
app/components/jobStatusBanner/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './jobStatusBanner';
|
||||
export { default } from './jobStatusBanner';
|
||||
27
app/components/jobStatusBanner/jobStatusBanner.styles.ts
Normal file
27
app/components/jobStatusBanner/jobStatusBanner.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
27
app/components/jobStatusBanner/jobStatusBanner.tsx
Normal file
27
app/components/jobStatusBanner/jobStatusBanner.tsx
Normal 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;
|
||||
2
app/components/loadingOverlay/index.ts
Normal file
2
app/components/loadingOverlay/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './loadingOverlay';
|
||||
export { default } from './loadingOverlay';
|
||||
31
app/components/loadingOverlay/loadingOverlay.styles.ts
Normal file
31
app/components/loadingOverlay/loadingOverlay.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
29
app/components/loadingOverlay/loadingOverlay.tsx
Normal file
29
app/components/loadingOverlay/loadingOverlay.tsx
Normal 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;
|
||||
2
app/components/locationRow/index.ts
Normal file
2
app/components/locationRow/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './locationRow';
|
||||
export { default } from './locationRow';
|
||||
38
app/components/locationRow/locationRow.styles.ts
Normal file
38
app/components/locationRow/locationRow.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
33
app/components/locationRow/locationRow.tsx
Normal file
33
app/components/locationRow/locationRow.tsx
Normal 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;
|
||||
2
app/components/mapBottomSheet/index.ts
Normal file
2
app/components/mapBottomSheet/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './mapBottomSheet';
|
||||
export { default } from './mapBottomSheet';
|
||||
30
app/components/mapBottomSheet/mapBottomSheet.styles.ts
Normal file
30
app/components/mapBottomSheet/mapBottomSheet.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
21
app/components/mapBottomSheet/mapBottomSheet.tsx
Normal file
21
app/components/mapBottomSheet/mapBottomSheet.tsx
Normal 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;
|
||||
2
app/components/mapView/index.ts
Normal file
2
app/components/mapView/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './mapView';
|
||||
export { default } from './mapView';
|
||||
28
app/components/mapView/mapView.styles.ts
Normal file
28
app/components/mapView/mapView.styles.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
77
app/components/mapView/mapView.tsx
Normal file
77
app/components/mapView/mapView.tsx
Normal 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;
|
||||
2
app/components/numericKeypad/index.ts
Normal file
2
app/components/numericKeypad/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './numericKeypad';
|
||||
export { default } from './numericKeypad';
|
||||
34
app/components/numericKeypad/numericKeypad.styles.ts
Normal file
34
app/components/numericKeypad/numericKeypad.styles.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
62
app/components/numericKeypad/numericKeypad.tsx
Normal file
62
app/components/numericKeypad/numericKeypad.tsx
Normal 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;
|
||||
2
app/components/onlineToggleButton/index.ts
Normal file
2
app/components/onlineToggleButton/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './onlineToggleButton';
|
||||
export { default } from './onlineToggleButton';
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
38
app/components/onlineToggleButton/onlineToggleButton.tsx
Normal file
38
app/components/onlineToggleButton/onlineToggleButton.tsx
Normal 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;
|
||||
2
app/components/orderCard/index.ts
Normal file
2
app/components/orderCard/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './orderCard';
|
||||
export { default } from './orderCard';
|
||||
49
app/components/orderCard/orderCard.styles.ts
Normal file
49
app/components/orderCard/orderCard.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
63
app/components/orderCard/orderCard.tsx
Normal file
63
app/components/orderCard/orderCard.tsx
Normal 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;
|
||||
2
app/components/otpInput/index.ts
Normal file
2
app/components/otpInput/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './otpInput';
|
||||
export { default } from './otpInput';
|
||||
36
app/components/otpInput/otpInput.styles.ts
Normal file
36
app/components/otpInput/otpInput.styles.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
40
app/components/otpInput/otpInput.tsx
Normal file
40
app/components/otpInput/otpInput.tsx
Normal 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;
|
||||
2
app/components/screenHeader/index.ts
Normal file
2
app/components/screenHeader/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './screenHeader';
|
||||
export { default } from './screenHeader';
|
||||
34
app/components/screenHeader/screenHeader.styles.ts
Normal file
34
app/components/screenHeader/screenHeader.styles.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
39
app/components/screenHeader/screenHeader.tsx
Normal file
39
app/components/screenHeader/screenHeader.tsx
Normal 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;
|
||||
2
app/components/sectionHeader/index.ts
Normal file
2
app/components/sectionHeader/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './sectionHeader';
|
||||
export { default } from './sectionHeader';
|
||||
24
app/components/sectionHeader/sectionHeader.styles.ts
Normal file
24
app/components/sectionHeader/sectionHeader.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
31
app/components/sectionHeader/sectionHeader.tsx
Normal file
31
app/components/sectionHeader/sectionHeader.tsx
Normal 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;
|
||||
2
app/components/statusBadge/index.ts
Normal file
2
app/components/statusBadge/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './statusBadge';
|
||||
export { default } from './statusBadge';
|
||||
49
app/components/statusBadge/statusBadge.styles.ts
Normal file
49
app/components/statusBadge/statusBadge.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
60
app/components/statusBadge/statusBadge.tsx
Normal file
60
app/components/statusBadge/statusBadge.tsx
Normal 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;
|
||||
2
app/components/toast/index.ts
Normal file
2
app/components/toast/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './toast';
|
||||
export { default } from './toast';
|
||||
51
app/components/toast/toast.styles.ts
Normal file
51
app/components/toast/toast.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
82
app/components/toast/toast.tsx
Normal file
82
app/components/toast/toast.tsx
Normal 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;
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export const DEFAULT_OTP = '111111';
|
||||
@ -76,4 +76,14 @@ export const getStyles = (colors: any) => StyleSheet.create({
|
||||
color: colors.primary,
|
||||
fontWeight: typography.fontWeight.bold,
|
||||
},
|
||||
otpWrapper: {
|
||||
marginTop: 10,
|
||||
},
|
||||
otpText: {
|
||||
flex: 1,
|
||||
marginVertical: 10,
|
||||
},
|
||||
loginButton: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
});
|
||||
|
||||
@ -8,13 +8,18 @@ import {
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { getStyles } from './LoginScreen.styles';
|
||||
import { getStyles } from './loginScreen.styles';
|
||||
import { CustomInput, PrimaryButton, SocialButton } 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 LoginScreen: React.FC = () => {
|
||||
const { colors } = useAppTheme();
|
||||
const styles = getStyles(colors);
|
||||
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
|
||||
|
||||
const [mobileNumber, setMobileNumber] = useState('');
|
||||
// const [password, setPassword] = useState('');
|
||||
@ -49,7 +54,7 @@ export const LoginScreen: React.FC = () => {
|
||||
// Simulate API request
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
Alert.alert('Success', 'Logged in successfully!');
|
||||
navigation.navigate(RouteNames.Otp, { phone: mobileNumber });
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
@ -128,8 +133,8 @@ export const LoginScreen: React.FC = () => {
|
||||
<Text style={styles.checkboxText}>Remember me</Text>
|
||||
</TouchableOpacity>
|
||||
</View> */}
|
||||
<View style={{ marginTop: 10 }}>
|
||||
<Text style={{ flex: 1, marginVertical: 10 }}>
|
||||
<View style={styles.otpWrapper}>
|
||||
<Text style={styles.otpText}>
|
||||
OTP will send on this number
|
||||
</Text>
|
||||
</View>
|
||||
@ -138,7 +143,7 @@ export const LoginScreen: React.FC = () => {
|
||||
title="Login"
|
||||
onPress={handleLogin}
|
||||
isLoading={isLoading}
|
||||
style={{ marginBottom: 12 }}
|
||||
style={styles.loginButton}
|
||||
/>
|
||||
|
||||
<View style={styles.dividerContainer}>
|
||||
@ -169,3 +174,5 @@ export const LoginScreen: React.FC = () => {
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginScreen;
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './LoginScreen';
|
||||
export * from './LoginScreen.styles';
|
||||
export { default } from './loginScreen';
|
||||
export * from './loginScreen';
|
||||
export * from './loginScreen.styles';
|
||||
|
||||
@ -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;
|
||||
2
app/features/screens/OtpScreen/index.ts
Normal file
2
app/features/screens/OtpScreen/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default } from './otpScreen';
|
||||
export * from './otpScreen';
|
||||
@ -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;
|
||||
1
app/features/screens/arrivedAtCustomerScreen/index.ts
Normal file
1
app/features/screens/arrivedAtCustomerScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./arrivedAtCustomerScreen";
|
||||
@ -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;
|
||||
1
app/features/screens/arrivedAtStoreScreen/index.ts
Normal file
1
app/features/screens/arrivedAtStoreScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./arrivedAtStoreScreen";
|
||||
@ -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;
|
||||
1
app/features/screens/completeProfileScreen/index.ts
Normal file
1
app/features/screens/completeProfileScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './completeProfileScreen';
|
||||
@ -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;
|
||||
1
app/features/screens/confirmPickupScreen/index.ts
Normal file
1
app/features/screens/confirmPickupScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./confirmPickupScreen";
|
||||
14
app/features/screens/dashboardScreen/dashboardScreen.tsx
Normal file
14
app/features/screens/dashboardScreen/dashboardScreen.tsx
Normal 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;
|
||||
1
app/features/screens/dashboardScreen/index.ts
Normal file
1
app/features/screens/dashboardScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './dashboardScreen';
|
||||
@ -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;
|
||||
1
app/features/screens/deliverOrderScreen/index.ts
Normal file
1
app/features/screens/deliverOrderScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./deliverOrderScreen";
|
||||
@ -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;
|
||||
1
app/features/screens/deliveryCompletedScreen/index.ts
Normal file
1
app/features/screens/deliveryCompletedScreen/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./deliveryCompletedScreen";
|
||||
18
app/features/screens/documentsScreen/documentsScreen.tsx
Normal file
18
app/features/screens/documentsScreen/documentsScreen.tsx
Normal 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
Loading…
x
Reference in New Issue
Block a user