浏览代码

fix UI, add coupon and other services

Ian Fung 1 年之前
父节点
当前提交
faea0aba87

+ 13 - 0
app/(auth)/(tabs)/(home)/(vehicle)/chooseCarPage.tsx

@@ -0,0 +1,13 @@
+import { View, Text } from 'react-native';
+import React from 'react';
+import ChooseCarPageComponent from '../../../../../component/chooseCar/chooseCarPageComponent';
+
+const ChooseCarPage = () => {
+    return (
+        <View className="flex-1">
+            <ChooseCarPageComponent />
+        </View>
+    );
+};
+
+export default ChooseCarPage;

+ 3 - 2
app/(public)/test.tsx

@@ -1,9 +1,10 @@
 import { StyleSheet, Text, View } from 'react-native';
+import ChooseCarPage from '../../component/chooseCar/chooseCarPageComponent';
 
 export default function Test() {
     return (
-        <View className="flex-1 items-center justify-center">
-            <Text>Test</Text>
+        <View className="flex-1 ">
+            <ChooseCarPage />
         </View>
     );
 }

二进制
assets/ccLogo.png


+ 1 - 1
component/accountPages/addVehiclePageComponent.tsx

@@ -73,7 +73,7 @@ const AddVehiclePageComponent = () => {
                 >
                     <Pressable
                         style={styles.button}
-                        onPress={() => console.log('goToChooseCarPage')}
+                        onPress={() => router.push('chooseCarPage')}
                     >
                         <TextInput
                             style={styles.fakeTextInput}

+ 31 - 3
component/accountPages/addVehicleSuccessfulPageComponent.tsx

@@ -9,11 +9,36 @@ import {
 import { SafeAreaView } from 'react-native-safe-area-context';
 import NormalButton from '../global/normal_button';
 import { PreviousPageBlackSvg, TickLogoSvg } from '../global/SVG';
-import { router } from 'expo-router';
+import { router, useLocalSearchParams } from 'expo-router';
+import { useEffect, useState } from 'react';
+import { chargeStationService } from '../../service/chargeStationService';
 
 const AddVehicleSuccessfulPageComponent = () => {
     const { height: deviceHeight, width: deviceWidth } =
         Dimensions.get('window');
+
+    const params = useLocalSearchParams();
+    const selectedTypeID = params.selectedTypeID;
+    const [vehicleName, setVehicleName] = useState('');
+    console.log(selectedTypeID);
+
+    useEffect(() => {
+        const fetchData = async () => {
+            try {
+                const result = await chargeStationService.fetchCarBrand();
+                console.log(result.data);
+                const carType = result.data
+                    .flatMap((brand) => brand.car_types)
+                    .find((type) => type.id === selectedTypeID);
+                console.log(carType.name);
+                setVehicleName(carType.name);
+            } catch (error) {
+                console.log(error);
+            }
+        };
+        fetchData();
+    }, []);
+
     return (
         <SafeAreaView className="flex-1 bg-white">
             <ScrollView
@@ -49,8 +74,8 @@ const AddVehicleSuccessfulPageComponent = () => {
                             />
                         </View>
 
-                        <Text className="text-3xl font-light pb-4">
-                            TESLA Model 3
+                        <Text className="text-3xl font-light pb-4 text-center">
+                            {vehicleName}
                         </Text>
                         <Text className="text-lg font-light pb-4">
                             已加入我的車輛裡
@@ -74,3 +99,6 @@ const AddVehicleSuccessfulPageComponent = () => {
 };
 
 export default AddVehicleSuccessfulPageComponent;
+function useRoute() {
+    throw new Error('Function not implemented.');
+}

+ 131 - 20
component/accountPages/manageVehiclePageComponent.tsx

@@ -1,20 +1,98 @@
-import { router } from 'expo-router';
+import { router, useLocalSearchParams } from 'expo-router';
 import {
     View,
     Text,
     ScrollView,
     Pressable,
     Image,
-    Dimensions
+    Dimensions,
+    Alert,
+    ActivityIndicator
 } from 'react-native';
 import { SafeAreaView } from 'react-native-safe-area-context';
 import { PreviousPageBlackSvg, StarSvg } from '../global/SVG';
 import NormalButton from '../global/normal_button';
+import { chargeStationService } from '../../service/chargeStationService';
+import { useState } from 'react';
+import { formatDate } from '../../util/lib';
 
 const ManageVehiclePageComponent = () => {
     const { height: deviceHeight, width: deviceWidth } =
         Dimensions.get('window');
+    const [loading, setLoading] = useState(false);
+    const [loading2, setLoading2] = useState(false);
+    const params = useLocalSearchParams();
+    const carID = params?.carID;
+    const carCapacitance = params?.capacitance;
+    const carCapacitanceUnit = params?.capacitance_unit;
+    const carCreatedAt = params?.createdAt;
+    const carModel = params?.carModel;
+    const licensePlate = params?.licensePlate;
+    console.log(carID, carCapacitance, carCapacitanceUnit, carCreatedAt);
 
+    const handleDeleteCar = async (carID) => {
+        const confirmDelete = () => {
+            return new Promise((resolve) => {
+                Alert.alert(
+                    'Confirm Delete',
+                    'Are you sure you want to delete this car?',
+                    [
+                        {
+                            text: 'No',
+                            onPress: () => resolve(false),
+                            style: 'cancel'
+                        },
+                        { text: 'Yes', onPress: () => resolve(true) }
+                    ],
+                    { cancelable: false }
+                );
+            });
+        };
+        const userConfirmed = await confirmDelete();
+        if (userConfirmed) {
+            try {
+                setLoading2(true);
+                const isDeleted = await chargeStationService.deleteCar(carID);
+                if (isDeleted) {
+                    Alert.alert('Deletion Successful', 'Car has been deleted');
+                    router.push('/mainPage');
+                } else {
+                    // Show an error message if deletion was not successful
+                    Alert.alert(
+                        'Deletion Failed',
+                        'Unable to delete the car. Please try again.'
+                    );
+                }
+            } catch (error) {
+                console.error('Error deleting car:', error);
+                Alert.alert(
+                    'Error',
+                    'An error occurred while deleting the car.'
+                );
+            } finally {
+                setLoading2(false);
+            }
+        }
+    };
+
+    const handleSetDefaultCar = async (carID) => {
+        setLoading(true);
+        try {
+            const isSetDefault = await chargeStationService.setDefaultCar(
+                carID
+            );
+            if (isSetDefault) {
+                Alert.alert('設置成功', '已成功設置預設車輛');
+                router.push('/mainPage');
+            } else {
+                console.log('Unable to set default car');
+            }
+        } catch (error) {
+            console.log(error);
+        } finally {
+            setLoading(false);
+        }
+    };
     return (
         <SafeAreaView
             className="flex-1 bg-white"
@@ -68,35 +146,66 @@ const ManageVehiclePageComponent = () => {
                         }}
                     />
                     <View>
-                        <Text className="text-2xl">TESLA Model 3</Text>
-                        <Text className="text-lg text-[#888888]">CE1107</Text>
+                        <Text className="text-2xl">{carModel}</Text>
+                        <Text className="text-lg text-[#888888]">
+                            {licensePlate}
+                        </Text>
                     </View>
                 </View>
-
                 <NormalButton
                     title={
-                        <Text
-                            style={{
-                                fontWeight: '500',
-                                fontSize: 20,
-                                color: '#fff'
-                            }}
-                        >
-                            設置為預設車輛 <StarSvg />
-                        </Text>
+                        loading ? (
+                            <View
+                                style={{
+                                    flexDirection: 'row',
+                                    alignItems: 'center'
+                                }}
+                            >
+                                <ActivityIndicator size="small" color="#fff" />
+                                <Text
+                                    style={{
+                                        fontWeight: '500',
+                                        fontSize: 20,
+                                        color: '#fff',
+                                        marginLeft: 10
+                                    }}
+                                >
+                                    設置中...
+                                </Text>
+                            </View>
+                        ) : (
+                            <Text
+                                style={{
+                                    fontWeight: '500',
+                                    fontSize: 20,
+                                    color: '#fff'
+                                }}
+                            >
+                                設置為預設車輛 <StarSvg />
+                            </Text>
+                        )
                     }
-                    onPress={() => console.log('設置為預設車輛')}
+                    onPress={() => {
+                        if (!loading) {
+                            handleSetDefaultCar(carID);
+                        }
+                    }}
                     extendedStyle={{ marginTop: 24 }}
+                    disabled={loading}
                 />
                 <View className="w-full h-1 my-4 bg-[#DBE4E8]" />
                 <View>
                     <Text className="text-xl pb-4">車輛資訊</Text>
                     <Text className="text-base text-[#888888]">加入日期</Text>
-                    <Text className="text-base mb-3">2/24/2024</Text>
+                    <Text className="text-base mb-3">
+                        {formatDate(carCreatedAt)}
+                    </Text>
                     <Text className="text-base text-[#888888]">總充電量</Text>
-                    <Text className="text-base mb-3">307kWh</Text>
+                    <Text className="text-base mb-3">
+                        {carCapacitance + carCapacitanceUnit}
+                    </Text>
                     <Text className="text-base text-[#888888]">上一次充電</Text>
-                    <Text className="text-base mb-3">3/12/2024</Text>
+                    <Text className="text-base mb-3">待DATA</Text>
                     <View className="my-4 pb-8">
                         <NormalButton
                             title={
@@ -107,10 +216,12 @@ const ManageVehiclePageComponent = () => {
                                         fontWeight: '800'
                                     }}
                                 >
-                                    删除車輛
+                                    {loading2 ? '删除車輛中...' : '删除車輛'}
                                 </Text>
                             }
-                            onPress={() => console.log('删除車輛')}
+                            onPress={() => {
+                                handleDeleteCar(carID);
+                            }}
                             extendedStyle={{
                                 backgroundColor: '#D40000'
                             }}

+ 105 - 19
component/accountPages/myVehiclePageComponent.tsx

@@ -11,15 +11,46 @@ import {
 import { SafeAreaView } from 'react-native-safe-area-context';
 import { CrossLogoSvg, GreenStarSvg, RightArrowIconSvg } from '../global/SVG';
 import NormalButton from '../global/normal_button';
+import { useEffect, useState } from 'react';
+import { chargeStationService } from '../../service/chargeStationService';
+import { authenticationService } from '../../service/authService';
 
 const VehicleRowComponent = ({
     deviceWidth,
-    deviceHeight
+    deviceHeight,
+    carBrand,
+    carModel,
+    licensePlate,
+    carID,
+    capacitance,
+    capacitance_unit,
+    createdAt
 }: {
     deviceWidth: number;
     deviceHeight: number;
+    carBrand: string;
+    carModel: string;
+    licensePlate: string;
+    carID: string;
+    capacitance: number;
+    capacitance_unit: string;
+    createdAt: any;
 }) => (
-    <Pressable onPress={() => router.push('manageVehiclePage')}>
+    <Pressable
+        onPress={() =>
+            router.push({
+                pathname: 'manageVehiclePage',
+                params: {
+                    carID: carID,
+                    capacitance: capacitance,
+                    capacitance_unit: capacitance_unit,
+                    createdAt: createdAt,
+                    carModel: carModel,
+                    licensePlate: licensePlate
+                }
+            })
+        }
+    >
         <View
             className="rounded-xl overflow-hidden mb-3"
             style={{
@@ -55,10 +86,10 @@ const VehicleRowComponent = ({
                             fontWeight: '600'
                         }}
                     >
-                        TESLA
+                        {carBrand}
                     </Text>
                     <Text style={{ fontSize: deviceHeight < 600 ? 14 : 18 }}>
-                        Model S
+                        {carModel}
                     </Text>
                     <Text
                         style={{
@@ -67,7 +98,7 @@ const VehicleRowComponent = ({
                         }}
                         className=" text-[#02677D]"
                     >
-                        BF0992
+                        {licensePlate}
                     </Text>
                 </View>
                 <View className="pr-3">
@@ -82,6 +113,53 @@ const MyVehiclePageComponent = () => {
     const { height: deviceHeight, width: deviceWidth } =
         Dimensions.get('window');
     console.log(deviceHeight, deviceWidth);
+    const [myVehicleData, setMyVehicleData] = useState([]);
+    const [defaultCarInfo, setDefaultCarInfo] = useState(null);
+    const [defaultCarId, setDefaultCarId] = useState(null);
+
+    const extractDefaultCarInfo = (response) => {
+        const defaultCar = response.cars.find(
+            (car) => car.id === response.defaultCar.id
+        );
+        if (defaultCar) {
+            return {
+                name: defaultCar.name,
+                license_plate: defaultCar.license_plate
+            };
+        }
+
+        return null;
+    };
+
+    useEffect(() => {
+        const fetchData = async () => {
+            try {
+                const result = await chargeStationService.getUserCars();
+                // console.log(JSON.stringify(result.data));
+                setMyVehicleData(result.data);
+            } catch (error) {
+                console.log(error);
+            }
+        };
+        fetchData();
+    }, []);
+
+    useEffect(() => {
+        const fetchDefaultCar = async () => {
+            try {
+                const result = await authenticationService.getUserInfo();
+                const response = result.data;
+                const carInfo = extractDefaultCarInfo(response);
+                setDefaultCarInfo(carInfo);
+                setDefaultCarId(response.defaultCar.id);
+                console.log('i am car info' + defaultCarId);
+            } catch (error) {
+                console.log(error);
+            }
+        };
+        fetchDefaultCar();
+    }, []);
+
     return (
         <SafeAreaView
             className="flex-1 bg-white"
@@ -139,25 +217,33 @@ const MyVehiclePageComponent = () => {
                             </Text>
                             <GreenStarSvg />
                         </View>
-                        <Text className="text-2xl">TESLA Model 3</Text>
-                        <Text className="text-lg text-[#888888]">CE1107</Text>
+                        <Text className="text-2xl">{defaultCarInfo?.name}</Text>
+                        <Text className="text-lg text-[#888888]">
+                            {defaultCarInfo?.license_plate}
+                        </Text>
                     </View>
                 </View>
                 <View className="w-full h-1 my-4 bg-[#DBE4E8]" />
 
                 <Text className="text-xl mb-4">其他車輛</Text>
-                <VehicleRowComponent
-                    deviceHeight={deviceHeight}
-                    deviceWidth={deviceWidth}
-                />
-                <VehicleRowComponent
-                    deviceHeight={deviceHeight}
-                    deviceWidth={deviceWidth}
-                />
-                <VehicleRowComponent
-                    deviceHeight={deviceHeight}
-                    deviceWidth={deviceWidth}
-                />
+
+                {myVehicleData
+                    .filter((car) => car.id !== defaultCarId)
+                    .map((car, index) => (
+                        <VehicleRowComponent
+                            deviceHeight={deviceHeight}
+                            deviceWidth={deviceWidth}
+                            carBrand={car.car_brand.name}
+                            carModel={car.car_type.name}
+                            licensePlate={car.license_plate}
+                            carID={car.id}
+                            capacitance={car.car_type.capacitance}
+                            capacitance_unit={car.car_type.capacitance_unit}
+                            createdAt={car.car_type.createdAt}
+                            key={index}
+                        />
+                    ))}
+
                 <NormalButton
                     title={
                         <Text

+ 12 - 8
component/accountPages/uberUploadPageComponent.tsx

@@ -73,14 +73,18 @@ const UberUploadPageComponent = () => {
                                 </Text>
                             </View>
                         </Pressable>
-                        <NormalButton
-                            title={
-                                <Text className="text-xl text-white">提交</Text>
-                            }
-                            onPress={() =>
-                                router.push('uberUploadCompletePage')
-                            }
-                        />
+                        <View className="mt-4">
+                            <NormalButton
+                                title={
+                                    <Text className="text-xl text-white">
+                                        提交
+                                    </Text>
+                                }
+                                onPress={() =>
+                                    router.push('uberUploadCompletePage')
+                                }
+                            />
+                        </View>
                     </View>
                 </View>
             </ScrollView>

+ 84 - 92
component/accountPages/walletPageComponent.tsx

@@ -10,28 +10,9 @@ import { SafeAreaView } from 'react-native-safe-area-context';
 import { router } from 'expo-router';
 import { CrossLogoSvg } from '../global/SVG';
 import { useEffect, useState } from 'react';
-import * as SecureStore from 'expo-secure-store';
 import { walletService } from '../../service/walletService';
-const coupons = [
-    {
-        title: '迎新優惠券',
-        price: '999',
-        detail: '這是一段有關迎新優惠券的說明',
-        date: '至14/3/2025'
-    },
-    {
-        title: '折扣優惠券',
-        price: '888',
-        detail: '這是另一段有關迎新優惠券的說明',
-        date: '至15/4/2025'
-    },
-    {
-        title: '三張優惠券',
-        price: '777',
-        detail: '這是第三段有關迎新優惠券的說明',
-        date: '至16/9/2026'
-    }
-];
+
+import { formatCouponDate, formatDate } from '../../util/lib';
 
 export const IndividualCouponComponent = ({
     title,
@@ -43,75 +24,81 @@ export const IndividualCouponComponent = ({
     price: string;
     detail: string;
     date: string;
-}) => (
-    <View className="bg-[#e7f2f8] h-[124px]  rounded-xl flex-row mb-3">
-        <View className="bg-white mx-3 my-3 w-[28%] rounded-xl">
-            <View className="flex-row justify-center items-center pr-4 pt-4 ">
-                <Text className="color-[#02677d] text-2xl pl-2 pr-1">$</Text>
-                <Text
-                    className="color-[#02677d] text-3xl font-bold"
-                    adjustsFontSizeToFit={true}
-                >
-                    {price}
-                </Text>
-            </View>
-            <View className="items-center justify-center">
-                <Text className="text-base mt-1">{title}</Text>
+}) => {
+    return (
+        <View className="bg-[#e7f2f8] h-[124px]  rounded-xl flex-row mb-3">
+            <View className="bg-white mx-3 my-3 w-[28%] rounded-xl">
+                <View className="flex-row justify-center items-center pr-4 pt-4 ">
+                    <Text className="color-[#02677d] text-2xl pl-2 pr-1">
+                        $
+                    </Text>
+                    <Text
+                        className="color-[#02677d] text-3xl font-bold"
+                        adjustsFontSizeToFit={true}
+                    >
+                        {price}
+                    </Text>
+                </View>
+                <View className="items-center justify-center">
+                    <Text className="text-base mt-1">{title}</Text>
+                </View>
             </View>
-        </View>
 
-        {/* //dash line */}
-        <View style={{ overflow: 'hidden' }}>
-            <View
-                style={{
-                    borderStyle: 'dashed',
-                    borderWidth: 1,
-                    borderColor: '#CCCCCC',
-                    margin: -1,
-                    width: 0,
-                    marginRight: 0,
-                    height: '100%'
-                }}
-            >
-                <View style={{ height: 60 }}></View>
+            {/* //dash line */}
+            <View style={{ overflow: 'hidden' }}>
+                <View
+                    style={{
+                        borderStyle: 'dashed',
+                        borderWidth: 1,
+                        borderColor: '#CCCCCC',
+                        margin: -1,
+                        width: 0,
+                        marginRight: 0,
+                        height: '100%'
+                    }}
+                >
+                    <View style={{ height: 60 }}></View>
+                </View>
             </View>
-        </View>
 
-        <View className="flex-col flex-1  m-[5%] justify-center ">
-            <Text className="text-lg">{title}</Text>
-            <Text className="color-[#888888] text-sm">{detail}</Text>
-            <View className="flex-row items-center ">
-                <Text className="text-base">有效期 </Text>
-                <Text className="text-base font-bold text-[#02677d]">
-                    {'   '}
-                    {date}
-                </Text>
+            <View className="flex-col flex-1  m-[5%] justify-center ">
+                <Text className="text-lg">{title}</Text>
+                <Text className="color-[#888888] text-sm">{detail}</Text>
+                <View className="flex-row items-center ">
+                    <Text className="text-base">有效期 </Text>
+                    <Text className="text-base font-bold text-[#02677d]">
+                        {'   '}
+                        {date}
+                    </Text>
+                </View>
             </View>
         </View>
-    </View>
-);
+    );
+};
 
 const WalletPageComponent = () => {
-    const [token, setToken] = useState<string | null>(null);
     const [walletBalance, setWalletBalance] = useState<string | null>(null);
-    useEffect(() => {
-        const getToken = async () => {
-            const storedToken = await SecureStore.getItemAsync('accessToken');
-            setToken(storedToken);
-        };
-        getToken();
-    }, []);
+    const [loading, setLoading] = useState<boolean>(false);
+    const [coupons, setCoupons] = useState([]);
 
     useEffect(() => {
-        const fetchWalletBalance = async () => {
-            if (token) {
-                await new Promise((resolve) => setTimeout(resolve, 2000));
-                const balance = await walletService.getWalletBalance(token);
+        try {
+            setLoading(true);
+
+            const fetchWalletBalanceAndCoupon = async () => {
+                await new Promise((resolve) => setTimeout(resolve, 5000));
+                const balance = await walletService.getWalletBalance();
+                const coupon = await walletService.getCoupon();
+                console.log(JSON.stringify(coupon));
+                setCoupons(coupon);
                 setWalletBalance(balance);
-            }
-        };
-        fetchWalletBalance();
-    }, [token]);
+                setLoading(false);
+            };
+            fetchWalletBalanceAndCoupon();
+        } catch (error) {
+            console.log(error);
+        }
+    }, []);
 
     const formatMoney = (amount: any) => {
         if (typeof amount !== 'number') {
@@ -159,14 +146,13 @@ const WalletPageComponent = () => {
                                         style={{ fontSize: 52 }}
                                         className=" text-white font-bold"
                                     >
-                                        {walletBalance ? (
-                                            formatMoney(walletBalance)
-                                        ) : (
+                                        {loading ? (
                                             <View className="items-center">
                                                 <ActivityIndicator />
                                             </View>
+                                        ) : (
+                                            formatMoney(walletBalance)
                                         )}
-                                        {/* Format the wallet balance */}
                                     </Text>
                                     <Pressable
                                         className="rounded-2xl items-center justify-center p-3 px-5 pr-6 "
@@ -199,17 +185,23 @@ const WalletPageComponent = () => {
                         <Text className="text-xl text-[#888888]">顯示所有</Text>
                     </Pressable>
                 </View>
-
                 <View className="flex-1 flex-col mx-[5%]">
-                    {coupons.slice(0, 2).map((coupon, index) => (
-                        <IndividualCouponComponent
-                            key={index}
-                            title={coupon.title}
-                            price={coupon.price}
-                            detail={coupon.detail}
-                            date={coupon.date}
-                        />
-                    ))}
+                    {coupons
+                        .filter(
+                            (coupon) =>
+                                coupon.is_consumned === false &&
+                                new Date(coupon.expire_date) > new Date()
+                        )
+                        .slice(0, 2)
+                        .map((coupon, index) => (
+                            <IndividualCouponComponent
+                                key={index}
+                                title={coupon.name}
+                                price={coupon.amount}
+                                detail={coupon.description}
+                                date={formatCouponDate(coupon.expire_date)}
+                            />
+                        ))}
                 </View>
             </ScrollView>
         </SafeAreaView>

+ 238 - 0
component/chooseCar/chooseCarPageComponent.tsx

@@ -0,0 +1,238 @@
+import { router } from 'expo-router';
+import {
+    View,
+    Text,
+    ScrollView,
+    Pressable,
+    Image,
+    Dimensions,
+    StyleSheet,
+    TextInput,
+    Alert,
+    ActivityIndicator
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { PreviousPageBlackSvg } from '../global/SVG';
+import NormalButton from '../global/normal_button';
+import { useEffect, useState } from 'react';
+import Checkbox from 'expo-checkbox';
+import DropdownSelect from '../global/dropdown_select';
+import { chargeStationService } from '../../service/chargeStationService';
+
+const chooseCarPageComponent = () => {
+    const [isChecked, setChecked] = useState(false);
+    const { height: deviceHeight, width: deviceWidth } =
+        Dimensions.get('window');
+    const [brandNameDropdownOptions, setBrandNameDropdownOptions] = useState(
+        []
+    );
+    const [isLoading, setIsLoading] = useState(false);
+    const [licensePlate, setLicensePlate] = useState('');
+    const [selectedBrandID, setSelectedBrandID] = useState('');
+    const [selectedTypeID, setSelectedTypeID] = useState('');
+    const [caName, setCarName] = useState('');
+    // const [carImg, setCarImg] = useState('');
+    const [brandData, setBrandData] = useState([]);
+    const [brandTypeDropdownOptions, setBrandTypeDropdownOptions] = useState([
+        {
+            label: '車輛型號',
+            value: '車輛型號'
+        }
+    ]);
+
+    const handleSubmit = async () => {
+        setIsLoading(true);
+        try {
+            const result = await chargeStationService.addCar(
+                licensePlate,
+                selectedBrandID,
+                selectedTypeID,
+                isChecked
+            );
+            if (result) {
+                router.push({
+                    pathname: 'addVehicleSuccessfulPage',
+                    params: { selectedTypeID: selectedTypeID }
+                });
+            } else {
+                Alert.alert('failed to add car');
+            }
+        } catch (error) {
+            console.log(error, 'unexpected');
+        } finally {
+            setIsLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        const fetchData = async () => {
+            try {
+                const result = await chargeStationService.fetchCarBrand();
+                setBrandData(result.data);
+                const brandInfo = result.data.map((item) => ({
+                    name: item.name,
+                    id: item.id,
+                    img_url: item.img_url
+                }));
+
+                // console.log(JSON.stringify(brandInfo, null, 2));
+                const brandName = brandInfo.map((item) => item.name);
+                console.log(brandName);
+                const brandNameDropdownOptions = brandInfo.map((item) => ({
+                    label: item.name,
+                    value: item.id
+                }));
+                setBrandNameDropdownOptions(brandNameDropdownOptions);
+            } catch (error) {
+                console.log(error);
+            }
+        };
+        fetchData();
+    }, []);
+
+    useEffect(() => {
+        if (selectedBrandID) {
+            const selectedBrand = brandData.find(
+                (brand) => brand.id === selectedBrandID
+            );
+            
+
+            if (selectedBrand && selectedBrand.car_types) {
+                const typeOptions = selectedBrand.car_types.map((carType) => ({
+                    label: carType.name,
+                    value: carType.id
+                }));
+                setBrandTypeDropdownOptions(typeOptions);
+            } else {
+                setBrandTypeDropdownOptions([]);
+            }
+        } else {
+            setBrandTypeDropdownOptions([]);
+        }
+    }, [selectedBrandID, brandData]);
+
+    return (
+        <SafeAreaView
+            className="flex-1 bg-white"
+            edges={['top', 'right', 'left']}
+        >
+            <ScrollView
+                showsVerticalScrollIndicator={false}
+                className="flex-1 mx-[5%]"
+            >
+                <View style={{ marginTop: 25 }}>
+                    <Pressable
+                        className="self-start"
+                        onPress={() => {
+                            if (router.canGoBack()) {
+                                router.back();
+                            } else {
+                                router.replace('/accountMainPage');
+                            }
+                        }}
+                    >
+                        <PreviousPageBlackSvg />
+                    </Pressable>
+                    <Text
+                        style={{
+                            fontSize: 30,
+                            marginTop: 25,
+                            marginBottom: 20
+                        }}
+                    >
+                        新增車輛
+                    </Text>
+                </View>
+
+                <View
+                    style={{
+                        display: 'flex',
+                        flexDirection: 'column',
+                        gap: 10
+                    }}
+                >
+                    <DropdownSelect
+                        dropdownOptions={brandNameDropdownOptions}
+                        placeholder={'車輛品牌'}
+                        onSelect={(ID) => {
+                            setSelectedBrandID(ID);
+                            console.log(ID);
+                        }}
+                        extendedStyle={{
+                            borderWidth: 1,
+                            padding: 20,
+                            borderRadius: 12,
+                            borderColor: '#bbbbbb'
+                        }}
+                    />
+
+                    <DropdownSelect
+                        dropdownOptions={brandTypeDropdownOptions}
+                        placeholder={'車輛品牌'}
+                        onSelect={(carTypeID) => setSelectedTypeID(carTypeID)}
+                        extendedStyle={{
+                            borderWidth: 1,
+                            padding: 20,
+                            borderRadius: 12,
+                            borderColor: '#bbbbbb'
+                        }}
+                    />
+
+                    <TextInput
+                        style={styles.fakeTextInput}
+                        onChangeText={(text) => {
+                            setLicensePlate(text);
+                            // console.log(licensePlate);
+                        }}
+                        value={licensePlate}
+                        placeholder="車輛牌照號碼"
+                        placeholderTextColor="#888"
+                    />
+                </View>
+
+                <View className="flex-row items-center">
+                    <Text className="mt-4 mb-4 text-lg">設置為預設車輛</Text>
+                    <Checkbox
+                        style={styles.checkbox}
+                        value={isChecked}
+                        onValueChange={setChecked}
+                        color={isChecked ? '#025c72' : undefined}
+                    />
+                </View>
+                <NormalButton
+                    title={
+                        <Text
+                            style={{
+                                fontWeight: '700',
+                                fontSize: 20,
+                                color: '#fff'
+                            }}
+                        >
+                            {isLoading ? <ActivityIndicator /> : '新增'}
+                        </Text>
+                    }
+                    onPress={() => {
+                        handleSubmit();
+                    }}
+                    disabled={isLoading}
+                />
+                <Text> </Text>
+            </ScrollView>
+        </SafeAreaView>
+    );
+};
+const styles = StyleSheet.create({
+    button: { flex: 1, gap: 10, marginTop: 5 },
+    fakeTextInput: {
+        fontSize: 16,
+        borderWidth: 1,
+        padding: 24,
+        borderRadius: 12,
+        borderColor: '#bbbbbb',
+        color: '#888'
+    },
+    checkbox: {
+        margin: 8
+    }
+});
+export default chooseCarPageComponent;

+ 51 - 80
component/global/couponTabView.tsx

@@ -12,6 +12,9 @@ import {
 import { TabView, SceneMap, TabBar } from 'react-native-tab-view';
 
 import { IndividualCouponComponent } from '../accountPages/walletPageComponent';
+import { formatCouponDate } from '../../util/lib';
+import { useEffect, useState } from 'react';
+import { walletService } from '../../service/walletService';
 
 export interface TabItem {
     imgURL: ImageSourcePropType;
@@ -26,101 +29,69 @@ interface TabViewComponentProps {
     titles: string[];
 }
 
-const expiredCoupons = [
-    {
-        title: 'abc優惠券',
-        price: '999',
-        detail: '這是一段有關迎新優惠券的說明',
-        date: '至14/3/2025'
-    },
-    {
-        title: 'abc優惠券',
-        price: '888',
-        detail: '這是另一段有關迎新優惠券的說明',
-        date: '至15/4/2025'
-    },
-    {
-        title: 'abc優惠券',
-        price: '777',
-        detail: '這是第三段有關迎新優惠券的說明',
-        date: '至16/9/2026'
-    },
-    {
-        title: 'abc優惠券',
-        price: '777',
-        detail: '這是第三段有關迎新優惠券的說明',
-        date: '至16/9/2026'
-    },
-    {
-        title: 'abc優惠券',
-        price: '777',
-        detail: '這是第三段有關迎新優惠券的說明',
-        date: '至16/9/2026'
-    },
-    {
-        title: 'abc優惠券',
-        price: '777',
-        detail: '這是第三段有關迎新優惠券的說明',
-        date: '至16/9/2026'
-    }
-    // Add more coupon objects as needed
-];
-
-const coupons = [
-    {
-        title: '迎新優惠券',
-        price: '111',
-        detail: 'aaaaasdasdasdasd',
-        date: '至14/3/2025'
-    },
-    {
-        title: '折扣優惠券',
-        price: '222',
-        detail: 'dfgdfgdfgdfg',
-        date: '至15/4/2025'
-    },
-    {
-        title: '三張優惠券',
-        price: '333',
-        detail: 'zxczxczxczxczxc',
-        date: '至16/9/2026'
-    }
-];
 const CouponTabViewComponent: React.FC<TabViewComponentProps> = ({
     titles
 }) => {
+    const [coupons, setCoupons] = useState([]);
     const layout = useWindowDimensions();
 
+    useEffect(() => {
+        try {
+            const fetchCoupon = async () => {
+                const coupon = await walletService.getCoupon();
+                setCoupons(coupon);
+            };
+            fetchCoupon();
+        } catch (error) {
+            console.log(error);
+        }
+    }, []);
     //tab 1
     const FirstRoute = () => (
-        <ScrollView style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}>
+        <ScrollView
+            style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}
+        >
             <View className="flex-1 flex-col">
-                {coupons.map((coupon, index) => (
-                    <IndividualCouponComponent
-                        key={index}
-                        title={coupon.title}
-                        price={coupon.price}
-                        detail={coupon.detail}
-                        date={coupon.date}
-                    />
-                ))}
+                {coupons
+                    .filter(
+                        (coupon) =>
+                            coupon.is_consumned === false &&
+                            new Date(coupon.expire_date) > new Date()
+                    )
+                    .map((coupon, index) => (
+                        <IndividualCouponComponent
+                            key={index}
+                            title={coupon.name}
+                            price={coupon.amount}
+                            detail={coupon.description}
+                            date={formatCouponDate(coupon.expire_date)}
+                        />
+                    ))}
             </View>
         </ScrollView>
     );
 
     //tab 2
     const SecondRoute = () => (
-        <ScrollView style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}>
+        <ScrollView
+            style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}
+        >
             <View className="flex-1 flex-col">
-                {expiredCoupons.map((coupon, index) => (
-                    <IndividualCouponComponent
-                        key={index}
-                        title={coupon.title}
-                        price={coupon.price}
-                        detail={coupon.detail}
-                        date={coupon.date}
-                    />
-                ))}
+                {coupons
+                    .filter(
+                        (coupon) =>
+                            coupon.is_consumned ||
+                            new Date(coupon.expire_date) < new Date()
+                    )
+                    .map((coupon, index) => (
+                        <IndividualCouponComponent
+                            key={index}
+                            title={coupon.name}
+                            price={coupon.amount}
+                            detail={coupon.description}
+                            date={formatCouponDate(coupon.expire_date)}
+                        />
+                    ))}
             </View>
         </ScrollView>
     );

+ 0 - 1
component/global/dropdown_select.tsx

@@ -56,7 +56,6 @@ const DropdownSelect: React.FC<DropdownSelectProps> = ({
 
 const styles = StyleSheet.create({
     container: {
-       
         flex: 1,
         maxWidth: '100%',
         position: 'relative'

+ 1 - 1
component/global/logo.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { Image, View, StyleProp, ImageStyle, ViewStyle } from 'react-native';
 
 const Logo = () => {
-    const logoSource = require('../../assets/logo.png');
+    const logoSource = require('../../assets/ccLogo.png');
 
     const imageStyle: ImageStyle = {
         width: '60%',

+ 180 - 11
component/registrationMultiStepForm/formComponent/formPages/loginPage.tsx

@@ -1,23 +1,27 @@
 import {
     View,
     Text,
+    Image,
     StyleSheet,
     Pressable,
     Alert,
     Dimensions,
     ActivityIndicator
 } from 'react-native';
-import NormalButton from '../../../global/normal_button';
+
 import Logo from '../../../global/logo';
+import NormalButton from '../../../global/normal_button';
 import NormalInput from '../../../global/normal_input';
 import { useState } from 'react';
 import { useAuth } from '../../../../context/AuthProvider';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
 type LoginPageProps = {
     goToNextPage: () => void;
     goToForgetPassWordPage: () => void;
 };
 
 const screenHeight = Dimensions.get('window').height;
+console.log(screenHeight);
 const LoginPage: React.FC<LoginPageProps> = ({
     goToNextPage,
     goToForgetPassWordPage
@@ -40,13 +44,183 @@ const LoginPage: React.FC<LoginPageProps> = ({
         }
         setIsLoading(false);
     };
+    const insets = useSafeAreaInsets();
 
     return (
-        <View style={styles.container} className="">
-            <View style={styles.topContainerForLogo}>
-                <Logo />
+        // <View style={styles.container} className="">
+        //     <View style={styles.topContainerForLogo}>
+        //         <Logo />
+        //     </View>
+        //     <View style={styles.bottomContainer} className="">
+        //         <NormalInput
+        //             placeholder="電子郵件"
+        //             onChangeText={(email) => setLoginEmail(email)}
+        //             extendedStyle={{ borderRadius: 12, padding: 20 }}
+        //             textContentType="username"
+        //             autoComplete="username"
+        //         />
+        //         <NormalInput
+        //             placeholder="密碼"
+        //             onChangeText={(password) => setLoginPassword(password)}
+        //             secureTextEntry={true}
+        //             extendedStyle={{ borderRadius: 12, padding: 20 }}
+        //             textContentType="password"
+        //             autoComplete="password"
+        //         />
+
+        //         <Pressable onPress={() => goToForgetPassWordPage()}>
+        //             <Text style={styles.text}>忘記密碼</Text>
+        //         </Pressable>
+        //         <NormalButton
+        //             extendedStyle={{ padding: 20 }}
+        //             onPress={() => _login(loginEmail, loginPassword)}
+        //             title={
+        //                 isLoading ? (
+        //                     <Text
+        //                         style={{
+        //                             fontWeight: '700',
+        //                             fontSize: 20,
+        //                             color: '#fff'
+        //                         }}
+        //                     >
+        //                         <ActivityIndicator />
+        //                     </Text>
+        //                 ) : (
+        //                     <Text
+        //                         style={{
+        //                             fontWeight: '700',
+        //                             fontSize: 20,
+        //                             color: '#fff'
+        //                         }}
+        //                     >
+        //                         登入
+        //                     </Text>
+        //                 )
+        //             }
+        //         />
+        //         <Pressable onPress={goToNextPage}>
+        //             <Text style={styles.text}>註冊會員</Text>
+        //         </Pressable>
+        //     </View>
+        // </View>
+
+        //         &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
+        //         <View className="flex-1 h-screen">
+        //             <View
+        //                 className="items-center"
+        //                 style={{
+        //                     paddingTop: Math.max(insets.top + 20, 20)
+        //                 }}
+        //             >
+        //                 <Image
+        //                     source={require('../../../../assets/ccLogo.png')}
+        //                     resizeMode="contain"
+        //                     style={{ width: 200, height: 200 }}
+        //                 />
+        //             </View>
+        //             <View
+        //                 style={{
+        //                     gap: 10,
+        //                     paddingBottom: Math.max(insets.bottom, 20)
+        //                 }}
+        //                 className="mx-[5%] flex-1  justify-end mb-10"
+        //             >
+        //                 <NormalInput
+        //                     placeholder="電子郵件"
+        //                     onChangeText={(email) => setLoginEmail(email)}
+        //                     extendedStyle={{ borderRadius: 12, padding: 20 }}
+        //                     textContentType="username"
+        //                     autoComplete="username"
+        //                 />
+        //                 <NormalInput
+        //                     placeholder="密碼"
+        //                     onChangeText={(password) => setLoginPassword(password)}
+        //                     secureTextEntry={true}
+        //                     extendedStyle={{ borderRadius: 12, padding: 20 }}
+        //                     textContentType="password"
+        //                     autoComplete="password"
+        //                 />
+
+        //                 <Pressable onPress={() => goToForgetPassWordPage()}>
+        //                     <Text style={styles.text}>忘記密碼</Text>
+        //                 </Pressable>
+        //                 <NormalButton
+        //                     extendedStyle={{ padding: 20 }}
+        //                     onPress={() => _login(loginEmail, loginPassword)}
+        //                     title={
+        //                         isLoading ? (
+        //                             <Text
+        //                                 style={{
+        //                                     fontWeight: '700',
+        //                                     fontSize: 20,
+        //                                     color: '#fff'
+        //                                 }}
+        //                             >
+        //                                 <ActivityIndicator />
+        //                             </Text>
+        //                         ) : (
+        //                             <Text
+        //                                 style={{
+        //                                     fontWeight: '700',
+        //                                     fontSize: 20,
+        //                                     color: '#fff'
+        //                                 }}
+        //                             >
+        //                                 登入
+        //                             </Text>
+        //                         )
+        //                     }
+        //                 />
+        //                 <Pressable onPress={goToNextPage}>
+        //                     <Text style={styles.text}>註冊會員</Text>
+        //                 </Pressable>
+        //             </View>
+        //         </View>
+        //     );
+        // };
+
+        // const styles = StyleSheet.create({
+        //     container: {
+        //         flex: 1,
+        //         height: Dimensions.get('window').height,
+        //         justifyContent: 'center',
+        //         marginHorizontal: 20
+        //     },
+        //     topContainerForLogo: {},
+
+        //     text: {
+        //         color: '#02677D',
+        //         fontSize: 16,
+        //         paddingVertical: 5
+        //     }
+        // });
+        // export default LoginPage;
+
+        // <View className="flex-1 h-screen justify-center">
+        <View
+            className={`flex-1 justify-center ${
+                screenHeight < 750 ? 'h-screen' : 'h-[80vh] space-y-8'
+              
+            }`}
+        >
+            <View className="flex-3 items-center  justify-end" style={{}}>
+                <Image
+                    source={require('../../../../assets/ccLogo.png')}
+                    resizeMode="contain"
+                    style={{
+                        width: screenHeight > 750 ? 250 : 200,
+                        height: screenHeight > 750 ? 250 : 200
+                    }}
+                />
             </View>
-            <View style={styles.bottomContainer} className="">
+            <View
+                style={{
+                    gap: 10
+                    // paddingBottom: Math.max(insets.bottom, 20)
+                    // marginTop: screenHeight > 750 ? 40 : 0
+                }}
+                className="mx-[5%] flex-2 "
+            >
                 <NormalInput
                     placeholder="電子郵件"
                     onChangeText={(email) => setLoginEmail(email)}
@@ -108,12 +282,7 @@ const styles = StyleSheet.create({
         justifyContent: 'center',
         marginHorizontal: 20
     },
-    topContainerForLogo: { marginBottom: screenHeight < 700 ? '0%' : '20%' },
-    bottomContainer: {
-        marginBottom: '20%',
-        justifyContent: 'flex-start',
-        gap: 10
-    },
+    topContainerForLogo: {},
 
     text: {
         color: '#02677D',

+ 22 - 0
service/authService.tsx

@@ -365,5 +365,27 @@ class AuthenticationService {
             return false;
         }
     }
+    async getUserInfo() {
+        try {
+            const response = await axios.get(
+                `${this.apiUrl}/clients/customer`,
+                {
+                    headers: {
+                        Authorization: `Bearer ${await SecureStore.getItemAsync(
+                            'accessToken'
+                        )}`
+                    }
+                }
+            );
+            if (response.status === 200 || response.status === 201) {
+                // console.log(response);
+                return response;
+            } else {
+                console.log('invalid response');
+            }
+        } catch (error) {
+            console.log(error);
+        }
+    }
 }
 export const authenticationService = new AuthenticationService();

+ 138 - 0
service/chargeStationService.tsx

@@ -15,6 +15,144 @@ class ChargeStationService {
         }
     }
 
+    async fetchCarBrand() {
+        try {
+            const response = await axios.get(
+                `${this.apiUrl}/public/client/car/brand`
+            );
+
+            if (response.status === 200 || response.status === 201) {
+                // console.log(response.data);
+                return response.data;
+            } else {
+                console.log('invalid response');
+            }
+        } catch (error) {
+            if (axios.isAxiosError(error)) {
+                console.error(
+                    'error:',
+                    error.response?.data?.message || error.message
+                );
+            } else {
+                console.error('An unexpected error occurred:', error);
+            }
+            return false;
+        }
+    }
+
+    async getUserCars() {
+        try {
+            const response = await axios.get(
+                `${this.apiUrl}/clients/customer/car/all`,
+                {
+                    headers: {
+                        Authorization: `Bearer ${await SecureStore.getItemAsync(
+                            'accessToken'
+                        )}`
+                    }
+                }
+            );
+            if (response.status === 200 || response.status === 201) {
+                // console.log(response.data.data);
+                return response.data;
+            } else {
+                console.log('invalid response');
+            }
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
+    async addCar(
+        licensePlate: string,
+        carBrandFk: string,
+        carTypeFk: string,
+        isDefault: boolean
+    ) {
+        try {
+            const response = await axios.post(
+                `${this.apiUrl}/clients/customer/car`,
+                {
+                    licensePlate: licensePlate,
+                    carBrandFk: carBrandFk,
+                    carTypeFk: carTypeFk,
+                    isDefault: isDefault
+                },
+                {
+                    headers: {
+                        Authorization: `Bearer ${await SecureStore.getItemAsync(
+                            'accessToken'
+                        )}`
+                    }
+                }
+            );
+            if (response.status === 200 || response.status === 201) {
+                console.log('add car successful');
+                return true;
+            } else {
+                console.log('invalid response');
+            }
+        } catch (error) {
+            if (axios.isAxiosError(error)) {
+                console.error(
+                    'error:',
+                    error.response?.data?.message || error.message
+                );
+            } else {
+                console.error('An unexpected error occurred:', error);
+            }
+            return false;
+        }
+    }
+
+    async deleteCar(carID) {
+        try {
+            // console.log('i receive this carID', carID);
+            const response = await axios.delete(
+                `${this.apiUrl}/clients/customer/car?carId=${carID}`,
+                {
+                    headers: {
+                        Authorization: `Bearer ${await SecureStore.getItemAsync(
+                            'accessToken'
+                        )}`
+                    }
+                }
+            );
+            // console.log('Full response:', JSON.stringify(response, null, 2));
+            if (response.status === 200 || response.status === 201) {
+                console.log('delete car successful');
+                return true;
+            } else {
+                console.log('invalid response');
+            }
+        } catch (error) {
+            console.log(error);
+        }
+    }
+    async setDefaultCar(carID) {
+        try {
+            const response = await axios.put(
+                `${this.apiUrl}/clients/customer/car/default?carId=${carID}`,
+                {},
+                {
+                    headers: {
+                        Authorization: `Bearer ${await SecureStore.getItemAsync(
+                            'accessToken'
+                        )}`
+                    }
+                }
+            );
+            if (response.status === 200 || response.status === 201) {
+                console.log('set default car successful');
+                return true;
+            } else {
+                console.log('invalid response');
+            }
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
     async fetchChargeStations() {
         try {
             const response = await axios.get(

+ 27 - 3
service/walletService.tsx

@@ -1,5 +1,5 @@
 import axios from 'axios';
-
+import * as SecureStore from 'expo-secure-store';
 import { EXPO_PUBLIC_API_URL } from '@env';
 
 class WalletService {
@@ -11,14 +11,38 @@ class WalletService {
             throw new Error('API URL is not defined in environment variables');
         }
     }
+    async getCoupon() {
+        try {
+            const response = await axios.get(
+                `${this.apiUrl}/clients/promotion/coupons`,
+                {
+                    headers: {
+                        Authorization: `Bearer ${await SecureStore.getItemAsync(
+                            'accessToken'
+                        )}`
+                    }
+                }
+            );
 
-    async getWalletBalance(token: string | null) {
+            if (response.status === 200) {
+                console.log('getCoupon successful!');
+                return response.data;
+            } else {
+                console.log('getCoupon failed:', response.status);
+            }
+        } catch (error) {
+            console.log(error);
+        }
+    }
+    async getWalletBalance() {
         try {
             const response = await axios.get(
                 `${this.apiUrl}/clients/customer/wallet`,
                 {
                     headers: {
-                        Authorization: `Bearer ${token}`
+                        Authorization: `Bearer ${await SecureStore.getItemAsync(
+                            'accessToken'
+                        )}`
                     }
                 }
             );

+ 15 - 0
util/lib.tsx

@@ -0,0 +1,15 @@
+export function formatDate(dateString) {
+    const date = new Date(dateString);
+
+    const day = date.getUTCDate().toString().padStart(2, '0');
+    const month = (date.getUTCMonth() + 1).toString().padStart(2, '0'); // getUTCMonth() returns 0-11
+    const year = date.getUTCFullYear();
+
+    return `${day}/${month}/${year}`;
+}
+
+export function formatCouponDate(dateString) {
+    const [datePart] = dateString.split(' ');
+    const [year, month, day] = datePart.split('-');
+    return `${day}/${month}/${year}`;
+}