Browse Source

我的名字id

main
Mac 1 month ago
parent
commit
f346a325f2
  1. 33
      App.tsx
  2. 23
      app/components/LocationPinIcon.tsx
  3. 1
      app/navigation/types.ts
  4. 161
      app/screens/CartScreen.tsx
  5. 11
      app/screens/ChatScreen.tsx
  6. 375
      app/screens/ProductCard.tsx
  7. 753
      app/screens/ProductDetailScreen.tsx
  8. 27
      app/screens/ProfileScreen.tsx
  9. 259
      app/screens/Recipient/Recipient.tsx
  10. 293
      app/screens/pay/PaySuccess.tsx
  11. 228
      app/screens/setting/MyAddress.tsx
  12. 20
      app/screens/setting/SettingList.tsx
  13. 75
      app/services/api/orders.ts
  14. 19
      app/services/api/userApi.ts
  15. BIN
      assets/img/image_1c498f1f.jpeg
  16. BIN
      assets/img/image_637dba57.png
  17. BIN
      assets/img/image_946febdc.png
  18. BIN
      assets/img/image_b1c2e380.jpeg
  19. BIN
      assets/img/image_d9359f45.jpeg
  20. BIN
      assets/img/image_f5a80fba.jpeg

33
App.tsx

@ -22,6 +22,9 @@ import { AddRess } from "./app/screens/Recipient/Address";
import { SettingList } from "./app/screens/setting/SettingList";
import { CountrySetting } from "./app/screens/setting/CountrySetting";
import { MySetting } from "./app/services/api/setting";
import { MyAddress } from "./app/screens/setting/MyAddress";
import { CartScreen } from "./app/screens/CartScreen";
import { PaymentSuccessScreen } from "./app/screens/pay/PaySuccess";
export type RootStackParamList = {
CountrySelect: undefined;
MainApp: undefined;
@ -38,6 +41,9 @@ export type RootStackParamList = {
AddRess:undefined;
SettingList:undefined;
CountrySetting: { mySetting?: MySetting };
MyAddress:undefined;
CartScreen:undefined;
PaymentSuccessScreen:undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
@ -174,6 +180,33 @@ export default function App() {
gestureDirection: "horizontal",
}}
/>
<Stack.Screen
name="MyAddress"
component={MyAddress}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
<Stack.Screen
name="CartScreen"
component={CartScreen}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
<Stack.Screen
name="PaymentSuccessScreen"
component={PaymentSuccessScreen}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthProvider>

23
app/components/LocationPinIcon.tsx

@ -0,0 +1,23 @@
// 推荐图标
import React from 'react';
import Svg, { Path } from 'react-native-svg';
const LocationPinIcon = ({size = 20}:{size?:number}) => (
<Svg
viewBox="0 0 1024 1024"
width={size}
height={size}
>
<Path
d="M914.432 818.389333l-114.858667 16.362667-72.533333 91.733333a43.242667 43.242667 0 0 1-59.093333-15.829333l-109.226667-189.162667a339.392 339.392 0 0 1-48.021333 3.776 343.872 343.872 0 0 1-49.941334-4.053333l-109.525333 189.696a42.666667 42.666667 0 0 1-58.282667 15.616l-71.466666-90.517333-113.28-16.149334a42.666667 42.666667 0 0 1-15.616-58.282666l121.109333-209.770667a341.333333 341.333333 0 1 1 595.413333-2.368l121.130667 209.792a43.264 43.264 0 0 1-15.808 59.157333zM158.72 775.168l94.805333 5.461333 52.992 79.872 89.6-155.285333a341.525333 341.525333 0 0 1-141.589333-96zM510.741333 106.666667a277.333333 277.333333 0 1 0 277.333334 277.333333 277.333333 277.333333 0 0 0-277.333334-277.333333zM768 608a341.333333 341.333333 0 0 1-143.488 97.6l88.917333 154.026667 53.76-81.066667 96.128-5.546667z"
fill="#d81e06"
/>
<Path
d="M512 576a192 192 0 1 1 192-192 192 192 0 0 1-192 192z m-0.661333-320.661333a128 128 0 1 0 128 128 128 128 0 0 0-128-128z"
fill="#d81e06"
/>
</Svg>
);
export default LocationPinIcon;

1
app/navigation/types.ts

@ -11,4 +11,5 @@ export type RootStackParamList = {
Recipient: undefined;
SettingList: undefined;
CountrySetting: { mySetting?: MySetting | undefined };
MyAddress: undefined;
};

161
app/screens/CartScreen.tsx

@ -6,7 +6,6 @@ import {
StyleSheet,
TouchableOpacity,
ScrollView,
Alert,
Modal,
} from "react-native";
import BackIcon from "../components/BackIcon";
@ -23,7 +22,8 @@ import { Swipeable } from "react-native-gesture-handler";
import OrangeCircleIcon from "../components/OrangeCircleIcon";
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useFocusEffect } from "@react-navigation/native";
import { useCallback } from "react";
export const CartScreen = () => {
const [cartList, setCartList] = useState<GetCartList[]>([]);
const [selectedItems, setSelectedItems] = useState<{
@ -39,11 +39,11 @@ export const CartScreen = () => {
const navigation = useNavigation<NativeStackNavigationProp<any>>();
const changeAllSelected = () => {
const allSkusSelected = cartList.every(item =>
item.skus.every(sku => sku.selected === 1)
const allSkusSelected = cartList.every((item) =>
item.skus.every((sku) => sku.selected === 1)
);
setAllSelected(!allSkusSelected);
}
};
const toggleSelection = async (
cartItemId: string,
index1: number,
@ -94,16 +94,16 @@ export const CartScreen = () => {
}
// 检查所有商品的 skus 数组中的 selected 属性是否都为 1
changeAllSelected()
changeAllSelected();
};
const getCart = async () => {
const res = await getCartList();
setCartList(res.items);
// 检查所有商品的 skus 数组中的 selected 属性是否都为 1
const allSkusSelected = res.items.every(item =>
item.skus.every(sku => sku.selected === 1)
const allSkusSelected = res.items.every((item) =>
item.skus.every((sku) => sku.selected === 1)
);
setAllSelected(allSkusSelected);
};
@ -112,9 +112,13 @@ export const CartScreen = () => {
setAllSelected(!allSelected);
setCartList((prev) => {
return prev.map((item) => {
return { ...item, selected: allSelected ? 0 : 1,skus:item.skus.map((sku)=>{
return { ...sku, selected: allSelected ? 0 : 1 };
}) };
return {
...item,
selected: allSelected ? 0 : 1,
skus: item.skus.map((sku) => {
return { ...sku, selected: allSelected ? 0 : 1 };
}),
};
});
});
};
@ -132,7 +136,7 @@ export const CartScreen = () => {
const confirmDelete = () => {
if (itemToDelete) {
const { cartId, cartItemId, cartId1 } = itemToDelete;
console.log(itemToDelete);
setCartList((prev) => {
// 先找到要删除的商品
const itemToUpdate = prev.find((item) => item.cart_id === cartId);
@ -162,8 +166,11 @@ export const CartScreen = () => {
});
}
});
deleteCartItem(cartId, cartItemId).then((res) => {
console.log(res);
});
}
// 关闭确认对话框
setDeleteModalVisible(false);
setItemToDelete(null);
@ -175,9 +182,31 @@ export const CartScreen = () => {
setItemToDelete(null);
};
useEffect(() => {
getCart();
}, []);
// useEffect(() => {
// getCart();
// }, []);
useFocusEffect(
useCallback(() => {
getCart();
}, [])
);
const gotoOrder = () => {
const items:{cart_item_id:number}[] = []
cartList.forEach((item) => {
item.skus.forEach((sku) => {
if (sku.selected === 1) {
if (sku.cart_item_id) {
items.push({
cart_item_id: sku.cart_item_id,
})
}
}
});
});
navigation.navigate("Recipient",{items});
};
return (
<View style={styles.shoppingCartLayout}>
<ScrollView
@ -259,14 +288,9 @@ export const CartScreen = () => {
)
}
>
<View
style={[
styles.iconContainer,
]}
>
<View style={[styles.iconContainer]}>
{sku.selected === 1 ? (
<OrangeCircleIcon size={fontSize(24)} />
<OrangeCircleIcon size={fontSize(24)} />
) : (
<CircleOutlineIcon size={fontSize(24)} />
)}
@ -385,9 +409,10 @@ export const CartScreen = () => {
<Text style={styles.highlightedText1}>24928</Text>
<Text style={styles.priceLabel}>FCFA</Text>
</View>
<TouchableOpacity style={styles.submitButtonStyle} onPress={()=>{
navigation.navigate("Recipient")
}}>
<TouchableOpacity
style={styles.submitButtonStyle}
onPress={gotoOrder}
>
<Text
style={{
color: "white",
@ -403,31 +428,35 @@ export const CartScreen = () => {
</View>
<Modal
visible={deleteModalVisible}
transparent
animationType="fade"
onRequestClose={cancelDelete}
>
<View style={styles.overlay}>
<View style={styles.popup}>
{/* <Image
visible={deleteModalVisible}
transparent
animationType="fade"
onRequestClose={cancelDelete}
>
<View style={styles.overlay}>
<View style={styles.popup}>
{/* <Image
source={require("../assets/image_5f59afb0.png")} // 替换成你实际的路径
style={styles.image}
/> */}
<Text style={styles.promptText}>Supprimer l'article ?</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.cancelButton1} onPress={cancelDelete}>
<Text style={styles.cancelText}>Non</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.confirmButton} onPress={confirmDelete}>
<Text style={styles.confirmText}>Oui</Text>
</TouchableOpacity>
<Text style={styles.promptText}>Supprimer l'article ?</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.cancelButton1}
onPress={cancelDelete}
>
<Text style={styles.cancelText}>Non</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.confirmButton}
onPress={confirmDelete}
>
<Text style={styles.confirmText}>Oui</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</Modal>
</Modal>
</View>
);
};
@ -928,54 +957,54 @@ const styles = StyleSheet.create({
},
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "center",
alignItems: "center",
},
modalContent: {
backgroundColor: 'white',
backgroundColor: "white",
borderRadius: 10,
padding: 20,
width: '80%',
alignItems: 'center',
width: "80%",
alignItems: "center",
},
modalTitle: {
fontSize: fontSize(18),
fontWeight: 'bold',
fontWeight: "bold",
marginBottom: 10,
},
modalMessage: {
fontSize: fontSize(16),
marginBottom: 20,
textAlign: 'center',
textAlign: "center",
},
modalButtons: {
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
},
modalButton: {
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 5,
width: '45%',
alignItems: 'center',
width: "45%",
alignItems: "center",
},
cancelButton: {
backgroundColor: '#f0f0f0',
backgroundColor: "#f0f0f0",
},
deleteButton: {
backgroundColor: '#ff5217',
backgroundColor: "#ff5217",
},
cancelButtonText: {
color: '#333',
color: "#333",
fontSize: fontSize(16),
fontWeight: '500',
fontWeight: "500",
},
deleteButtonText: {
color: 'white',
color: "white",
fontSize: fontSize(16),
fontWeight: '500',
fontWeight: "500",
},
overlay: {
flex: 1,

11
app/screens/ChatScreen.tsx

@ -32,6 +32,17 @@ export const ChatScreen = () => {
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
<View style={styles.container}>
<Text style={styles.text}>Chat Screen</Text>
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate("PaymentSuccessScreen")}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
</View>
);
};

375
app/screens/ProductCard.tsx

@ -6,6 +6,7 @@ import {
StyleSheet,
TouchableOpacity,
ScrollView,
Modal
} from "react-native";
import widthUtils from "../utils/widthUtils";
import fontSize from "../utils/fontsizeUtils";
@ -18,6 +19,8 @@ import {
} from "../services/api/productApi";
import { cartApi } from "../services/api/cart";
import { useState } from "react";
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
interface ProductCardProps {
onClose: () => void;
product: ProductDetailParams;
@ -33,6 +36,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
product: ProductDetailParams;
groupList: ProductGroupList[];
}) => {
const navigation = useNavigation<NativeStackNavigationProp<any>>();
const [groupList, setGroupList] =
useState<ProductGroupList[]>(localGroupList);
const [product, setProduct] = useState<ProductDetailParams>(localProduct);
@ -46,6 +50,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
const [selectedSize, setSelectedSize] = useState<number>(0);
const [totalPrice, setTotalPrice] = useState<number>(0);
const [flag, setFlag] = useState<boolean>(false);
const [deleteModalVisible, setDeleteModalVisible] = useState<boolean>(false);
useEffect(() => {
setPrice(product.price as number);
const imageItem = groupList.filter((item) => item.has_image);
@ -107,6 +112,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
}
});
});
console.log(processedImg);
setHasImg(processedImg);
} else {
@ -127,7 +133,6 @@ const ProductCard: React.FC<ProductCardProps> = ({
const handleColorSelect = (colorId: string, index: number) => {
if (!hasImg) return;
// setPrice(price ?? 0);
// 创建attributes的深拷贝
@ -156,7 +161,12 @@ const ProductCard: React.FC<ProductCardProps> = ({
});
};
const handleSizeSelect = (value: string, type: string, index: number) => {
const handleSizeSelect = (
value: string,
type: string,
index: number,
amount_on_sale: number
) => {
if (!hasImg) return;
const data = hasImg.attributes.find((item) => item.has_color);
@ -192,8 +202,18 @@ const ProductCard: React.FC<ProductCardProps> = ({
if (type === "+") {
newHasImg.attributes[colorIndex].size =
(newHasImg.attributes[colorIndex].size ?? 0) + 1;
if (newHasImg.attributes[colorIndex].size > amount_on_sale) {
newHasImg.attributes[colorIndex].size = amount_on_sale;
}
newHasImg.attributes[colorIndex].list[index].size =
(newHasImg.attributes[colorIndex].list[index].size ?? 0) + 1;
if (
newHasImg.attributes[colorIndex].list[index].size > amount_on_sale
) {
newHasImg.attributes[colorIndex].list[index].size =
amount_on_sale;
}
} else {
newHasImg.attributes[colorIndex].size =
(newHasImg.attributes[colorIndex].size ?? 0) - 1;
@ -220,13 +240,17 @@ const ProductCard: React.FC<ProductCardProps> = ({
const handleNoImgSizeSelect = (
value: string,
type: string,
index: number
index: number,
amount_on_sale: number
) => {
if (!noImgList) return;
console.log(noImgList);
if (type === "+") {
noImgList[index].size = (noImgList[index].size ?? 0) + 1;
if (noImgList[index].size > amount_on_sale) {
noImgList[index].size = amount_on_sale;
}
} else {
noImgList[index].size = (noImgList[index].size ?? 0) - 1;
}
@ -238,11 +262,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
let total = 0;
let priceSum = 0;
console.log(product);
noImgList.forEach((item) => {
console.log(item.size);
console.log(item.offer_price);
total += item.size ?? 0;
priceSum +=
(item.offer_price ??
@ -318,6 +338,12 @@ const ProductCard: React.FC<ProductCardProps> = ({
console.log(res);
});
}
setDeleteModalVisible(true);
};
const cancelDelete = () => {
// 关闭确认对话框
setDeleteModalVisible(false);
};
return (
<View style={styles.container}>
@ -485,7 +511,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
x{list?.size}
</Text>
)}
<Text
<Text
style={styles.sizeText}
numberOfLines={1}
ellipsizeMode="tail"
@ -504,7 +530,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
handleSizeSelect(
list?.attributes[0]?.value,
"-",
index1
index1,
list.amount_on_sale
)
}
>
@ -518,7 +545,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
handleSizeSelect(
list?.attributes[0]?.value,
"+",
index1
index1,
list.amount_on_sale
)
}
>
@ -533,6 +561,114 @@ const ProductCard: React.FC<ProductCardProps> = ({
</View>
)}
{hasImg && groupList.length > 1 && !hasImg.has_image && (
<View style={{ flex: 1 }}>
<Text style={styles.primaryTextHeading}>
{hasImg?.attribute_name}: {size}
</Text>
<View style={styles.specifications}>
<ScrollView
style={styles.sizeList}
showsHorizontalScrollIndicator={false}
>
{hasImg.attributes.map((item, index) => (
<TouchableOpacity
key={item.value}
style={[
styles.sizeItem,
item.has_color && styles.sizeActiveItem,
]}
onPress={() => handleColorSelect(item.value, index)}
>
<View style={styles.fixedCornerView}>
{item.size && (
<Text style={styles.fixedCornerText}>
x{item.size}
</Text>
)}
</View>
<Text
style={item.has_color && styles.sizeActiveItemText}
>
{item.value}
</Text>
</TouchableOpacity>
))}
<View>
<Text style={styles.sizeTitle}>{sizeTitle}</Text>
<View>
{hasImg.attributes
.find((item) => item.has_color)
?.list.map((list, index1) => (
<View
key={list?.sku_id}
style={styles.specificationsItem}
>
<View>
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
{(list?.size ?? 0) > 0 && (
<Text style={styles.selectedNumText}>
x{list?.size}
</Text>
)}
<Text
style={styles.sizeText}
numberOfLines={1}
ellipsizeMode="tail"
>
{list?.attributes[0]?.value}
</Text>
</View>
<Text style={styles.amountText}>
{list?.amount_on_sale}
</Text>
</View>
<View style={styles.numberContainer}>
<TouchableOpacity
onPress={() =>
handleSizeSelect(
list?.attributes[0]?.value,
"-",
index1,
list.amount_on_sale
)
}
>
<Text>-</Text>
</TouchableOpacity>
<Text style={styles.numText}>
{list?.size ?? 0}
</Text>
<TouchableOpacity
onPress={() =>
handleSizeSelect(
list?.attributes[0]?.value,
"+",
index1,
list.amount_on_sale
)
}
>
<Text>+</Text>
</TouchableOpacity>
</View>
</View>
))}
</View>
</View>
</ScrollView>
</View>
</View>
)}
{noImgList && groupList.length === 1 && !flag && (
<View style={styles.specifications}>
<Text style={styles.primaryTextHeading}>
@ -563,7 +699,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
x{list?.size}
</Text>
)}
<Text
<Text
style={styles.sizeText}
numberOfLines={1}
ellipsizeMode="tail"
@ -582,7 +718,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
handleNoImgSizeSelect(
list?.attributes[0]?.value,
"-",
index1
index1,
list.amount_on_sale
)
}
>
@ -594,7 +731,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
handleNoImgSizeSelect(
list?.attributes[0]?.value,
"+",
index1
index1,
list.amount_on_sale
)
}
>
@ -634,8 +772,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
>
<View
style={{
width: 50,
height: 50,
width: widthUtils(50,50).width,
height: widthUtils(50,50).height,
borderRadius: 5,
marginRight: 10,
}}
@ -645,8 +783,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
uri: list?.attributes[0]?.sku_image_url,
}}
style={{
width: 50,
height: 50,
width: widthUtils(50,50).width,
height: widthUtils(50,50).height,
borderRadius: 5,
}}
/>
@ -658,7 +796,6 @@ const ProductCard: React.FC<ProductCardProps> = ({
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
width: 250,
}}
>
{(list?.size ?? 0) > 0 && (
@ -666,7 +803,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
x{list?.size}
</Text>
)}
<Text
<Text
style={styles.sizeText}
numberOfLines={1}
ellipsizeMode="tail"
@ -689,7 +826,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
handleNoImgSizeSelect(
list?.attributes[0]?.value,
"-",
index1
index1,
list.amount_on_sale
)
}
>
@ -701,7 +839,8 @@ const ProductCard: React.FC<ProductCardProps> = ({
handleNoImgSizeSelect(
list?.attributes[0]?.value,
"+",
index1
index1,
list.amount_on_sale
)
}
>
@ -744,6 +883,31 @@ const ProductCard: React.FC<ProductCardProps> = ({
</TouchableOpacity>
</View>
</View>
<Modal
visible={deleteModalVisible}
transparent
animationType="fade"
onRequestClose={cancelDelete}
>
<View style={styles.overlay}>
<View style={styles.popup}>
{/* <Image
source={require("../assets/image_5f59afb0.png")} // 替换成你实际的路径
style={styles.image}
/> */}
<Text style={styles.promptText}>Supprimer l'article ?</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.cancelButton1} onPress={cancelDelete}>
<Text style={styles.cancelText}>Non</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.confirmButton} onPress={() => navigation.navigate("MainTabs", { screen: "Cart" })}>
<Text style={styles.confirmText}>Voir le panier</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</View>
);
};
@ -830,7 +994,7 @@ const styles = StyleSheet.create({
paddingBottom: 14,
margin: 0,
fontWeight: "700",
fontSize: 14,
fontSize: fontSize(14),
fontFamily: "Segoe UI",
color: "#ff5100",
},
@ -845,7 +1009,7 @@ const styles = StyleSheet.create({
margin: 0,
fontStyle: "italic",
fontWeight: "900",
fontSize: 11,
fontSize: fontSize(11),
fontFamily: "Segoe UI",
color: "#4e2000",
},
@ -856,7 +1020,7 @@ const styles = StyleSheet.create({
marginLeft: 1.25,
},
vipButton: {
width: 55,
width: widthUtils(55,55).width,
minWidth: 55,
height: 21,
marginLeft: -2.5,
@ -868,7 +1032,7 @@ const styles = StyleSheet.create({
italicBoldSegoeVip: {
fontStyle: "italic",
fontWeight: "900",
fontSize: 14,
fontSize: fontSize(14),
fontFamily: "Segoe UI",
},
pricingCardContainer: {
@ -923,8 +1087,8 @@ const styles = StyleSheet.create({
padding: 0,
margin: 0,
fontWeight: "600",
fontSize: 16,
lineHeight: 20,
fontSize: fontSize(16),
lineHeight: fontSize(20),
fontFamily: "Segoe UI",
color: "black",
},
@ -935,8 +1099,8 @@ const styles = StyleSheet.create({
flexDirection: "column",
alignItems: "stretch",
justifyContent: "flex-start",
width: 90,
height: 116,
width: widthUtils(116,116).width,
height: widthUtils(116,116).height,
paddingBottom: 5,
backgroundColor: "#f4f4f4",
borderRadius: 5,
@ -946,8 +1110,8 @@ const styles = StyleSheet.create({
alignItems: "flex-start",
justifyContent: "flex-start",
borderRadius: 5,
width: 90,
height: 90,
width: widthUtils(90,90).width,
height: widthUtils(90,90).height,
},
imageContainer: {
width: "100%",
@ -963,9 +1127,9 @@ const styles = StyleSheet.create({
fontWeight: "400",
fontFamily: "Segoe UI",
color: "black",
height: 26,
textAlign: "center",
width: 90,
width: widthUtils(90,26).width,
height: widthUtils(90,26).height,
fontSize: fontSize(18),
},
closeIconContainer: {
@ -982,8 +1146,8 @@ const styles = StyleSheet.create({
position: "absolute",
top: -6,
left: -1,
width: 30,
height: 16,
width: widthUtils(16,30).width,
height: widthUtils(16,30).height,
borderRadius: 8,
backgroundColor: "#ff5323",
zIndex: 1,
@ -992,16 +1156,42 @@ const styles = StyleSheet.create({
},
topLeftBadgeText: {
color: "white",
fontSize: 10,
fontSize: fontSize(10),
fontWeight: "400",
fontFamily: "Segoe UI",
textAlign: "center",
lineHeight: 16,
lineHeight: fontSize(16),
},
specifications: {
width: "100%",
flex: 1,
paddingVertical: 20,
paddingVertical: 10,
},
sizeList: {
gap: 5,
},
sizeItem: {
paddingHorizontal: 10,
paddingVertical: 8,
borderRadius: 5,
marginTop: 10,
backgroundColor: "#efefef",
},
sizeActiveItem: {
backgroundColor: "#ff5323",
color: "#ffffff",
},
sizeActiveItemText: {
color: "#ffffff",
},
sizeTitle: {
marginTop: 5,
padding: 0,
margin: 0,
fontWeight: "600",
fontSize: fontSize(16),
lineHeight: fontSize(20),
fontFamily: "Segoe UI",
color: "black",
},
specificationsList: {
marginTop: 10,
@ -1013,27 +1203,27 @@ const styles = StyleSheet.create({
alignItems: "center",
},
sizeText: {
fontSize: 16,
fontSize: fontSize(16),
fontWeight: "600",
fontFamily: "Segoe UI",
color: "black",
width: "100%",
width: widthUtils(200,200).width,
},
selectedNumText: {
width: 30,
height: 16,
width: widthUtils(16,30).width,
height: widthUtils(16,30).height,
backgroundColor: "#ff5217",
borderRadius: 5,
color: "white",
textAlign: "center",
fontSize: 12,
fontSize: fontSize(12),
fontWeight: "600",
fontFamily: "Segoe UI",
marginRight: 5,
lineHeight: 18,
},
amountText: {
fontSize: 16,
fontSize: fontSize(16),
fontWeight: "600",
fontFamily: "Segoe UI",
color: "#bdbdbd",
@ -1043,16 +1233,16 @@ const styles = StyleSheet.create({
alignItems: "center",
},
numText: {
width: 40,
height: 24,
width: widthUtils(24,40).width,
height: widthUtils(24,40).height,
textAlign: "center",
fontSize: 16,
fontSize: fontSize(16),
fontWeight: "600",
fontFamily: "Segoe UI",
color: "black",
backgroundColor: "#f4f4f4",
borderRadius: 5,
lineHeight: 28,
lineHeight: fontSize(28),
marginHorizontal: 5,
},
bottomFixedContainer: {
@ -1060,7 +1250,7 @@ const styles = StyleSheet.create({
bottom: 0,
left: 0,
width: "100%",
height: 118,
height: widthUtils(118,118).height,
backgroundColor: "white",
borderTopWidth: 1,
borderTopColor: "#f4f4f4",
@ -1080,25 +1270,104 @@ const styles = StyleSheet.create({
paddingBottom: 10,
},
bottomFixedContainerText: {
fontSize: 14,
fontSize: fontSize(14),
fontWeight: "600",
fontFamily: "PingFang SC",
color: "black",
},
bottomFixedContainer2: {
width: "100%",
height: 40,
height: widthUtils(40,40).height,
backgroundColor: "#ff5217",
borderRadius: 25,
alignItems: "center",
justifyContent: "center",
},
bottomFixedContainerButtonText: {
fontSize: 14,
fontSize: fontSize(14),
fontWeight: "600",
fontFamily: "PingFang SC",
color: "white",
},
fixedCornerView: {
position: "absolute",
top: -8,
left: 0,
width: widthUtils(16,30).width,
height: widthUtils(16,30).height,
zIndex: 10,
borderRadius: 5,
},
fixedCornerText: {
fontSize: fontSize(12),
fontWeight: "600",
fontFamily: "Segoe UI",
color: "white",
textAlign: "center",
lineHeight: fontSize(18),
backgroundColor: "#ff5217",
borderRadius: 5,
},
overlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.4)",
justifyContent: "center",
alignItems: "center",
},
popup: {
backgroundColor: "white",
borderRadius: 10,
paddingVertical: 27,
paddingHorizontal: 20,
alignItems: "center",
gap: 21,
},
image: {
width: widthUtils(80,80).width,
height: widthUtils(80,80).height,
borderRadius: 5,
resizeMode: "cover",
},
promptText: {
fontSize: fontSize(20),
fontWeight: "600",
color: "black",
fontFamily: "Segoe UI", // 注意要在项目中配置字体
},
buttonContainer: {
flexDirection: "row",
justifyContent: "center",
marginTop: 10,
},
cancelButton1: {
width: widthUtils(50,160).width,
height: widthUtils(50,160).height,
borderRadius: 25,
backgroundColor: "#f2f3f5",
justifyContent: "center",
alignItems: "center",
},
confirmButton: {
width: widthUtils(50,160).width,
height: widthUtils(50,160).height,
borderRadius: 25,
backgroundColor: "#002fa7",
justifyContent: "center",
alignItems: "center",
marginLeft: 20,
},
cancelText: {
fontSize: fontSize(16),
fontWeight: "500",
color: "#333333",
fontFamily: "Source Han Sans CN", // 注意要在项目中配置字体
},
confirmText: {
fontSize: fontSize(16),
fontWeight: "500",
color: "#ffffff",
fontFamily: "Source Han Sans CN", // 同上
},
});
export default ProductCard;

753
app/screens/ProductDetailScreen.tsx

@ -10,6 +10,7 @@ import {
useWindowDimensions,
Animated,
BackHandler,
ActivityIndicator,
} from "react-native";
import fontSize from "../utils/fontsizeUtils";
import widthUtils from "../utils/widthUtils";
@ -31,7 +32,6 @@ import { RouteProp } from "@react-navigation/native";
import BackIcon from "../components/BackIcon";
import CameraIcon from "../components/CameraIcon";
import ProductCard from "./ProductCard";
import CloseIcon from "../components/CloseIcon";
type ProductDetailRouteParams = {
offer_id: string;
searchKeyword?: string;
@ -53,6 +53,7 @@ export const ProductDetailScreen = () => {
const [product, setProduct] = useState<ProductDetailParams>();
const [groupList, setGroupList] = useState<ProductGroupList[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [similars, setSimilars] = useState<Similars>();
const [isSimilarsFlag, setIsSimilarsFlag] = useState(false);
@ -234,7 +235,7 @@ export const ProductDetailScreen = () => {
if(item.offer_price){
price = item.offer_price
}else{
price = product.sale_info.price_range_list[product.sale_info.price_range_list.length - 1].price
price = product?.sale_info?.price_range_list[product?.sale_info?.price_range_list?.length - 1]?.price
}
}
})
@ -248,44 +249,50 @@ export const ProductDetailScreen = () => {
const getProductDetail = async () => {
if (!route.params?.offer_id) return;
const res = await productApi.getProductDetail(route.params.offer_id);
if (res.skus != null) {
const priceSelectedSku = res.skus.find(
(item) => item.offer_price === route.params.price
);
setIsLoading(true);
try {
const res = await productApi.getProductDetail(route.params.offer_id);
if (res.skus != null) {
const priceSelectedSku = res.skus.find(
(item) => item.offer_price === route.params.price
);
if (priceSelectedSku) {
res.price = priceSelectedSku.offer_price;
} else {
res.price = res?.sale_info?.price_range_list[res?.sale_info?.price_range_list?.length - 1]?.price;
}
}else{
res.price = route.params.price
}
if (res.skus != null) {
const priceSelectedSku = res.skus.find(item => item.offer_price === route.params.price)
if (priceSelectedSku) {
res.price = priceSelectedSku.offer_price;
} else {
res.price = res.sale_info.price_range_list[res.sale_info.price_range_list.length - 1]?.price;
setPriceSelectedSku({
price:route.params.price,
})
}
}
setPriceSelectedSku(priceSelectedSku)
setProduct(res);
let list:ProductGroupList[] = []
if(res.skus != null){
list = groupData(res,priceSelectedSku?.attributes as SkuAttribute[]);
}else{
list = []
}
}else{
res.price = route.params.price
}
if (res.skus != null) {
const priceSelectedSku = res.skus.find(item => item.offer_price === route.params.price)
if (priceSelectedSku) {
setPriceSelectedSku({
price:route.params.price,
})
}
}
setPriceSelectedSku(priceSelectedSku)
setProduct(res);
let list:ProductGroupList[] = []
if(res.skus != null){
list = groupData(res,priceSelectedSku?.attributes as SkuAttribute[]);
}else{
list = []
}
const imageUrls = [];
const regex = /<img[^>]+src="([^"]+)"/g;
let match;
while ((match = regex.exec(res.description)) !== null) {
imageUrls.push(match[1]); // match[1] 是 src 属性的值
const imageUrls = [];
const regex = /<img[^>]+src="([^"]+)"/g;
let match;
while ((match = regex.exec(res.description)) !== null) {
imageUrls.push(match[1]); // match[1] 是 src 属性的值
}
setImageUrls(imageUrls);
setGroupList(list);
} catch (error) {
console.error("Error fetching product details:", error);
} finally {
setIsLoading(false);
}
setImageUrls(imageUrls);
setGroupList(list);
};
const getSimilars = () => {
@ -323,364 +330,373 @@ export const ProductDetailScreen = () => {
return (
<View style={{ flex: 1 }}>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewContent}
>
<View style={styles.productDetailsContainer3}>
<View style={styles.timeCardContainer}>
<View style={styles.searchContainer1}>
<TouchableOpacity onPress={() => {
if (showBottomSheet) {
setShowBottomSheet(false);
} else {
navigation.goBack();
}
}}>
<View style={styles.svgContainer}>
<BackIcon size={fontSize(20)}/>
</View>
</TouchableOpacity>
<View style={styles.searchContainer2}>
<Text style={styles.searchTitleTextStyle}>Recherche</Text>
<View style={styles.svgContainer1}>
<CameraIcon color="#373737" size={fontSize(18)}/>
{/* Replace with your SVG component or icon */}
</View>
</View>
<View style={styles.searchContainer}>
<View style={styles.svgContainer1}>
{/* Replace with your SVG component or icon */}
</View>
<View style={styles.svgContainer2}>
{/* Replace with your SVG component or icon */}
{isLoading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#FF5100" />
<Text style={styles.loadingText}>Loading product details...</Text>
</View>
) : (
<>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewContent}
>
<View style={styles.productDetailsContainer3}>
<View style={styles.timeCardContainer}>
<View style={styles.searchContainer1}>
<TouchableOpacity onPress={() => {
if (showBottomSheet) {
setShowBottomSheet(false);
} else {
navigation.goBack();
}
}}>
<View style={styles.svgContainer}>
<BackIcon size={fontSize(20)}/>
</View>
</TouchableOpacity>
<View style={styles.searchContainer2}>
<Text style={styles.searchTitleTextStyle}>Recherche</Text>
<View style={styles.svgContainer1}>
<CameraIcon color="#373737" size={fontSize(18)}/>
{/* Replace with your SVG component or icon */}
</View>
</View>
<View style={styles.searchContainer}>
<View style={styles.svgContainer1}>
{/* Replace with your SVG component or icon */}
</View>
<View style={styles.svgContainer2}>
{/* Replace with your SVG component or icon */}
</View>
</View>
</View>
</View>
</View>
</View>
<View style={styles.centerColumnWithPagination}>
<View style={{ position: "relative" }}>
<Carousel
loop
width={screenWidth}
data={product?.product_image_urls as string[]}
height={widthUtils(430, 430).height}
onSnapToItem={(index) => setActiveIndex(index)}
modeConfig={{
parallaxScrollingScale: 0.9,
parallaxScrollingOffset: 50,
}}
renderItem={({ item }) => (
<View style={styles.centerColumnWithPagination}>
<View style={{ position: "relative" }}>
<Carousel
loop
width={screenWidth}
data={product?.product_image_urls as string[]}
height={widthUtils(430, 430).height}
onSnapToItem={(index) => setActiveIndex(index)}
modeConfig={{
parallaxScrollingScale: 0.9,
parallaxScrollingOffset: 50,
}}
renderItem={({ item }) => (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#f2f2f2",
borderRadius: 10,
}}
>
<Image
source={{ uri: item }}
style={{ width: "100%", height: "100%" }}
/>
</View>
)}
/>
{/* 底部指示灯 - 固定在右下角 */}
<View
style={{
flex: 1,
position: "absolute",
bottom: 20,
right: 20,
backgroundColor: "gray", // 可选,看得清楚
borderRadius: 13,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#f2f2f2",
borderRadius: 10,
}}
>
<Image
source={{ uri: item }}
style={{ width: "100%", height: "100%" }}
/>
<Text style={styles.activeIndexText}>
{activeIndex + 1}/{product?.product_image_urls.length}
</Text>
</View>
)}
/>
{/* 底部指示灯 - 固定在右下角 */}
<View
style={{
position: "absolute",
bottom: 20,
right: 20,
backgroundColor: "gray", // 可选,看得清楚
borderRadius: 13,
justifyContent: "center",
alignItems: "center",
}}
>
<Text style={styles.activeIndexText}>
{activeIndex + 1}/{product?.product_image_urls.length}
</Text>
</View>
</View>
</View>
</View>
<View style={styles.productDetailsContainer2}>
<View style={styles.nightLampProductCard}>
<Text style={styles.creativeHeading}>{product?.subject}</Text>
<View style={styles.productInfoContainer1}>
<View style={styles.productInfoContainer}>
<View style={styles.salesInfoContainer}>
<Text style={styles.highlightedText}>{product?.price}</Text>
<Text style={styles.orangeHighlightedText}>FCFA</Text>
<View style={styles.priceBox}>
<Text style={styles.priceLabel}>3000FCFA</Text>
<View style={styles.priceContainer}></View>
</View>
</View>
<View style={styles.productDetailsContainer2}>
<View style={styles.nightLampProductCard}>
<Text style={styles.creativeHeading}>{product?.subject}</Text>
<View style={styles.productInfoContainer1}>
<View style={styles.productInfoContainer}>
<View style={styles.salesInfoContainer}>
<Text style={styles.highlightedText}>{product?.price}</Text>
<Text style={styles.orangeHighlightedText}>FCFA</Text>
<View style={styles.priceBox}>
<Text style={styles.priceLabel}>3000FCFA</Text>
<View style={styles.priceContainer}></View>
</View>
</View>
<Text style={styles.salesCountLabel}>
{product?.sold_out} ventes
</Text>
</View>
<Text style={styles.salesCountLabel}>
{product?.sold_out} ventes
</Text>
</View>
<View style={styles.discountInfoContainer}>
<Text style={styles.emphasizedTextWidget}>-5%</Text>
<View style={styles.svgContainer4}>
{/* Replace with your SVG component or icon */}
</View>
</View>
<View style={styles.specialHighlightedSection}>
<View style={styles.svgContainer5}>
{/* Replace with your SVG component or icon */}
</View>
</View>
<View style={styles.vipStatusContainer}>
<View style={styles.vipBadgeContainer}>
<Text style={styles.vipStatusNumeric}>
<Text style={styles.emphasizedTextVip}>VIP</Text>
<Text style={styles.emphasizedText}> 1</Text>
</Text>
<View style={styles.discountInfoContainer}>
<Text style={styles.emphasizedTextWidget}>-5%</Text>
<View style={styles.svgContainer4}>
{/* Replace with your SVG component or icon */}
</View>
</View>
<View style={styles.specialHighlightedSection}>
<View style={styles.svgContainer5}>
{/* Replace with your SVG component or icon */}
</View>
</View>
<View style={styles.vipStatusContainer}>
<View style={styles.vipBadgeContainer}>
<Text style={styles.vipStatusNumeric}>
<Text style={styles.emphasizedTextVip}>VIP</Text>
<Text style={styles.emphasizedText}> 1</Text>
</Text>
</View>
</View>
</View>
</View>
</View>
</View>
<View style={styles.productDetailsContainer}>
<View style={styles.productDetailsContainer1}>
<View style={styles.blackThemeContainer}>
{groupList.map((item, index) =>
item.has_image ? (
<View key={item.attribute_name}>
<Text style={styles.uniqueTextBlock}>
{item.attribute_name} : {item.attributes.find(item => item.has_color)?.value}
</Text>
<View style={styles.horizontalFlexContainer}>
{getDisplayAttributes(
item.attributes,
item.attribute_name
).map((attribute) => (
<TouchableOpacity
key={attribute.value}
onPress={() =>
handleColorSelect(attribute.value, index)
}
style={[
styles.colorImageContainer,
attribute.has_color &&
styles.selectedColorImageContainer,
]}
>
<Image
source={{ uri: attribute.sku_image_url }}
style={styles.imageContainer}
/>
</TouchableOpacity>
))}
<View style={styles.productDetailsContainer}>
<View style={styles.productDetailsContainer1}>
<View style={styles.blackThemeContainer}>
{groupList.map((item, index) =>
item.has_image ? (
<View key={item.attribute_name}>
<Text style={styles.uniqueTextBlock}>
{item.attribute_name} : {item.attributes.find(item => item.has_color)?.value}
</Text>
<View style={styles.horizontalFlexContainer}>
{getDisplayAttributes(
item.attributes,
item.attribute_name
).map((attribute) => (
<TouchableOpacity
key={attribute.value}
onPress={() =>
handleColorSelect(attribute.value, index)
}
style={[
styles.colorImageContainer,
attribute.has_color &&
styles.selectedColorImageContainer,
]}
>
<Image
source={{ uri: attribute.sku_image_url }}
style={styles.imageContainer}
/>
</TouchableOpacity>
))}
{!expandedGroups[item.attribute_name] &&
item.attributes.length > 6 && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
}
>
<Text style={styles.expandButtonText}>
</Text>
</TouchableOpacity>
)}
{!expandedGroups[item.attribute_name] &&
item.attributes.length > 6 && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
}
>
<Text style={styles.expandButtonText}>
</Text>
</TouchableOpacity>
)}
{expandedGroups[item.attribute_name] && (
<TouchableOpacity
style={styles.expandButton}
onPress={() => toggleExpand(item.attribute_name)}
>
<Text style={styles.expandButtonText}></Text>
{expandedGroups[item.attribute_name] && (
<TouchableOpacity
style={styles.expandButton}
onPress={() => toggleExpand(item.attribute_name)}
>
<Text style={styles.expandButtonText}></Text>
</TouchableOpacity>
)}
</View>
{
groupList.length > 1 && index === 0 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
</View>
</TouchableOpacity>
)}
</View>
{
groupList.length > 1 && index === 0 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
</View>
</TouchableOpacity>
)
}
{
groupList.length === 1 && index === 0 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
)
}
{
groupList.length === 1 && index === 0 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
</View>
</TouchableOpacity>
)
}
</View>
</TouchableOpacity>
)
}
</View>
) : (
<View key={item.attribute_name}>
<Text style={styles.uniqueTextBlock}>
{item.attribute_name}
</Text>
) : (
<View key={item.attribute_name}>
<Text style={styles.uniqueTextBlock}>
{item.attribute_name}
</Text>
<View style={styles.horizontalFlexContainer}>
{getDisplayAttributes(
item.attributes,
item.attribute_name
).map((attribute) => (
<TouchableOpacity
key={attribute.value}
onPress={() =>
handleSizeSelect(attribute.value, index)
}
style={[
styles.sizeButton,
attribute.has_color &&
styles.selectedSizeButton,
]}
>
<Text
style={[
styles.sizeButtonText,
attribute.has_color &&
styles.selectedSizeText,
]}
>
{attribute.value}
</Text>
</TouchableOpacity>
))}
<View style={styles.horizontalFlexContainer}>
{getDisplayAttributes(
item.attributes,
item.attribute_name
).map((attribute) => (
<TouchableOpacity
key={attribute.value}
onPress={() =>
handleSizeSelect(attribute.value, index)
}
style={[
styles.sizeButton,
attribute.has_color &&
styles.selectedSizeButton,
]}
>
<Text
style={[
styles.sizeButtonText,
attribute.has_color &&
styles.selectedSizeText,
]}
>
{attribute.value}
</Text>
</TouchableOpacity>
))}
{!expandedGroups[item.attribute_name] &&
item.attributes.length > 6 && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
}
>
<Text style={styles.expandButtonText}>
</Text>
</TouchableOpacity>
)}
{!expandedGroups[item.attribute_name] &&
item.attributes.length > 6 && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
}
>
<Text style={styles.expandButtonText}>
</Text>
</TouchableOpacity>
)}
{expandedGroups[item.attribute_name] && (
<TouchableOpacity
style={styles.expandButton}
onPress={() => toggleExpand(item.attribute_name)}
>
<Text style={styles.expandButtonText}></Text>
{expandedGroups[item.attribute_name] && (
<TouchableOpacity
style={styles.expandButton}
onPress={() => toggleExpand(item.attribute_name)}
>
<Text style={styles.expandButtonText}></Text>
</TouchableOpacity>
)}
</View>
{
groupList.length === 1 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
</View>
</TouchableOpacity>
)}
</View>
{
groupList.length === 1 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
</View>
</TouchableOpacity>
)
}
)
}
{
groupList.length === 2 && index === 0 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
{
groupList.length === 2 && index === 0 && (
<TouchableOpacity style={styles.svgContainer6} onPress={() => setShowBottomSheet(true)}>
<View style={styles.svgContainer6}>
<DiagonalArrowIcon size={fontSize(18)}/>
</View>
</TouchableOpacity>
)
}
</View>
</TouchableOpacity>
)
}
</View>
)
)}
)
)}
</View>
</View>
</View>
</View>
</View>
<View style={styles.storeRecommendationsContainer}>
<View style={styles.storeInfoContainer}>
<Text style={styles.storeSectionTitle}>
More from this store
</Text>
<Text style={styles.storeMoreLink}>View All</Text>
</View>
<View style={styles.storeRecommendationsContainer}>
<View style={styles.storeInfoContainer}>
<Text style={styles.storeSectionTitle}>
More from this store
</Text>
<Text style={styles.storeMoreLink}>View All</Text>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.productGridContainer}
>
{isSimilarsFlag &&
similars?.map((item) => (
<View style={styles.productCard} key={item.offer_id}>
<View style={styles.cardContainerWithPrice}>
<Image
source={{ uri: item.product_image_urls[0] }}
style={styles.imageContainerCompact}
/>
</View>
<View style={styles.priceContainerFlex}>
<Text style={styles.highlightedText1}>
{item.max_price}
</Text>
<Text style={styles.highlightedTextWithBorder}>
FCFA
</Text>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.productGridContainer}
>
{isSimilarsFlag &&
similars?.map((item) => (
<View style={styles.productCard} key={item.offer_id}>
<View style={styles.cardContainerWithPrice}>
<Image
source={{ uri: item.product_image_urls[0] }}
style={styles.imageContainerCompact}
/>
</View>
<View style={styles.priceContainerFlex}>
<Text style={styles.highlightedText1}>
{item.max_price}
</Text>
<Text style={styles.highlightedTextWithBorder}>
FCFA
</Text>
</View>
</View>
))}
</ScrollView>
</View>
<View style={{ width: "100%" }}>
{imageUrls.map((src, index) => (
<View key={index} style={{ width: "100%" }}>
<Image
style={{
width: "100%",
height: imageHeights[src] || 200,
backgroundColor: "#f5f5f5",
}}
source={{ uri: src }}
resizeMode="contain"
onLoad={(event) => handleImageLoad(src, event)}
/>
</View>
))}
</ScrollView>
</View>
<View style={{ width: "100%" }}>
{imageUrls.map((src, index) => (
<View key={index} style={{ width: "100%" }}>
<Image
style={{
width: "100%",
height: imageHeights[src] || 200,
backgroundColor: "#f5f5f5",
}}
source={{ uri: src }}
resizeMode="contain"
onLoad={(event) => handleImageLoad(src, event)}
/>
</View>
))}
</View>
</View>
</ScrollView>
<View style={styles.fixedBottomBar}>
<TouchableOpacity style={styles.chatNowButton}>
<WhiteCircleIcon color="#fff" size={fontSize(20)} />
<Text style={styles.chatNowText}>chatNow</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.addToCartButton}
// onPress={() => setShowBottomSheet(true)}
>
<ShoppingCartIcon color="#fff" size={fontSize(20)} />
<Text style={styles.addToCartText}>addToCart</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
<View style={styles.fixedBottomBar}>
<TouchableOpacity style={styles.chatNowButton}>
<WhiteCircleIcon color="#fff" size={fontSize(20)} />
<Text style={styles.chatNowText}>chatNow</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.addToCartButton}
// onPress={() => setShowBottomSheet(true)}
>
<ShoppingCartIcon color="#fff" size={fontSize(20)} />
<Text style={styles.addToCartText}>addToCart</Text>
</TouchableOpacity>
</View>
{showBottomSheet && product && (
<View style={styles.bottomSheetOverlay}>
<Animated.View
style={[
styles.bottomSheet,
]}
>
<ProductCard onClose={() => setShowBottomSheet(false)} product={product} groupList={groupList}></ProductCard>
</Animated.View>
</View>
{showBottomSheet && product && (
<View style={styles.bottomSheetOverlay}>
<Animated.View
style={[
styles.bottomSheet,
]}
>
<ProductCard onClose={() => setShowBottomSheet(false)} product={product} groupList={groupList}></ProductCard>
</Animated.View>
</View>
)}
</>
)}
</View>
);
@ -1344,4 +1360,15 @@ const styles = StyleSheet.create({
height: "85%",
zIndex: 1,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
loadingText: {
marginTop: 10,
fontSize: fontSize(16),
color: '#666',
},
});

27
app/screens/ProfileScreen.tsx

@ -1,12 +1,15 @@
import React from "react";
import React, {useState, useEffect,useCallback} from "react";
import { View, Text, Image, StyleSheet, ImageBackground,TouchableOpacity } from "react-native";
import SettingsIcon from "../components/SettingsIcon";
import fontSize from "../utils/fontsizeUtils";
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { userApi } from '../services/api/userApi';
import { userApi ,User} from '../services/api/userApi';
import { settingApi } from '../services/api/setting';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useFocusEffect } from '@react-navigation/native';
import { flagMap } from '../utils/flagMap';
type RootStackParamList = {
SettingList: undefined;
Home: undefined;
@ -32,6 +35,18 @@ export const ProfileScreen = () => {
}
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const [userInfo, setUserInfo] = useState<User | null>(null);
const getUserInfo = async () => {
const res = await userApi.getProfile();
console.log(res);
setUserInfo(res);
}
useFocusEffect(
useCallback(() => {
getUserInfo();
}, [])
);
return (
<View style={styles.flexColumnContainer1}>
<ImageBackground
@ -48,8 +63,8 @@ export const ProfileScreen = () => {
/>
<View style={styles.customerInfoPanel}>
<View>
<Text style={styles.uniqueHeaderTextStyle}>Bellael</Text>
<Text style={styles.elegantText}>ID: 11</Text>
<Text style={styles.uniqueHeaderTextStyle}>{userInfo?.username}</Text>
<Text style={styles.elegantText}>ID: {userInfo?.user_id}</Text>
</View>
<View style={styles.transactionSummaryBox}>
<View style={styles.svgContainer}>
@ -61,11 +76,11 @@ export const ProfileScreen = () => {
</View>
<View style={styles.flexRowWithContent}>
<Image
source={require("../../assets/img/image_d9d8fe1f.png")}
source={flagMap.get(userInfo?.country_en)}
style={styles.imageContainerWithText}
/>
<View style={styles.financialInfoContainer}>
<Text style={styles.whiteTextHeading}>FCFA</Text>
<Text style={styles.whiteTextHeading}>{userInfo?.currency}</Text>
<TouchableOpacity onPress={() => navigation.navigate("SettingList")}>
<View style={styles.svgContainer1}>
<SettingsIcon size={fontSize(24)} color="white" />

259
app/screens/Recipient/Recipient.tsx

@ -6,9 +6,9 @@ import {
TouchableOpacity,
ScrollView,
Modal,
Switch,
StyleSheet,
Platform,
Image,
} from "react-native";
import { Picker } from "@react-native-picker/picker";
import AddressIcon from "../../components/AddressIcon";
@ -24,9 +24,14 @@ import {
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { eventBus } from "../../utils/eventBus";
export function Recipient() {
import LocationPinIcon from "../../components/LocationPinIcon";
import fontSize from "../../utils/fontsizeUtils";
import { ordersApi,OrderData } from "../../services/api/orders";
export function Recipient({ route }:{route:{params:{items:{cart_item_id:number}[]}}}) {
const navigation = useNavigation<NativeStackNavigationProp<any>>();
const [showModal, setShowModal] = useState(false);
const [orderData, setOrderData] = useState<OrderData>();
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
@ -36,7 +41,7 @@ export function Recipient() {
setDefault: false,
});
const [shippingMethod, setShippingMethod] = useState(null);
const [shippingMethod, setShippingMethod] = useState('sea');
const [warehouse, setWarehouse] = useState("");
const [arrival, setArrival] = useState("-");
const [shippingFee, setShippingFee] = useState("-");
@ -53,9 +58,19 @@ export function Recipient() {
const response = await addressApi.getAddress();
setAddressList(response.items);
};
const getOrders = async () => {
const params = route.params;
const data = {
"items": params.items,
}
const response = await ordersApi.getOrders(data);
setOrderData(response);
};
useEffect(() => {
getAddress();
getAddressList();
getOrders();
const listener = (data: any) => {
if (data.type === "add") {
data.address_id = new Date().getTime();
@ -207,35 +222,7 @@ export function Recipient() {
}
};
const [expanded, setExpanded] = useState(false);
const [orderItems] = useState([
{
id: 1,
name: "Wireless Bluetooth Speaker Portable Outdoor Mini",
variant: "Color: Black | Size: Medium",
quantity: 1,
price: 29.99,
shipping: 8.5,
image: null,
},
{
id: 2,
name: "Wireless Charging Pad for iPhone and Android Devices",
variant: "Color: White",
quantity: 2,
price: 31.98,
shipping: 5.0,
image: null,
},
{
id: 3,
name: "Kitchen Multifunctional Food Slicer with 8 Blades",
variant: "Type: Stainless Steel",
quantity: 1,
price: 34.5,
shipping: 12.0,
image: null,
},
]);
const toggleExpanded = () => {
setExpanded(!expanded);
@ -269,14 +256,14 @@ export function Recipient() {
<AddressIcon size={25} />
</View>
<View style={styles.recipientInfoText}>
<Text style={{ fontSize: 16, color: "#a0a5ab" }}>
<Text style={{ fontSize: fontSize(16), color: "#a0a5ab" }}>
{defaultAddress?.receiver_first_name} .{" "}
{defaultAddress?.receiver_last_name}
</Text>
<Text style={{ fontSize: 20, paddingVertical: 4 }}>
<Text style={{ fontSize: fontSize(20), paddingVertical: 4 }}>
{defaultAddress?.country}
</Text>
<Text style={{ fontSize: 16, color: "#a0a5ab" }}>
<Text style={{ fontSize: fontSize(16), color: "#a0a5ab" }}>
{defaultAddress?.receiver_phone}
</Text>
</View>
@ -305,7 +292,7 @@ export function Recipient() {
detail: "Economical",
},
{ id: "air", label: "Air Shipping", icon: "✈", detail: "Express" },
].map((option) => (
].map((option, index) => (
<TouchableOpacity
key={option.id}
style={[
@ -314,9 +301,14 @@ export function Recipient() {
]}
onPress={() => {
setShippingMethod(option.id);
updateShippingInfo(option.id, warehouse); // 👈 这就是"再加一句"的地方
updateShippingInfo(option.id, warehouse);
}}
>
{index === 0 && (
<View style={styles.locationPin}>
<LocationPinIcon size={20} />
</View>
)}
<Text style={styles.shippingIcon}>{option.icon}</Text>
<Text style={styles.shippingLabel}>{option.label}</Text>
<Text style={styles.shippingDetail}>{option.detail}</Text>
@ -469,7 +461,7 @@ export function Recipient() {
</View>
<View style={styles.setOrderContent}>
<Text style={styles.noCouponsMessage}>Products(3 items)</Text>
<Text style={styles.noCouponsMessage}>Products({orderData?.items.length} items)</Text>
<TouchableOpacity onPress={toggleExpanded}>
<Text style={styles.sectionAction}>
{expanded ? "Hide Details" : "View Details"}
@ -479,11 +471,11 @@ export function Recipient() {
<View
style={[styles.orderItems, expanded && styles.orderItemsExpanded]}
>
{orderItems.map((item) => (
<View key={item.id} style={styles.orderItem}>
{item.image ? (
{orderData?.items.map((item) => (
<View key={item.sku_id} style={styles.orderItem}>
{item.sku_image_url ? (
<Image
source={{ uri: item.image }}
source={{ uri: item.sku_image_url }}
style={styles.itemImage}
/>
) : (
@ -492,16 +484,21 @@ export function Recipient() {
<View style={styles.itemDetails}>
<Text style={styles.itemName} numberOfLines={2}>
{item.name}
{item.product_name}
</Text>
<Text style={styles.itemVariant}>{item.variant}</Text>
{item.attributes.map((attribute) => (
<Text style={styles.itemVariant} key={attribute?.value} numberOfLines={1}>
{attribute?.attribute_name}: {attribute?.value}
</Text>
))}
<Text style={styles.itemQuantity}>Qty: {item.quantity}</Text>
</View>
<View style={styles.itemPrices}>
<Text style={styles.itemPrice}>${item.price.toFixed(2)}</Text>
<Text style={styles.itemPrice}>${item?.total_price}</Text>
<Text style={styles.itemShipping}>
+${item.shipping.toFixed(2)} domestic
{/* +${item?.shipping.toFixed(2)} domestic */}
</Text>
<Text style={styles.shippingNote}>
Supplier to warehouse shipping
@ -548,7 +545,7 @@ export function Recipient() {
<View style={styles.priceBox}>
<View style={styles.priceBox1}>
<Text>Subtotal</Text>
<Text>$231.00</Text>
<Text>{orderData?.total_amount}</Text>
</View>
<View style={styles.priceBox1}>
<Text>Domestic Shipping</Text>
@ -562,18 +559,15 @@ export function Recipient() {
</View>
{/* 实际支付金额 */}
{actualPayment && (
<View style={styles.actualPaymentBox}>
<Text style={{ fontSize: 13, color: "#666" }}>
Actual Payment:
</Text>
<Text
style={{ fontSize: 16, fontWeight: "600", color: "#ff6000" }}
>
{actualPayment}
</Text>
<View style={styles.actualPaymentBox1}>
<Text style={{fontSize:fontSize(18),fontWeight:"600",color:"#000"}}>Total</Text>
<Text style={{fontSize:fontSize(18),fontWeight:"600",color:"#ff6000"}}>{orderData?.total_amount}</Text>
</View>
<View style={styles.remarks}>
<Text style={styles.remarksText}>1</Text>
</View>
</View>
)}
{/* Coupon Modal */}
<Modal
@ -813,8 +807,8 @@ const styles = StyleSheet.create({
backgroundColor: "#fff",
elevation: 3,
},
back: { fontSize: 20, marginRight: 16 },
title: { fontSize: 18, fontWeight: "500", flex: 1, textAlign: "center" },
back: { fontSize: fontSize(20), marginRight: 16 },
title: { fontSize: fontSize(18), fontWeight: "500", flex: 1, textAlign: "center" },
section: {
backgroundColor: "#fff",
@ -827,9 +821,9 @@ const styles = StyleSheet.create({
paddingTop: 12,
paddingBottom: 12,
},
sectionIcon: { marginRight: 8, fontSize: 18 },
sectionTitle: { flex: 1, fontSize: 15, fontWeight: "500" },
sectionAction: { color: "#ff6000", fontSize: 13, fontWeight: "500" },
sectionIcon: { marginRight: 8, fontSize: fontSize(18) },
sectionTitle: { flex: 1, fontSize: fontSize(15), fontWeight: "500" },
sectionAction: { color: "#ff6000", fontSize: fontSize(13), fontWeight: "500" },
recipientInfo: {
backgroundColor: "#fff",
borderRadius: 8,
@ -847,7 +841,7 @@ const styles = StyleSheet.create({
},
recipientInfoText: {
flex: 1,
fontSize: 18,
fontSize: fontSize(18),
},
addRecipient: {
@ -860,8 +854,8 @@ const styles = StyleSheet.create({
alignItems: "center",
backgroundColor: "#fafafa",
},
addRecipientIcon: { fontSize: 20, color: "#ff6000", marginRight: 6 },
addRecipientText: { fontSize: 14, color: "#666" },
addRecipientIcon: { fontSize: fontSize(20), color: "#ff6000", marginRight: 6 },
addRecipientText: { fontSize: fontSize(14), color: "#666" },
shippingOptions: {
flexDirection: "row",
@ -879,12 +873,12 @@ const styles = StyleSheet.create({
backgroundColor: "#fff",
},
shippingCardSelected: { borderColor: "#ff6000", backgroundColor: "#fff8f3" },
shippingIcon: { fontSize: 22, marginBottom: 6 },
shippingLabel: { fontSize: 14, fontWeight: "500" },
shippingDetail: { fontSize: 12, color: "#888", marginTop: 3 },
shippingIcon: { fontSize: fontSize(22), marginBottom: 6 },
shippingLabel: { fontSize: fontSize(14), fontWeight: "500" },
shippingDetail: { fontSize: fontSize(12), color: "#888", marginTop: 3 },
selectBox: { marginBottom: 12 },
selectLabel: { fontSize: 14, marginBottom: 6, color: "#666" },
selectLabel: { fontSize: fontSize(14), marginBottom: 6, color: "#666" },
selectWrapper: {
borderWidth: 1,
borderColor: "#ddd",
@ -898,7 +892,7 @@ const styles = StyleSheet.create({
backgroundColor: "#f9f9f9",
borderRadius: 6,
},
shippingInfoRow: { fontSize: 13, marginBottom: 6, color: "#333" },
shippingInfoRow: { fontSize: fontSize(13), marginBottom: 6, color: "#333" },
shippingInfoLabel: { color: "#777", fontWeight: "500" },
modalOverlay: {
@ -919,23 +913,23 @@ const styles = StyleSheet.create({
alignItems: "center",
padding: 16,
},
closeButton: { fontSize: 20, color: "#555" },
formTitle: { flex: 1, fontSize: 16, fontWeight: "500", textAlign: "center" },
closeButton: { fontSize: fontSize(20), color: "#555" },
formTitle: { flex: 1, fontSize: fontSize(16), fontWeight: "500", textAlign: "center" },
formBody: { padding: 16 },
formGroup: { marginBottom: 16 },
formLabel: { marginBottom: 6, fontSize: 14, color: "#666" },
formLabel: { marginBottom: 6, fontSize: fontSize(14), color: "#666" },
input: {
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 6,
padding: 12,
fontSize: 14,
fontSize: fontSize(14),
backgroundColor: "#fff",
},
checkboxRow: { flexDirection: "row", alignItems: "center", marginTop: 8 },
checkboxLabel: { marginLeft: 8, fontSize: 13, color: "#666" },
checkboxLabel: { marginLeft: 8, fontSize: fontSize(13), color: "#666" },
switchRow: {
flexDirection: "row",
justifyContent: "space-between",
@ -948,7 +942,7 @@ const styles = StyleSheet.create({
confirmText: {
color: "#fff",
textAlign: "center",
fontSize: 15,
fontSize: fontSize(15),
fontWeight: "500",
},
paymentOption: {
@ -964,8 +958,8 @@ const styles = StyleSheet.create({
borderColor: "#ff6000",
backgroundColor: "#fff8f3",
},
paymentIcon: { fontSize: 20, marginRight: 10 },
paymentLabel: { fontSize: 14, fontWeight: "500" },
paymentIcon: { fontSize: fontSize(20), marginRight: 10 },
paymentLabel: { fontSize: fontSize(14), fontWeight: "500" },
mobileForm: { marginTop: 12 },
countryCode: {
@ -993,12 +987,25 @@ const styles = StyleSheet.create({
},
actualPaymentBox: {
marginTop: 16,
padding: 12,
backgroundColor: "#f9f9f9",
borderRadius: 6,
backgroundColor:"#fff8f4"
},
actualPaymentBox1: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
remarks: {
marginTop:6,
},
remarksText:{
color:"#666",
fontSize:fontSize(16),
fontWeight:"500",
},
section1: {
backgroundColor: "#fff",
@ -1025,7 +1032,7 @@ const styles = StyleSheet.create({
borderBottomColor: "#f5f5f5",
},
sectionIcon1: {
fontSize: 18,
fontSize: fontSize(18),
marginRight: 10,
color: "#666",
},
@ -1037,13 +1044,13 @@ const styles = StyleSheet.create({
paddingBottom: 12,
},
sectionTitle1: {
fontSize: 15,
fontSize: fontSize(15),
fontWeight: "500",
flex: 1,
},
sectionAction1: {
color: "#ff6000",
fontSize: 13,
fontSize: fontSize(13),
fontWeight: "500",
},
sectionContent: {
@ -1052,7 +1059,7 @@ const styles = StyleSheet.create({
},
noCouponsMessage: {
color: "#888",
fontSize: 13,
fontSize: fontSize(13),
marginBottom: 10,
},
@ -1077,15 +1084,15 @@ const styles = StyleSheet.create({
color: "#ff6000",
fontWeight: "500",
marginRight: 8,
fontSize: 13,
fontSize: fontSize(13),
},
couponTagDiscount: {
color: "#ff6000",
fontSize: 13,
fontSize: fontSize(13),
},
couponDelete: {
color: "#777",
fontSize: 16,
fontSize: fontSize(16),
marginLeft: 8,
},
@ -1112,11 +1119,11 @@ const styles = StyleSheet.create({
borderBottomColor: "#f0f0f0",
},
couponModalTitle: {
fontSize: 16,
fontSize: fontSize(16),
fontWeight: "500",
},
couponModalClose: {
fontSize: 20,
fontSize: fontSize(20),
color: "#777",
},
couponModalBody: {
@ -1144,15 +1151,15 @@ const styles = StyleSheet.create({
couponName: {
fontWeight: "500",
color: "#ff6000",
fontSize: 15,
fontSize: fontSize(15),
marginBottom: 3,
},
couponDiscount: {
fontSize: 13,
fontSize: fontSize(13),
color: "#666",
},
couponExpiry: {
fontSize: 11,
fontSize: fontSize(11),
color: "#999",
marginTop: 4,
},
@ -1169,7 +1176,7 @@ const styles = StyleSheet.create({
},
couponUseBtnText: {
color: "white",
fontSize: 13,
fontSize: fontSize(13),
fontWeight: "500",
},
orderItems: {
@ -1186,16 +1193,16 @@ const styles = StyleSheet.create({
borderBottomColor: "#f5f5f5",
},
itemImage: {
width: 70,
height: 70,
width: widthUtils(70,70).width,
height: widthUtils(70,70).height,
borderRadius: 6,
marginRight: 12,
borderWidth: 1,
borderColor: "#eee",
},
itemImagePlaceholder: {
width: 70,
height: 70,
width: widthUtils(70,70).width,
height: widthUtils(70,70).height,
borderRadius: 6,
marginRight: 12,
borderWidth: 1,
@ -1206,11 +1213,11 @@ const styles = StyleSheet.create({
flex: 1,
},
itemName: {
fontSize: 14,
fontSize: fontSize(14),
lineHeight: 18,
},
itemVariant: {
fontSize: 12,
fontSize: fontSize(12),
color: "#666",
backgroundColor: "#f7f7f7",
paddingVertical: 3,
@ -1220,27 +1227,27 @@ const styles = StyleSheet.create({
alignSelf: "flex-start",
},
itemQuantity: {
fontSize: 12,
fontSize: fontSize(12),
color: "#666",
marginTop: 4,
},
itemPrices: {
alignItems: "flex-end",
fontSize: 13,
fontSize: fontSize(13),
color: "#555",
},
itemPrice: {
fontWeight: "600",
color: "#ff6000",
fontSize: 15,
fontSize: fontSize(15),
marginBottom: 5,
},
itemShipping: {
fontSize: 12,
fontSize: fontSize(12),
color: "#777",
},
shippingNote: {
fontSize: 11,
fontSize: fontSize(11),
color: "#888",
marginTop: 2,
fontStyle: "italic",
@ -1271,8 +1278,8 @@ const styles = StyleSheet.create({
},
recipientSelectionTitle: {
alignSelf: "center",
fontSize: 20,
lineHeight: 22,
fontSize: fontSize(20),
lineHeight: fontSize(22),
fontFamily: "PingFang SC",
fontWeight: "600",
color: "black",
@ -1314,16 +1321,16 @@ const styles = StyleSheet.create({
marginRight: 8,
},
userCardInfo: {
fontSize: 18,
lineHeight: 22,
fontSize: fontSize(18),
lineHeight: fontSize(22),
fontFamily: "PingFang SC",
fontWeight: "500",
color: "black",
flex: 1,
},
userCardInfo1: {
fontSize: 18,
lineHeight: 22,
fontSize: fontSize(18),
lineHeight: fontSize(22),
fontFamily: "PingFang SC",
fontWeight: "500",
color: "#6b7280",
@ -1343,14 +1350,14 @@ const styles = StyleSheet.create({
borderRadius: 5,
},
blueHeadingTextStyle: {
fontSize: 13,
fontSize: fontSize(13),
fontFamily: "PingFang SC",
fontWeight: "500",
color: "#002fa7",
},
svgContainer: {
width: 24,
height: 24,
width: widthUtils(24,24).width,
height: widthUtils(24,24).height,
color: "#0051ff",
marginLeft: "auto",
},
@ -1395,8 +1402,8 @@ const styles = StyleSheet.create({
addCardRecipientText: {
width: "100%",
textAlign: "center",
fontSize: 16,
lineHeight: 22,
fontSize: fontSize(16),
lineHeight: fontSize(22),
fontFamily: "PingFang SC",
fontWeight: "500",
alignItems: "center",
@ -1421,8 +1428,8 @@ const styles = StyleSheet.create({
justifyContent: "center",
alignItems: "center",
fontFamily: "Source Han Sans CN",
fontSize: 16,
lineHeight: 22,
fontSize: fontSize(16),
lineHeight: fontSize(22),
fontWeight: "500",
color: "#333333",
backgroundColor: "#f2f3f5",
@ -1434,8 +1441,8 @@ const styles = StyleSheet.create({
justifyContent: "center",
alignItems: "center",
fontFamily: "Source Han Sans CN",
fontSize: 16,
lineHeight: 22,
fontSize: fontSize(16),
lineHeight: fontSize(22),
fontWeight: "500",
color: "white",
backgroundColor: "#002fa7",
@ -1443,16 +1450,22 @@ const styles = StyleSheet.create({
},
cancelButtonText: {
fontFamily: "Source Han Sans CN",
fontSize: 16,
lineHeight: 22,
fontSize: fontSize(16),
lineHeight: fontSize(22),
fontWeight: "500",
color: "#333333",
},
confirmButtonText: {
fontFamily: "Source Han Sans CN",
fontSize: 16,
lineHeight: 22,
fontSize: fontSize(16),
lineHeight: fontSize(22),
fontWeight: "500",
color: "white",
},
locationPin: {
position: "absolute",
top: 8,
right: 8,
zIndex: 1,
},
});

293
app/screens/pay/PaySuccess.tsx

@ -0,0 +1,293 @@
import React from "react";
import {
View,
Text,
Image,
TouchableOpacity,
StyleSheet,
ScrollView,
} from "react-native";
export const PaymentSuccessScreen = () => {
return (
<ScrollView style={styles.container}>
<View style={styles.header}>
<View style={styles.headerSuccess}>
<View style={styles.headerSuccessImg}></View>
<View style={styles.headerSuccessText}>
<Text style={styles.headerSuccessTextTitle}></Text>
</View>
<View style={styles.headerPriceText}>
<Text style={styles.headerPriceTextTitle}>73800FCFA</Text>
</View>
</View>
<View style={styles.headerSuccessInfo}>
<View style={styles.headerSuccessInfoItem}>
<Text style={styles.headerSuccessInfoItemText}></Text>
<Text style={styles.headerSuccessInfoItemText1}>17088752341</Text>
</View>
<View style={styles.headerSuccessInfoItem1}>
<Text style={styles.headerSuccessInfoItemText}></Text>
<Text style={styles.headerSuccessInfoItemText1}></Text>
</View>
</View>
<View style={styles.headerSuccessInfoItem2}>
<Text></Text>
</View>
<View style={styles.button}>
<View style={styles.buttonItem}>
<Text style={styles.buttonText}></Text>
</View>
<View style={styles.buttonItem}>
<Text style={styles.buttonText}></Text>
</View>
</View>
</View>
<View style={styles.recommend}>
<Text style={styles.footerItemText1}>1234567890</Text>
<View style={styles.productContainer}>
<View style={styles.productRow}>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥199</Text>
</View>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥299</Text>
</View>
</View>
<View style={styles.productRow}>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥399</Text>
</View>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥499</Text>
</View>
</View>
<View style={styles.productRow}>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥599</Text>
</View>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥699</Text>
</View>
</View>
<View style={styles.productRow}>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥799</Text>
</View>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥899</Text>
</View>
</View>
<View style={styles.productRow}>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥999</Text>
</View>
<View style={styles.productItem}>
<View style={styles.productImageContainer}>
<View style={styles.productImage} />
</View>
<Text style={styles.productName}></Text>
<Text style={styles.productPrice}>¥1099</Text>
</View>
</View>
</View>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f1f5f9",
},
header: {
padding: 30,
backgroundColor: "white",
},
headerSuccess: {
width: "100%",
marginBottom: 5,
},
headerSuccessImg: {
width: 60,
height: 60,
alignSelf: "center",
},
headerSuccessText: {
fontSize: 20,
color: "#000000",
alignSelf: "center",
marginTop: 10,
},
headerSuccessTextTitle: {
fontSize: 16,
color: "#000000",
alignSelf: "center",
},
headerPriceText: {
fontSize: 20,
color: "#000000",
alignSelf: "center",
},
headerPriceTextTitle: {
fontSize: 20,
color: "#000000",
alignSelf: "center",
marginTop: 10,
},
headerSuccessInfo: {
width: "100%",
backgroundColor: "#f0f6ff",
borderRadius: 5,
borderWidth: 1,
borderColor: "#dafcff",
marginTop: 10,
padding: 10,
},
headerSuccessInfoItem: {
flexDirection: "row",
fontSize: 16,
color: "#0046bf",
fontWeight: "600",
alignItems: "center",
},
headerSuccessInfoItem1: {
flexDirection: "row",
fontSize: 16,
color: "#0046bf",
fontWeight: "600",
alignItems: "center",
marginTop: 5,
},
headerSuccessInfoItemText: {
fontSize: 16,
color: "#0046bf",
width: "20%",
fontWeight: "600",
alignItems: "center",
},
headerSuccessInfoItemText1: {
fontSize: 16,
color: "#0046bf",
marginLeft: 10,
fontWeight: "600",
alignItems: "center",
},
headerSuccessInfoItem2: {
marginTop: 5,
flexDirection: "row",
},
button: {
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 20,
paddingRight: 20,
marginTop: 20,
},
buttonItem: {
width: "40%",
height: 40,
backgroundColor: "#0030a7",
borderRadius: 20,
justifyContent: "center",
alignItems: "center",
},
buttonText: {
fontSize: 16,
color: "white",
},
recommend: {
padding: 20,
},
footerItemText1: {
fontSize: 16,
color: "#000000",
fontWeight: "600",
marginBottom: 15,
},
productContainer: {
marginTop: 10,
},
productRow: {
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 15,
},
productItem: {
width: "48%",
backgroundColor: "white",
borderRadius: 8,
padding: 10,
},
productImageContainer: {
alignItems: "center",
marginBottom: 8,
width: "100%",
},
productImage: {
width: "100%",
height: 120,
backgroundColor: "#f1f5f9",
borderRadius: 4,
},
productName: {
fontSize: 14,
color: "#333",
marginTop: 5,
marginBottom: 5,
},
productPrice: {
fontSize: 16,
color: "#E53935",
fontWeight: "bold",
},
});

228
app/screens/setting/MyAddress.tsx

@ -0,0 +1,228 @@
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
} from "react-native";
import BackIcon from "../../components/BackIcon";
import FileEditIcon from "../../components/FileEditIcon";
import { useNavigation } from "@react-navigation/native";
import { addressApi, AddressItem, } from "../../services/api/addressApi";
import { useState ,useEffect,useCallback} from "react";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useFocusEffect } from '@react-navigation/native';
import fontSize from "../../utils/fontsizeUtils";
import widthUtils from "../../utils/widthUtils";
export function MyAddress() {
const navigation = useNavigation<NativeStackNavigationProp<any>>();
const [addressList, setAddressList] = useState<AddressItem[]>();
const [address, setAddress] = useState<number>();
const getAddress = async () => {
const response = await addressApi.getAddress();
setAddressList(response.items);
};
useEffect(() => {
getAddress();
}, []);
const deleteAddress = async (address_id: number) => {
setAddressList(addressList?.filter((item) => item.address_id !== address_id));
addressApi.deleteAddress(address_id)
}
useFocusEffect(
useCallback(() => {
getAddress();
}, [])
);
const setAddressId = (address_id: number) => {
setAddress(address_id);
}
return (
<View style={styles.container}>
<View style={styles.header}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<BackIcon />
</TouchableOpacity>
</View>
<ScrollView style={{flex:1}} showsVerticalScrollIndicator={false}>
{
addressList?.map((item) => (
<TouchableOpacity
key={item.address_id}
onPress={() => {
setAddressId(item.address_id);
}}
>
<View
style={[
styles.userCardContainer,
item.address_id === address ? styles.addressItemSelected : styles.addressItemNoSelected
]}
>
<View style={styles.userInfoCard}>
<View style={styles.userCardInfo2}>
<Text
style={styles.userCardInfo}
numberOfLines={1}
ellipsizeMode="tail"
>
{item.receiver_first_name} .{" "}
{item.receiver_last_name}
</Text>
<Text
style={styles.userCardInfo1}
numberOfLines={1}
ellipsizeMode="tail"
>
{item.receiver_phone}
</Text>
<View style={styles.addressEmit}>
<Text></Text>
<TouchableOpacity
onPress={() => deleteAddress(item.address_id)}
>
<Text></Text>
</TouchableOpacity>
</View>
</View>
{item.is_default === 1 && (
<View style={styles.centeredBoxWithText}>
<Text style={styles.blueHeadingTextStyle}></Text>
</View>
)}
</View>
<TouchableOpacity
onPress={() => {
navigation.navigate("AddRess", {
address: item,
});
}}
>
<View style={styles.svgContainer}>
<FileEditIcon size={24} />
</View>
</TouchableOpacity>
</View>
</TouchableOpacity>
))
}
</ScrollView>
<TouchableOpacity
style={styles.addButton}
onPress={() => navigation.navigate("AddRess")}
>
<Text style={styles.addButtonText}></Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: "#f8f8f8",
},
header: {
paddingVertical: 16,
},
userCardContainer1: {
marginTop: 20,
},
addressItemSelected: {
borderColor: "#002fa7",
borderWidth: 2,
},
addressItemNoSelected: {
borderColor: "#d0d0d0",
borderWidth: 2,
},
userCardContainer: {
flexDirection: "row",
gap: 8,
alignItems: "flex-start",
justifyContent: "space-between",
width: "100%",
paddingTop: 15,
paddingRight: 10,
paddingBottom: 10,
paddingLeft: 11,
backgroundColor: "white",
borderRadius: 5,
marginBottom: 10,
},
userInfoCard: {
flexDirection: "row",
alignItems: "flex-start",
justifyContent: "flex-start",
flex: 1,
marginRight: 8,
},
userCardInfo2: {
flex: 1,
marginRight: 8,
},
userCardInfo: {
fontSize: fontSize(18),
lineHeight: 22,
fontFamily: "PingFang SC",
fontWeight: "500",
color: "black",
flex: 1,
},
userCardInfo1: {
fontSize: fontSize(18),
lineHeight: 22,
fontFamily: "PingFang SC",
fontWeight: "500",
color: "#6b7280",
marginTop: 10,
flex: 1,
width: "100%",
},
centeredBoxWithText: {
flexDirection: "column",
alignItems: "stretch",
justifyContent: "center",
height: 26,
paddingRight: 11,
paddingLeft: 11,
marginLeft: 8,
backgroundColor: "#edf3ff",
borderRadius: 5,
},
blueHeadingTextStyle: {
fontSize: fontSize(13),
fontFamily: "PingFang SC",
fontWeight: "500",
color: "#002fa7",
},
svgContainer: {
width: widthUtils(24,24).width,
height: widthUtils(24,24).height,
color: "#0051ff",
marginLeft: "auto",
},
addressEmit: {
paddingTop: 10,
flexDirection: "row",
gap: 10,
},
addButton: {
width: "100%",
height: widthUtils(60,60).height,
backgroundColor: "#002fa7",
justifyContent: "center",
alignItems: "center",
borderRadius: 5,
marginTop: 10,
},
addButtonText: {
color: "white",
fontSize: fontSize(16),
fontWeight: "500",
},
});

20
app/screens/setting/SettingList.tsx

@ -14,7 +14,6 @@ export const SettingList = () => {
const getMySetting = async () => {
const res = await settingApi.getMySetting()
console.log(res);
setMySetting(res);
}
@ -61,12 +60,19 @@ export const SettingList = () => {
</View>
<View style={styles.content}>
<View style={styles.item}>
<Text></Text>
<Text>
<LeftArrowIcon size={fontSize(20)} color="#acacac" />
</Text>
</View>
<TouchableOpacity
onPress={() => {
if (mySetting?.language && mySetting?.currency) {
navigation.navigate("MyAddress");
}
}}
style={styles.item}
>
<Text></Text>
<Text>
<LeftArrowIcon size={fontSize(20)} color="#acacac" />
</Text>
</TouchableOpacity>
<View style={styles.item}>
<Text></Text>
<Text>

75
app/services/api/orders.ts

@ -0,0 +1,75 @@
import apiService from './apiClient';
// 地址类型
interface Address {
address_id: number;
user_id: number;
receiver_first_name: string;
receiver_last_name: string;
country: string;
receiver_phone: string;
whatsapp_phone: string;
province: string | null;
city: string | null;
district: string | null;
detail_address: string | null;
is_default: number;
create_time: string;
update_time: string;
}
// 订单商品项类型
interface OrderItem {
offer_id: number;
sku_id: number;
product_name: string;
sku_image_url: string;
product_name_en: string;
product_name_fr: string;
product_name_ar: string;
quantity: number;
unit_price: number;
total_price: number;
attributes:{
attribute_name:string;
attribute_name_trans:string,
attribute_value:string,
attribute_value_trans:string,
value:string,
value_trans:string,
value_trans_ar:string,
value_trans_fr:string,
}[]
}
// 订单汇总类型
interface OrderSummary {
total_amount: number;
shipping_fee: number;
discount_amount: number;
actual_amount: number;
currency: string;
}
// 完整订单数据类型
export interface OrderData {
address: Address;
items: OrderItem[];
total_amount: number;
shipping_fee: number;
discount_amount: number;
actual_amount: number;
currency: string;
}
export interface OrderPreviewData {
"items":
{
"cart_item_id": number
}[],
}
export const ordersApi = {
getOrders: (data:OrderPreviewData) => apiService.post<OrderData>("/api/orders/preview",data),
};

19
app/services/api/userApi.ts

@ -2,11 +2,18 @@ import apiService from './apiClient';
// 用户接口定义
export interface User {
id: string;
username: string;
email: string;
avatar?: string;
createdAt: string;
"username": string,
"email": string,
"phone": string,
"avatar_url": string,
"status": number,
"user_id": number,
"last_login": string,
"create_time": string,
"update_time": string
currency:string,
country:string,
country_en:string,
}
// 登录参数
@ -61,7 +68,7 @@ export const userApi = {
// 获取用户信息
getProfile: () => {
return apiService.get<User>('/user/profile');
return apiService.get<User>('/api/users/me');
},
// 更新用户信息

BIN
assets/img/image_1c498f1f.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
assets/img/image_637dba57.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
assets/img/image_946febdc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

BIN
assets/img/image_b1c2e380.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

BIN
assets/img/image_d9359f45.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

BIN
assets/img/image_f5a80fba.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 KiB

Loading…
Cancel
Save