optionPage.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // app/(auth)/(tabs)/(home)/optionPage.tsx
  2. import {
  3. View,
  4. Text,
  5. ScrollView,
  6. Pressable,
  7. Image,
  8. Dimensions,
  9. ImageBackground,
  10. BackHandler,
  11. Alert
  12. } from 'react-native';
  13. import React, { useCallback, useEffect, useState } from 'react';
  14. import { SafeAreaView } from 'react-native-safe-area-context';
  15. import { router, useFocusEffect, useNavigation } from 'expo-router';
  16. import { PreviousPageBlackSvg } from '../../../../component/global/SVG';
  17. import { useChargingStore } from '../../../../providers/scan_qr_payload_store';
  18. import { walletService } from '../../../../service/walletService';
  19. import { chargeStationService } from '../../../../service/chargeStationService';
  20. import { useTranslation } from '../../../../util/hooks/useTranslation';
  21. const optionPage = () => {
  22. const { t } = useTranslation();
  23. const {
  24. total_power,
  25. setTotalPower,
  26. promotion_code,
  27. setPromotionCode,
  28. sum_of_coupon,
  29. setSumOfCoupon,
  30. setCouponDetail,
  31. current_price_store,
  32. setCurrentPriceStore,
  33. setProcessedCouponStore
  34. } = useChargingStore();
  35. const buttonText = [
  36. {
  37. power: t('chargingOption.option.kwh_20'),
  38. minute: t('chargingOption.option.min_25'),
  39. total_power: 20
  40. },
  41. {
  42. power: t('chargingOption.option.kwh_25'),
  43. minute: t('chargingOption.option.min_30'),
  44. total_power: 25
  45. },
  46. {
  47. power: t('chargingOption.option.kwh_30'),
  48. minute: t('chargingOption.option.min_40'),
  49. total_power: 30
  50. },
  51. {
  52. power: t('chargingOption.option.kwh_40'),
  53. minute: t('chargingOption.option.min_45'),
  54. total_power: 40
  55. },
  56. {
  57. power: t('chargingOption.option.full_charge'),
  58. minute: t('chargingOption.option.up_to_80kwh'),
  59. total_power: 80
  60. }
  61. ];
  62. const screenWidth = Dimensions.get('window').width;
  63. const [useableCoupon, setUseableCoupon] = useState([]);
  64. const navigation = useNavigation();
  65. //forbid user leftswipping
  66. useEffect(() => {
  67. navigation.setOptions({
  68. gestureEnabled: false
  69. });
  70. }, [navigation]);
  71. // 优惠券注释
  72. useEffect(() => {
  73. const fetchData = async () => {
  74. try {
  75. const info = await walletService.getCustomerInfo();
  76. const coupon = await walletService.getCouponForSpecificUser(info.id);
  77. //filter by expire date is not past today.and is_consumed is fa.se.
  78. const useableConpon = coupon.filter((couponObj: any) => {
  79. const today = new Date();
  80. if (couponObj.expire_date === null) {
  81. return couponObj.is_consumed === false;
  82. }
  83. const expireDate = new Date(couponObj.expire_date);
  84. return expireDate > today && couponObj.is_consumed === false;
  85. });
  86. setUseableCoupon(useableConpon);
  87. } catch (error) {}
  88. };
  89. fetchData();
  90. }, []);
  91. const cleanupData = () => {
  92. setTotalPower(null);
  93. setSumOfCoupon(0);
  94. setPromotionCode([]);
  95. setCouponDetail([]);
  96. setProcessedCouponStore([]);
  97. };
  98. // Add this effect to handle Android back button
  99. useFocusEffect(
  100. useCallback(() => {
  101. const onBackPress = () => {
  102. cleanupData();
  103. if (router.canGoBack()) {
  104. router.back();
  105. } else {
  106. router.replace('/scanQrPage');
  107. }
  108. return true;
  109. };
  110. const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress);
  111. return () => subscription.remove();
  112. }, [])
  113. );
  114. const displayedText =
  115. promotion_code.length > 0
  116. ? t('chargingOption.option.coupon_value', { value: sum_of_coupon })
  117. : useableCoupon.length > 0
  118. ? t('chargingOption.option.available_coupon')
  119. : t('chargingOption.option.no_coupon');
  120. return (
  121. <SafeAreaView className="flex-1 bg-white" edges={['top', 'right', 'left']}>
  122. <ScrollView className="flex-1 mx-[5%]" showsVerticalScrollIndicator={false}>
  123. <View style={{ marginTop: 25 }}>
  124. <Pressable
  125. onPress={() => {
  126. if (router.canGoBack()) {
  127. cleanupData();
  128. router.back();
  129. } else {
  130. cleanupData();
  131. router.replace('/scanQrPage');
  132. }
  133. }}
  134. >
  135. <PreviousPageBlackSvg />
  136. </Pressable>
  137. </View>
  138. <View className="flex-1 flex-col justify-center items-center">
  139. {/* top word and logo */}
  140. <View className="flex-col justify-center items-center gap-2">
  141. <Text className="text-2xl font-[600]">{t('chargingOption.option.select_charge_amount')}</Text>
  142. <Image
  143. source={require('../../../../assets/battery.png')}
  144. style={{ width: screenWidth * 0.3, height: undefined, aspectRatio: 1 }}
  145. className="mb-2 md:mb-4 lg:mb-4 xl:mb-6 "
  146. />
  147. </View>
  148. {/* middle button */}
  149. <View className="flex-1 w-full mt-6">
  150. <View className="flex-row flex-wrap justify-center items-center gap-4 ">
  151. {/* not showing full power because it needs to be full width */}
  152. {buttonText
  153. .filter((item) => item.power != t('chargingOption.option.full_charge'))
  154. .map((buttonTextObj: any, index) => (
  155. <Pressable
  156. key={index}
  157. className={`${total_power === buttonTextObj.total_power ? 'bg-[#02677D]' : ''}
  158. w-[45%] border border-[#02677D] p-1 md:p-2 lg:p-3 xl:p-4 py-4 lg:py-6 rounded-xl`}
  159. onPress={() => {
  160. setTotalPower(buttonTextObj.total_power);
  161. }}
  162. >
  163. <Text
  164. className={`${
  165. total_power === buttonTextObj.total_power
  166. ? 'text-white'
  167. : 'text-[#02677D]'
  168. } text-base lg:text-lg xl:text-xl text-center`}
  169. >
  170. {buttonTextObj.power}({buttonTextObj.minute})
  171. </Text>
  172. </Pressable>
  173. ))}
  174. {/* this is 充滿停機only */}
  175. {buttonText
  176. .filter((item) => item.power === t('chargingOption.option.full_charge'))
  177. .map((item, index) => (
  178. <Pressable
  179. key={index}
  180. onPress={() => {
  181. setTotalPower(item.total_power);
  182. }}
  183. className={`${
  184. total_power === item.total_power ? 'bg-[#02677D]' : ''
  185. } w-[94%] border border-[#02677D] p-1 md:p-2 lg:p-3 xl:p-4 py-4 lg:py-6 rounded-xl`}
  186. >
  187. <Text
  188. className={`${
  189. total_power === item.total_power ? 'text-white' : 'text-[#02677D]'
  190. } text-base lg:text-lg xl:text-xl text-center`}
  191. >
  192. {item.power}({item.minute})
  193. </Text>
  194. </Pressable>
  195. ))}
  196. </View>
  197. {/* coupon row */}
  198. <Pressable
  199. onPress={() => {
  200. if (!total_power) {
  201. Alert.alert(
  202. t('chargingOption.option.alert.select_plan_title'),
  203. t('chargingOption.option.alert.select_plan_message'),
  204. [{ text: t('common.confirm'), style: 'default' }],
  205. {
  206. cancelable: true
  207. }
  208. );
  209. } else {
  210. Alert.alert(
  211. t('chargingOption.option.alert.reminder_title'),
  212. t('chargingOption.option.alert.reminder_message'), [
  213. { text: t('common.confirm'), onPress: () => router.push('/selectCoupon') }
  214. ]);
  215. }
  216. }}
  217. style={{ flex: 1, borderRadius: 10, overflow: 'hidden', marginTop: 10 }}
  218. >
  219. <ImageBackground
  220. source={require('../../../../assets/coupon.png')}
  221. style={{
  222. width: '100%',
  223. height: 72,
  224. borderRadius: 24,
  225. justifyContent: 'center',
  226. alignItems: 'flex-end'
  227. }}
  228. resizeMode="contain"
  229. >
  230. <Text className="text-white text-center text-base lg:text-lg xl:text-xl pr-6 md:pr-8 lg:pr-10 xl:pr-12">
  231. {displayedText}
  232. </Text>
  233. </ImageBackground>
  234. </Pressable>
  235. </View>
  236. {/* confirm and cancel */}
  237. <View className="flex-row flex-wrap justify-center items-center gap-4 my-8 lg:my-6">
  238. <Pressable
  239. onPress={() => {
  240. cleanupData();
  241. if (router.canGoBack()) {
  242. router.back();
  243. } else {
  244. router.replace('/scanQrPage');
  245. }
  246. }}
  247. className="w-[45%] border border-[#02677D] p-1 md:p-2 lg:p-3 xl:p-4 py-4 lg:py-6 rounded-xl "
  248. >
  249. <Text className="text-[#02677D] text-base lg:text-lg xl:text-xl text-center">
  250. {t('common.cancel')}
  251. </Text>
  252. </Pressable>
  253. <Pressable
  254. onPress={() => {
  255. if (!total_power) {
  256. Alert.alert(
  257. t('chargingOption.option.alert.select_plan_title'),
  258. t('chargingOption.option.alert.confirm_plan_message'),
  259. [{ text: t('common.confirm'), style: 'default' }],
  260. {
  261. cancelable: true
  262. }
  263. );
  264. } else {
  265. router.push('/totalPayment');
  266. }
  267. }}
  268. className="w-[45%] border border-[#02677D] p-1 md:p-2 lg:p-3 xl:p-4 py-4 lg:py-6 rounded-xl "
  269. >
  270. <Text className="text-[#02677D] text-base lg:text-lg xl:text-xl text-center">
  271. {t('common.confirm')}
  272. </Text>
  273. </Pressable>
  274. </View>
  275. </View>
  276. </ScrollView>
  277. </SafeAreaView>
  278. );
  279. };
  280. export default optionPage;