瀏覽代碼

Merge branch 'new/1' of kuns/crazycharge-app into main

#1
kuns 2 月之前
父節點
當前提交
4f1cb152e9

+ 2 - 5
app.json

@@ -2,7 +2,7 @@
     "expo": {
         "name": "Crazy Charge",
         "slug": "template",
-        "version": "1.3.11",
+        "version": "1.3.12",
         "orientation": "portrait",
         "icon": "./assets/CC_Logo.png",
         "userInterfaceStyle": "light",
@@ -40,10 +40,7 @@
                 "backgroundColor": "#000000"
             },
             "icon": "./assets/cc_android_ccLogo.png",
-            "notification": {
-                "icon": "./assets/cc_android_notification_icon.png",
-                "color": "#02677D"
-            },
+
             "permissions": [
                 "android.permission.INTERNET",
                 "android.permission.CAMERA",

+ 11 - 14
app/(auth)/(tabs)/(account)/accountMainPage.tsx

@@ -1,14 +1,11 @@
-import { Button, ScrollView, StyleSheet } from 'react-native';
-import { View, Text } from 'react-native';
-import { useAuth } from '../../../../context/AuthProvider';
-import { useEffect } from 'react';
-import { SafeAreaView } from 'react-native-safe-area-context';
-import AccountMainPageComponent from '../../../../component/accountPages/accountMainPageComponent';
-
-export default function Account() {
-    return (
-        <View className="flex-1">
-            <AccountMainPageComponent />
-        </View>
-    );
-}
+import { Button, ScrollView, StyleSheet } from 'react-native';
+import { View, Text } from 'react-native';
+import AccountMainPageComponent from '../../../../component/accountPages/accountMainPageComponent';
+
+export default function Account() {
+    return (
+        <View className="flex-1">
+            <AccountMainPageComponent />
+        </View>
+    );
+}

+ 14 - 0
app/(auth)/(tabs)/(account)/accountUserTermsPage.tsx

@@ -0,0 +1,14 @@
+import { View, Text, ScrollView, Pressable, Button, Alert } from 'react-native';
+import React, { useContext } from 'react';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import UserTermsPageComponent from '../../../../component/accountPages/userTermsPageComponent';
+const UserTermsPage = () => {
+    return (
+        <View className="flex-1">
+          <UserTermsPageComponent ></UserTermsPageComponent>
+        </View>
+        );
+};
+
+export default UserTermsPage;

+ 14 - 0
app/(public)/userTermsPage.tsx

@@ -0,0 +1,14 @@
+import { View, Text, ScrollView, Pressable, Button, Alert } from 'react-native';
+import React, { useContext } from 'react';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import UserTermsPageComponent from '../../component/accountPages/userTermsPageComponent';
+const UserTermsPage = () => {
+    return (
+        <View className="flex-1">
+          <UserTermsPageComponent></UserTermsPageComponent>
+        </View>
+        );
+};
+
+export default UserTermsPage;

+ 1 - 0
app/_layout.tsx

@@ -55,6 +55,7 @@ export default function RootLayout() {
                     <Stack.Screen name="(public)/login" options={{ headerShown: false }} />
                     <Stack.Screen name="(public)/registerChooseVehiclesOne" options={{ headerShown: false }} />
                     <Stack.Screen name="(public)/registerChooseVehiclesTwo" options={{ headerShown: false }} />
+                    <Stack.Screen name="(public)/userTermsPage" options={{ headerShown: false }} />
                     {/* Testing Purpose */}
                     {NODE_ENV == 'development' ? (
                         <Stack.Screen

+ 1 - 0
babel.config.js

@@ -6,6 +6,7 @@ module.exports = function (api) {
             "nativewind/babel",
         ],
         plugins: [
+            'react-native-worklets/plugin',
         ]
     };
 };

+ 19 - 1
component/accountPages/accountMainPageComponent.tsx

@@ -15,7 +15,9 @@ import {
     SettingIconSvg,
     VipCodeIcoonSvg,
     WalletSvg,
-    NotificationSvg
+    NotificationSvg,
+    BookingIconSvg,
+    UserTermsSvg
 } from '../global/SVG';
 import { usePushNotifications } from '../../app/hooks/usePushNotifications';
 import useUserInfoStore from '../../providers/userinfo_store';
@@ -211,6 +213,22 @@ const AccountMainPageComponent = () => {
                         </Pressable>
                     </View> */}
                     <View className="h-0.5  bg-[#f4f4f4] dark:bg-[#5E6C70]" />
+                    <View className="py-4">
+                        <Pressable
+                            onPress={() => router.push('/accountUserTermsPage')}
+                            className="flex-row items-center"
+                            hitSlop={{
+                                top: 10,
+                                bottom: 10,
+                                left: 10,
+                                right: 10
+                            }}
+                        >
+                            <UserTermsSvg />
+                            <Text className="text-lg pl-2 text-black dark:text-white">用戶條款</Text>
+                        </Pressable>
+                    </View>
+                    <View className="h-0.5  bg-[#f4f4f4] dark:bg-[#5E6C70]" />
                     <View className="py-4">
                         <Pressable
                             onPress={logout}

+ 4 - 3
component/accountPages/activityRecordPageComponent.tsx

@@ -13,7 +13,7 @@ const ActivityRecordPageComponent = () => {
     return (
         <SafeAreaView className="flex-1 bg-white" edges={['top']}>
             <View style={{ minHeight: screenHeight, flex: 1 }}>
-                <View className="mx-[5%]" style={{ marginTop: 25 }}>
+                <View className="mx-[5%]" style={{ marginTop: 25}}>
                     <Pressable
                         onPress={() => {
                             if (router.canGoBack()) {
@@ -25,11 +25,12 @@ const ActivityRecordPageComponent = () => {
                     >
                         <CrossLogoSvg />
                     </Pressable>
-                    <Text style={{ fontSize: 45, marginVertical: 25 }}>充電記錄</Text>
+                    <Text style={{ fontSize: 45, marginTop: 25, marginBottom: 10 }}>充電記錄</Text>
                 </View>
-                <View className="flex-1 ">
+                <View className="flex-1">
                     <BookingTabViewComponent titles={['已預約', '已完成']} />
                 </View>
+                <View style={{ width: "100%",height: 130 }} />
             </View>
         </SafeAreaView>
     );

+ 159 - 0
component/accountPages/userTermsPageComponent.tsx

@@ -0,0 +1,159 @@
+import { View, Text, ScrollView, Pressable, Button, Alert, StyleSheet } from 'react-native';
+import React, { useContext } from 'react';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { router } from 'expo-router';
+import { CrossLogoSvg, RightArrowIconSvg } from '../global/SVG';
+import NormalButton from '../global/normal_button';
+
+type TermsSectionProps = {
+  title: string| undefined;
+  clauses: string[];
+};
+
+const TermsSection: React.FC<TermsSectionProps> = ({ title, clauses }) => (
+  <View className="mb-3">
+    {title && (
+      <Text style={styles.sectionTitle}>{title}</Text>
+    )}
+    {clauses.map((clause, index) => (
+      <Text key={index} style={styles.clause}>
+        {clause}
+      </Text>
+    ))}
+  </View>
+);
+const UserTermsPageComponent: React.FC = () => {
+  const sections = [
+    {
+      clauses: [
+        '歡迎使用 Crazy Charge(以下簡稱「本公司」或「我們」)提供的電動車充電服務(以下簡稱「服務」)。請仔細閱讀以下條款及細則(以下簡稱「本條款」),使用 Crazy Charge 此應用程式即表示您同意遵守本條款的所有內容。'
+      ]
+    },
+    {
+      title: '1. 服務使用',
+      clauses: [
+        '1.1 賬戶與服務接入:用戶須透過本公司APP進行充值、付款及啓動充電服務。',
+        '1.2 賬戶資料責任:用戶須確保所提供的資料真實、準確及完整,並對其帳戶的一切活動負責。',
+        '1.3 服務變更權限:本公司保留隨時修改、暫停或終止服務的權利,而無須事先通知用戶。'
+      ]
+    },
+    {
+      title: '2. 充值及付款',
+      clauses: [
+        '2.1 充值與餘額:用戶可透過APP進行充值,充值金額將存入用戶帳戶。',
+        '2.2 充值金額有效期:充值金額沒有使用期限。',
+        '2.3 優惠政策:本公司可能不時提供優惠,相關優惠受特定條款約束。'
+      ]
+    },
+    {
+      title: '3. 退款政策',
+      clauses: [
+        '3.1 退款申請方式:如用戶要求退款,須透過WhatsApp提交申請。',
+        '3.2 退款優惠扣除:退款金額將扣除該次充值所涉及的所有優惠。',
+        '3.3 退款行政費用:每筆退款將收取港幣150元作為行政費用。',
+        '3.4 退款建議:建議用戶將未使用的充值金額留作日後使用。',
+        '3.5 退款處理時間:退款處理時間由本公司決定。'
+      ]
+    },
+    {
+      title: '4. 責任限制',
+      clauses: [
+        '4.1 服務穩定性承諾:本公司將盡力確保服務的穩定性及準確性。',
+        '4.2 用戶損失責任:因使用本服務而導致的任何損失,本公司概不負責。',
+        '4.3 不可抗力責任:如因不可抗力導致服務中斷,本公司不承擔責任。'
+      ]
+    }
+  ];
+
+  const handleAgree = () => {
+    router.back()
+    // onAgree();
+  };
+
+  return (
+    <SafeAreaView className="flex-1 bg-white" edges={['top', 'right', 'left']}>
+    <View style={styles.container}>
+      <ScrollView contentContainerStyle={styles.scrollContainer}>
+        <Text style={styles.header}>Crazy Charge 用戶條款及細則</Text>
+        <View style={styles.section}>
+          {sections.map((section, index) => (
+            <TermsSection 
+              key={index}
+              title={section.title}
+              clauses={section.clauses}
+            />
+          ))}
+        </View>
+      </ScrollView>
+      <NormalButton
+        extendedStyle={styles.agreeButton}
+        onPress={() => handleAgree()}
+        title={<Text
+            style={{
+                fontWeight: '600',
+                fontSize: 18,
+                color: '#fff'
+            }}
+        >
+            同意並繼續
+        </Text>}
+        >
+
+        </NormalButton>
+    </View>
+    </SafeAreaView>
+  );
+};
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    padding: 8,
+    backgroundColor: '#f8f9fa'
+  },
+  scrollContainer: {
+    paddingBottom: 70
+  },
+  header: {
+    fontSize: 24,
+    fontWeight: 'bold',
+    marginBottom: 15,
+    color: '#2c3e50',
+    textAlign: 'center'
+  },
+  description: {
+    fontSize: 14, 
+    color: '#2c3e50',
+    paddingLeft: 20,
+    paddingRight: 20,
+    marginBottom: 10,
+  },
+  section: {
+    marginBottom: 10,
+    backgroundColor: 'white',
+    borderRadius: 8,
+    padding: 16,
+    elevation: 2
+  },
+  sectionTitle: {
+    fontSize: 18,
+    fontWeight: '600',
+    marginBottom: 12,
+    color: '#025c72'
+  },
+  clause: {
+    fontSize: 14,
+    lineHeight: 20,
+    marginBottom: 5,
+    color: '#34495e'
+  },
+  agreeButton: {
+    position: 'absolute',
+    bottom: 10,
+    left: 20,
+    right: 20,
+    padding: 16,
+    alignItems: 'center'
+  },
+});
+
+export default UserTermsPageComponent;

+ 1 - 1
component/accountPages/walletPageComponent.tsx

@@ -35,7 +35,7 @@ const AmountInputModal = ({ visible, onClose, onConfirm }) => {
         { amount: 200, percentage: 0 },
         { amount: 500, percentage: 25 },
         { amount: 1000, percentage: 100 },
-        { amount: 2000, percentage: 200 }
+        { amount: 2000, percentage: 300 }
     ];
 
     const getFontSize = () => {

+ 7 - 0
component/global/SVG.tsx

@@ -409,3 +409,10 @@ export const NotificationSvg = ({ isDark }: { isDark: boolean }) => (
         />
     </Svg>
 );
+export const UserTermsSvg = () => (
+    <Svg viewBox="0 0 1024 1024" width="26" height="22" fill="#02677D">
+        <Path
+           d="M157.76 896h690.24v-786.24h-690.24v786.24zM925.76 32v941.76h-845.76v-941.76h845.76zM430.88 286.88v77.76h288v-77.76h-288zM286.88 286.88v77.76h77.76v-77.76h-77.76zM430.88 454.88v77.76h288v-77.76h-288zM286.88 454.88v77.76h77.76v-77.76h-77.76zM430.88 622.88v77.76h288v-77.76h-288zM286.88 622.88v77.76h77.76v-77.76h-77.76z" p-id="5801" fill="#02677D"
+        />
+    </Svg>
+)

+ 8 - 4
component/global/bookingTabViewComponent.tsx

@@ -72,17 +72,20 @@ const processReservations = (reservations: any [], allStations: string [], isFut
             }
 
             let snapshot = {} as any;
+            let snapshot_price = {} as any;
             try {
-                snapshot = reservation.snapshot ? JSON.parse(reservation.snapshot) : {};
+                snapshot = reservation.connector.EquipmentID.StationID.snapshot ? JSON.parse(reservation.connector.EquipmentID.StationID.snapshot) : {};
+                snapshot_price = reservation.snapshot ? JSON.parse(reservation.snapshot) : {};
             } catch (e) {
                 console.warn('Error parsing snapshot:', e);
             }
             let stationInfo = null;
-            if (snapshot?.stationID) {
-                stationInfo = validStations.find((station) => station?.id === snapshot.stationID);
+            if (snapshot?.StationID) {
+                stationInfo = validStations.find((station) => station?.id === snapshot.StationID);
             } else if (snapshot?.connector) {
                 stationInfo = findStationByConnectorId(validStations, snapshot.connector);
             }
+
             // 确保时间字段存在
             const bookTime = reservation.book_time ? new Date(reservation.book_time) : new Date();
             const actualEndTime = reservation.actual_end_time ? new Date(reservation.actual_end_time) : new Date();
@@ -108,7 +111,7 @@ const processReservations = (reservations: any [], allStations: string [], isFut
                 total_fee: reservation.total_fee || 0,
                 withdraw_fee: reservation.withdraw_fee || 0,
                 actual_fee: (reservation.total_fee || 0) - (reservation.withdraw_fee || 0),
-                current_price: snapshot?.current_price || 0,
+                current_price: snapshot_price?.current_price || 0,
                 total_power: reservation.total_power || 0
             } as TabItem;
         });
@@ -146,6 +149,7 @@ const BookingTabViewComponentInner: React.FC<BookingTabViewComponentProps> = ({
     const futureReservations = processReservations(reservations, stations, true);
     const completedReservations = processReservations(reservations, stations, false);
     const allReservationItems = [...futureReservations, ...completedReservations];
+    // const allReservationItems = [...futureReservations];
 
     return (
         <TabViewComponent

+ 0 - 1
component/global/chargingRecord.tsx

@@ -63,7 +63,6 @@ const TabViewComponent: React.FC<TabViewComponentProps> = ({
         getCurrentLocation();
     }, []);
 
-    // 修复 FirstRoute 组件
     const FirstRoute = ({ tabItems, isLoading, currentLocation }: { 
             tabItems: TabItem[]; 
             isLoading?: boolean; 

+ 12 - 178
component/global/phone_input.tsx

@@ -1,179 +1,5 @@
-// import React, { useState } from "react";
-// import { View, Text, TextInput, StyleSheet, ViewStyle, StyleProp } from "react-native";
-// import { HandleSignUpFormDataChange } from "../../types/signup";
-// interface PhoneInputProps {
-// 	placeholder: string;
-// 	extendedStyle?: StyleProp<ViewStyle>;
-// 	handleFormDataChange?: HandleSignUpFormDataChange;
-// 	editable?: boolean;
-// }
-
-// const PhoneInput: React.FC<PhoneInputProps> = ({ placeholder, extendedStyle, handleFormDataChange, editable }) => {
-// 	const [error, setError] = useState("");
-// 	const handleTextChange = (text: string) => {
-// 		if (text.length >= 8) {
-// 			setError("");
-// 			handleFormDataChange?.("phone", text);
-// 			handleFormDataChange?.("phoneVerificationStatus", true);
-// 		} else {
-// 			setError("Please enter at least 8 digits");
-// 			handleFormDataChange?.("phone", text);
-// 			handleFormDataChange?.("phoneVerificationStatus", false);
-// 		}
-// 	};
-
-// 	return (
-// 		<View>
-// 			<View style={[styles.inputContainer, extendedStyle]}>
-// 				<Text style={styles.prefix}>+852</Text>
-// 				<View style={styles.horizontalLine} />
-// 				<TextInput
-// 					keyboardType="numeric"
-// 					onChangeText={handleTextChange}
-// 					placeholder={placeholder}
-// 					style={[styles.input]}
-// 					placeholderTextColor="#888888"
-// 					editable={editable}
-// 				/>
-// 			</View>
-// 			{error && <Text style={styles.errorMessage}>{error}</Text>}
-// 		</View>
-// 	);
-// };
-
-// const styles = StyleSheet.create({
-// 	inputContainer: {
-// 		maxWidth: "100%",
-// 		flexDirection: "row",
-// 		alignItems: "center",
-// 		justifyContent: "center",
-// 		borderWidth: 1,
-// 		borderColor: "#bbbbbb",
-// 		borderRadius: 12,
-// 		padding: 20,
-// 	},
-// 	prefix: {
-// 		marginRight: 5,
-// 		fontSize: 16,
-// 	},
-// 	horizontalLine: {
-// 		width: 24,
-// 		borderColor: "#bbbbbb",
-// 		borderWidth: 0.5,
-// 		transform: [{ rotate: "90deg" }],
-// 	},
-// 	input: {
-// 		flex: 1,
-// 		marginLeft: 5,
-// 		fontSize: 16,
-// 	},
-// 	errorMessage: {
-// 		fontSize: 14,
-// 		color: "#ff0033",
-// 		fontWeight: "400",
-// 		marginLeft: 10,
-// 		marginTop: 10,
-// 	},
-// });
-// export default PhoneInput;
-
-// import React, { useState } from "react";
-// import { View, Text, TextInput, StyleSheet, ViewStyle, StyleProp } from "react-native";
-// import { SignUpFormData } from "../../types/signUpFormData";
-
-// interface PhoneInputProps {
-// 	placeholder: string;
-// 	extendedStyle?: StyleProp<ViewStyle>;
-// 	editable?: boolean;
-// 	signUpFormData?: SignUpFormData;
-// 	setSignUpFormData?: (newFormData: Partial<SignUpFormData>) => void;
-// }
-
-// const PhoneInput: React.FC<PhoneInputProps> = ({
-// 	placeholder,
-// 	extendedStyle,
-// 	editable,
-// 	signUpFormData,
-// 	setSignUpFormData,
-// }) => {
-// 	const [error, setError] = useState("");
-
-// 	const handleTextChange = (text: string) => {
-// 		if (text.length >= 8) {
-// 			setError("");
-// 			setSignUpFormData({
-// 				...signUpFormData,
-// 				phone: text,
-// 				phoneVerificationStatus: true,
-// 			});
-// 		} else {
-// 			setError("Please enter at least 8 digits");
-// 			setSignUpFormData({
-// 				...signUpFormData,
-// 				phone: text,
-// 				phoneVerificationStatus: false,
-// 			});
-// 		}
-// 	};
-
-// 	return (
-// 		<View>
-// 			<View style={[styles.inputContainer, extendedStyle]}>
-// 				<Text style={styles.prefix}>+852</Text>
-// 				<View style={styles.horizontalLine} />
-// 				<TextInput
-// 					value={signUpFormData?.phone}
-// 					keyboardType="numeric"
-// 					onChangeText={handleTextChange}
-// 					placeholder={placeholder}
-// 					style={[styles.input]}
-// 					placeholderTextColor="#888888"
-// 					editable={editable}
-// 				/>
-// 			</View>
-// 			{error && <Text style={styles.errorMessage}>{error}</Text>}
-// 		</View>
-// 	);
-// };
-
-// const styles = StyleSheet.create({
-// 	inputContainer: {
-// 		maxWidth: "100%",
-// 		flexDirection: "row",
-// 		alignItems: "center",
-// 		justifyContent: "center",
-// 		borderWidth: 1,
-// 		borderColor: "#bbbbbb",
-// 		borderRadius: 12,
-// 		padding: 20,
-// 	},
-// 	prefix: {
-// 		marginRight: 5,
-// 		fontSize: 16,
-// 	},
-// 	horizontalLine: {
-// 		width: 24,
-// 		borderColor: "#bbbbbb",
-// 		borderWidth: 0.5,
-// 		transform: [{ rotate: "90deg" }],
-// 	},
-// 	input: {
-// 		flex: 1,
-// 		marginLeft: 5,
-// 		fontSize: 16,
-// 	},
-// 	errorMessage: {
-// 		fontSize: 14,
-// 		color: "#ff0033",
-// 		fontWeight: "400",
-// 		marginLeft: 10,
-// 		marginTop: 10,
-// 	},
-// });
-// export default PhoneInput;
-
 import React from "react";
-import { View, Text, TextInput, StyleSheet, ViewStyle, StyleProp } from "react-native";
+import { View, Text, TextInput, StyleSheet, ViewStyle, StyleProp, KeyboardTypeOptions, TextInputProps } from "react-native";
 
 interface PhoneInputProps {
 	value: string | undefined;
@@ -181,9 +7,13 @@ interface PhoneInputProps {
 	placeholder: string;
 	extendedStyle?: StyleProp<ViewStyle>;
 	editable?: boolean;
+    textContentType?: TextInputProps['textContentType']; 
+    autoComplete?: TextInputProps['autoComplete']
+    keyboardType?: KeyboardTypeOptions
+    autoCapitalize?: TextInputProps['autoCapitalize']
 }
 
-const PhoneInput: React.FC<PhoneInputProps> = ({ value, onChangeText, placeholder, extendedStyle, editable }) => {
+const PhoneInput: React.FC<PhoneInputProps> = ({ value, onChangeText, placeholder, extendedStyle, editable, keyboardType, textContentType, autoComplete, autoCapitalize }) => {
 	return (
 		<View>
 			<View style={[styles.inputContainer, extendedStyle]}>
@@ -192,11 +22,15 @@ const PhoneInput: React.FC<PhoneInputProps> = ({ value, onChangeText, placeholde
 				<TextInput
 					value={value}
 					onChangeText={onChangeText}
-					keyboardType="numeric"
+					keyboardType={keyboardType}
 					placeholder={placeholder}
 					style={[styles.input]}
 					placeholderTextColor="#888888"
-					editable={editable}
+					editable={editable}       
+                    textContentType={textContentType}
+                    autoComplete={autoComplete}
+                    autoCapitalize={autoCapitalize}
+             
 				/>
 			</View>
 		</View>

+ 32 - 40
component/global/rippleEffectBatteryIcon.tsx

@@ -1,19 +1,19 @@
 import { useEffect } from 'react';
 import { StyleSheet, View } from 'react-native';
-import { widthPercentageToDP as wp } from 'react-native-responsive-screen';
-import Animated, {
-    useAnimatedStyle,
-    useSharedValue,
-    withRepeat,
-    withTiming
+import {
+  useAnimatedStyle,
+  useSharedValue,
+  withRepeat,
+  withTiming, // 确保导入了 withTiming
 } from 'react-native-reanimated';
+import Animated from 'react-native-reanimated';
 import Svg, { Path, Rect } from 'react-native-svg';
 
 const BatterySvg = () => (
     <Svg width="40" height="40" viewBox="0 0 40 40" fill="none">
         <Rect width="40" height="40" rx="20" fill="#02677D" />
         <Path
-            d="M25.1202 32.2255V28.7039H22.5962L26.274 22.8746V26.3962H28.798L25.1202 32.2255ZM14.8798 31.925C14.5597 31.925 14.2914 31.8168 14.0748 31.6002C13.8583 31.3837 13.75 31.1153 13.75 30.7952V11.4804C13.75 11.1603 13.8583 10.8919 14.0748 10.6754C14.2914 10.4589 14.5597 10.3506 14.8798 10.3506H17.1394V8.17511H21.7547V10.3506H24.0168C24.3373 10.3506 24.6053 10.4589 24.8208 10.6754C25.0364 10.8919 25.1441 11.1603 25.1441 11.4804V20.1703C24.8108 20.1863 24.4899 20.2228 24.1814 20.2796C23.8729 20.3365 23.5689 20.4179 23.2692 20.5236V12.2135H15.6249V27.514H18.2452C18.2452 28.3441 18.3758 29.1342 18.637 29.8842C18.8982 30.6342 19.2708 31.3145 19.7547 31.925H14.8798Z"
+            d="M25.1202 32.2255V28.7039H22.5962L26.274 22.8746V26.3962H28.798L25.1202 32.2255ZM14.8798 31.925C14.5597 31.925 14.2914 31.8168 14.0748 31.6002C13.8583 31.3837 13.75 31.1153 13.75 30.7952V11.4804C13.75 11.1603 13.8583 10.8919 14.0748 10.6754C14.2914 10.4589 14.5597 10.3506 14.8798 10.3506H17.1394V8.17511H21.7547V10.3506H24.0168C24.3373 10.3506 24.6053 10.4589 24.8208 10.6754C25.0364 10.8919 25.1441 11.1603 25.1441 11.4804V20.1703C24.8108 20.1863 24.4899 24.2228 24.1814 20.2796C23.8729 20.3365 23.5689 20.4179 23.2692 20.5236V12.2135H15.6249V27.514H18.2452C18.2452 28.3441 18.3758 29.1342 18.637 29.8842C18.8982 30.6342 19.2708 31.3145 19.7547 31.925H14.8798Z"
             fill="#FAFAFA"
         />
     </Svg>
@@ -22,7 +22,11 @@ const BatterySvg = () => (
 const RippleEffectBatteryIcon = () => {
     const scaleValue = useSharedValue(1);
     const opacityValue = useSharedValue(1);
-    const textScaleValue = useSharedValue(0.4);
+
+    useEffect(() => {
+        scaleValue.value = withRepeat(withTiming(3, { duration: 2000 }), -1, false); // 使用 withTiming
+        opacityValue.value = withRepeat(withTiming(0, { duration: 2000 }), -1, false); // 使用 withTiming
+    }, []);
 
     const animatedCircle = useAnimatedStyle(() => {
         return {
@@ -31,29 +35,14 @@ const RippleEffectBatteryIcon = () => {
         };
     });
 
-    const startRippleAnimation = () => {
-        scaleValue.value = withRepeat(withTiming(3, { duration: 2000 }), -1);
-        opacityValue.value = withRepeat(withTiming(0, { duration: 2000 }), -1);
-        textScaleValue.value = withRepeat(
-            withTiming(1, { duration: 10000 }),
-            -1
-        );
-    };
-
-    useEffect(() => {
-        startRippleAnimation();
-    }, []);
-
     return (
-        <View className="relative">
-            <View className="absolute">
-                <BatterySvg></BatterySvg>
-            </View>
-            <View style={{ width: 40, height: 40 }}>
-                <Animated.View
-                    style={[animatedCircle, styles.innerCircle]}
-                ></Animated.View>
+        <View style={styles.container}>
+            <View style={styles.batteryContainer}>
+                <BatterySvg />
             </View>
+            <Animated.View
+                style={[styles.innerCircle, animatedCircle]}
+            />
         </View>
     );
 };
@@ -61,18 +50,21 @@ const RippleEffectBatteryIcon = () => {
 export default RippleEffectBatteryIcon;
 
 const styles = StyleSheet.create({
+    container: {
+        position: 'relative',
+        width: 40,
+        height: 40,
+    },
+    batteryContainer: {
+        position: 'absolute',
+        zIndex: 2,
+    },
     innerCircle: {
-        width: '100%',
-        height: '100%',
-        borderRadius: 200,
+        width: 40,
+        height: 40,
+        borderRadius: 20,
         backgroundColor: '#025c72',
-        justifyContent: 'center'
-    },
-    innerText: {
-        fontSize: 14,
-        alignSelf: 'center',
-        fontWeight: 'bold',
         position: 'absolute',
-        top: wp(10)
+        zIndex: 1,
     }
-});
+});

+ 0 - 200
component/global/testingCar.tsx

@@ -1,200 +0,0 @@
-// interface CarBrand {
-//     name: string;
-//     logo?: any; // This would be the require() statement for the logo image
-// }
-
-// interface AlphabetSection {
-//     letter: string;
-//     brands: CarBrand[];
-// }
-// const carBrands: AlphabetSection[] = [
-//     {
-//         letter: 'A',
-//         brands: [
-//             { name: 'Audi', logo: require('../../assets/audi.png') },
-//             { name: 'Acura', logo: require('../../assets/acura.png') }
-//         ]
-//     },
-//     {
-//         letter: 'B',
-//         brands: [
-//             { name: 'BMW', logo: require('../../assets/bmw1.png') },
-//             { name: 'BYD', logo: require('../../assets/byd.png') }
-//         ]
-//     },
-//     {
-//         letter: 'C',
-//         brands: [
-//             { name: 'Chevrolet', logo: require('../../assets/chevrolet1.png') },
-//             { name: 'Cadillac', logo: require('../../assets/cadillac1.png') }
-//         ]
-//     },
-//     {
-//         letter: 'F',
-//         brands: [
-//             { name: 'Ford', logo: require('../../assets/ford.png') },
-//             { name: 'Fiat', logo: require('../../assets/fiat.png') }
-//         ]
-//     },
-//     {
-//         letter: 'G',
-//         brands: [{ name: 'Genesis', logo: require('../../assets/genesis.png') }]
-//     },
-//     {
-//         letter: 'H',
-//         brands: [
-//             { name: 'Honda', logo: require('../../assets/honda.png') },
-//             { name: 'Hyundai', logo: require('../../assets/hyundai.png') }
-//         ]
-//     },
-//     {
-//         letter: 'J',
-//         brands: [{ name: 'Jaguar', logo: require('../../assets/jaguar.png') }]
-//     },
-//     {
-//         letter: 'K',
-//         brands: [{ name: 'Kia', logo: require('../../assets/kia.png') }]
-//     },
-
-//     {
-//         letter: 'M',
-//         brands: [
-//             { name: 'Mercedes-Benz', logo: require('../../assets/benz.png') },
-//             { name: 'Mini', logo: require('../../assets/mini.png') },
-//             { name: 'Mazda', logo: require('../../assets/mazda.png') }
-//         ]
-//     },
-//     {
-//         letter: 'N',
-//         brands: [{ name: 'Nissan', logo: require('../../assets/nissan.png') }]
-//     },
-//     {
-//         letter: 'P',
-//         brands: [{ name: 'Porsche', logo: require('../../assets/porsche.png') }]
-//     },
-
-//     {
-//         letter: 'S',
-//         brands: [{ name: 'Subaru', logo: require('../../assets/subaru.png') }]
-//     },
-//     {
-//         letter: 'T',
-//         brands: [
-//             { name: 'Tesla', logo: require('../../assets/tesla.png') },
-//             { name: 'Toyota', logo: require('../../assets/toyota.png') }
-//         ]
-//     },
-//     {
-//         letter: 'V',
-//         brands: [
-//             { name: 'Volkswagen', logo: require('../../assets/volkswagen.png') },
-//             { name: 'Volvo', logo: require('../../assets/volvo.png') }
-//         ]
-//     }
-// ];
-
-// import React, { useEffect } from 'react';
-// import { View, Text, Image, Pressable, StyleSheet, SafeAreaView, ScrollView } from 'react-native';
-// import useVehicleStore from '../../providers/vehicle_store';
-// import { FlashList } from '@shopify/flash-list';
-// import { router } from 'expo-router';
-// import { PreviousPageBlackSvg } from './SVG';
-
-// const CarBrandSelector = () => {
-//     const { vehicleBrand, vehicleModel, licensePlate, setVehicleBrand, setVehicleModel, setLicensePlate } =
-//         useVehicleStore();
-
-//     useEffect(() => {
-//         console.log('vehicleBrand in zustand', vehicleBrand);
-//     }, [vehicleBrand]);
-//     const renderBrandItem = ({ item: brand }: { item: CarBrand }) => (
-//         <Pressable
-//             style={styles.brandItem}
-//             onPress={() => {
-//                 setVehicleBrand(brand.name);
-//                 console.log(brand.name);
-//             }}
-//         >
-//             <Image source={brand.logo} style={styles.logo} resizeMode="contain" />
-//             <Text style={styles.brandName}>{brand.name}</Text>
-//         </Pressable>
-//     );
-
-//     const renderAlphabetSection = ({ item: section }: { item: AlphabetSection }) => (
-//         <View>
-//             <View style={styles.letterHeader} className="h-9">
-//                 <Text style={styles.letterText}>{section.letter}</Text>
-//             </View>
-//             <View style={styles.brandRow}>{section.brands.map((brand) => renderBrandItem({ item: brand }))}</View>
-//         </View>
-//     );
-
-//     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: 10
-//                         }}
-//                     >
-//                         選擇品牌
-//                     </Text>
-//                 </View>
-//                 <View className="flex-1">
-//                     <FlashList
-//                         data={carBrands}
-//                         renderItem={renderAlphabetSection}
-//                         keyExtractor={(item) => item.letter}
-//                     />
-//                 </View>
-//             </ScrollView>
-//         </SafeAreaView>
-//     );
-// };
-
-// const styles = StyleSheet.create({
-//     letterHeader: {
-//         backgroundColor: '#E3F2F8',
-//         padding: 10,
-//         marginVertical: 10
-//     },
-//     letterText: {
-//         color: '#02677D',
-//         fontSize: 14,
-//         fontWeight: 'bold'
-//     },
-//     brandRow: {
-//         flexDirection: 'row',
-//         flexWrap: 'wrap'
-//     },
-//     brandItem: {
-//         width: '33.33%',
-//         alignItems: 'center',
-//         padding: 10
-//     },
-//     logo: {
-//         width: 80,
-//         height: 80
-//     },
-//     brandName: {
-//         marginTop: 5,
-//         textAlign: 'center'
-//     }
-// });
-
-// export default CarBrandSelector;

+ 2 - 2
component/homePage/homePage.tsx

@@ -280,7 +280,7 @@ const HomePage: React.FC<HomePageProps> = () => {
                         className="flex-1 bg-black/50 items-center justify-center"
                         onPress={() => setShowOnboarding(false)}
                     >
-                        <View className="w-[120%] rounded-2xl ">
+                        <View className="w-[98%] rounded-2xl ">
                             <Image
                                 source={{ uri: mainPromotionImage }}
                                 className="w-full aspect-square "
@@ -384,7 +384,7 @@ const HomePage: React.FC<HomePageProps> = () => {
                         <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>
+                                <Text className="text-lg text-left pb-1">好!</Text>
                                 <View className="relative z-5">
                                     <Pressable
                                         onPress={() => router.push({ pathname: 'notificationPage' })}

+ 44 - 13
component/registrationMultiStepForm/formComponent/formPages/loginPage.tsx

@@ -33,6 +33,7 @@ const LoginPage: React.FC<LoginPageProps> = ({ goToNextPage, goToForgetPassWordP
     const [loginPhone, setLoginPhone] = useState('');
     const [loginPassword, setLoginPassword] = useState('');
     const [saveAccount, setSaveAccount] = useState(false);
+    const [userTerms, setUserTerms] = useState(false);
     const [isLoading, setIsLoading] = useState(false);
     const { login } = useAuth();
     const [isChecked, setChecked] = useState(false);
@@ -52,6 +53,7 @@ const LoginPage: React.FC<LoginPageProps> = ({ goToNextPage, goToForgetPassWordP
                 setLoginPhone(savedPhone);
                 setLoginPassword(savedPassword);
                 setSaveAccount(true);
+                setUserTerms(true);
                 setChecked(true);
             }
         } catch (error) {
@@ -68,25 +70,32 @@ const LoginPage: React.FC<LoginPageProps> = ({ goToNextPage, goToForgetPassWordP
                 { text: '我已綁定,帶我回登入頁面', onPress: () => router.replace('/login') }
             ]);
         } else {
-            // const lowerCaseUsername = username.toLowerCase();
-            const isBinding = false;
-            const response = await login(username, password, isBinding);
+            if (userTerms) {
+                const isBinding = false;
+                const response = await login(username, password, isBinding);
 
-            if (response === 'login successful') {
-                if (saveAccount) {
-                    await AsyncStorage.setItem('savedPhone', username);
-                    await AsyncStorage.setItem('savedPassword', password);
+                if (response) {
+                    if (saveAccount) {
+                        await AsyncStorage.setItem('savedPhone', username);
+                        await AsyncStorage.setItem('savedPassword', password);
+                    } else {
+                        await AsyncStorage.removeItem('savedPhone');
+                        await AsyncStorage.removeItem('savedPassword');
+                    }
                 } else {
-                    await AsyncStorage.removeItem('savedPhone');
-                    await AsyncStorage.removeItem('savedPassword');
+                    Alert.alert('登入失敗', `原因: ${response}`);
                 }
             } else {
-                Alert.alert('登入失敗', `原因: ${response}`);
+                Alert.alert('請先同意用戶條款');
+
             }
+            
         }
         setIsLoading(false);
     };
-
+    const goToUserTermsPage = () => {
+        router.push('(public)/userTermsPage');
+    };
     const insets = useSafeAreaInsets();
     console.log(screenHeight);
     return (
@@ -165,11 +174,25 @@ const LoginPage: React.FC<LoginPageProps> = ({ goToNextPage, goToForgetPassWordP
                             color={saveAccount ? '#02677D' : '#02677D'}
                             onValueChange={(newValue) => {
                                 setSaveAccount(newValue);
-                                console.log(newValue);
                             }}
                         />
 
                         <Text style={styles.text}>記住我的電話號碼</Text>
+                        
+                    </View>
+                    <View className="flex flex-row items-center">
+                        <Checkbox
+                            style={styles.checkbox}
+                            value={userTerms}
+                            color={userTerms ? '#02677D' : '#02677D'}
+                            onValueChange={(newValue) => {
+                                setUserTerms(newValue);
+                            }}
+                        />
+
+                        <Text style={styles.text}>登入即同意
+                            <Text style={styles.userTerms} onPress={goToUserTermsPage}>用戶條款</Text>
+                        </Text>
                     </View>
 
                     <NormalButton
@@ -223,12 +246,20 @@ const styles = StyleSheet.create({
     },
     topContainerForLogo: {},
     checkbox: {
-        margin: 8
+        marginLeft: 8,
+        marginRight: 8
     },
     text: {
         color: '#02677D',
         fontSize: 16,
         paddingVertical: 5
+    },
+    userTerms: {
+        color: '#02677D',
+        fontSize: 16,
+        paddingVertical: 5,
+        textDecorationLine: 'underline'
+
     }
 });
 export default LoginPage;

+ 2 - 2
component/searchPage/searchResultComponent.tsx

@@ -11,14 +11,14 @@ import {
 } from 'react-native';
 import React, { useState, useEffect, useRef, useMemo } from 'react';
 import { SafeAreaView } from 'react-native-safe-area-context';
-import MapView, { Marker, Region } from 'react-native-maps';
+import MapView from 'react-native-maps';
 import * as Location from 'expo-location';
 import { router, useLocalSearchParams } from 'expo-router';
 import { ArrowIconSvg, CheckMarkLogoSvg } from '../global/SVG';
 import NormalInput from '../global/normal_input';
 import BottomSheet, { BottomSheetScrollView } from '@gorhom/bottom-sheet';
 import { chargeStationService } from '../../service/chargeStationService';
-import { PROVIDER_GOOGLE } from 'react-native-maps';
+import { PROVIDER_GOOGLE,  Marker, Region  } from 'react-native-maps';
 import { calculateDistance } from '../global/distanceCalculator';
 
 interface TabItem {

+ 4 - 5
component/test/map.tsx

@@ -32,12 +32,11 @@ export default function Index() {
                     customMapStyle={mapStyle}
                     followsUserLocation={true}
                     initialRegion={{
-                        latitude: 22.3193,
-                        longitude: 114.1694,
-                        latitudeDelta: 0.1,
-                        longitudeDelta: 0.1
+                        latitude: location.coords.latitude,
+                        longitude: location.coords.longitude,
+                        latitudeDelta: 0.01,
+                        longitudeDelta: 0.01
                     }}
-                    showsPointsOfInterest={false}
                     showsUserLocation={true}
                     showsBuildings={false}
                     showsTraffic={false}

+ 0 - 1
index.js

@@ -1,5 +1,4 @@
 import { registerRootComponent } from 'expo';
-
 import App from './App';
 
 // registerRootComponent calls AppRegistry.registerComponent('main', () => App);

File diff suppressed because it is too large
+ 235 - 154
package-lock.json


+ 37 - 29
package.json

@@ -7,41 +7,45 @@
     "android": "expo run:android",
     "ios": "expo run:ios",
     "web": "expo start --web",
-    "postinstall": "patch-package"
+    "postinstall": "patch-package",
+    "remove": "rm -rf node_modules package-lock.json"
   },
   "dependencies": {
-    "@gorhom/bottom-sheet": "^5.1.8",
+    "@expo/metro-runtime": "~6.1.2",
+    "@gorhom/bottom-sheet": "^5.2.6",
     "@react-native-async-storage/async-storage": "2.2.0",
-    "@shopify/flash-list": "2.0.2",
+    "@shopify/flash-list": "2.1.0",
     "@tanstack/react-query": "^5.85.0",
     "@types/lodash": "^4.17.20",
     "@types/react-native-datepicker": "^1.7.6",
-    "axios": "^1.11.0",
+    "axios": "^1.12.2",
     "crypto-js": "^4.2.0",
     "date-fns": "4.1.0",
     "date-fns-tz": "^3.2.0",
     "dotenv": "^17.2.1",
-    "expo": "^53.0.0",
-    "expo-camera": "~16.1.11",
-    "expo-checkbox": "~4.1.4",
-    "expo-constants": "~17.1.7",
-    "expo-dev-client": "~5.2.4",
-    "expo-device": "~7.1.4",
+    "expo": "^54.0.9",
+    "expo-camera": "~17.0.8",
+    "expo-checkbox": "~5.0.7",
+    "expo-clipboard": "~8.0.7",
+    "expo-constants": "~18.0.9",
+    "expo-dev-client": "~6.0.12",
+    "expo-device": "~8.0.8",
     "expo-env": "^1.1.1",
-    "expo-file-system": "~18.1.11",
-    "expo-image-picker": "~16.1.4",
-    "expo-linking": "~7.1.7",
-    "expo-location": "~18.1.6",
-    "expo-notifications": "~0.31.4",
-    "expo-router": "~5.1.4",
-    "expo-secure-store": "~14.2.3",
-    "expo-status-bar": "~2.2.3",
-    "expo-updates": "~0.28.17",
+    "expo-file-system": "~19.0.14",
+    "expo-image-picker": "~17.0.8",
+    "expo-linking": "~8.0.8",
+    "expo-location": "~19.0.7",
+    "expo-notifications": "~0.32.11",
+    "expo-router": "~6.0.7",
+    "expo-secure-store": "~15.0.7",
+    "expo-status-bar": "~3.0.8",
+    "expo-updates": "~29.0.11",
     "lodash": "^4.17.21",
-    "nativewind": "^4.1.23",
+    "nativewind": "^4.2.1",
     "prettier-plugin-tailwindcss": "^0.6.14",
-    "react": "19.0.0",
-    "react-native": "0.79.5",
+    "react": "19.1.0",
+    "react-dom": "19.1.0",
+    "react-native": "0.81.4",
     "react-native-element-dropdown": "^2.12.4",
     "react-native-gesture-handler": "~2.28.0",
     "react-native-keyboard-aware-scroll-view": "^0.9.5",
@@ -49,26 +53,30 @@
     "react-native-modal": "14.0.0-rc.1",
     "react-native-modern-datepicker": "^1.0.0-beta.91",
     "react-native-pager-view": "^6.9.1",
-    "react-native-reanimated": "~3.17.4",
+    "react-native-reanimated": "~4.1.2",
     "react-native-responsive-screen": "^1.4.2",
-    "react-native-safe-area-context": "5.6.0",
-    "react-native-screens": "~4.13.1",
+    "react-native-safe-area-context": "5.6.1",
+    "react-native-screens": "~4.16.0",
     "react-native-svg": "15.12.1",
     "react-native-tab-view": "4.1.3",
-    "react-native-webview": "13.15.0",
-    "zustand": "^4.5.2",
-    "expo-clipboard": "~7.1.5"
+    "react-native-webview": "13.16.0",
+    "react-native-worklets": "0.5.1",
+    "zustand": "5.0.8"
   },
   "devDependencies": {
     "@babel/core": "^7.28.0",
     "@types/crypto-js": "^4.2.2",
-    "@types/react": "~19.0.10",
+    "@types/react": "19.1.0",
     "@types/react-native-modern-datepicker": "^1.0.5",
     "lightningcss": "^1.30.1",
     "patch-package": "^8.0.0",
     "postinstall-postinstall": "^2.1.0",
+    "babel-preset-expo": "^54.0.1",
     "tailwindcss": "^3.4.17",
     "typescript": "~5.9.2"
   },
+  "engines": {
+    "node": ">=22.20.0"
+  },
   "private": true
 }

+ 1 - 1
service/chargeStationService.tsx

@@ -467,7 +467,6 @@ class ChargeStationService {
                 }
             );
             if (response.status === 200 || response.status === 201) {
-                console.log('received data from fetchOngoingChargingData at chargingStationService', response.data);
                 return response.data;
             } else {
                 console.log('invalid response');
@@ -485,6 +484,7 @@ class ChargeStationService {
                 }
             });
             if (response.status === 200 || response.status === 201) {
+
                 return response.data;
             } else {
                 console.log('invalid response');

Some files were not shown because too many files changed in this diff