Browse Source

购物车和聊天界面未登录不可以使用

main
Mac 2 weeks ago
parent
commit
7b7bfca490
  1. 244
      app/locales/en/translation.json
  2. 29
      app/locales/fr/translation.json
  3. 26
      app/locales/zh/translation.json
  4. 200
      app/screens/CartScreen.tsx
  5. 312
      app/screens/ChatScreen.tsx
  6. 72
      app/screens/loginList/index.tsx

244
app/locales/en/translation.json

@ -96,24 +96,104 @@
"popularCategories": "Popular Categories",
"my":"My",
"categories": "Categories",
"chat": "Chat",
"customerServiceChat": "Customer Service",
"productChat": "Product Support",
"notificationChat": "Notifications",
"inputMessage": "Type a message...",
"chat": {
"customer_service": "Customer Service",
"product_support": "Product Support",
"notifications": "Notifications",
"input_message": "Type a message...",
"send": "Send",
"searchProducts": "Search products",
"recommendations": "Recommendations",
"sales": "sales",
"productPicture": "Product Picture",
"no_stock": "No stock",
"setting": {
"title": "Select Language and Currency",
"country": "Country",
"currency": "Currency",
"language": "Language",
"typing_message": "Typing",
"default_response": "Thank you for your message. Our team will get back to you shortly.",
"error_response": "Sorry, there was an error processing your request. Please try again later.",
"login_required_title": "Please Login First",
"login_required_subtitle": "Login to use chat features",
"login_now": "Login Now"
},
"banner": {
"today": "Today",
"shipping": {
"fee": "Shipping Fee",
"calculation_result": "Shipping Fee Calculation Result",
"estimated_fee": "Estimated Shipping Fee",
"confirm": "Confirm",
"success": "Settings saved successfully"
"maritime": "Sea Shipping",
"airway": "Air Shipping",
"select_country": "Select Country",
"parcel_volume": "Parcel Volume",
"enter_parcel_volume": "Please enter parcel volume",
"calculate_shipping": "Calculate Shipping Fee",
"select_country_modal_title": "Select Country",
"close": "Close"
},
"inquiry": {
"title": "Quote Request",
"upload_image": "Upload Image",
"hint": "Please upload an image",
"quantity_required": "Please enter quantity",
"error": "Error",
"submission_failed": "Submission failed, please try again",
"image_inquiry": "Image Inquiry",
"product_name": "Product Name",
"enter_product_name": "Please enter product name",
"quantity": "Quantity",
"enter_quantity": "Please enter quantity",
"material": "Material",
"enter_material": "Please enter material",
"link": "Link",
"enter_link": "Please enter link",
"remark": "Remark",
"enter_remark": "Please enter remark",
"cancel": "Cancel",
"confirm": "Confirm",
"submitting": "Submitting...",
"tab_request": "Request Quote",
"tab_in_progress": "In Progress",
"tab_completed": "Completed",
"loading": "Loading...",
"no_more_data": "No more data",
"no_in_progress": "No inquiries in progress",
"no_completed": "No completed inquiries",
"take_photo": "Take Photo",
"choose_from_gallery": "Choose from Gallery",
"reset_camera": "Reset Camera Function",
"camera_reset": "Reset",
"camera_reset_message": "Now you can use camera function",
"upload_image_get_quote": "Get product quote via image",
"upload_image_get_quote_message": "Upload product images,\nwe will provide you with detailed quotes\nand product information.",
"take_photo_or_upload": "Take Photo or Upload",
"fetch_failed": "Failed to fetch inquiry list",
"item_name": "Item Name",
"none": "None",
"more": "More"
},
"tiktok": {
"store": "Store TikTok",
"live": "Live",
"video": "Video",
"shorts": "Shorts",
"category": "Category"
},
"chat": {
"login_required_title": "Please Login First",
"login_required_subtitle": "Login to use chat features",
"login_now": "Login Now"
}
},
"settings": {
"title": "Settings",
"profile": "My Profile",
"change_password": "Change Password",
"change_phone": "Change Phone Number",
"my_address": "My Address",
"feedback": "Feedback",
"privacy_policy": "Brainnel Privacy Policy",
"terms_of_use": "Terms of Use",
"clear_cache": "Clear Cache",
"language_currency": "Select Language and Currency",
"add_new_address": "Add New Address",
"address_management": "Address Management",
"set_default": "Set Default Address",
"delete": "Delete"
},
"login.now": "Login Now",
"login.button": "Login",
@ -300,70 +380,6 @@
"submit_order": "Submit Order",
"select_payment": "Please select payment method"
},
"settings": {
"title": "Settings",
"profile": "My Profile",
"change_password": "Change Password",
"change_phone": "Change Phone Number",
"my_address": "My Address",
"feedback": "Feedback",
"privacy_policy": "Brainnel Privacy Policy",
"terms_of_use": "Terms of Use",
"clear_cache": "Clear Cache",
"language_currency": "Select Language and Currency",
"add_new_address": "Add New Address",
"address_management": "Address Management",
"set_default": "Set Default Address",
"delete": "Delete"
},
"typingMessage": "Typing",
"defaultResponse": "Thank you for your message. Our team will get back to you shortly.",
"errorResponse": "Sorry, there was an error processing your request. Please try again later.",
"loginRequired": "Login Required",
"loginPrompt": "Please log in to access this feature. Your login will provide a better experience and allow you to track your orders.",
"login": {
"logInOrSignUp": "Log in or sign up",
"phoneNumber": "Phone number",
"enterPassword": "Please re-enter your password",
"passwordIncorrect": "Password incorrect, please confirm your password.",
"verificationCodeInfo": "We will send a verification code on your number to confirm it's you.",
"continue": "Continue",
"pleaseEnterEmail": "Please enter your e-mail address",
"forgotPassword": {
"title": "Forget your password?",
"phoneDescription": "Enter your phone number below, and we'll send you a 6-digit password reset code.",
"emailDescription": "Enter your email address below, and we'll send you a 6-digit password reset code.",
"submit": "Submit",
"invalidPhone": "Invalid phone number",
"requiresDigits": "Requires digits",
"invalidEmail": "Invalid email address"
},
"verification": {
"title": "Enter the verification code",
"description": "Please check your phone now! Enter the 6-digit password reset code sent to",
"expiration": "The code expires after 2 hours.",
"incorrect": "Incorrect verification code",
"resend": "Resend code",
"didntReceive": "Didn't receive the code?",
"helpPoint1": "1. Make sure your phone number is correct.",
"helpPoint2": "2. Please check if your phone can receive SMS messages."
},
"resetPassword": {
"title": "Setting a password",
"enterPassword": "Please enter your password",
"confirmPassword": "Please reconfirm your password",
"required": "Password is required",
"minLength": "Password must be at least 6 characters",
"confirmRequired": "Please confirm your password",
"mismatch": "Passwords do not match",
"failed": "Failed to reset password. Please try again.",
"error": "An error occurred. Please try again.",
"submit": "Submit"
},
"loginRequired": "Login Required",
"loginPrompt": "Please log in to access this feature. Your login will provide a better experience and allow you to track your orders.",
"loginNow": "Login Now"
},
"homePage":{
"searchPlaceholder": "Search products",
"shipping": "Shipping Calculator",
@ -455,71 +471,9 @@
"yes": "Yes",
"modify_quantity": "Modify Quantity",
"cancel": "Cancel",
"confirm": "Confirm"
},
"banner": {
"today": "Today",
"shipping": {
"fee": "Shipping Fee",
"calculation_result": "Shipping Fee Calculation Result",
"estimated_fee": "Estimated Shipping Fee",
"confirm": "Confirm",
"maritime": "Sea Shipping",
"airway": "Air Shipping",
"select_country": "Select Country",
"parcel_volume": "Parcel Volume",
"enter_parcel_volume": "Please enter parcel volume",
"calculate_shipping": "Calculate Shipping Fee",
"select_country_modal_title": "Select Country",
"close": "Close"
},
"inquiry": {
"title": "Quote Request",
"upload_image": "Upload Image",
"hint": "Please upload an image",
"quantity_required": "Please enter quantity",
"error": "Error",
"submission_failed": "Submission failed, please try again",
"image_inquiry": "Image Inquiry",
"product_name": "Product Name",
"enter_product_name": "Please enter product name",
"quantity": "Quantity",
"enter_quantity": "Please enter quantity",
"material": "Material",
"enter_material": "Please enter material",
"link": "Link",
"enter_link": "Please enter link",
"remark": "Remark",
"enter_remark": "Please enter remark",
"cancel": "Cancel",
"confirm": "Confirm",
"submitting": "Submitting...",
"tab_request": "Request Quote",
"tab_in_progress": "In Progress",
"tab_completed": "Completed",
"loading": "Loading...",
"no_more_data": "No more data",
"no_in_progress": "No inquiries in progress",
"no_completed": "No completed inquiries",
"take_photo": "Take Photo",
"choose_from_gallery": "Choose from Gallery",
"reset_camera": "Reset Camera Function",
"camera_reset": "Reset",
"camera_reset_message": "Now you can use camera function",
"upload_image_get_quote": "Get product quote via image",
"upload_image_get_quote_message": "Upload product images,\nwe will provide you with detailed quotes\nand product information.",
"take_photo_or_upload": "Take Photo or Upload",
"fetch_failed": "Failed to fetch inquiry list",
"item_name": "Item Name",
"none": "None",
"more": "More"
},
"tiktok": {
"store": "Store TikTok",
"live": "Live",
"video": "Video",
"shorts": "Shorts",
"category": "Category"
}
"login_required_title": "Please Login First",
"login_required_subtitle": "Login to use cart features",
"login_now": "Login Now"
}
}

29
app/locales/fr/translation.json

@ -96,12 +96,19 @@
"popularCategories": "Catégories populaires",
"my":"Mon",
"categories":"Catégories",
"chat":"Chat",
"customerServiceChat":"Chat client",
"productChat":"Chat produit",
"notificationChat":"Chat notifications",
"inputMessage":"Message",
"send":"Envoyer",
"chat": {
"customer_service": "Service Client",
"product_support": "Support Produit",
"notifications": "Notifications",
"input_message": "Tapez un message...",
"send": "Envoyer",
"typing_message": "En cours de frappe",
"default_response": "Merci pour votre message. Notre équipe vous répondra sous peu.",
"error_response": "Désolé, une erreur s'est produite lors du traitement de votre demande. Veuillez réessayer plus tard.",
"login_required_title": "Veuillez vous connecter d'abord",
"login_required_subtitle": "Connectez-vous pour utiliser les fonctionnalités de chat",
"login_now": "Se connecter maintenant"
},
"searchProducts":"Rechercher des produits",
"recommendations":"Recommandations",
"sales":"ventes",
@ -392,6 +399,11 @@
},
"searchCountry": "Rechercher un pays",
"noCountriesFound": "Aucun pays trouvé",
"typingMessage": "En cours de frappe",
"defaultResponse": "Merci pour votre message. Notre équipe vous répondra sous peu.",
"errorResponse": "Désolé, une erreur s'est produite lors du traitement de votre demande. Veuillez réessayer plus tard.",
"loginRequired": "Connexion Requise",
"loginPrompt": "Veuillez vous connecter pour accéder à cette fonctionnalité. Votre connexion vous offrira une meilleure expérience et vous permettra de suivre vos commandes.",
"balance": {
"screen": {
"title": "Mon Solde",
@ -450,7 +462,10 @@
"yes": "Oui",
"modify_quantity": "Modifier la quantité",
"cancel": "Annuler",
"confirm": "Confirmer"
"confirm": "Confirmer",
"login_required_title": "Veuillez vous connecter d'abord",
"login_required_subtitle": "Connectez-vous pour utiliser les fonctionnalités du panier",
"login_now": "Se connecter maintenant"
},
"banner": {
"today": "Aujourd'hui",

26
app/locales/zh/translation.json

@ -93,6 +93,27 @@
"noMoreProducts": "没有更多商品",
"chatNow": "立即聊天",
"popularCategories": "热门分类",
"my": "我的",
"categories": "分类",
"chat": {
"customer_service": "客服聊天",
"product_support": "产品咨询",
"notifications": "消息通知",
"input_message": "输入消息...",
"send": "发送",
"typing_message": "正在输入",
"default_response": "感谢您的消息。我们的团队会尽快回复您。",
"error_response": "抱歉,处理您的请求时出现错误。请稍后再试。",
"login_required_title": "请先登录",
"login_required_subtitle": "登录后即可使用聊天功能",
"login_now": "立即登录"
},
"recommendations": "推荐",
"sales": "销量",
"productPicture": "商品图片",
"no_stock": "缺货",
"loginRequired": "需要登录",
"loginPrompt": "请登录以访问此功能。登录后您将获得更好的体验并能跟踪您的订单。",
"setting": {
"title": "选择语言和货币",
"country": "国家",
@ -152,6 +173,9 @@
"yes": "是",
"modify_quantity": "修改数量",
"cancel": "取消",
"confirm": "确认"
"confirm": "确认",
"login_required_title": "请先登录",
"login_required_subtitle": "登录后即可使用购物车功能",
"login_now": "立即登录"
}
}

200
app/screens/CartScreen.tsx

@ -62,6 +62,11 @@ export const CartScreen = () => {
// 货币转换函数
const convertCurrency = async () => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
console.log(country_code);
// 如果 country_code 是 255,不需要转换
@ -140,6 +145,11 @@ export const CartScreen = () => {
index1: number,
index: number | null
) => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
if (index != null) {
// 处理子类 SKU 的选择
const data = {
@ -268,6 +278,11 @@ export const CartScreen = () => {
};
const getCart = async () => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
const res = await getCartList();
// 修正父商品的选择状态,确保与子商品状态一致
@ -293,6 +308,11 @@ export const CartScreen = () => {
};
const selectAllHandel = () => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
const newAllSelected = !allSelected;
setAllSelected(newAllSelected);
@ -345,13 +365,22 @@ export const CartScreen = () => {
cartItemId: number,
cartId1: number
) => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
// 设置要删除的商品信息并显示确认对话框
setItemToDelete({ cartId, cartItemId, cartId1 });
setDeleteModalVisible(true);
};
const confirmDelete = () => {
if (itemToDelete) {
// 如果用户未登录,直接返回
if (!user_id || !itemToDelete) {
return;
}
const { cartId, cartItemId, cartId1 } = itemToDelete;
console.log(itemToDelete);
@ -408,7 +437,6 @@ export const CartScreen = () => {
deleteCartItem(cartId, cartItemId).then((res) => {
console.log(res);
});
}
// 关闭确认对话框
setDeleteModalVisible(false);
@ -426,9 +454,12 @@ export const CartScreen = () => {
// }, []);
useFocusEffect(
useCallback(() => {
// 只有在用户已登录时才执行API调用
if (user_id) {
getCart();
convertCurrency(); // 添加货币转换调用
}, [])
}
}, [user_id])
);
const gotoOrder = () => {
@ -498,6 +529,11 @@ export const CartScreen = () => {
cartItemId: number,
newQuantity: number
) => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
try {
// 更新本地状态
setCartList((prev) => {
@ -538,6 +574,11 @@ export const CartScreen = () => {
cartItemId: number,
currentQuantity: number
) => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
if (currentQuantity > 1) {
updateQuantity(cartId, cartItemId, currentQuantity - 1);
}
@ -549,12 +590,20 @@ export const CartScreen = () => {
cartItemId: number,
currentQuantity: number
) => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
updateQuantity(cartId, cartItemId, currentQuantity + 1);
};
// 处理数量输入框确认
const handleQuantityInputConfirm = () => {
if (!editingItem) return;
// 如果用户未登录,直接返回
if (!user_id || !editingItem) {
return;
}
const newQuantity = parseInt(quantityInput);
if (isNaN(newQuantity) || newQuantity < 1) {
@ -574,11 +623,21 @@ export const CartScreen = () => {
cartItemId: number,
currentQuantity: number
) => {
// 如果用户未登录,直接返回
if (!user_id) {
return;
}
setEditingItem({ cartId, cartItemId, currentQuantity });
setQuantityInput(currentQuantity.toString());
setQuantityInputVisible(true);
};
// 添加导航到登录页面的函数
const goToLogin = () => {
navigation.navigate("Login"); // 假设登录页面的路由名为 "Login"
};
return (
<SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
@ -605,6 +664,7 @@ export const CartScreen = () => {
onPress={() =>
toggleSelection(String(item.cart_id), index1, null)
}
disabled={!user_id}
>
<View style={[styles.iconContainer]}>
{item.selected === 1 ? (
@ -632,6 +692,7 @@ export const CartScreen = () => {
{item.skus.map((sku, index) => (
<Swipeable
key={sku.cart_item_id}
enabled={!!user_id}
renderRightActions={() => (
<TouchableOpacity
style={{
@ -647,6 +708,7 @@ export const CartScreen = () => {
item.cart_id
)
}
disabled={!user_id}
>
<Text
style={{ color: "white", fontWeight: "bold" }}
@ -658,11 +720,13 @@ export const CartScreen = () => {
>
<TouchableOpacity
onPress={() => {
if (user_id) {
navigation.navigate("ProductDetail", {
offer_id: item.offer_id,
searchKeyword: item.subject,
price: sku.price,
});
}
}}
style={[
styles.productCardContainer5,
@ -672,12 +736,13 @@ export const CartScreen = () => {
<View style={styles.svgContainer1}>
<TouchableOpacity
onPress={() =>
toggleSelection(
user_id && toggleSelection(
String(sku.cart_item_id),
index1,
index
)
}
disabled={!user_id}
>
<View style={[styles.iconContainer]}>
{sku.selected === 1 ? (
@ -753,12 +818,13 @@ export const CartScreen = () => {
{ borderRightWidth: 0 },
]}
onPress={() =>
handleDecreaseQuantity(
user_id && handleDecreaseQuantity(
item.cart_id,
sku.cart_item_id,
sku.quantity
)
}
disabled={!user_id}
>
<Text
style={{
@ -773,12 +839,13 @@ export const CartScreen = () => {
<TouchableOpacity
style={styles.quantityLabelContainer}
onPress={() =>
handleQuantityPress(
user_id && handleQuantityPress(
item.cart_id,
sku.cart_item_id,
sku.quantity
)
}
disabled={!user_id}
>
<Text style={styles.quantityText}>
{sku.quantity}
@ -790,12 +857,13 @@ export const CartScreen = () => {
{ borderLeftWidth: 0, marginLeft: 0 },
]}
onPress={() =>
handleIncreaseQuantity(
user_id && handleIncreaseQuantity(
item.cart_id,
sku.cart_item_id,
sku.quantity
)
}
disabled={!user_id}
>
<Text
style={{
@ -853,7 +921,7 @@ export const CartScreen = () => {
<View style={styles.flexboxContainerWithButton}>
<View style={styles.productInfoContainer}>
<TouchableOpacity onPress={selectAllHandel}>
<TouchableOpacity onPress={user_id ? selectAllHandel : undefined} disabled={!user_id}>
<View style={styles.svgContainer1}>
{allSelected ? (
<OrangeCircleIcon size={fontSize(24)} />
@ -872,8 +940,9 @@ export const CartScreen = () => {
<Text style={styles.priceLabel}>{currency}</Text>
</View>
<TouchableOpacity
style={styles.submitButtonStyle}
onPress={gotoOrder}
style={[styles.submitButtonStyle, !user_id && styles.disabledButton]}
onPress={user_id ? gotoOrder : undefined}
disabled={!user_id}
>
<Text
style={{
@ -888,11 +957,38 @@ export const CartScreen = () => {
</View>
</View>
</View>
{/* 未登录遮罩 */}
{!user_id && (
<View style={styles.loginOverlay}>
<View style={styles.blurContainer}>
<View style={styles.loginPromptContainer}>
<View style={styles.loginIcon}>
<Text style={styles.loginIconText}>🔒</Text>
</View>
<Text style={styles.loginPromptTitle}>
{t("cart.login_required_title", "请先登录")}
</Text>
<Text style={styles.loginPromptSubtitle}>
{t("cart.login_required_subtitle", "登录后即可使用购物车功能")}
</Text>
<TouchableOpacity
style={styles.loginButton}
onPress={goToLogin}
>
<Text style={styles.loginButtonText}>
{t("cart.login_now", "立即登录")}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
)}
</View>
</View>
<Modal
visible={deleteModalVisible}
visible={deleteModalVisible && !!user_id}
transparent
animationType="fade"
onRequestClose={cancelDelete}
@ -924,7 +1020,7 @@ export const CartScreen = () => {
{/* 数量输入弹窗 */}
<Modal
visible={quantityInputVisible}
visible={quantityInputVisible && !!user_id}
transparent
animationType="fade"
onRequestClose={() => setQuantityInputVisible(false)}
@ -1641,4 +1737,82 @@ const styles = StyleSheet.create({
flexDirection: "column",
justifyContent: "center",
},
disabledButton: {
opacity: 0.6,
},
loginOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
backdropFilter: 'blur(10px)', // iOS 毛玻璃效果
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
blurContainer: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: Platform.OS === 'android' ? 'rgba(255, 255, 255, 0.95)' : 'transparent',
},
loginPromptContainer: {
backgroundColor: 'white',
borderRadius: 20,
padding: 40,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
maxWidth: '80%',
},
loginIcon: {
width: 80,
height: 80,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(255, 81, 0, 0.1)',
borderRadius: 40,
},
loginIconText: {
fontSize: 40,
fontWeight: 'bold',
color: '#FF5100',
},
loginPromptTitle: {
fontSize: fontSize(24),
fontWeight: '700',
color: '#333',
marginBottom: 10,
textAlign: 'center',
},
loginPromptSubtitle: {
fontSize: fontSize(16),
color: '#666',
marginBottom: 30,
textAlign: 'center',
lineHeight: fontSize(22),
},
loginButton: {
backgroundColor: '#FF5100',
paddingHorizontal: 40,
paddingVertical: 15,
borderRadius: 25,
minWidth: 160,
},
loginButtonText: {
color: 'white',
fontSize: fontSize(18),
fontWeight: '700',
textAlign: 'center',
},
});

312
app/screens/ChatScreen.tsx

@ -7,7 +7,6 @@ import {
TextInput,
TouchableOpacity,
FlatList,
KeyboardAvoidingView,
Platform,
ImageBackground,
StatusBar,
@ -20,7 +19,8 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { chatService } from "../services/api/chat";
import useUserStore from "../store/user";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useTranslation } from 'react-i18next';
import { t } from "../i18n";
interface Message {
id?: string;
mimetype: string;
@ -33,31 +33,28 @@ interface Message {
isMe?: boolean;
timestamp?: Date;
}
type TabType = "customer" | "product" | "notification";
type RootStackParamList = {
Login: undefined;
// other screens...
};
export const ChatScreen = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [inputText, setInputText] = useState("");
const [activeTab, setActiveTab] = useState<TabType>("customer");
const [loginModalVisible, setLoginModalVisible] = useState(false);
const [country, setCountry] = useState<string>(""); // Store the country code
const { t } = useTranslation();
const { user } = useUserStore();
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
// Add FlatList ref for auto-scrolling
const flatListRef = useRef<FlatList>(null);
// Add animation values
const [fadeAnim] = useState(new Animated.Value(0));
const [slideAnim] = useState(new Animated.Value(300));
// Auto-scroll to bottom when messages change
useEffect(() => {
if (messages.length > 0) {
if (messages.length > 0 && user.user_id) {
setTimeout(() => {
flatListRef.current?.scrollToEnd({ animated: true });
}, 100);
@ -78,56 +75,25 @@ export const ChatScreen = () => {
}
};
// Only get country if user is logged in
if (user.user_id) {
getCountry();
}, []);
// Check if user is logged in
useEffect(() => {
if (!user.user_id) {
setLoginModalVisible(true);
// Animate modal appearance
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
Animated.spring(slideAnim, {
toValue: 0,
tension: 50,
friction: 9,
useNativeDriver: true,
})
]).start();
} else {
setLoginModalVisible(false);
}
}, [user.user_id]);
const handleCloseModal = () => {
// Animate modal exit
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: 300,
duration: 200,
useNativeDriver: true,
})
]).start(() => {
setLoginModalVisible(false);
});
};
const handleGoToLogin = () => {
handleCloseModal();
// Navigate to login after modal is closed
setTimeout(() => {
navigation.navigate('Login');
}, 200);
// 添加导航到登录页面的函数
const goToLogin = () => {
navigation.navigate("Login");
};
const sendMessage = () => {
// 如果用户未登录,直接返回
if (!user.user_id) {
return;
}
if (inputText.trim() === "") return;
const newMessage: Message = {
mimetype: "text/plain",
userWs: "unknown",
@ -140,6 +106,7 @@ export const ChatScreen = () => {
timestamp: new Date(),
id: Date.now().toString(), // Add unique id for keyExtractor
};
// Extract only the properties that chatService.sendMessage expects
const chatServiceMessage = {
type: newMessage.type,
@ -150,6 +117,7 @@ export const ChatScreen = () => {
body: newMessage.body,
text: newMessage.text
};
// Add user message to the chat UI
setMessages([...messages, newMessage]);
setInputText("");
@ -162,7 +130,7 @@ export const ChatScreen = () => {
app_id: "system",
country: user.country_code,
body: "",
text: `${t('typingMessage')}...`,
text: `${t('chat.typing_message')}...`,
type: "chat",
isMe: false,
timestamp: new Date(),
@ -177,6 +145,7 @@ export const ChatScreen = () => {
const data = {
newMessage:chatServiceMessage,
}
// Send actual message to API
chatService.sendMessage(data)
.then(response => {
@ -192,7 +161,7 @@ export const ChatScreen = () => {
app_id: "system",
country: user.country_code,
body: "",
text: response?.reply || t('defaultResponse'),
text: response?.reply || t('chat.default_response'),
type: "chat",
isMe: false,
timestamp: new Date(),
@ -214,7 +183,7 @@ export const ChatScreen = () => {
app_id: "system",
country: user.country_code,
body: "",
text: t('errorResponse'),
text: t('chat.error_response'),
type: "chat",
isMe: false,
timestamp: new Date(),
@ -225,10 +194,12 @@ export const ChatScreen = () => {
});
});
};
// Generate a unique key for each message
const keyExtractor = (item: Message, index: number): string => {
return item.id || index.toString();
};
const renderMessage = ({ item }: { item: Message }) => (
<View
style={[
@ -245,6 +216,7 @@ export const ChatScreen = () => {
</Text>
</View>
);
const renderTabContent = () => {
switch (activeTab) {
case "customer":
@ -257,6 +229,7 @@ export const ChatScreen = () => {
keyExtractor={keyExtractor}
contentContainerStyle={styles.messageList}
showsVerticalScrollIndicator={false}
scrollEnabled={!!user.user_id}
/>
</View>
);
@ -270,6 +243,7 @@ export const ChatScreen = () => {
keyExtractor={keyExtractor}
contentContainerStyle={styles.messageList}
showsVerticalScrollIndicator={false}
scrollEnabled={!!user.user_id}
/>
</View>
);
@ -283,11 +257,13 @@ export const ChatScreen = () => {
keyExtractor={keyExtractor}
contentContainerStyle={styles.messageList}
showsVerticalScrollIndicator={false}
scrollEnabled={!!user.user_id}
/>
</View>
);
}
};
return (
<SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
@ -301,7 +277,8 @@ export const ChatScreen = () => {
<View style={styles.tabBar}>
<TouchableOpacity
style={[styles.tab, activeTab === "customer" && styles.activeTab]}
onPress={() => setActiveTab("customer")}
onPress={() => user.user_id && setActiveTab("customer")}
disabled={!user.user_id}
>
<Text
style={[
@ -309,12 +286,13 @@ export const ChatScreen = () => {
activeTab === "customer" && styles.activeTabText,
]}
>
{t('customerServiceChat')}
{t('chat.customer_service')}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === "product" && styles.activeTab]}
onPress={() => setActiveTab("product")}
onPress={() => user.user_id && setActiveTab("product")}
disabled={!user.user_id}
>
<Text
style={[
@ -322,12 +300,13 @@ export const ChatScreen = () => {
activeTab === "product" && styles.activeTabText,
]}
>
{t('productChat')}
{t('chat.product_support')}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === "notification" && styles.activeTab]}
onPress={() => setActiveTab("notification")}
onPress={() => user.user_id && setActiveTab("notification")}
disabled={!user.user_id}
>
<Text
style={[
@ -335,89 +314,62 @@ export const ChatScreen = () => {
activeTab === "notification" && styles.activeTabText,
]}
>
{t('notificationChat')}
{t('chat.notifications')}
</Text>
</TouchableOpacity>
</View>
{renderTabContent()}
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
style={[styles.input, !user.user_id && styles.disabledInput]}
value={inputText}
onChangeText={setInputText}
placeholder={t('inputMessage')}
onChangeText={user.user_id ? setInputText : undefined}
placeholder={t('chat.input_message')}
multiline
editable={!!user.user_id}
/>
<TouchableOpacity style={styles.sendButton} onPress={sendMessage}>
<Text style={styles.sendButtonText}>{t('send')}</Text>
<TouchableOpacity
style={[styles.sendButton, !user.user_id && styles.disabledButton]}
onPress={user.user_id ? sendMessage : undefined}
disabled={!user.user_id}
>
<Text style={styles.sendButtonText}>{t('chat.send')}</Text>
</TouchableOpacity>
</View>
</ImageBackground>
{/* Login Modal */}
<Modal
visible={loginModalVisible}
transparent={true}
animationType="none"
onRequestClose={handleCloseModal}
>
<Animated.View
style={[
styles.modalContainer,
{
opacity: fadeAnim,
}
]}
>
<Animated.View
style={[
styles.modalOverlay,
{
opacity: fadeAnim,
}
]}
>
<TouchableOpacity
style={styles.modalOverlayTouch}
activeOpacity={1}
onPress={handleCloseModal}
/>
</Animated.View>
<Animated.View
style={[
styles.modalContent,
{
transform: [{ translateY: slideAnim }],
},
]}
>
<TouchableOpacity
style={styles.closeButton}
onPress={handleCloseModal}
>
<Text style={styles.closeButtonText}></Text>
</TouchableOpacity>
<View style={styles.modalHeader}>
<View style={styles.modalIndicator} />
{/* 未登录遮罩 */}
{!user.user_id && (
<View style={styles.loginOverlay}>
<View style={styles.blurContainer}>
<View style={styles.loginPromptContainer}>
<View style={styles.loginIcon}>
<Text style={styles.loginIconText}>💬</Text>
</View>
<Text style={styles.modalTitle}>{t('loginRequired')}</Text>
<Text style={styles.modalText}>
{t('pleaseLoginToChat')}
<Text style={styles.loginPromptTitle}>
{t("chat.login_required_title", "请先登录")}
</Text>
<Text style={styles.loginPromptSubtitle}>
{t("chat.login_required_subtitle", "登录后即可使用聊天功能")}
</Text>
<TouchableOpacity
style={styles.modalButton}
onPress={handleGoToLogin}
style={styles.loginButton}
onPress={goToLogin}
>
<Text style={styles.modalButtonText}>{t('loginNow')}</Text>
<Text style={styles.loginButtonText}>
{t("chat.login_now", "立即登录")}
</Text>
</TouchableOpacity>
</Animated.View>
</Animated.View>
</Modal>
</View>
</View>
</View>
)}
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
@ -522,75 +474,85 @@ const styles = StyleSheet.create({
color: "white",
fontSize: 16,
},
// Modal styles
modalContainer: {
flex: 1,
justifyContent: 'flex-end',
},
modalOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalOverlayTouch: {
flex: 1,
},
modalContent: {
backgroundColor: '#fff',
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
paddingHorizontal: 20,
paddingBottom: Platform.OS === 'ios' ? 40 : 20,
position: 'relative',
},
closeButton: {
loginOverlay: {
position: 'absolute',
top: 20,
right: 20,
width: 24,
height: 24,
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
backdropFilter: 'blur(10px)', // iOS 毛玻璃效果
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
},
blurContainer: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1,
backgroundColor: Platform.OS === 'android' ? 'rgba(255, 255, 255, 0.95)' : 'transparent',
},
loginPromptContainer: {
backgroundColor: 'white',
borderRadius: 20,
padding: 40,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
closeButtonText: {
fontSize: 20,
color: '#999',
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
maxWidth: '80%',
},
modalHeader: {
loginIcon: {
width: 80,
height: 80,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 12,
paddingBottom: 20,
},
modalIndicator: {
width: 40,
height: 4,
backgroundColor: '#E0E0E0',
borderRadius: 2,
},
modalTitle: {
fontSize: 20,
fontWeight: '600',
color: '#000',
backgroundColor: 'rgba(0, 122, 108, 0.1)',
borderRadius: 40,
},
loginIconText: {
fontSize: 40,
fontWeight: 'bold',
color: '#007a6c',
},
loginPromptTitle: {
fontSize: 24,
fontWeight: '700',
color: '#333',
marginBottom: 10,
textAlign: 'center',
marginBottom: 12,
},
modalText: {
loginPromptSubtitle: {
fontSize: 16,
color: '#666',
marginBottom: 30,
textAlign: 'center',
marginBottom: 24,
lineHeight: 22,
},
modalButton: {
loginButton: {
backgroundColor: '#007a6c',
height: 50,
paddingHorizontal: 40,
paddingVertical: 15,
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center',
minWidth: 160,
},
modalButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
loginButtonText: {
color: 'white',
fontSize: 18,
fontWeight: '700',
textAlign: 'center',
},
disabledInput: {
opacity: 0.6,
},
disabledButton: {
opacity: 0.6,
},
});

72
app/screens/loginList/index.tsx

@ -15,22 +15,35 @@ import { useTranslation } from "react-i18next";
import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import fontSize from "../../utils/fontsizeUtils";
import {
GoogleSignin,
GoogleSigninButton,
statusCodes,
} from '@react-native-google-signin/google-signin';
import EmailLoginModal from "./EmailLoginModal";
import PhoneLoginModal from "./PhoneLoginModal";
// 配置 Google 登录
GoogleSignin.configure({
// 条件导入Google Sign-in(仅在生产模式)
let GoogleSignin: any = null;
let GoogleSigninButton: any = null;
let statusCodes: any = null;
const isDevelopment = __DEV__; // 开发模式检测
if (!isDevelopment) {
try {
const googleSigninModule = require('@react-native-google-signin/google-signin');
GoogleSignin = googleSigninModule.GoogleSignin;
GoogleSigninButton = googleSigninModule.GoogleSigninButton;
statusCodes = googleSigninModule.statusCodes;
// 配置 Google 登录
GoogleSignin.configure({
iosClientId: "YOUR_IOS_CLIENT_ID_HERE.apps.googleusercontent.com", // iOS CLIENT_ID
webClientId: "529750832779-d0jpf2493plgm8eutkmk9e9t3rhkta8b.apps.googleusercontent.com", // Web CLIENT_ID (用于Android)
scopes: ['profile', 'email'],
offlineAccess: true,
});
forceCodeForRefreshToken: true,
});
} catch (error) {
console.log('Google Sign-in模块未安装或配置错误:', error);
}
}
type RootStackParamList = {
Login: undefined;
@ -93,6 +106,32 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
// 处理谷歌登录
const handleGoogleLogin = async () => {
try {
// 开发模式下的模拟登录
if (isDevelopment) {
console.log('开发模式:模拟Google登录成功');
const mockUserInfo = {
user: {
id: 'dev_user_123',
name: 'Test User',
email: 'test@example.com',
photo: null,
},
};
console.log('模拟用户信息:', mockUserInfo);
// 这里可以处理登录成功后的逻辑
// 比如导航到主页面或保存用户信息
// navigation.navigate("MainTabs", { screen: "Home" });
return;
}
// 生产模式下的真实Google登录
if (!GoogleSignin) {
console.log('Google Sign-in模块未配置');
return;
}
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
console.log('Google 登录成功:', userInfo);
@ -103,11 +142,18 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
} catch (error: any) {
console.log('Google 登录错误:', error);
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// 开发模式下的错误处理
if (isDevelopment) {
console.log('开发模式:忽略Google登录错误');
return;
}
if (statusCodes && error.code === statusCodes.SIGN_IN_CANCELLED) {
console.log('用户取消登录');
} else if (error.code === statusCodes.IN_PROGRESS) {
} else if (statusCodes && error.code === statusCodes.IN_PROGRESS) {
console.log('登录正在进行中');
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
} else if (statusCodes && error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
console.log('Play Services 不可用');
} else {
console.log('其他错误:', error.message);
@ -222,7 +268,7 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => {
/>
</View>
<Text style={styles.loginButtonText}>
{t("continueWithGoogle")}
{isDevelopment ? '🧪 ' + t("continueWithGoogle") + ' (测试模式)' : t("continueWithGoogle")}
</Text>
</TouchableOpacity>

Loading…
Cancel
Save