You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

476 lines
12 KiB

// 余额管理
import React, { useState, useCallback, useRef } from "react";
import {
View,
Text,
StyleSheet,
Image,
ScrollView,
TouchableOpacity,
Modal,
SafeAreaView,
StatusBar,
Platform,
FlatList,
ActivityIndicator,
} from "react-native";
import { useFocusEffect } from "@react-navigation/native";
import fontSize from "../../utils/fontsizeUtils";
import widthUtils from "../../utils/widthUtils";
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { RootStackParamList } from "../../navigation/types";
import RechargeScreen from "./RechargeScreen";
import BackIcon from "../../components/BackIcon";
import useUserStore from "../../store/user";
import { Transaction, payApi } from "../../services/api/payApi";
import { useTranslation } from "react-i18next";
type BalanceScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
"Balance"
>;
export const BalanceScreen = () => {
const { t } = useTranslation();
const { user } = useUserStore();
const navigation = useNavigation<BalanceScreenNavigationProp>();
const [isModalVisible, setIsModalVisible] = useState(false);
const [rechargeHistory, setRechargeHistory] = useState<Transaction[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const pageSize = 10;
const handleOpenModal = () => {
setIsModalVisible(true);
};
const handleCloseModal = () => {
setIsModalVisible(false);
};
const fetchRechargeHistory = async (page: number, refresh = false) => {
if (loading || (!hasMore && !refresh)) return;
try {
setLoading(true);
const response = await payApi.getTransactionHistory(page, pageSize);
if (response.items.length < pageSize) {
setHasMore(false);
}
if (refresh) {
setRechargeHistory(response.items);
} else {
setRechargeHistory(prev => [...prev, ...response.items]);
}
setCurrentPage(page);
} catch (error) {
console.error("Failed to fetch transaction history:", error);
} finally {
setLoading(false);
}
};
const handleLoadMore = () => {
if (!loading && hasMore) {
fetchRechargeHistory(currentPage + 1);
}
};
useFocusEffect(
useCallback(() => {
fetchRechargeHistory(1, true);
}, [])
);
const renderTransactionItem = ({ item }: { item: Transaction }) => (
<View style={styles.transactionHistoryList} key={item.transaction_id}>
<View style={styles.transactionDetailsPanel}>
<View style={styles.transactionDetailsRow}>
<Text style={styles.transactionDescriptionBold}>
{item.type}
</Text>
<Text
style={[
styles.transactionAmountDisplay,
{
color:
Number(item.amount) < 0
? "#0035a4"
: "#ff5217",
},
]}
>
{item.amount} {item.currency}
</Text>
</View>
<View style={styles.transactionInfoRow}>
<Text style={styles.transactionDate}>
{item.timestamp}
</Text>
<Text style={styles.shipmentReference}>
{item.type}
</Text>
</View>
</View>
</View>
);
const renderFooter = () => {
if (!loading) return null;
return (
<View style={styles.loaderContainer}>
<ActivityIndicator size="small" color="#FF8000" />
</View>
);
};
return (
<SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
<View style={styles.safeAreaContent}>
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<BackIcon size={fontSize(22)} />
</TouchableOpacity>
<Text style={styles.headerTitle}>{t("balance.screen.title")}</Text>
<View style={styles.placeholder} />
</View>
<View style={styles.container}>
<FlatList
data={rechargeHistory}
renderItem={renderTransactionItem}
keyExtractor={(item) => item.transaction_id.toString()}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
ListHeaderComponent={() => (
<View style={styles.balanceCard}>
<View style={styles.sunriseGradientContainer}>
<View style={styles.timeContainer}>
<View style={styles.timeContainer2}></View>
</View>
<View style={styles.balanceWidget}>
<Text style={styles.balanceText}>
{t("balance.screen.balance_card")}
</Text>
<Image
source={require("../../../assets/img/image_11d1b9c.png")}
style={styles.balanceImage}
/>
</View>
</View>
<View style={styles.balanceDetailsContainer}>
<View style={styles.totalBalanceCard}>
<View style={styles.cardContainer}>
<View style={styles.financialInfoContainer}>
<Text style={styles.largeBlackText}>{user?.balance}</Text>
<View style={styles.svgContainer}></View>
</View>
<View style={styles.totalSoldInfoContainer}>
<Text style={styles.totalSoldeText}>
{t("balance.screen.total_balance")} ({user?.currency})
</Text>
</View>
</View>
<View
style={[
styles.buttonContainer,
{ backgroundColor: "#FF8000", alignSelf: "center" },
]}
>
<TouchableOpacity
style={styles.button}
onPress={handleOpenModal}
>
<Text style={styles.buttonText}>
{t("balance.screen.recharge_now")}
</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.transactionDetailsContainer}>
<View style={styles.balanceDetailContainer}>
<Text style={styles.balanceDetailTitle}>
{t("balance.screen.balance_detail")}
</Text>
</View>
</View>
</View>
</View>
)}
/>
</View>
<Modal
animationType="slide"
transparent={true}
visible={isModalVisible}
onRequestClose={handleCloseModal}
>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<RechargeScreen onClose={handleCloseModal} />
</View>
</View>
</Modal>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "#fff",
},
safeAreaContent: {
flex: 1,
paddingTop: 0,
},
container: {
flex: 1,
backgroundColor: "#f0f0f0",
},
header: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 15,
paddingVertical: 10,
backgroundColor: "white",
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
backButton: {
padding: 5,
},
headerTitle: {
fontSize: fontSize(18),
fontWeight: "600",
textAlign: "center",
},
placeholder: {
width: widthUtils(24, 24).width,
},
balanceCard: {
display: "flex",
flexDirection: "column",
width: "100%",
backgroundColor: "#f0f0f0",
},
sunriseGradientContainer: {
backgroundColor:
"linear-gradient(180deg, rgba(255, 119, 0, 1) 0%, rgba(255, 77, 0, 1) 100%)",
flexDirection: "column",
alignItems: "stretch",
},
timeContainer: {
height: widthUtils(43, 43).height,
paddingTop: 21,
},
timeContainer2: {
flexDirection: "row",
justifyContent: "space-between",
},
timeContainer1: {
flexDirection: "row",
justifyContent: "center",
},
timeDisplay: {
fontSize: fontSize(17),
fontWeight: "bold",
color: "white",
},
timeIndicatorContainer: {
width: widthUtils(10, 124).width,
height: widthUtils(10, 124).height,
},
timeDisplayContainer: {
width: widthUtils(13, 153).width,
height: widthUtils(13, 153).height,
},
balanceWidget: {
flexDirection: "row",
justifyContent: "space-between",
padding: 9,
},
balanceText: {
fontSize: fontSize(32),
fontWeight: "bold",
color: "white",
},
balanceImage: {
width: widthUtils(139, 139).width,
height: widthUtils(139, 139).height,
},
balanceDetailsContainer: {
marginTop: -53,
flexDirection: "column",
},
totalBalanceCard: {
paddingLeft: 20,
paddingRight: 20,
paddingTop: 20,
},
cardContainer: {
backgroundColor: "white",
borderRadius: 10,
padding: 20,
shadowColor: "rgba(186, 186, 186, 0.25)",
shadowOffset: { width: 2, height: 4 },
shadowOpacity: 0.7,
},
financialInfoContainer: {
flexDirection: "row",
justifyContent: "space-between",
},
largeBlackText: {
fontSize: fontSize(36),
fontWeight: "bold",
color: "black",
},
svgContainer: {
width: widthUtils(24, 24).width,
height: widthUtils(24, 24).height,
color: "#019847",
},
totalSoldInfoContainer: {
marginTop: 7,
},
totalSoldeText: {
fontSize: fontSize(14),
color: "#7c7c7c",
},
totalBalanceInfoContainer: {
flexDirection: "row",
justifyContent: "space-between",
marginTop: 22,
},
verticalCenteredTextBox: {
flexDirection: "column",
justifyContent: "center",
width: "54.21%",
},
highlightedText: {
fontSize: fontSize(20),
fontWeight: "bold",
color: "#fe1e00",
},
expirationInfo: {
fontSize: fontSize(14),
color: "#7c7c7c",
},
deadlineInfoContainer: {
flexDirection: "column",
justifyContent: "center",
width: "45.79%",
},
deadlineText: {
fontSize: fontSize(20),
fontWeight: "bold",
color: "black",
},
transactionDetailsContainer: {
paddingLeft: 20,
paddingRight: 20,
paddingBottom: 20,
},
balanceDetailContainer: {
marginTop: 37,
},
balanceDetailTitle: {
fontSize: fontSize(20),
fontWeight: "bold",
color: "black",
marginBottom: 10,
},
transactionDetailsContainer1: {
flexDirection: "column",
},
transactionHistoryList: {
flexDirection: "column",
},
transactionDetailsPanel: {
flexDirection: "column",
padding: 15.5,
borderBottomWidth: 1,
borderBottomColor: "#dddddd",
borderStyle: "dashed",
backgroundColor: "#FFFFFF",
},
transactionDetailsRow: {
flexDirection: "row",
justifyContent: "space-between",
},
transactionDescriptionBold: {
fontSize: fontSize(15),
fontWeight: "bold",
color: "black",
},
transactionAmountDisplay: {
fontSize: fontSize(24),
fontWeight: "bold",
},
transactionInfoRow: {
flexDirection: "row",
justifyContent: "space-between",
marginTop: 7,
},
transactionDate: {
fontSize: fontSize(13),
color: "#7f7e7e",
},
shipmentReference: {
fontSize: fontSize(13),
color: "#353029",
},
buttonContainer: {
borderRadius: 25,
width: widthUtils(50, 300).width,
height: widthUtils(50, 300).height,
justifyContent: "center",
alignItems: "center",
marginTop: 30,
},
button: {
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center",
borderRadius: 25,
},
buttonText: {
fontSize: fontSize(16),
fontWeight: "600",
color: "white",
textAlign: "center",
},
modalContainer: {
flex: 1,
backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "flex-end",
},
modalContent: {
height: "90%",
backgroundColor: "#FFFFFF",
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
},
loaderContainer: {
paddingVertical: 20,
alignItems: 'center',
justifyContent: 'center',
},
});