diff --git a/src/components/ui/Header.tsx b/src/components/ui/Header.tsx index 1e6f3d1..47c99a8 100644 --- a/src/components/ui/Header.tsx +++ b/src/components/ui/Header.tsx @@ -10,9 +10,10 @@ interface HeaderProps { title: string; showBack?: boolean; rightComponent?: React.ReactNode; + onBack?: () => void; } -export const Header: React.FC = ({ title, showBack = false, rightComponent }) => { +export const Header: React.FC = ({ title, showBack = false, rightComponent, onBack }) => { const navigation = useNavigation(); const { screenPadding } = useResponsiveLayout(); @@ -21,7 +22,7 @@ export const Header: React.FC = ({ title, showBack = false, rightCo {showBack && ( navigation.goBack()} + onPress={() => onBack ? onBack() : navigation.goBack()} style={styles.backButton} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > diff --git a/src/components/ui/OtpInput.tsx b/src/components/ui/OtpInput.tsx new file mode 100644 index 0000000..d5be6fe --- /dev/null +++ b/src/components/ui/OtpInput.tsx @@ -0,0 +1,116 @@ +import React, { useState, useRef } from 'react'; +import { View, TextInput, StyleSheet, Pressable } from 'react-native'; + +interface OtpInputProps { + length?: number; + value: string; + onChange: (value: string) => void; + error?: boolean; +} + +export const OtpInput: React.FC = ({ length = 6, value, onChange, error }) => { + const [isFocused, setIsFocused] = useState(false); + const inputRef = useRef(null); + + const handlePress = () => { + inputRef.current?.focus(); + }; + + const renderBoxes = () => { + const boxes = []; + for (let i = 0; i < length; i++) { + const char = value[i] || ''; + const isCurrentDigit = i === value.length; + const isLastDigit = i === length - 1; + const isBoxFocused = isFocused && (isCurrentDigit || (value.length === length && isLastDigit)); + + boxes.push( + + + + ); + } + return boxes; + }; + + return ( + + + {renderBoxes()} + + { + // Allow only numbers and up to `length` + const cleanedText = text.replace(/[^0-9]/g, ''); + if (cleanedText.length <= length) { + onChange(cleanedText); + } + }} + keyboardType="number-pad" + textContentType="oneTimeCode" + maxLength={length} + style={styles.hiddenInput} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginVertical: 8, + alignItems: 'center', + width: '100%', + }, + boxesContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + }, + box: { + width: 45, + height: 55, + borderWidth: 1, + borderColor: '#D1D5DB', // gray-300 + borderRadius: 12, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F9FAFB', // gray-50 + }, + boxFocused: { + borderColor: '#4F46E5', // indigo-600 + borderWidth: 2, + backgroundColor: '#EEF2FF', // indigo-50 + }, + boxError: { + borderColor: '#DC2626', // red-600 + backgroundColor: '#FEF2F2', // red-50 + }, + text: { + fontSize: 24, + fontWeight: 'bold', + color: '#111827', // gray-900 + textAlign: 'center', + }, + hiddenInput: { + position: 'absolute', + width: 1, + height: 1, + opacity: 0, + }, +}); diff --git a/src/constants/theme.ts b/src/constants/theme.ts index 86b6000..f030e84 100644 --- a/src/constants/theme.ts +++ b/src/constants/theme.ts @@ -1,12 +1,12 @@ export const THEME = { colors: { - primary: 'navy', + primary: '#1E3A8A', primaryLight: '#E0E7FF', primaryLighter: '#EEF2FF', background: '#F9FAFB', surface: '#FFFFFF', text: { - primary: '#111827', + primary: '#1e3a8aff', secondary: '#6B7280', muted: '#9CA3AF', }, diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 373f22c..e365565 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -15,6 +15,7 @@ import { AddSkillScreen } from '../screens/AddSkillScreen'; import { AvailabilityListScreen } from '../screens/AvailabilityListScreen'; import { AddAvailabilityScreen } from '../screens/AddAvailabilityScreen'; import { EditAvailabilityScreen } from '../screens/EditAvailabilityScreen'; +import { PaymentProfileScreen } from '../screens/PaymentProfileScreen'; export type RootStackParamList = { Login: undefined; @@ -30,6 +31,7 @@ export type RootStackParamList = { start_time: string; end_time: string; }; + PaymentProfile: undefined; }; export type TabParamList = { @@ -117,6 +119,7 @@ export const AppNavigator = () => { + ); diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx index a45158e..955fa1b 100644 --- a/src/screens/LoginScreen.tsx +++ b/src/screens/LoginScreen.tsx @@ -4,6 +4,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { RootStackParamList } from '../navigation/AppNavigator'; import { Typography } from '../components/ui/Typography'; import { Input } from '../components/ui/Input'; +import { OtpInput } from '../components/ui/OtpInput'; import { Button } from '../components/ui/Button'; import { authService } from '../services/authService'; import AsyncStorage from '@react-native-async-storage/async-storage'; @@ -108,15 +109,17 @@ export const LoginScreen: React.FC = ({ navigation }) => { onChangeText={setPhone} /> ) : ( - + + + Enter OTP + + + )}