upload 04/08

This commit is contained in:
2025-08-04 10:17:58 +07:00
parent cc29aaabb4
commit 67939d2f54
24 changed files with 1917 additions and 655 deletions

10
App.tsx
View File

@@ -1,9 +1,17 @@
import React from "react"; import React, { useEffect } from "react";
import { NavigationContainer } from "@react-navigation/native"; import { NavigationContainer } from "@react-navigation/native";
import { Platform } from "react-native";
import { SafeAreaProvider } from "react-native-safe-area-context"; import { SafeAreaProvider } from "react-native-safe-area-context";
import AppNavigator from "./src/navigation/AppNavigator"; import AppNavigator from "./src/navigation/AppNavigator";
export default function App() { export default function App() {
useEffect(() => {
if (Platform.OS === "web") {
// Fix scroll bị chặn trên web
document.body.style.overflow = "auto";
document.documentElement.style.overflow = "auto";
}
}, []);
return ( return (
<SafeAreaProvider> <SafeAreaProvider>
<NavigationContainer> <NavigationContainer>

View File

@@ -4,7 +4,7 @@ export default {
expo: { expo: {
name: "bestpc_mobile", name: "bestpc_mobile",
slug: "bestpc-mobile", slug: "bestpc-mobile",
platforms: ["ios", "android"], platforms: ["ios", "android", "web"],
version: "1.0.0", version: "1.0.0",
orientation: "portrait", orientation: "portrait",
splash: { splash: {

View File

@@ -14,5 +14,6 @@ module.exports = {
}, },
}, },
], ],
'react-native-reanimated/plugin'
], ],
}; };

93
package-lock.json generated
View File

@@ -18,12 +18,13 @@
"@react-navigation/stack": "^7.3.2", "@react-navigation/stack": "^7.3.2",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"expo-image-picker": "~16.1.4", "expo-image-picker": "~16.1.4",
"expo-linear-gradient": "~14.1.4",
"expo-status-bar": "~2.2.3", "expo-status-bar": "~2.2.3",
"hermes-engine": "^0.11.0", "hermes-engine": "^0.11.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"metro-runtime": "^0.82.3", "metro-runtime": "^0.82.3",
"react": "19.0.0", "react": "^19.1.0",
"react-dom": "^19.0.0", "react-dom": "^19.1.0",
"react-native": "^0.79.2", "react-native": "^0.79.2",
"react-native-collapsible": "^1.6.2", "react-native-collapsible": "^1.6.2",
"react-native-gesture-handler": "~2.24.0", "react-native-gesture-handler": "~2.24.0",
@@ -5203,16 +5204,16 @@
} }
}, },
"node_modules/compression": { "node_modules/compression": {
"version": "1.8.0", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"compressible": "~2.0.18", "compressible": "~2.0.18",
"debug": "2.6.9", "debug": "2.6.9",
"negotiator": "~0.6.4", "negotiator": "~0.6.4",
"on-headers": "~1.0.2", "on-headers": "~1.1.0",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"vary": "~1.1.2" "vary": "~1.1.2"
}, },
@@ -5999,6 +6000,17 @@
"react": "*" "react": "*"
} }
}, },
"node_modules/expo-linear-gradient": {
"version": "14.1.5",
"resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-14.1.5.tgz",
"integrity": "sha512-BSN3MkSGLZoHMduEnAgfhoj3xqcDWaoICgIr4cIYEx1GcHfKMhzA/O4mpZJ/WC27BP1rnAqoKfbclk1eA70ndQ==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-modules-autolinking": { "node_modules/expo-modules-autolinking": {
"version": "2.1.10", "version": "2.1.10",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.10.tgz", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.10.tgz",
@@ -8951,9 +8963,9 @@
} }
}, },
"node_modules/on-headers": { "node_modules/on-headers": {
"version": "1.0.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@@ -9584,9 +9596,9 @@
} }
}, },
"node_modules/react": { "node_modules/react": {
"version": "19.0.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -9624,17 +9636,23 @@
} }
}, },
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "19.0.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"scheduler": "^0.25.0" "scheduler": "^0.26.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^19.0.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-dom/node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"license": "MIT"
},
"node_modules/react-freeze": { "node_modules/react-freeze": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
@@ -15619,15 +15637,15 @@
} }
}, },
"compression": { "compression": {
"version": "1.8.0", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"requires": { "requires": {
"bytes": "3.1.2", "bytes": "3.1.2",
"compressible": "~2.0.18", "compressible": "~2.0.18",
"debug": "2.6.9", "debug": "2.6.9",
"negotiator": "~0.6.4", "negotiator": "~0.6.4",
"on-headers": "~1.0.2", "on-headers": "~1.1.0",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"vary": "~1.1.2" "vary": "~1.1.2"
}, },
@@ -16136,6 +16154,12 @@
"integrity": "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA==", "integrity": "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA==",
"requires": {} "requires": {}
}, },
"expo-linear-gradient": {
"version": "14.1.5",
"resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-14.1.5.tgz",
"integrity": "sha512-BSN3MkSGLZoHMduEnAgfhoj3xqcDWaoICgIr4cIYEx1GcHfKMhzA/O4mpZJ/WC27BP1rnAqoKfbclk1eA70ndQ==",
"requires": {}
},
"expo-modules-autolinking": { "expo-modules-autolinking": {
"version": "2.1.10", "version": "2.1.10",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.10.tgz", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.10.tgz",
@@ -18140,9 +18164,9 @@
} }
}, },
"on-headers": { "on-headers": {
"version": "1.0.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
@@ -18551,9 +18575,9 @@
} }
}, },
"react": { "react": {
"version": "19.0.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==" "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="
}, },
"react-devtools-core": { "react-devtools-core": {
"version": "6.1.2", "version": "6.1.2",
@@ -18573,11 +18597,18 @@
} }
}, },
"react-dom": { "react-dom": {
"version": "19.0.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"requires": { "requires": {
"scheduler": "^0.25.0" "scheduler": "^0.26.0"
},
"dependencies": {
"scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
}
} }
}, },
"react-freeze": { "react-freeze": {

View File

@@ -24,8 +24,8 @@
"hermes-engine": "^0.11.0", "hermes-engine": "^0.11.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"metro-runtime": "^0.82.3", "metro-runtime": "^0.82.3",
"react": "19.0.0", "react": "^19.1.0",
"react-dom": "^19.0.0", "react-dom": "^19.1.0",
"react-native": "^0.79.2", "react-native": "^0.79.2",
"react-native-collapsible": "^1.6.2", "react-native-collapsible": "^1.6.2",
"react-native-gesture-handler": "~2.24.0", "react-native-gesture-handler": "~2.24.0",

View File

@@ -221,7 +221,7 @@ export function CreateBuildpc() {
</View> </View>
{/* Popup */} {/* Popup */}
{showPopup && ( {showPopup && (
<PopupBuildpc visible={showPopup} onClose={() => setShowPopup(false)} /> <PopupBuildpc show={showPopup} onClose={() => setShowPopup(false)} />
)} )}
</ScrollView> </ScrollView>
); );

View File

@@ -4,7 +4,7 @@ import {
Text, Text,
TouchableOpacity, TouchableOpacity,
StyleSheet, StyleSheet,
ScrollView, Platform,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { NavigationProp } from "@react-navigation/native"; import { NavigationProp } from "@react-navigation/native";
@@ -33,7 +33,7 @@ const ButtonFooter: React.FC<Props> = ({ navigation, activeTab }) => {
onPress={() => navigation?.navigate(tab.route)} onPress={() => navigation?.navigate(tab.route)}
> >
<Ionicons <Ionicons
name={tab.icon} name={tab?.icon}
size={22} size={22}
color={isActive ? "#4a00e0" : "#666"} color={isActive ? "#4a00e0" : "#666"}
/> />
@@ -54,9 +54,15 @@ const styles = StyleSheet.create({
backgroundColor: "#fff", backgroundColor: "#fff",
paddingVertical: 8, paddingVertical: 8,
justifyContent: "space-around", justifyContent: "space-around",
position: "absolute",
bottom: 0, bottom: 0,
left: 0,
width: "100%", width: "100%",
...Platform.select({
web: { position: "fixed" as any },
default: {
position: "absolute",
},
}),
}, },
navItem: { navItem: {
alignItems: "center", alignItems: "center",

View File

@@ -82,15 +82,19 @@ const Footer: React.FC<Props> = ({ navigation, activeTab = "homepage" }) => {
/> />
</TouchableOpacity> </TouchableOpacity>
<Collapsible collapsed={activeIndex !== index}> <Collapsible collapsed={activeIndex !== index}>
{item.links?.map((link, linkIndex) => ( {item.links && item.links.length > 0 ? (
<TouchableOpacity item.links.map((link, linkIndex) => (
key={linkIndex} <TouchableOpacity
style={styles.linkItem} key={linkIndex}
onPress={() => handleLinkPress(link.route)} style={styles.linkItem}
> onPress={() => handleLinkPress(link.route)}
<Text style={styles.linkText}>{link.title}</Text> >
</TouchableOpacity> <Text style={styles.linkText}>{link.title}</Text>
))} </TouchableOpacity>
))
) : item.content ? (
<Text style={styles.accordionContent}>{item.content}</Text>
) : null}
</Collapsible> </Collapsible>
</View> </View>
))} ))}

View File

@@ -7,6 +7,7 @@ import {
TextInput, TextInput,
TouchableOpacity, TouchableOpacity,
Dimensions, Dimensions,
Platform,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { Picker } from "@react-native-picker/picker"; import { Picker } from "@react-native-picker/picker";
@@ -14,25 +15,15 @@ import { NavigationContainer } from "@react-navigation/native";
import { createDrawerNavigator } from "@react-navigation/drawer"; import { createDrawerNavigator } from "@react-navigation/drawer";
import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { createNativeStackNavigator } from "@react-navigation/native-stack";
const Header = () => { const Header = ({ toggleMenu }: { toggleMenu: () => void }) => {
const [openSort, setopenSort] = useState(true); const [openSort, setopenSort] = useState(true);
const [openFilter, setOpenFilter] = useState(false); const [openFilter, setOpenFilter] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const toggleDrawer = () => {
//Props to open/close the drawer
setIsMenuOpen(!isMenuOpen);
};
const closeMenu = () => {
setIsMenuOpen(false); // Đóng menu
};
return ( return (
<View> <View style={styles.BoxHeader}>
<View style={styles.headerTop}> <View style={styles.headerTop}>
<View style={styles.headerTopLeft}> <View style={styles.headerTopLeft}>
<TouchableOpacity onPress={toggleDrawer} style={styles.menu}> <TouchableOpacity onPress={toggleMenu} style={styles.menu}>
<Ionicons style={styles.iconMenu} name="menu-outline" size={30} /> <Ionicons style={styles.iconMenu} name="menu-outline" size={30} />
</TouchableOpacity> </TouchableOpacity>
<Image <Image
@@ -58,36 +49,6 @@ const Header = () => {
name="caret-down-outline" name="caret-down-outline"
/> />
</TouchableOpacity> </TouchableOpacity>
<View
style={
openSort
? styles.boxSortList
: [styles.boxSortList, styles.boxSortListActive]
}
>
<TouchableOpacity
style={[styles.boxSortItem, styles.boxSortItemActive]}
>
<Text
style={[
styles.boxSortItemText,
styles.boxSortItemTextActive,
]}
>
nội
</Text>
<Ionicons
style={styles.boxSortItemTextIcon}
name="checkmark-outline"
/>
</TouchableOpacity>
<TouchableOpacity style={styles.boxSortItem}>
<Text style={styles.boxSortItemText}>HCM</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.boxSortItem}>
<Text style={styles.boxSortItemText}>Đà nẵng</Text>
</TouchableOpacity>
</View>
</View> </View>
</View> </View>
</View> </View>
@@ -102,102 +63,60 @@ const Header = () => {
<Ionicons style={styles.iconMenu} name="search-outline" size={19} /> <Ionicons style={styles.iconMenu} name="search-outline" size={19} />
</View> </View>
</View> </View>
{isMenuOpen && ( <View
<View style={styles.overlay} onTouchStart={closeMenu}> style={
<MainMenu closeMenu={closeMenu} /> openSort
</View> ? styles.boxSortList
)} : [styles.boxSortList, styles.boxSortListActive]
</View> }
); >
}; <TouchableOpacity
style={[styles.boxSortItem, styles.boxSortItemActive]}
const MainMenu = ({ closeMenu }: { closeMenu: () => void }) => { >
return ( <Text style={[styles.boxSortItemText, styles.boxSortItemTextActive]}>
<View style={styles.menuFixed}> nội
<View style={styles.headerMenu}> </Text>
<Text style={styles.headerMenuText}>Menu</Text> <Ionicons
<TouchableOpacity onPress={closeMenu}> style={styles.boxSortItemTextIcon}
<Ionicons style={styles.closeMenu} name="close-outline" size={20} /> name="checkmark-outline"
/>
</TouchableOpacity> </TouchableOpacity>
</View> <TouchableOpacity style={styles.boxSortItem}>
<View> <Text style={styles.boxSortItemText}>HCM</Text>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_buildpc.png")}
style={styles.iconBuild}
/>
<Text style={styles.itemText}>Build PC</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}> <TouchableOpacity style={styles.boxSortItem}>
<View style={styles.textLeft}> <Text style={styles.boxSortItemText}>Đà nẵng</Text>
<Image
source={require("../../../assets/images/icon_store.png")}
style={styles.iconStore}
/>
<Text style={styles.itemText}>Tìm người bán</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_location.png")}
style={styles.iconLocation}
/>
<Text style={styles.itemText}>Đa chỉ sửa chữa</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_question.png")}
style={styles.iconProduct}
/>
<Text style={styles.itemText}>Hỏi đáp</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_box.png")}
style={styles.iconBox}
/>
<Text style={styles.itemText}>Sản phẩm đã lưu</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_article.png")}
style={styles.iconArticle}
/>
<Text style={styles.itemText}>Tin rao vặt</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
); );
}; };
export { MainMenu };
let winWidth = Dimensions.get("window").width; //full width let winWidth = Dimensions.get("window").width; //full width
let winHeight = Dimensions.get("window").height; //full height let winHeight = Dimensions.get("window").height; //full height
const styles = StyleSheet.create({ const styles = StyleSheet.create({
BoxHeader: {
width: winWidth,
zIndex: 9999,
...Platform.select({
web: {
position: "fixed" as any,
top: 0,
},
default: {
position: "absolute",
top: 0,
left: 0,
},
}),
},
headerTop: { headerTop: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
width: winWidth, width: winWidth,
paddingRight: 10, paddingRight: 10,
paddingLeft: 10, paddingLeft: 10,
paddingTop: 45, paddingTop: 10,
paddingBottom: 10, paddingBottom: 10,
backgroundColor: "#462F91", backgroundColor: "#462F91",
justifyContent: "space-between", justifyContent: "space-between",
@@ -212,12 +131,14 @@ const styles = StyleSheet.create({
height: 20, height: 20,
}, },
searchContainer: { searchContainer: {
width: winWidth, width: "100%",
paddingLeft: 10, paddingLeft: 10,
paddingRight: 10, paddingRight: 10,
paddingTop: 9, paddingTop: 9,
paddingBottom: 9, paddingBottom: 9,
backgroundColor: "#462F91", backgroundColor: "#462F91",
position: "relative",
zIndex: 1,
}, },
searchBar: { searchBar: {
borderRadius: 4, borderRadius: 4,
@@ -236,9 +157,8 @@ const styles = StyleSheet.create({
color: "#000000", color: "#000000",
height: 32, height: 32,
lineHeight: 32, lineHeight: 32,
paddingTop: 7,
marginTop: 5,
width: winWidth - 60, width: winWidth - 60,
outlineWidth: 0,
}, },
searchIconContainer: { searchIconContainer: {
flexDirection: "row", flexDirection: "row",
@@ -270,6 +190,7 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
gap: 5, gap: 5,
color: "#FFFFFF", color: "#FFFFFF",
position: "relative",
}, },
textLocation: { textLocation: {
color: "#FFFFFF", color: "#FFFFFF",
@@ -298,10 +219,15 @@ const styles = StyleSheet.create({
paddingHorizontal: 10, paddingHorizontal: 10,
position: "absolute", position: "absolute",
right: 0, right: 0,
top: 40, top: 60,
display: "none", display: "none",
backgroundColor: "#fff", backgroundColor: "#fff",
width: 100, width: 100,
zIndex: 999,
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 5,
}, },
boxSortListActive: { boxSortListActive: {
display: "flex", display: "flex",
@@ -343,91 +269,5 @@ const styles = StyleSheet.create({
justifyContent: "center", justifyContent: "center",
display: "flex", display: "flex",
}, },
headerMenu: {
alignItems: "center",
justifyContent: "space-between",
flexDirection: "row",
padding: 10,
backgroundColor: "#462F91",
paddingTop: 55,
},
menuFixed: {
position: "absolute",
top: 0,
left: 0,
width: "85%",
height: winHeight,
backgroundColor: "#fff",
zIndex: 99,
},
headerMenuText: {
color: "#fff",
fontWeight: "bold",
},
closeMenu: {
backgroundColor: "#fff",
width: 20,
height: 20,
color: "#000",
borderRadius: 50,
},
iconBuild: {
width: 30,
objectFit: "contain",
},
iconStore: {
width: 20,
objectFit: "contain",
},
iconCart: {
width: 20,
objectFit: "contain",
},
iconProduct: {
width: 20,
objectFit: "contain",
},
iconArticle: {
width: 20,
objectFit: "contain",
},
iconLocation: {
width: 20,
objectFit: "contain",
},
iconBox: {
width: 20,
objectFit: "contain",
},
itemMenu: {
alignItems: "center",
flexDirection: "row",
justifyContent: "space-between",
gap: 10,
marginBottom: 10,
padding: 10,
borderBottomWidth: 1,
borderBottomColor: "#e1e1e1",
height: 50,
},
itemText: {
fontSize: 15,
color: "#000",
fontWeight: "bold",
},
textLeft: {
alignItems: "center",
gap: 10,
flexDirection: "row",
},
overlay: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.5)", // Lớp nền bán trong suốt
zIndex: 98, // Đảm bảo lớp overlay nằm dưới menu
},
}); });
export default Header; export default Header;

View File

@@ -0,0 +1,197 @@
import React, { useState, useRef, useEffect } from "react";
import {
View,
StyleSheet,
Image,
Text,
TextInput,
TouchableOpacity,
Dimensions,
Modal,
Animated,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
const MainMenu = ({
visible,
onClose,
}: {
visible: boolean;
onClose: () => void;
}) => {
const [internalVisible, setInternalVisible] = useState(visible);
const slideAnim = useRef(new Animated.Value(-winWidth * 0.85)).current;
useEffect(() => {
if (visible) {
setInternalVisible(true);
Animated.timing(slideAnim, {
toValue: 0,
duration: 250,
useNativeDriver: false,
}).start();
} else {
Animated.timing(slideAnim, {
toValue: -winWidth * 0.85,
duration: 250,
useNativeDriver: false,
}).start(() => {
setInternalVisible(false); // Tắt Modal sau animation
});
}
}, [visible]);
if (!internalVisible) return null;
return (
<Modal visible={internalVisible} transparent animationType="none">
<TouchableOpacity
style={styles.overlay}
onPress={onClose}
activeOpacity={1}
/>
<Animated.View style={[styles.menuFixed, { left: slideAnim }]}>
<View style={styles.headerMenu}>
<Text style={styles.headerMenuText}>Menu</Text>
<TouchableOpacity onPress={onClose}>
<Ionicons name="close-outline" size={20} style={styles.closeMenu} />
</TouchableOpacity>
</View>
<View>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_buildpc.png")}
style={styles.iconFix}
resizeMode="contain"
/>
<Text style={styles.itemText}>Build PC</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_store.png")}
style={styles.iconFix}
resizeMode="contain"
/>
<Text style={styles.itemText}>Tìm người bán</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_location.png")}
style={styles.iconFix}
resizeMode="contain"
/>
<Text style={styles.itemText}>Đa chỉ sửa chữa</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_question.png")}
style={styles.iconFix}
resizeMode="contain"
/>
<Text style={styles.itemText}>Hỏi đáp</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_box.png")}
style={styles.iconFix}
resizeMode="contain"
/>
<Text style={styles.itemText}>Sản phẩm đã lưu</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
<TouchableOpacity style={styles.itemMenu}>
<View style={styles.textLeft}>
<Image
source={require("../../../assets/images/icon_article.png")}
style={styles.iconFix}
resizeMode="contain"
/>
<Text style={styles.itemText}>Tin rao vặt</Text>
</View>
<Ionicons name="chevron-forward-outline" size={20} />
</TouchableOpacity>
</View>
</Animated.View>
</Modal>
);
};
export { MainMenu };
let winWidth = Dimensions.get("window").width; //full width
let winHeight = Dimensions.get("window").height; //full height
const styles = StyleSheet.create({
headerMenu: {
alignItems: "center",
justifyContent: "space-between",
flexDirection: "row",
padding: 10,
backgroundColor: "#462F91",
},
menuFixed: {
width: "85%",
height: "100%",
backgroundColor: "#fff",
zIndex: 999,
},
headerMenuText: {
color: "#fff",
fontWeight: "bold",
},
closeMenu: {
backgroundColor: "#fff",
width: 20,
height: 20,
color: "#000",
borderRadius: 50,
},
iconFix: {
width: 25,
resizeMode: "contain",
},
itemMenu: {
alignItems: "center",
flexDirection: "row",
justifyContent: "space-between",
gap: 10,
marginBottom: 10,
padding: 10,
borderBottomWidth: 1,
borderBottomColor: "#e1e1e1",
height: 50,
},
itemText: {
fontSize: 15,
color: "#000",
fontWeight: "bold",
},
textLeft: {
alignItems: "center",
gap: 10,
flexDirection: "row",
},
overlay: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.5)", // Lớp nền bán trong suốt
zIndex: 98, // Đảm bảo lớp overlay nằm dưới menu
},
});

View File

@@ -99,7 +99,7 @@ const styles = StyleSheet.create({
backgroundColor: "#fff", backgroundColor: "#fff",
}, },
productImage: { productImage: {
width: winWidth / 2 - 44, width: "100%",
height: 150, height: 150,
position: "relative", position: "relative",
marginBottom: 5, marginBottom: 5,
@@ -112,8 +112,8 @@ const styles = StyleSheet.create({
}, },
boxSaleoff: { boxSaleoff: {
position: "absolute", position: "absolute",
right: 10, right: 0,
top: 10, top: 0,
width: 35, width: 35,
height: 35, height: 35,
borderRadius: 25, borderRadius: 25,

View File

@@ -37,6 +37,7 @@ const ItemProductSave = ({ product }: { product: any }) => {
source={require("../../../assets/images/icon_heart.png")} source={require("../../../assets/images/icon_heart.png")}
style={styles.imgsave} style={styles.imgsave}
alt="icon-heart" alt="icon-heart"
resizeMode="contain"
/> />
</View> </View>
</TouchableOpacity> </TouchableOpacity>
@@ -74,10 +75,10 @@ const ItemProductSave = ({ product }: { product: any }) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
productItem: { productItem: {
width: winWidth / 2 - 20, width: winWidth / 2 - 15,
}, },
productImage: { productImage: {
width: winWidth / 2 - 20, width: "100%",
height: 200, height: 200,
position: "relative", position: "relative",
marginBottom: 5, marginBottom: 5,
@@ -85,6 +86,7 @@ const styles = StyleSheet.create({
borderColor: "#d3d3d3", borderColor: "#d3d3d3",
backgroundColor: "#fff", backgroundColor: "#fff",
borderRadius: 8, borderRadius: 8,
overflow: "hidden",
}, },
productImg: { productImg: {
width: "100%", width: "100%",

View File

@@ -1,12 +1,24 @@
import React from "react"; import React, { useState } from "react";
import { View, StyleSheet, ScrollView } from "react-native"; import {
View,
StyleSheet,
ScrollView,
Dimensions,
Platform,
} from "react-native";
import { import {
useNavigation, useNavigation,
NavigationProp, NavigationProp,
NavigationContainer, NavigationContainer,
} from "@react-navigation/native"; } from "@react-navigation/native";
import { SafeAreaView } from "react-native-safe-area-context";
import Header from "../components/header/Header"; import Header from "../components/header/Header";
import { MainMenu } from "@components/header/Menu";
import Footer from "../components/footer/Footer";
import ButtonFooter from "../components/footer/ButtonFooter"; import ButtonFooter from "../components/footer/ButtonFooter";
var winWidth = Dimensions.get("window").width; //full width
const winHeight = Dimensions.get("window").height;
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
@@ -15,27 +27,49 @@ type Props = {
const AppLayout = ({ children, activeTab }: Props) => { const AppLayout = ({ children, activeTab }: Props) => {
const navigation = useNavigation<NavigationProp<any>>(); const navigation = useNavigation<NavigationProp<any>>();
const [isMenuOpen, setIsMenuOpen] = useState(false);
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.wrapper}>
<Header /> <Header toggleMenu={() => setIsMenuOpen(true)} />
<View style={styles.content}>{children}</View> <MainMenu visible={isMenuOpen} onClose={() => setIsMenuOpen(false)} />
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.scrollContainer}
keyboardShouldPersistTaps="handled"
>
{children}
</ScrollView>
<Footer navigation={navigation} />
<ButtonFooter navigation={navigation} activeTab={activeTab} /> <ButtonFooter navigation={navigation} activeTab={activeTab} />
</View> </SafeAreaView>
); );
}; };
export default AppLayout; export default AppLayout;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { wrapper: {
marginLeft: "auto",
marginRight: "auto",
maxWidth: 480,
flex: 1, flex: 1,
backgroundColor: "#fff", backgroundColor: "#fff",
}, },
content: { scroll: {
flex: 1, flex: 1,
...Platform.select({
web: { marginTop: 113 },
default: { marginTop: 0 },
}),
},
scrollWrapper: {
flex: 1,
overflow: "scroll",
},
scrollContainer: {
flexGrow: 1,
},
webScrollArea: {
flex: 1,
overflow: "auto" as any,
}, },
}); });

View File

@@ -25,6 +25,7 @@ const AppNavigator: React.FC = () => {
<Stack.Screen name="buildpc" component={Buildpc} /> <Stack.Screen name="buildpc" component={Buildpc} />
<Stack.Screen name="comparebuildpc" component={CompareBuildpc} /> <Stack.Screen name="comparebuildpc" component={CompareBuildpc} />
<Stack.Screen name="buildpcdetail" component={BuildpcDeail} /> <Stack.Screen name="buildpcdetail" component={BuildpcDeail} />
<Stack.Screen name="classifieds" component={BuildpcDeail} />
</Stack.Navigator> </Stack.Navigator>
); );
}; };

View File

@@ -1,5 +1,5 @@
// src/screens/HomeScreen.tsx // src/screens/HomeScreen.tsx
import React, { useState } from "react"; import React, { useState, useRef } from "react";
import { useNavigation, NavigationProp } from "@react-navigation/native"; import { useNavigation, NavigationProp } from "@react-navigation/native";
import { import {
View, View,
@@ -11,9 +11,9 @@ import {
ImageBackground, ImageBackground,
TouchableOpacity, TouchableOpacity,
SafeAreaView, SafeAreaView,
ScrollViewBase,
} from "react-native"; } from "react-native";
import { globalStyles } from "../styles/globalStyles"; import { globalStyles } from "../styles/globalStyles";
import Swiper from "react-native-swiper";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { products } from "../data/product"; import { products } from "../data/product";
import ProductItem from "../components/product/ItemProduct"; import ProductItem from "../components/product/ItemProduct";
@@ -23,17 +23,19 @@ import chunk from "lodash/chunk";
import { globalAgent } from "http"; import { globalAgent } from "http";
import { WebView } from "react-native-webview"; import { WebView } from "react-native-webview";
import AppLayout from "../layouts/AppLayout"; import AppLayout from "../layouts/AppLayout";
import Footer from "../components/footer/Footer"; import Carousel, { ICarouselInstance } from "react-native-reanimated-carousel";
var winWidth = Dimensions.get("window").width; //full width var winWidth = Dimensions.get("window").width; //full width
var winHeight = Dimensions.get("window").height; //full height var winHeight = Dimensions.get("window").height; //full height
const ratio = winWidth / 930;
const HomeScreen: React.FC = () => { const HomeScreen: React.FC = () => {
const navigation = useNavigation<NavigationProp<any>>(); const navigation = useNavigation<NavigationProp<any>>();
return ( return (
<AppLayout activeTab="homepage"> <AppLayout activeTab="homepage">
<ScrollView> <ScrollView
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={true}
>
<SliderHome /> <SliderHome />
<BoxMenuHome /> <BoxMenuHome />
<BoxProductReviewTop /> <BoxProductReviewTop />
@@ -45,67 +47,58 @@ const HomeScreen: React.FC = () => {
<BoxArticleClassifieds /> <BoxArticleClassifieds />
<BoxBannerSaleHome /> <BoxBannerSaleHome />
<BoxPromotionHome /> <BoxPromotionHome />
<Footer navigation={navigation} />
</ScrollView> </ScrollView>
</AppLayout> </AppLayout>
); );
}; };
const SliderHome = () => { const SliderHome = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const carouselRef = useRef<ICarouselInstance>(null);
const handleDotPress = (index: number) => {
if (carouselRef.current) {
carouselRef.current?.scrollTo({ index, animated: true });
}
};
const banner_list = [
require("../../assets/images/banner_slider.png"),
require("../../assets/images/banner_slider.png"),
require("../../assets/images/banner_slider.png"),
require("../../assets/images/banner_slider.png"),
];
return ( return (
<View style={styles.slider}> <View style={styles.slider}>
<Swiper <Carousel
style={styles.sliderSwipper} loop
autoplay={false} autoPlay
autoplayTimeout={3} autoPlayInterval={3000}
showsButtons={true} width={winWidth}
showsPagination={false} height={200}
nextButton={ data={banner_list}
<Ionicons scrollAnimationDuration={1000}
name="chevron-forward-outline" onSnapToItem={(index) => {
size={30} setCurrentIndex(index);
color="white" }}
style={styles.buttonNext} renderItem={({ item }) => (
/> <Image source={item} style={styles.imgSlider} resizeMode="cover" />
} )}
prevButton={ />
<Ionicons <View style={globalStyles.dotWrapper}>
name="chevron-back-outline" {banner_list.map((_, index) => (
size={30} <TouchableOpacity key={index} onPress={() => handleDotPress(index)}>
color="white" <View
style={styles.buttonPrev} key={index}
/> style={[
} globalStyles.dot,
> currentIndex === index && globalStyles.activeDot,
<View style={styles.imgContainer}> ]}
<Image />
style={styles.imgSlider} </TouchableOpacity>
source={require("../../assets/images/banner_slider.png")} ))}
resizeMode="cover" </View>
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="cover"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="cover"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="cover"
/>
</View>
</Swiper>
</View> </View>
); );
}; };
@@ -252,124 +245,132 @@ const BoxProductReviewTop = () => {
const BoxCategoryHome = () => { const BoxCategoryHome = () => {
const chunkedItems = chunk(categories, 8); const chunkedItems = chunk(categories, 8);
const [activeIndex, setActiveIndex] = useState(0);
const carouselRef = useRef<ICarouselInstance>(null);
const handleDotPress = (index: number) => {
if (carouselRef.current) {
carouselRef.current?.scrollTo({ index, animated: true });
}
};
return ( return (
<View style={globalStyles.boxCategoryHome}> <View style={globalStyles.boxCategoryHome}>
<Text style={globalStyles.textBoxCategoryHome}>Tìm theo danh mục</Text> <Text style={globalStyles.textBoxCategoryHome}>Tìm theo danh mục</Text>
<Swiper <Carousel
style={globalStyles.listCategoryBox}
loop={false} loop={false}
showsPagination={true} width={winWidth}
dot={<View style={globalStyles.dot} />} height={300}
activeDot={<View style={globalStyles.activeDot} />} data={chunkedItems}
paginationStyle={{ scrollAnimationDuration={500}
bottom: 10, onSnapToItem={(index) => setActiveIndex(index)}
}} renderItem={({ item }) => (
>
{chunkedItems.map((group, slideIndex) => (
<View <View
key={`slide-${slideIndex}`}
style={{ style={{
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap", flexWrap: "wrap",
justifyContent: "space-between",
paddingHorizontal: 10,
}} }}
> >
{group.map((item) => ( {item.map((cat) => (
<TouchableOpacity key={item.id} style={globalStyles.categoryItem}> <TouchableOpacity key={cat.id} style={globalStyles.categoryItem}>
<View style={globalStyles.boxImageCategory}> <View style={globalStyles.boxImageCategory}>
<Image <Image
source={require("../../assets/images/category-laptop.png")} source={require("../../assets/images/category-laptop.png")}
style={globalStyles.iconCategory} style={globalStyles.iconCategory}
/> />
</View> </View>
<Text style={globalStyles.categoryName}>{item.name}</Text> <Text style={globalStyles.categoryName}>{cat.name}</Text>
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>
)}
/>
<View style={globalStyles.dotWrapper}>
{chunkedItems.map((_, index) => (
<TouchableOpacity key={index} onPress={() => handleDotPress(index)}>
<View
key={index}
style={[
globalStyles.dot,
activeIndex === index && globalStyles.activeDot,
]}
/>
</TouchableOpacity>
))} ))}
</Swiper> </View>
</View> </View>
); );
}; };
const BoxBusiness = () => { const BoxBusiness = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const carouselRef = useRef<ICarouselInstance>(null);
const handleDotPress = (index: number) => {
if (carouselRef.current) {
carouselRef.current?.scrollTo({ index, animated: true });
}
};
const slides = [
[
require("../../assets/images/logo-hacom.png"),
require("../../assets/images/logo-hacom.png"),
require("../../assets/images/logo-anphat.png"),
require("../../assets/images/logo-gearvn.png"),
],
[
require("../../assets/images/logo-hacom.png"),
require("../../assets/images/logo-hacom.png"),
require("../../assets/images/logo-anphat.png"),
require("../../assets/images/logo-gearvn.png"),
],
];
return ( return (
<View style={globalStyles.BoxBusiness}> <View style={globalStyles.BoxBusiness}>
<Text style={globalStyles.textBoxBusiness}>DOANH NGHIỆP NỔI BẬT</Text> <Text style={globalStyles.textBoxBusiness}>DOANH NGHIỆP NỔI BẬT</Text>
<Swiper <Carousel
style={globalStyles.sliderBusinesses} style={globalStyles.sliderBusinesses}
autoplay={false} loop={false}
showsPagination={true} width={winWidth}
dot={<View style={globalStyles.dot} />} height={45}
activeDot={<View style={globalStyles.activeDot} />} data={slides}
paginationStyle={{ scrollAnimationDuration={500}
bottom: 10, onSnapToItem={(index) => setCurrentIndex(index)}
renderItem={({ item }) => (
<View style={{ flexDirection: "row", paddingHorizontal: 10 }}>
{item.map((logo, idx) => (
<TouchableOpacity key={idx} style={globalStyles.logoItem}>
<Image source={logo} style={globalStyles.logoImage} />
</TouchableOpacity>
))}
</View>
)}
/>
{/* Custom Dots */}
<View
style={{
flexDirection: "row",
justifyContent: "center",
marginTop: 10,
}} }}
> >
<View {slides.map((_, index) => (
style={{ <TouchableOpacity key={index} onPress={() => handleDotPress(index)}>
flexDirection: "row", <View
paddingHorizontal: 10, key={index}
}} style={[
> globalStyles.dot,
<TouchableOpacity style={globalStyles.logoItem}> currentIndex === index && globalStyles.activeDot,
<Image ]}
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/> />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}> ))}
<Image </View>
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-anphat.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-gearvn.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
</View>
<View
style={{
flexDirection: "row",
paddingHorizontal: 10,
}}
>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-anphat.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-gearvn.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
</View>
</Swiper>
</View> </View>
); );
}; };
@@ -738,67 +739,65 @@ const BoxArticleClassifieds = () => {
}; };
const BoxBannerSaleHome = () => { const BoxBannerSaleHome = () => {
const [activeIndex, setActiveIndex] = useState(0);
const carouselRef = useRef<ICarouselInstance>(null);
const slides = [
[
require("../../assets/images/banner-sale-1.png"),
require("../../assets/images/banner-sale-2.png"),
],
[
require("../../assets/images/banner-sale-2.png"),
require("../../assets/images/banner-sale-3.png"),
],
[
require("../../assets/images/banner-sale-3.png"),
require("../../assets/images/banner-sale-1.png"),
],
];
const handleDotPress = (index: number) => {
if (carouselRef.current) {
carouselRef.current.scrollTo({ index, animated: true });
}
};
return ( return (
<View style={globalStyles.BoxBannerSaleHome}> <View style={globalStyles.BoxBannerSaleHome}>
<Swiper <Carousel
style={globalStyles.sliderBannerSale} ref={carouselRef}
autoplay={false} width={winWidth}
showsPagination={true} height={220}
dot={<View style={globalStyles.dot} />} data={slides}
activeDot={<View style={globalStyles.activeDot} />} loop
paginationStyle={{ autoPlay
bottom: 10, autoPlayInterval={5000}
}} scrollAnimationDuration={600}
> onSnapToItem={(index) => setActiveIndex(index)}
<View style={{ flexDirection: "row" }}> renderItem={({ item }) => (
<TouchableOpacity style={globalStyles.itemBannerSale}> <View style={{ flexDirection: "row", paddingHorizontal: 10 }}>
<Image {item.map((logo, idx) => (
source={require("../../assets/images/banner-sale-1.png")} <TouchableOpacity key={idx} style={globalStyles.itemBannerSale}>
style={globalStyles.imgBannerSale} <Image source={logo} style={globalStyles.imgBannerSale} />
alt="anh tin tuc" </TouchableOpacity>
))}
</View>
)}
/>
<View style={globalStyles.dotWrapper}>
{slides.map((_, index) => (
<TouchableOpacity key={index} onPress={() => handleDotPress(index)}>
<View
style={
index === activeIndex
? globalStyles.activeDot
: globalStyles.dot
}
/> />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={globalStyles.itemBannerSale}> ))}
<Image </View>
source={require("../../assets/images/banner-sale-2.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
</View>
<View style={{ flexDirection: "row" }}>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-2.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-3.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
</View>
<View style={{ flexDirection: "row" }}>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-3.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-1.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
</View>
</Swiper>
</View> </View>
); );
}; };
@@ -899,6 +898,7 @@ const styles = StyleSheet.create({
overflow: "hidden", overflow: "hidden",
borderRadius: 10, borderRadius: 10,
margin: 10, margin: 10,
flex: 1,
}, },
sliderSwipper: { sliderSwipper: {
height: 210, height: 210,
@@ -913,11 +913,12 @@ const styles = StyleSheet.create({
objectFit: "cover", objectFit: "cover",
}, },
imgSlider: { imgSlider: {
width: winWidth, width: winWidth - 20,
height: "100%", height: "100%",
objectFit: "cover", objectFit: "cover",
overflow: "hidden", overflow: "hidden",
borderRadius: 10, borderRadius: 10,
marginRight: 10,
}, },
buttonNext: { buttonNext: {
color: "#fff", // Màu chữ cho các nút chuyển color: "#fff", // Màu chữ cho các nút chuyển
@@ -930,15 +931,19 @@ const styles = StyleSheet.create({
fontWeight: "bold", fontWeight: "bold",
}, },
boxMenuHome: { boxMenuHome: {
marginTop: 10,
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap", flexWrap: "wrap",
justifyContent: "flex-start",
marginLeft: 20, marginLeft: 20,
marginRight: 20, gap: 10,
gap: 20,
backgroundColor: "#fff", backgroundColor: "#fff",
}, },
MenuItem: { MenuItem: {
width: winWidth / 4 - 25, width: winWidth / 4 - 20,
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}, },
boxIconMenu: { boxIconMenu: {
width: 50, width: 50,
@@ -948,14 +953,15 @@ const styles = StyleSheet.create({
justifyContent: "center", justifyContent: "center",
backgroundColor: "#eeeeee", backgroundColor: "#eeeeee",
borderRadius: 25, borderRadius: 25,
marginLeft: "auto", flexDirection: "row",
marginRight: "auto", margin: "auto",
}, },
textMenu: { textMenu: {
textAlign: "center", textAlign: "center",
fontSize: 14, fontSize: 14,
color: "#333", color: "#333",
fontWeight: "bold", fontWeight: "bold",
height: 35,
}, },
iconMenuItem: { iconMenuItem: {
width: 20, width: 20,

View File

@@ -92,7 +92,6 @@ export default function Buildpc() {
{/* Buildpc Content */} {/* Buildpc Content */}
<CreateBuildpc /> <CreateBuildpc />
</View> </View>
<Footer navigation={navigation} />
</ScrollView> </ScrollView>
</AppLayout> </AppLayout>
); );
@@ -136,6 +135,7 @@ const styles = StyleSheet.create({
marginTop: 12, marginTop: 12,
backgroundColor: "#fff", backgroundColor: "#fff",
borderRadius: 8, borderRadius: 8,
marginBottom: 10,
}, },
inputBox: { inputBox: {
flexDirection: "row", flexDirection: "row",

View File

@@ -81,7 +81,6 @@ export function BuildpcDeail() {
{/* đánh giá */} {/* đánh giá */}
<ReviewList /> <ReviewList />
</View> </View>
<Footer navigation={navigation} />
</ScrollView> </ScrollView>
</AppLayout> </AppLayout>
); );

View File

@@ -58,7 +58,6 @@ export function CompareBuildpc() {
<ListCompare /> <ListCompare />
</View> </View>
<Footer navigation={navigation} />
</ScrollView> </ScrollView>
</AppLayout> </AppLayout>
); );

View File

@@ -0,0 +1,76 @@
import React from "react";
import {
View,
Text,
Image,
TouchableOpacity,
ScrollView,
StyleSheet,
Dimensions,
} from "react-native";
const { width } = Dimensions.get("window");
import { useNavigation, NavigationProp } from "@react-navigation/native";
import Footer from "@components/footer/Footer";
import AppLayout from "@layouts/AppLayout";
import { Ionicons } from "@expo/vector-icons"; // hoặc icon_2025 nếu bạn có icon font riêng
export default function ListCompare() {
const navigation = useNavigation<NavigationProp<any>>();
return (
<AppLayout activeTab="classifieds">
<ScrollView>
<View style={styles.container}>
{/* Breadcrumb */}
<View style={styles.breadcrumb}>
<View style={styles.breadcrumbItem}>
<TouchableOpacity>
<Ionicons
name="home"
size={16}
color="#637381"
style={styles.icon}
/>
</TouchableOpacity>
<Text style={styles.angle}></Text>
</View>
<View style={styles.breadcrumbItem}>
<Text style={styles.text}>Rao vặt</Text>
</View>
</View>
<View style={styles.boxCategory}>
<Text>Danh mục</Text>
</View>
</View>
<Footer navigation={navigation} />
</ScrollView>
</AppLayout>
);
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 10,
},
breadcrumb: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 12,
flexWrap: "wrap",
},
breadcrumbItem: {
flexDirection: "row",
alignItems: "center",
marginRight: 8,
},
text: {
color: "#000",
},
icon: {
marginRight: 5,
},
angle: {
marginLeft: 12,
color: "#888",
},
});

View File

@@ -24,7 +24,6 @@ import ProductInformation from "./ProductInformation";
import ProductSpecification from "./ProductSpecification"; import ProductSpecification from "./ProductSpecification";
import { products } from "../../data/product"; import { products } from "../../data/product";
import ProductItem from "@components/product/ItemProduct"; import ProductItem from "@components/product/ItemProduct";
import Footer from "@components/footer/Footer";
const ProductDetail = () => { const ProductDetail = () => {
const navigation = useNavigation<NavigationProp<any>>(); const navigation = useNavigation<NavigationProp<any>>();
@@ -192,7 +191,6 @@ const ProductDetail = () => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
<Footer navigation={navigation} />
</ScrollView> </ScrollView>
</AppLayout> </AppLayout>
); );
@@ -358,7 +356,7 @@ const styles = StyleSheet.create({
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap", flexWrap: "wrap",
gap: 5, gap: 5,
marginLeft: 7,
marginTop: 10, marginTop: 10,
marginLeft: 7,
}, },
}); });

View File

@@ -70,7 +70,6 @@ const ProductList = () => {
))} ))}
</View> </View>
</View> </View>
<Footer navigation={navigation} />
</ScrollView> </ScrollView>
</AppLayout> </AppLayout>
); );

View File

@@ -67,7 +67,6 @@ const ProductListBig = () => {
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
/> />
</View> </View>
<Footer navigation={navigation} />
</ScrollView> </ScrollView>
</AppLayout> </AppLayout>
); );

View File

@@ -51,7 +51,7 @@ export const globalStyles = StyleSheet.create({
marginBottom: 10, marginBottom: 10,
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap', flexWrap: 'wrap',
gap: 10 gap: 10,
}, },
moreAll: { moreAll: {
width: 110, width: 110,
@@ -85,8 +85,11 @@ export const globalStyles = StyleSheet.create({
height: 300 height: 300
}, },
categoryItem: { categoryItem: {
width: '25%', width: winWidth / 4 - 15,
marginBottom: 25 marginBottom: 25,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}, },
boxImageCategory: { boxImageCategory: {
width: 70, width: 70,
@@ -99,7 +102,8 @@ export const globalStyles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
iconCategory: { iconCategory: {
width: 50, width: 45,
height: 45,
objectFit: 'contain', objectFit: 'contain',
}, },
categoryName: { categoryName: {
@@ -107,6 +111,15 @@ export const globalStyles = StyleSheet.create({
fontWeight: '500', fontWeight: '500',
marginTop: 5, marginTop: 5,
textAlign: 'center', textAlign: 'center',
height: 40
},
dotWrapper: {
position: 'absolute',
bottom: 10,
left: 0,
right: 0,
flexDirection: 'row',
justifyContent: 'center',
}, },
dot: { dot: {
backgroundColor: 'rgba(0,0,0,.2)', backgroundColor: 'rgba(0,0,0,.2)',
@@ -116,11 +129,7 @@ export const globalStyles = StyleSheet.create({
margin: 3, margin: 3,
}, },
activeDot: { activeDot: {
backgroundColor: '#ff7a00', backgroundColor: '#f9ef06',
width: 40,
height: 10,
borderRadius: 5,
margin: 3,
}, },
BoxBusiness: { BoxBusiness: {
marginTop: 25, marginTop: 25,
@@ -135,10 +144,11 @@ export const globalStyles = StyleSheet.create({
}, },
sliderBusinesses: { sliderBusinesses: {
marginTop: 20, marginTop: 20,
height: 85 width: winWidth - 10,
marginBottom: 10
}, },
logoItem: { logoItem: {
width: itemWidth, width: itemWidth - 3,
height: 40, height: 40,
borderRadius: 8, borderRadius: 8,
backgroundColor: '#fff', backgroundColor: '#fff',
@@ -159,7 +169,7 @@ export const globalStyles = StyleSheet.create({
marginTop: 20, marginTop: 20,
width: winWidth, width: winWidth,
paddingLeft: 10, paddingLeft: 10,
paddingRight: 10, paddingRight: 10
}, },
textBoxProductSaveHome: { textBoxProductSaveHome: {
fontSize: 20, fontSize: 20,
@@ -289,18 +299,13 @@ export const globalStyles = StyleSheet.create({
imgBannerSale: { imgBannerSale: {
width: '100%', width: '100%',
height: '100%', height: '100%',
objectFit: 'cover', resizeMode: 'cover',
borderRadius: 12 borderRadius: 12
}, },
sliderBannerSale: {
flexDirection: 'row',
alignItems: 'center',
height: 200
},
itemBannerSale: { itemBannerSale: {
width: winWidth - 50, width: winWidth - 80,
height: 160, height: 180,
objectFit: 'cover', resizeMode: 'cover',
marginRight: 10 marginRight: 10
}, },
BoxPromotionHome: { BoxPromotionHome: {

1349
yarn.lock

File diff suppressed because it is too large Load Diff