| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- import {
- View,
- Text,
- ScrollView,
- FlatList,
- Pressable,
- ActivityIndicator,
- Image,
- Modal,
- Alert,
- TextInput
- } from 'react-native';
- import NormalButton from '../global/normal_button';
- import { SafeAreaView } from 'react-native-safe-area-context';
- import { router, useFocusEffect } from 'expo-router';
- import { useColorScheme } from 'nativewind';
- import RecentlyBookedScrollView from '../global/recentlyBookedScrollView';
- import {
- BellIconSvg,
- HomeIconSvg,
- MyBookingIconSvg,
- WhatsAppSvg,
- WalletSvg,
- MyWalletSvg,
- QrCodeIconSvg,
- VipCodeIconSvg
- } from '../global/SVG';
- import { AuthContext } from '../../context/AuthProvider';
- import { useCallback, useContext, useEffect, useState } from 'react';
- import { authenticationService } from '../../service/authService';
- import { chargeStationService } from '../../service/chargeStationService';
- import { walletService } from '../../service/walletService';
- import useUserInfoStore from '../../providers/userinfo_store';
- import NormalInput from '../global/normal_input';
- import { usePushNotifications } from '../../app/hooks/usePushNotifications';
- import { notificationStorage } from '../notificationStorage';
- import { handleGoWhatsApp } from '../../util/index';
- interface HomePageProps {}
- const HomePage: React.FC<HomePageProps> = () => {
- const now = new Date();
- const { user } = useContext(AuthContext);
- const { userID, currentPrice, setUserID, setCurrentPrice, setNotifySessionID } = useUserInfoStore();
- const { colorScheme, toggleColorScheme } = useColorScheme();
- const [showLicencePlateMessage, setShowLicencePlateMessage] = useState<boolean>(false);
- const [licensePlate, setLicensePlate] = useState<string>('');
- const [showConfirmationModal, setShowConfirmationModal] = useState<boolean>(false);
- const [showOnboarding, setShowOnboarding] = useState(true);
- const [mainPromotion, setMainPromotion] = useState([]);
- const [mainPromotionImage, setMainPromotionImage] = useState('');
- const [reservationAfter2025, setReservationAfter2025] = useState([]);
- const [isLoadingReservations, setIsLoadingReservations] = useState(true);
- const [unreadCount, setUnreadCount] = useState(0);
- useEffect(() => {
- const fetchIDandCheckLicensePlate = async () => {
- try {
- const response = await authenticationService.getUserInfo();
- //if success, set user ID,
- if (response) {
- setNotifySessionID(response.data.notify_session_id);
- setUserID(response.data.id);
- //after setting id, also check if the user has a valid license plate, if not, show message
- if (!response.data.cars || !Array.isArray(response.data.cars)) {
- Alert.alert('無法檢測車輛資訊', '請稍後再試');
- setShowLicencePlateMessage(false);
- }
- if (response.data.cars.length === 1 && response.data.cars[0].license_plate === '0000') {
- setShowLicencePlateMessage(true);
- }
- } else {
- Alert.alert('fail to set user/notification session ID');
- }
- } catch (error) {
- console.log(error);
- }
- };
- const fetchCurrentPrice = async () => {
- try {
- const response = await chargeStationService.getCurrentPrice();
- if (response) {
- setCurrentPrice(response);
- }
- } catch (error) {
- console.log('main page fetch current price error', error);
- }
- };
- const fetchMainPromotion = async () => {
- try {
- const response = await chargeStationService.getAdvertise();
- if (response) {
- const mainPromo = response.filter((item: any) => item.is_main)[0];
- setMainPromotion(mainPromo);
- if (mainPromo) {
- const mainPromoImage = await chargeStationService.getProcessedImageUrl(mainPromo.image_url);
- if (mainPromoImage) {
- setMainPromotionImage(mainPromoImage);
- }
- }
- }
- } catch (error) {
- console.log('Error fetching promotion:', error);
- }
- };
- fetchMainPromotion();
- const fetchWithAllSettled = async () => {
- const results = await Promise.allSettled([
- fetchIDandCheckLicensePlate(),
- fetchCurrentPrice(),
- fetchMainPromotion()
- ]);
- };
- fetchWithAllSettled();
- }, []);
- useFocusEffect(
- useCallback(() => {
- let isActive = true;
- const fetchData = async () => {
- setIsLoadingReservations(true); // Start loading
- try {
- const results = await Promise.allSettled([
- chargeStationService.fetchReservationHistories(),
- chargeStationService.getAdvertise()
- ]);
- if (!isActive) return;
- // Handle reservation data
- if (results[0].status === 'fulfilled') {
- const year2025 = new Date('2025-02-01T00:00:00.000Z');
- const reservationAfter2025 = results[0].value.filter((r: any) => {
- const date = new Date(r.createdAt);
- return date > year2025;
- });
- setReservationAfter2025(reservationAfter2025);
- } else if (results[0].status === 'rejected') {
- Alert.alert('Error fetching reservations:', results[0].reason);
- }
- // Get viewed notifications
- const viewedNotifications = await notificationStorage.getViewedNotifications();
- let totalUnread = 0;
- // Count unread reservations
- if (results[0].status === 'fulfilled') {
- const unreadReservations = reservationAfter2025.filter((r: any) => {
- return !viewedNotifications.some((vn: any) => vn.id === r.id);
- });
- totalUnread += unreadReservations.length;
- }
- // Count unread promotions
- if (results[1].status === 'fulfilled') {
- const unreadPromotions = results[1].value.filter((p: any) => {
- return !viewedNotifications.some((vn) => vn.id === p.id);
- });
- totalUnread += unreadPromotions.length;
- }
- setUnreadCount(totalUnread);
- } catch (error) {
- if (!isActive) return;
- Alert.alert('Error fetching data');
- } finally {
- if (isActive) {
- setIsLoadingReservations(false);
- }
- }
- };
- fetchData();
- console.log('iiiiiii');
- return () => {
- isActive = false;
- };
- }, [])
- );
- const saveLicensePlate = async (licensePlate: string) => {
- try {
- const response = await chargeStationService.addCar(
- licensePlate,
- '1834d087-bfc1-4f90-8f09-805e3d9422b5',
- 'f599470d-53a5-4026-99c0-2dab34c77f39',
- true
- );
- if (response === true) {
- console.log('License plate saved successfully');
- } else {
- Alert.alert('無法保存車牌號碼', '請稍後再試');
- }
- } catch (error) {
- Alert.alert('暫時無法保存車牌號碼', '請稍後再試');
- }
- };
- return (
- <SafeAreaView edges={['top', 'left', 'right']} className="flex-1 bg-white">
- {/* Add Modal component */}
- {mainPromotionImage && (
- <Modal
- animationType="fade"
- transparent={true}
- visible={showOnboarding}
- onRequestClose={() => setShowOnboarding(false)}
- >
- <Pressable
- className="flex-1 bg-black/50 items-center justify-center"
- onPress={() => setShowOnboarding(false)}
- >
- <View className="w-[120%] rounded-2xl ">
- <Image
- source={{ uri: mainPromotionImage }}
- className="w-full aspect-square "
- resizeMode="contain"
- />
- <Text className="text-center mt-4 mb-2 text-gray-200">點擊任意位置關閉</Text>
- </View>
- </Pressable>
- </Modal>
- )}
- {showLicencePlateMessage && (
- <Modal
- animationType="fade"
- transparent={true}
- visible={showLicencePlateMessage}
- onRequestClose={() => setShowLicencePlateMessage(false)}
- >
- <View className="flex-1 bg-black/50 items-center justify-center">
- {!showConfirmationModal ? (
- // License Plate Input Modal
- <View className="flex flex-col rounded-2xl bg-white overflow-hidden w-[80%]">
- <View className="bg-[#E3F2F8]">
- <Text className="text-base lg:text-lg font-[500] text-center p-4">
- 請添加您的車牌號碼
- </Text>
- </View>
- <View className="p-4 ">
- <Text className="text-sm lg:text-base font-[500] text-left mb-4">
- 為更好地為您提供服務,請在您的帳戶中添加車牌號碼。
- </Text>
- <NormalInput
- value={licensePlate}
- placeholder="車牌號碼"
- onChangeText={(s) => setLicensePlate(s)}
- extendedStyle={{ borderRadius: 12, marginBottom: 0 }}
- textContentType="none"
- autoComplete="off"
- keyboardType="default"
- />
- </View>
- <View className="pr-4 pl-4 pb-4 ">
- <NormalButton
- title={<Text className="text-white text-sm lg:text-lg">確定</Text>}
- onPress={() => {
- //here when users click confirm, i want to pop another modal that say you have entered "xxxxxx", click confirm to continue
- if (!licensePlate.trim()) {
- Alert.alert('請輸入車牌號碼');
- return;
- }
- if (licensePlate.trim().length < 4 || licensePlate.trim().length > 10) {
- Alert.alert('無效的車牌號碼', '請輸入有效的車牌號碼');
- return;
- }
- setShowConfirmationModal(true);
- }}
- />
- </View>
- </View>
- ) : (
- // Confirmation Modal
- <View className="flex flex-col rounded-2xl bg-white overflow-hidden w-[80%]">
- <View className="bg-[#E3F2F8]">
- <Text className="text-base lg:text-lg font-[500] text-center p-4">
- 確認車牌號碼
- </Text>
- </View>
- <View className="p-4">
- <Text className="text-sm lg:text-base font-[500] text-center mb-4">
- 您輸入的車牌號碼為:{licensePlate}
- </Text>
- </View>
- <View className="flex-row p-4 space-x-4">
- <View className="flex-1">
- <NormalButton
- title={<Text className="text-white text-sm lg:text-lg">取消</Text>}
- onPress={() => setShowConfirmationModal(false)}
- />
- </View>
- <View className="flex-1">
- <NormalButton
- title={<Text className="text-white text-sm lg:text-lg">確認</Text>}
- onPress={() => {
- saveLicensePlate(licensePlate);
- setShowConfirmationModal(false);
- setShowLicencePlateMessage(false);
- setLicensePlate('');
- }}
- />
- </View>
- </View>
- </View>
- )}
- </View>
- </Modal>
- )}
- <ScrollView showsVerticalScrollIndicator={false} className="flex-1 mx-[5%] ">
- <View className=" flex-1 pt-8 ">
- <View className="flex-row items-center pb-4">
- <HomeIconSvg />
- <View className="pl-2 flex-1 flex-column ">
- <View className="flex-row justify-between mr-[10%]">
- <Text className="text-lg text-left pb-1">你好!</Text>
- <View className="relative z-5">
- <Pressable
- onPress={() => router.push({ pathname: 'notificationPage' })}
- disabled={isLoadingReservations}
- className="z-10 w-10 items-center justify-center"
- hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
- >
- <View className="w-6 h-6">
- <BellIconSvg />
- </View>
- {unreadCount > 0 && (
- <View className="absolute -top-2 -right-[0.5] bg-red-500 rounded-full w-5 h-5 items-center justify-center">
- <Text className="text-white text-xs font-bold">{unreadCount}</Text>
- </View>
- )}
- </Pressable>
- <Pressable className="z-10 top-9 right-0" onPress={() => handleGoWhatsApp()}>
- <View className="w-8 h-8">
- <WhatsAppSvg />
- </View>
- </Pressable>
- </View>
- </View>
- <Text className="text-4xl font-light ">{user?.nickname}</Text>
- </View>
- </View>
- <View className=" flex-1 justify-center ">
- {/* <Pressable onPress={() => router.push('searchPage')}>
- <View
- style={{
- borderWidth: 1,
- padding: 24,
- borderRadius: 12,
- borderColor: '#bbbbbb',
- maxWidth: '100%'
- }}
- >
- <Text style={{ color: '#888888', fontSize: 16 }}>搜尋充電站或地區..</Text>
- </View>
- </Pressable> */}
- </View>
- </View>
- <View className="flex-1">
- <View className="my-4">
- <NormalButton
- onPress={() => router.push('scanQrPage')}
- // onPress={() => router.push('optionPage')}
- title={
- <View className="flex flex-row justify-start">
- <QrCodeIconSvg />
- <Text className="text-white font-bold text-lg ml-2">掃描及充電</Text>
- </View>
- }
- extendedStyle={{
- alignItems: 'flex-start',
- padding: 24
- }}
- />
- </View>
- <View className="flex-1 flex-row justify-between gap-6">
- {/* <View className="flex-1">
- <NormalButton
- // onPress={() => router.push('bookingMenuPage')}
- onPress={() => Alert.alert('即將推出', '此功能即將推出,敬請期待!')}
- //onPress={() => notificationStorage.clearStorage()}
- title={
- <View className="flex flex-row space-x-2 items-center ">
- <MyBookingIconSvg />
- <Text className="text-white font-bold text-lg ml-2">我的預約</Text>
- </View>
- }
- extendedStyle={{
- alignItems: 'flex-start',
- padding: 24
- }}
- />
- </View> */}
- <View className="flex-1">
- <NormalButton
- onPress={() => router.push('/(account)/(wallet)/walletPage')}
- title={
- <View className="flex flex-row space-x-2 items-center">
- <MyWalletSvg />
- <Text className="text-white font-bold text-lg ml-2">錢包</Text>
- </View>
- }
- extendedStyle={{
- alignItems: 'flex-start',
- padding: 24
- }}
- />
- </View>
- </View>
- <View className="mt-4">
- <NormalButton
- // onPress={() => console.log('掃瞄及充電')}
- onPress={() => router.push('vipQrPage')}
- title={
- <View className="flex flex-row items-center space-x-2">
- <VipCodeIconSvg />
- <Text className="text-white font-bold text-lg ml-2">專屬會員二維碼</Text>
- </View>
- }
- extendedStyle={{
- alignItems: 'flex-start',
- padding: 24
- }}
- />
- </View>
- </View>
- </ScrollView>
- </SafeAreaView>
- );
- };
- export default HomePage;
|