I am integrating the Paymob React Native SDK into my application and facing an issue with the payment callback flow. The payment is being processed successfully, and the transaction is completed from Paymob’s side. However, after success, the SDK modal/sheet remains stuck displaying the raw success JSON response instead of closing or triggering the success listener/callback. Current behavior: • Payment completes successfully • Success response JSON is displayed inside the sheet/modal • The SDK does not dismiss automatically • Success listener / callback is not triggered Example response shown on the sheet: { “success”: true, “message”: “Payment successful”, “data”: { …
Here Is My Code
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
TextInput,
StyleSheet,
TouchableOpacity,
Image,
ScrollView,
Alert,
ActivityIndicator,
KeyboardAvoidingView,
Platform,
SafeAreaView,
NativeModules,
} from 'react-native';
import Paymob, { PaymentStatus } from 'paymob-reactnative';
import * as api from '../../api/apiService';
import colors from '../../utils/colors';
import { useNavigation, useRoute, CommonActions } from '@react-navigation/native';
import { usePaymentReserveMutation } from '../../api/rtkApi';
import { getLocalizedText } from '../../utils/language';
import { useLanguage } from '../../context/LanguageContext';
import { formatPrice } from '../../utils/constants';
import { tracking } from '../../utils/tracking';
import CurrencySymbol from '../../components/CurrencySymbol';
import BackButton from '../../components/BackButton';
const PaymentDetailScreen: React.FC = () => {
const { language } = useLanguage();
const isArabic = language === 'ar';
const [step, setStep] = useState<'data' | 'payment'>('data');
const [loading, setLoading] = useState(false);
const [paymentReserveMutation] = usePaymentReserveMutation();
const navigation = useNavigation();
const route = useRoute();
const { car } = route.params || {};
// State flag used to trigger navigation from React's render cycle
// (calling navigation directly from SDK callback can fail on native threads)
const [paymentResult, setPaymentResult] = useState<'success' | 'fail' | null>(null);
// React-side navigation triggered by paymentResult state change
useEffect(() => {
if (paymentResult === 'success') {
// Try to nudge the SDK to close by removing the listener
Paymob.removeSdkListener();
// Forcefully dismiss any native modal (Paymob sheet)
if (NativeModules.DismissModal) {
NativeModules.DismissModal.dismiss();
} else {
console.warn('DismissModal native module is not available. Please rebuild the app and ensure files are added to the Xcode project.');
}
// Forceful stack reset to Success screen (destroys previous stack)
setTimeout(() => {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'PaymentSuccess' }],
})
);
}, 500);
}
}, [paymentResult, navigation]);
// Global SDK configuration on mount
useEffect(() => {
Paymob.setAppName('Tira Cars');
Paymob.setButtonBackgroundColor('#000000');
Paymob.setButtonTextColor('#FFFFFF');
Paymob.setShowTransactionResult(false);
Paymob.setShowConfirmationPage(false);
Paymob.setShowSaveCard(false);
Paymob.setSaveCardDefault(false);
Paymob.setKeyboardHandlingEnabled(true);
}, []);
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [city, setCity] = useState('');
const totalAmount = parseFloat(car?.total_price);
const reserveAmount = parseFloat(car?.reservation_fee);
const handleSubmitData = () => {
if (!firstName.trim() || !lastName.trim() || !email.trim()) {
Alert.alert(
getLocalizedText(language, 'missingInfo'),
getLocalizedText(language, 'fillAllFields'),
);
return;
}
setStep('payment');
};
/* ---------------- HEADER ---------------- */
const renderHeader = () => (
<View
style={[
styles.header,
{ flexDirection: isArabic ? 'row-reverse' : 'row' },
]}
>
<TouchableOpacity
onPress={() => {
if (step === 'payment') {
setStep('data');
} else {
navigation.goBack();
}
}}
style={styles.backButton}
>
<Image
source={require('../../assets/goback.png')}
style={[
styles.backIcon,
{ transform: [{ scaleX: isArabic ? -1 : 1 }] },
]}
/>
<Text style={styles.headerTitle}>
{getLocalizedText(language, 'paymentDetails')}
</Text>
<View style={{ width: 24 }} />
</View>
);
/* ---------------- STEP 1: DATA ENTRY ---------------- */
const renderDataEntry = () => (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
>
<ScrollView
contentContainerStyle={[styles.page, isArabic && { direction: 'ltr' }]}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<Text
style={[styles.heading, { textAlign: isArabic ? 'right' : 'center' }]}
>
{getLocalizedText(language, 'dataEntry')}
<View style={styles.box}>
<Text
style={{
fontSize: 14,
color: 'gray',
marginTop: 6,
marginBottom: 4,
textAlign: isArabic ? 'right' : 'left',
}}
>
{getLocalizedText(language, 'deductedNote')}
</Text>
<View style={styles.detailBox}>
<Text
style={{
textAlign: isArabic ? 'right' : 'left',
color: colors.black,
}}
>
{getLocalizedText(language, 'basePrice')}
</Text>
<Text
style={{
textAlign: isArabic ? 'right' : 'left',
color: colors.black,
}}
>
{getLocalizedText(language, 'vatFee')}
</Text>
<Text
style={{
textAlign: isArabic ? 'right' : 'left',
color: colors.black,
}}
>
{getLocalizedText(language, 'customDuty')}
</Text>
<Text
style={{
textAlign: isArabic ? 'right' : 'left',
color: colors.black,
}}
>
{getLocalizedText(language, 'brokerFee')}
</Text>
<Text
style={{
textAlign: isArabic ? 'right' : 'left',
color: colors.black,
}}
>
{getLocalizedText(language, 'insuranceFee')}
</Text>
</View>
<View
style={[
styles.row,
{ flexDirection: isArabic ? 'row-reverse' : 'row' },
]}
>
<Text
style={[
styles.rowText,
{ textAlign: isArabic ? 'right' : 'left', color: colors.black },
]}
>
{getLocalizedText(language, 'totalAmount')}:
</Text>
<View
style={{
flexDirection: isArabic ? 'row-reverse' : 'row',
alignItems: 'center',
}}
>
<Text
style={[
styles.rowText,
{
textAlign: isArabic ? 'left' : 'right',
color: colors.black,
},
]}
>
{formatPrice(totalAmount, '', language)}
</Text>
<CurrencySymbol style={{ marginHorizontal: 3 }} />
</View>
</View>
<View
style={[
styles.row,
{ flexDirection: isArabic ? 'row-reverse' : 'row' },
]}
>
<Text
style={[
styles.rowText,
{ textAlign: isArabic ? 'right' : 'left', color: colors.black },
]}
>
{getLocalizedText(language, 'reservationFee')}:
</Text>
<View
style={{
flexDirection: isArabic ? 'row-reverse' : 'row',
alignItems: 'center',
}}
>
<Text
style={[
styles.rowText,
{
textAlign: isArabic ? 'left' : 'right',
color: colors.black,
},
]}
>
{formatPrice(reserveAmount, '', language)}
</Text>
<CurrencySymbol style={{ marginHorizontal: 3 }} />
</View>
</View>
</View>
<Text
style={[styles.label, { textAlign: isArabic ? 'right' : 'left' }]}
>
{getLocalizedText(language, 'deliveryInfo')}
</Text>
<TextInput
placeholder={getLocalizedText(language, 'firstName')}
style={[styles.input, isArabic && { textAlign: 'right' }]}
value={firstName}
onChangeText={setFirstName}
placeholderTextColor={colors.gray}
/>
<TextInput
placeholder={getLocalizedText(language, 'lastName')}
style={[styles.input, isArabic && { textAlign: 'right' }]}
value={lastName}
onChangeText={setLastName}
placeholderTextColor={colors.gray}
/>
<TextInput
placeholder={getLocalizedText(language, 'email')}
style={[styles.input, isArabic && { textAlign: 'right' }]}
keyboardType="email-address"
value={email}
onChangeText={setEmail}
placeholderTextColor={colors.gray}
/>
<Text
style={[styles.label, { textAlign: isArabic ? 'right' : 'left' }]}
>
{getLocalizedText(language, 'city')}
</Text>
<TextInput
placeholder={getLocalizedText(language, 'city')}
style={[styles.input, isArabic && { textAlign: 'right' }]}
value={city}
onChangeText={setCity}
placeholderTextColor={colors.gray}
/>
<TouchableOpacity style={styles.bookBtn} onPress={handleSubmitData}>
<View
style={{
flexDirection: isArabic ? 'row-reverse' : 'row',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={styles.bookText}>
{getLocalizedText(language, 'reserveCar')}
</Text>
<Text style={styles.bookText}></Text>
</View>
</TouchableOpacity>
</ScrollView>
</KeyboardAvoidingView>
);
/* ---------------- STEP 2: PAYMENT ---------------- */
const handlePayment = async () => {
try {
setLoading(true);
const resp = await paymentReserveMutation({
amount: reserveAmount,
car_id: car.id,
billing_data: {
first_name: firstName,
last_name: lastName,
email,
phone_number: '05xxxxxxxx',
},
}).unwrap();
console.log(resp, 'payment response');
const clientSecret = resp?.data?.client_secret;
const publicKey = resp?.data?.public_key;
const merchantOrderId = resp?.data?.merchant_order_id;
console.log('Payment Keys:', { clientSecret, publicKey, merchantOrderId });
if (clientSecret && publicKey) {
tracking.track('start_checkout', { amount: reserveAmount, car_id: car.id });
Paymob.setSdkListener((status: PaymentStatus) => {
console.log('Paymob SDK Result:', status);
switch (status) {
case PaymentStatus.SUCCESS:
console.log('Payment Successful');
setPaymentResult('success');
break;
case PaymentStatus.FAIL:
console.log('Payment Failed');
setPaymentResult('fail');
Alert.alert('Payment Failed', 'Transaction was not successful. Please try again.');
break;
case PaymentStatus.PENDING:
console.log('Payment Pending');
// Optionally handle pending
break;
}
});
Paymob.setAppName('Tira Cars');
Paymob.setButtonBackgroundColor('#000000');
Paymob.setButtonTextColor('#FFFFFF');
Paymob.setShowTransactionResult(false);
Paymob.setShowConfirmationPage(false);
Paymob.presentPayVC(clientSecret, publicKey);
} else {
Alert.alert('Error', 'Payment initialization failed. Please try again.');
}
} catch (e) {
console.error(e, 'payment error');
Alert.alert(
getLocalizedText(language, 'error'),
getLocalizedText(language, 'errorPayment'),
);
} finally {
setLoading(false);
}
};
const renderPayment = () => (
<ScrollView
contentContainerStyle={[styles.page, isArabic && { direction: 'ltr' }]}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<Text
style={[styles.heading, { textAlign: isArabic ? 'right' : 'center' }]}
>
{getLocalizedText(language, 'payment')}
<View
style={[
styles.card,
{ flexDirection: isArabic ? 'row-reverse' : 'row' },
]}
>
<Image
source={{ uri: car?.images?.[0]?.image }}
style={{
width: 120,
height: 80,
borderRadius: 8,
marginHorizontal: 10,
}}
/>
<View
style={{
flex: 1,
marginLeft: isArabic ? 0 : 10,
marginRight: isArabic ? 10 : 0,
}}
>
<Text
style={[
styles.cardText,
{
textAlign: isArabic ? 'right' : 'left',
maxWidth: '100%',
flexShrink: 1,
},
]}
numberOfLines={2}
ellipsizeMode="tail"
>
{car?.name}
</Text>
<Text style={{ fontWeight: 'bold', marginTop: 5 }}>
{car?.information.brand}
</Text>
</View>
</View>
<View
style={{
flexDirection: isArabic ? 'row-reverse' : 'row',
alignItems: 'center',
marginTop: 10,
}}
>
<Text style={{ fontWeight: '600', fontSize: 15 }}>
{getLocalizedText(language, 'totalOrder')} (1):{' '}
</Text>
<Text style={{ fontWeight: 'bold', fontSize: 16, marginHorizontal: 4 }}>
{formatPrice(reserveAmount, '', language)}
</Text>
<CurrencySymbol />
</View>
<TouchableOpacity
style={styles.confirmBtn}
onPress={handlePayment}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={{ color: '#fff', fontSize: 18 }}>
{getLocalizedText(language, 'completePayment')}
</Text>
)}
</TouchableOpacity>
</ScrollView>
);
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
{renderHeader()}
{step === 'data' && renderDataEntry()}
{step === 'payment' && renderPayment()}
);
};
/* ---------------- STEP PROGRESS ---------------- */
const StepProgress = ({ currentStep }: { currentStep: 'data' | 'payment' }) => {
const { language } = useLanguage();
const isArabic = language === 'ar';
const steps = [
{ label: getLocalizedText(language, 'dataEntry'), key: 'data' },
{ label: getLocalizedText(language, 'payment'), key: 'payment' },
];
const getStepIndex = (key: string) => steps.findIndex(s => s.key === key);
return (
<View
style={[
styles.progressContainer,
isArabic && { flexDirection: 'row-reverse' },
]}
>
{steps.map((step, index) => {
const isActive = currentStep === step.key;
const isCompleted = getStepIndex(currentStep) > index;
return (
<React.Fragment key={step.key}>
<View
style={[
styles.circle,
{
backgroundColor: isActive || isCompleted ? 'black' : '#ccc',
},
]}
/>
<Text
style={{
fontSize: 14,
color: isActive || isCompleted ? 'black' : '#999',
marginTop: 4,
textAlign: 'center',
}}
>
{step.label}
{index < steps.length - 1 && (
<View
style={[
styles.line,
{
backgroundColor:
getStepIndex(currentStep) > index ? 'black' : '#ccc',
},
]}
/>
)}
</React.Fragment>
);
})}
);
};
const styles = StyleSheet.create({
page: { padding: 20, flexGrow: 1 },
header: {
alignItems: 'center',
justifyContent: 'space-between',
padding: 15,
borderBottomWidth: 1,
borderColor: '#eee',
margin: 16,
},
headerTitle: { fontSize: 20, fontWeight: '600', color: '#000' },
heading: { marginTop: 10, fontSize: 22, fontWeight: '600', color: '#000' },
box: {
backgroundColor: colors.white,
borderRadius: 10,
padding: 15,
marginTop: 20,
borderWidth: 1,
borderColor: colors.black,
},
detailBox: {
marginTop: 10,
backgroundColor: '#f7f7f7',
padding: 10,
borderRadius: 6,
borderWidth: 1,
borderColor: '#ccc',
},
row: {
justifyContent: 'space-between',
marginBottom: 2,
marginTop: 10,
},
rowText: {
fontWeight: '500',
},
label: { marginTop: 15, fontWeight: '600', color: '#000' },
input: {
borderWidth: 1,
borderColor: colors.gray,
borderRadius: 8,
padding: 10,
marginTop: 12,
color: colors.black,
backgroundColor: colors.white,
},
bookBtn: {
backgroundColor: 'black',
padding: 15,
borderRadius: 8,
marginTop: 20,
marginBottom: 20,
},
bookText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
phone: {
marginTop: 150,
fontSize: 20,
fontWeight: 'bold',
color: 'brown',
textAlign: 'center',
},
otpRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 40,
},
otpBox: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
width: 40,
height: 50,
textAlign: 'center',
fontSize: 20,
},
confirmBtn: {
backgroundColor: 'black',
padding: 15,
borderRadius: 8,
marginTop: 30,
marginBottom: 20,
alignItems: 'center',
},
card: {
backgroundColor: colors.white,
padding: 10,
borderRadius: 10,
marginTop: 20,
alignItems: 'center',
borderWidth: 1,
borderColor: colors.black,
width: '100%',
maxWidth: '100%',
overflow: 'hidden',
},
cardText: {
fontWeight: 'bold',
flexWrap: 'wrap',
},
cardPrice: {
fontWeight: 'bold',
marginTop: 5,
},
progressContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 20,
},
stepItem: { alignItems: 'center', flex: 1 },
circle: { width: 22, height: 22, borderRadius: 11 },
line: { height: 2, flex: 1, marginHorizontal: 4 },
backButton: { padding: 8 },
backIcon: { width: 24, height: 24, resizeMode: 'contain' },
methodsContainer: { marginTop: 20 },
methodBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 15,
borderRadius: 8,
borderWidth: 1,
borderColor: '#ddd',
backgroundColor: '#fff',
},
selectedMethod: { borderColor: 'black', backgroundColor: '#f9f9f9' },
methodIcon: { width: 30, height: 20, resizeMode: 'contain', marginHorizontal: 10 },
methodText: { fontSize: 16, color: '#333', fontWeight: '500' },
radio: { width: 20, height: 20, borderRadius: 10, borderWidth: 2, borderColor: '#ccc' },
radioSelected: { borderColor: 'black', backgroundColor: 'black' },
});
export default PaymentDetailScreen;
I am integrating the Paymob React Native SDK into my application and facing an issue with the payment callback flow. The payment is being processed successfully, and the transaction is completed from Paymob’s side. However, after success, the SDK modal/sheet remains stuck displaying the raw success JSON response instead of closing or triggering the success listener/callback. Current behavior: • Payment completes successfully • Success response JSON is displayed inside the sheet/modal • The SDK does not dismiss automatically • Success listener / callback is not triggered Example response shown on the sheet: { “success”: true, “message”: “Payment successful”, “data”: { …
Here Is My Code
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
TextInput,
StyleSheet,
TouchableOpacity,
Image,
ScrollView,
Alert,
ActivityIndicator,
KeyboardAvoidingView,
Platform,
SafeAreaView,
NativeModules,
} from 'react-native';
import Paymob, { PaymentStatus } from 'paymob-reactnative';
import * as api from '../../api/apiService';
import colors from '../../utils/colors';
import { useNavigation, useRoute, CommonActions } from '@react-navigation/native';
import { usePaymentReserveMutation } from '../../api/rtkApi';
import { getLocalizedText } from '../../utils/language';
import { useLanguage } from '../../context/LanguageContext';
import { formatPrice } from '../../utils/constants';
import { tracking } from '../../utils/tracking';
import CurrencySymbol from '../../components/CurrencySymbol';
import BackButton from '../../components/BackButton';
const PaymentDetailScreen: React.FC = () => {
const { language } = useLanguage();
const isArabic = language === 'ar';
const [step, setStep] = useState<'data' | 'payment'>('data');
const [loading, setLoading] = useState(false);
const [paymentReserveMutation] = usePaymentReserveMutation();
const navigation = useNavigation();
const route = useRoute();
const { car } = route.params || {};
// State flag used to trigger navigation from React's render cycle
// (calling navigation directly from SDK callback can fail on native threads)
const [paymentResult, setPaymentResult] = useState<'success' | 'fail' | null>(null);
// React-side navigation triggered by paymentResult state change
useEffect(() => {
if (paymentResult === 'success') {
// Try to nudge the SDK to close by removing the listener
Paymob.removeSdkListener();
// Forcefully dismiss any native modal (Paymob sheet)
if (NativeModules.DismissModal) {
NativeModules.DismissModal.dismiss();
} else {
console.warn('DismissModal native module is not available. Please rebuild the app and ensure files are added to the Xcode project.');
}
// Forceful stack reset to Success screen (destroys previous stack)
setTimeout(() => {
}, [paymentResult, navigation]);
// Global SDK configuration on mount
useEffect(() => {
Paymob.setAppName('Tira Cars');
Paymob.setButtonBackgroundColor('#000000');
Paymob.setButtonTextColor('#FFFFFF');
Paymob.setShowTransactionResult(false);
Paymob.setShowConfirmationPage(false);
Paymob.setShowSaveCard(false);
Paymob.setSaveCardDefault(false);
Paymob.setKeyboardHandlingEnabled(true);
}, []);
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [city, setCity] = useState('');
const totalAmount = parseFloat(car?.total_price);
const reserveAmount = parseFloat(car?.reservation_fee);
const handleSubmitData = () => {
if (!firstName.trim() || !lastName.trim() || !email.trim()) {
Alert.alert(
getLocalizedText(language, 'missingInfo'),
getLocalizedText(language, 'fillAllFields'),
);
return;
}
setStep('payment');
};
/* ---------------- HEADER ---------------- */
const renderHeader = () => (
<View
style={[
styles.header,
{ flexDirection: isArabic ? 'row-reverse' : 'row' },
]}
>
<TouchableOpacity
onPress={() => {
if (step === 'payment') {
setStep('data');
} else {
navigation.goBack();
}
}}
style={styles.backButton}
>
<Image
source={require('../../assets/goback.png')}
style={[
styles.backIcon,
{ transform: [{ scaleX: isArabic ? -1 : 1 }] },
]}
/>
);
/* ---------------- STEP 1: DATA ENTRY ---------------- */
const renderDataEntry = () => (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
>
<ScrollView
contentContainerStyle={[styles.page, isArabic && { direction: 'ltr' }]}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<Text
style={[styles.heading, { textAlign: isArabic ? 'right' : 'center' }]}
>
{getLocalizedText(language, 'dataEntry')}
);
/* ---------------- STEP 2: PAYMENT ---------------- */
const handlePayment = async () => {
try {
setLoading(true);
const resp = await paymentReserveMutation({
amount: reserveAmount,
car_id: car.id,
billing_data: {
first_name: firstName,
last_name: lastName,
email,
phone_number: '05xxxxxxxx',
},
}).unwrap();
console.log(resp, 'payment response');
tracking.track('start_checkout', { amount: reserveAmount, car_id: car.id });
Paymob.setSdkListener((status: PaymentStatus) => {
console.log('Paymob SDK Result:', status);
switch (status) {
case PaymentStatus.SUCCESS:
console.log('Payment Successful');
setPaymentResult('success');
break;
case PaymentStatus.FAIL:
console.log('Payment Failed');
setPaymentResult('fail');
Alert.alert('Payment Failed', 'Transaction was not successful. Please try again.');
break;
case PaymentStatus.PENDING:
console.log('Payment Pending');
// Optionally handle pending
break;
}
});
};
const renderPayment = () => (
<ScrollView
contentContainerStyle={[styles.page, isArabic && { direction: 'ltr' }]}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<Text
style={[styles.heading, { textAlign: isArabic ? 'right' : 'center' }]}
>
{getLocalizedText(language, 'payment')}
);
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
{renderHeader()}
{step === 'data' && renderDataEntry()}
{step === 'payment' && renderPayment()}
);
};
/* ---------------- STEP PROGRESS ---------------- */
const StepProgress = ({ currentStep }: { currentStep: 'data' | 'payment' }) => {
const { language } = useLanguage();
const isArabic = language === 'ar';
const steps = [
{ label: getLocalizedText(language, 'dataEntry'), key: 'data' },
{ label: getLocalizedText(language, 'payment'), key: 'payment' },
];
const getStepIndex = (key: string) => steps.findIndex(s => s.key === key);
return (
<View
style={[
styles.progressContainer,
isArabic && { flexDirection: 'row-reverse' },
]}
>
{steps.map((step, index) => {
const isActive = currentStep === step.key;
const isCompleted = getStepIndex(currentStep) > index;
return (
<React.Fragment key={step.key}>
<View
style={[
styles.circle,
{
backgroundColor: isActive || isCompleted ? 'black' : '#ccc',
},
]}
/>
<Text
style={{
fontSize: 14,
color: isActive || isCompleted ? 'black' : '#999',
marginTop: 4,
textAlign: 'center',
}}
>
{step.label}
{index < steps.length - 1 && (
<View
style={[
styles.line,
{
backgroundColor:
getStepIndex(currentStep) > index ? 'black' : '#ccc',
},
]}
/>
)}
</React.Fragment>
);
})}
);
};
const styles = StyleSheet.create({
page: { padding: 20, flexGrow: 1 },
header: {
alignItems: 'center',
justifyContent: 'space-between',
padding: 15,
borderBottomWidth: 1,
borderColor: '#eee',
margin: 16,
},
headerTitle: { fontSize: 20, fontWeight: '600', color: '#000' },
heading: { marginTop: 10, fontSize: 22, fontWeight: '600', color: '#000' },
box: {
backgroundColor: colors.white,
borderRadius: 10,
padding: 15,
marginTop: 20,
borderWidth: 1,
borderColor: colors.black,
},
detailBox: {
marginTop: 10,
backgroundColor: '#f7f7f7',
padding: 10,
borderRadius: 6,
borderWidth: 1,
borderColor: '#ccc',
},
row: {
justifyContent: 'space-between',
marginBottom: 2,
marginTop: 10,
},
rowText: {
fontWeight: '500',
},
label: { marginTop: 15, fontWeight: '600', color: '#000' },
input: {
borderWidth: 1,
borderColor: colors.gray,
borderRadius: 8,
padding: 10,
marginTop: 12,
color: colors.black,
backgroundColor: colors.white,
},
bookBtn: {
backgroundColor: 'black',
padding: 15,
borderRadius: 8,
marginTop: 20,
marginBottom: 20,
},
bookText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
phone: {
marginTop: 150,
fontSize: 20,
fontWeight: 'bold',
color: 'brown',
textAlign: 'center',
},
otpRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 40,
},
otpBox: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
width: 40,
height: 50,
textAlign: 'center',
fontSize: 20,
},
confirmBtn: {
backgroundColor: 'black',
padding: 15,
borderRadius: 8,
marginTop: 30,
marginBottom: 20,
alignItems: 'center',
},
card: {
backgroundColor: colors.white,
padding: 10,
borderRadius: 10,
marginTop: 20,
alignItems: 'center',
borderWidth: 1,
borderColor: colors.black,
width: '100%',
maxWidth: '100%',
overflow: 'hidden',
},
cardText: {
fontWeight: 'bold',
flexWrap: 'wrap',
},
cardPrice: {
fontWeight: 'bold',
marginTop: 5,
},
progressContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 20,
},
stepItem: { alignItems: 'center', flex: 1 },
circle: { width: 22, height: 22, borderRadius: 11 },
line: { height: 2, flex: 1, marginHorizontal: 4 },
backButton: { padding: 8 },
backIcon: { width: 24, height: 24, resizeMode: 'contain' },
methodsContainer: { marginTop: 20 },
methodBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 15,
borderRadius: 8,
borderWidth: 1,
borderColor: '#ddd',
backgroundColor: '#fff',
},
selectedMethod: { borderColor: 'black', backgroundColor: '#f9f9f9' },
methodIcon: { width: 30, height: 20, resizeMode: 'contain', marginHorizontal: 10 },
methodText: { fontSize: 16, color: '#333', fontWeight: '500' },
radio: { width: 20, height: 20, borderRadius: 10, borderWidth: 2, borderColor: '#ccc' },
radioSelected: { borderColor: 'black', backgroundColor: 'black' },
});
export default PaymentDetailScreen;