This commit is contained in:
2025-06-05 10:09:29 +07:00
parent eb69d8bbc4
commit bee191d7c6
40 changed files with 69930 additions and 823 deletions

3
.env Normal file
View File

@@ -0,0 +1,3 @@
EXPO_HOME=D:/bestpc_mobile/.expo
EXPO_CACHE_DIR=D:/bestpc_mobile/.expo/cache
EXPO_PROJECT_DIR=D:/bestpc_mobile

53
App.tsx
View File

@@ -1,20 +1,53 @@
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, ScrollView } from "react-native";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { NavigationContainer } from "@react-navigation/native";
import {
createStackNavigator,
StackScreenProps,
} from "@react-navigation/stack";
import {
createDrawerNavigator,
DrawerItemList,
DrawerItem,
DrawerContentScrollView,
} from "@react-navigation/drawer";
import Header from "./src/components/header/Header";
import Footer from "./src/components/footer/Footer";
import HomePage from "./src/screens/HomeScreen";
export default function App() { export default function App() {
return ( return (
<View style={styles.container}> <SafeAreaProvider>
<Text>Open up App.tsx to start working on your app!</Text> <Header />
<StatusBar style="auto" /> <NavigationContainer>
</View> <AllPage />
</NavigationContainer>
{/* <Footer /> */}
</SafeAreaProvider>
); );
} }
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
const AllPage = () => {
return (
<Stack.Navigator
initialRouteName="homepage"
screenOptions={{
headerShown: false, // Ẩn header cho tất cả màn hình
}}
>
<Stack.Screen name="homepage" component={HomePage} />
</Stack.Navigator>
);
};
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, marginLeft: "auto",
backgroundColor: '#fff', marginRight: "auto",
alignItems: 'center', maxWidth: 480,
justifyContent: 'center',
}, },
}); });

37
app.config.js Normal file
View File

@@ -0,0 +1,37 @@
import 'dotenv/config'; // Đảm bảo tải biến môi trường
export default {
expo: {
name: "bestpc_mobile",
slug: "bestpc-mobile",
platforms: ["ios", "android"],
version: "1.0.0",
orientation: "portrait",
splash: {
resizeMode: "contain",
backgroundColor: "#ffffff"
},
updates: {
fallbackToCacheTimeout: 0
},
assetBundlePatterns: [
"**/*"
],
ios: {
supportsTablet: true
},
android: {
adaptiveIcon: {
backgroundColor: "#ffffff"
}
},
packagerOpts: {
sourceExts: ["js", "json", "ts", "tsx"]
},
extra: {
expoHome: process.env.EXPO_HOME,
expoCacheDir: process.env.EXPO_CACHE_DIR,
expoProjectDir: process.env.EXPO_PROJECT_DIR
}
}
};

View File

@@ -1,29 +1,33 @@
{ {
"expo": { "expo": {
"name": "bestpc_mobile", "name": "bestpc_mobile",
"slug": "bestpc_mobile", "slug": "bestpc-mobile",
"platforms": ["ios", "android", "web"],
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": { "splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain", "resizeMode": "contain",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": { "ios": {
"supportsTablet": true "supportsTablet": true
}, },
"android": { "android": {
"adaptiveIcon": { "adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}
}, },
"edgeToEdgeEnabled": true "packagerOpts": {
"sourceExts": ["js", "json", "ts", "tsx"]
}, },
"web": { "extra": {
"favicon": "./assets/favicon.png" "expoHome": "E:/bestpc_mobile/.expo",
"expoCacheDir": "E:/bestpc_mobile/.expo/cache",
"expoProjectDir": "E:/bestpc_mobile"
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
assets/images/icon_2025.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

BIN
assets/images/icon_box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

56761
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,14 +10,34 @@
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"expo": "~53.0.9", "@expo/metro-runtime": "~5.0.4",
"@expo/vector-icons": "^14.1.0",
"@react-native-picker/picker": "^2.11.0",
"@react-navigation/drawer": "^7.3.12",
"@react-navigation/native": "^7.1.9",
"@react-navigation/native-stack": "^7.3.13",
"@react-navigation/stack": "^7.3.2",
"dotenv": "^16.5.0",
"expo-cli": "^4.9.1",
"expo-status-bar": "~2.2.3", "expo-status-bar": "~2.2.3",
"hermes-engine": "^0.11.0",
"metro-runtime": "^0.82.3",
"react": "19.0.0", "react": "19.0.0",
"react-native": "0.79.2" "react-dom": "^19.0.0",
"react-native": "^0.79.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.10.0",
"react-native-swiper": "^1.6.0",
"react-native-web": "^0.20.0",
"react-navigation": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@types/react": "~19.0.10", "@types/react": "~19.0.10",
"@types/react-native": "^0.73.0",
"expo": "^53.0.9",
"typescript": "~5.8.3" "typescript": "~5.8.3"
}, },
"private": true "private": true

View File

@@ -0,0 +1,102 @@
import * as React from "react";
import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
type FooterItemProps = {
label: string;
icon: string;
};
const FooterItem = ({ label, icon }: FooterItemProps) => (
<TouchableOpacity style={styles.footerItem}>
<Text style={styles.footerItemText}>{label}</Text>
<Image
source={{ uri: icon }}
style={styles.footerItemIcon}
resizeMode="contain"
/>
</TouchableOpacity>
);
const Footer = () => {
return (
<View style={styles.container}>
<View style={styles.spacer} />
<View style={styles.content}>
<FooterItem
label="Về chúng tôi"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/0f5cda0be55412240422e2274330e3f6e64023d3?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<FooterItem
label="Tuyển dụng"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/0f5cda0be55412240422e2274330e3f6e64023d3?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<FooterItem
label="Liên kết"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/0f5cda0be55412240422e2274330e3f6e64023d3?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<FooterItem
label="Giới thiệu"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/5263024b89e68e21be091071a684ede939b2cbf7?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<Image
source={{
uri: "https://cdn.builder.io/api/v1/image/assets/TEMP/e7727a56c76a2e7c02cd1646e68e8b20cefb30e1?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756",
}}
style={styles.logo}
resizeMode="contain"
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
alignSelf: "stretch",
marginTop: 28,
width: "100%",
paddingBottom: 21,
},
spacer: {
display: "flex",
minHeight: 10,
width: "100%",
},
content: {
marginTop: 17,
width: "100%",
paddingLeft: 10,
paddingRight: 10,
},
footerItem: {
borderRadius: 4,
display: "flex",
paddingLeft: 9,
paddingRight: 9,
paddingTop: 6,
paddingBottom: 13,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 8,
},
footerItemText: {
color: "#444",
fontFamily: "Shopee Display, -apple-system, Roboto, Helvetica, sans-serif",
fontSize: 13,
fontWeight: "700",
},
footerItemIcon: {
width: 11,
height: 6,
marginTop: 7,
},
logo: {
width: 95,
height: 40,
marginTop: 17,
marginLeft: 10,
},
});
export default Footer;

View File

@@ -0,0 +1,431 @@
import React, { useState } from "react";
import {
View,
StyleSheet,
Image,
Text,
TextInput,
TouchableOpacity,
Dimensions,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { Picker } from "@react-native-picker/picker";
import { NavigationContainer } from "@react-navigation/native";
import { createDrawerNavigator } from "@react-navigation/drawer";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
const Header = () => {
const [openSort, setopenSort] = useState(true);
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 (
<View>
<View style={styles.headerTop}>
<View style={styles.headerTopLeft}>
<TouchableOpacity onPress={toggleDrawer} style={styles.menu}>
<Ionicons style={styles.iconMenu} name="menu-outline" size={30} />
</TouchableOpacity>
<Image
source={require("../../../assets/images/logo.png")}
style={styles.logo}
resizeMode="contain"
/>
</View>
<View style={styles.boxLocation}>
<Ionicons style={styles.location} name="location-outline" size={34} />
<View>
<Text style={styles.textLocation}>Bạn đang </Text>
<View style={styles.boxSort}>
<TouchableOpacity
style={styles.boxSortButton}
onPress={() => setopenSort(!openSort)}
>
<Text style={styles.boxSortButtonActive}> nội</Text>
<Ionicons
style={styles.boxSortButtonIcon}
name="caret-down-outline"
/>
</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 style={styles.searchContainer}>
<View style={styles.searchBar}>
<TextInput
style={styles.searchInput}
placeholder="Tìm kiếm khuyến mãi, cửa hàng, sản phẩm,..."
placeholderTextColor="rgba(169, 169, 169, 1)"
/>
<Ionicons style={styles.iconMenu} name="search-outline" size={19} />
</View>
</View>
{isMenuOpen && (
<View style={styles.overlay} onTouchStart={closeMenu}>
<MainMenu closeMenu={closeMenu} />
</View>
)}
</View>
);
};
const MainMenu = ({ closeMenu }: { closeMenu: () => void }) => {
return (
<View style={styles.menuFixed}>
<View style={styles.headerMenu}>
<Text style={styles.headerMenuText}>Menu</Text>
<TouchableOpacity onPress={closeMenu}>
<Ionicons style={styles.closeMenu} name="close-outline" size={20} />
</TouchableOpacity>
</View>
<View>
<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 style={styles.itemMenu}>
<View style={styles.textLeft}>
<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>
</View>
</View>
);
};
export { MainMenu };
let winWidth = Dimensions.get("window").width; //full width
let winHeight = Dimensions.get("window").height; //full height
const styles = StyleSheet.create({
headerTop: {
flexDirection: "row",
alignItems: "center",
width: winWidth,
paddingRight: 10,
paddingLeft: 10,
paddingTop: 45,
paddingBottom: 10,
backgroundColor: "#462F91",
justifyContent: "space-between",
},
headerTopLeft: {
flexDirection: "row",
alignItems: "center",
gap: 5,
},
logo: {
width: 99,
height: 20,
},
searchContainer: {
width: winWidth,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 9,
paddingBottom: 9,
backgroundColor: "#462F91",
},
searchBar: {
borderRadius: 4,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 6,
paddingBottom: 6,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "#FFFFFF",
},
searchInput: {
flex: 1,
fontSize: 12,
fontFamily: "Roboto, -apple-system, Roboto, Helvetica, sans-serif",
fontWeight: "500",
color: "#000000",
height: 32,
},
searchIconContainer: {
flexDirection: "row",
alignItems: "center",
gap: 9,
},
divider: {
borderColor: "rgba(179, 179, 179, 1)",
borderStyle: "solid",
borderWidth: 1,
width: 1,
height: 20,
},
iconMenu: {
color: "#462f91",
},
location: {
color: "#FFFFFF",
width: 34,
height: 34,
},
boxlocalistion: {
height: 20,
width: 40,
color: "#fff",
},
boxLocation: {
flexDirection: "row",
alignItems: "center",
gap: 5,
color: "#FFFFFF",
},
textLocation: {
color: "#FFFFFF",
fontSize: 10,
},
boxSort: {
zIndex: 1,
},
boxSortButton: {
flexDirection: "row",
alignItems: "center",
height: 30,
},
boxSortButtonTextBold: {
fontSize: 14,
fontWeight: "bold",
},
boxSortButtonActive: {
color: "#fff",
},
boxSortButtonIcon: {
marginLeft: 5,
color: "#fff",
},
boxSortList: {
paddingHorizontal: 10,
position: "absolute",
right: 0,
top: 40,
display: "none",
backgroundColor: "#fff",
width: 100,
},
boxSortListActive: {
display: "flex",
},
boxSortItem: {
paddingVertical: 10,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
},
boxSortItemActive: {
borderBottomColor: "#D8262F",
borderBottomWidth: 1,
},
boxSortItemText: {
fontSize: 14,
},
boxSortItemTextActive: {
color: "#D8262F",
},
boxSortItemTextIcon: {
color: "#D8262F",
},
boxFilter: {
width: 70,
height: 30,
alignItems: "center",
flexDirection: "row",
justifyContent: "flex-end",
borderLeftWidth: 1,
borderLeftColor: "#e1e1e1",
},
menu: {
backgroundColor: "#fff",
width: 30,
height: 30,
borderRadius: 4,
alignItems: "center",
justifyContent: "center",
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;

View File

@@ -0,0 +1,198 @@
import React, { useState } from "react";
import {
View,
StyleSheet,
Image,
Text,
TextInput,
TouchableOpacity,
Dimensions,
} from "react-native";
var winWidth = Dimensions.get("window").width; //full width
var winHeight = Dimensions.get("window").height;
const ItemProduct = ({ product }: { product: any }) => {
function formatCurrency(a: number | string): string {
let b = parseFloat(a.toString())
.toFixed(2)
.replace(/(\d)(?=(\d{3})+\.)/g, "$1.")
.toString();
var len = b.length;
b = b.substring(0, len - 3);
return b;
}
return (
<View style={styles.productItem}>
<TouchableOpacity style={styles.productImage}>
<Image
source={{ uri: product.productImage.large }}
style={styles.productImg}
alt={product.productName}
/>
<View style={styles.boxSaleoff}>
<View style={styles.beforeSaleoff}></View>
<Text style={styles.saleoffText}>{product.saleOff}</Text>
</View>
</TouchableOpacity>
<View>
<TouchableOpacity>
<Text numberOfLines={2} style={styles.productName}>
{product.productName}
</Text>
</TouchableOpacity>
<View style={styles.BoxPriceRating}>
<View>
<Text style={styles.price}>{formatCurrency(product.price)}đ</Text>
<Text style={styles.oldPrice}>
{formatCurrency(product.oldPrice)}đ
</Text>
</View>
<View>
<Image
source={
product.review.rate !== undefined
? require(`../../../assets/images/icon_star_${product.review.rate}.png`)
: require("../../../assets/images/icon_star_0.png")
}
style={styles.iconReviewRating}
alt="icon review"
resizeMode="contain"
/>
</View>
</View>
<View style={styles.boxStore}>
<View style={styles.boxIconStore}>
<Image
source={require("../../../assets/images/icon_store_white.png")}
style={styles.iconStore}
alt="icon store"
resizeMode="contain"
/>
</View>
<Text style={styles.storeName}>
{product.toltalStore} cửa hàng bán
</Text>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
productItem: {
width: winWidth / 2 - 20,
marginRight: 10,
marginBottom: 10,
padding: 12,
},
productImage: {
width: winWidth / 2 - 20,
height: 150,
position: "relative",
marginBottom: 5,
borderWidth: 1,
borderColor: "#d3d3d3",
backgroundColor: "#fff",
borderRadius: 8,
},
productImg: {
width: "100%",
height: "100%",
objectFit: "contain",
},
boxSaleoff: {
position: "absolute",
right: 10,
top: 10,
width: 35,
height: 35,
borderRadius: 25,
backgroundColor: "#da251c",
justifyContent: "center",
alignItems: "center",
},
beforeSaleoff: {
position: "absolute",
top: "50%",
left: "50%",
transform: [{ translateY: "-50%" }, { translateX: "-50%" }],
width: 30,
height: 30,
borderWidth: 1,
borderStyle: "dashed",
borderColor: "#fff",
borderRadius: 25,
},
saleoffText: {
fontSize: 8,
fontWeight: 700,
backgroundColor: "#da251c",
borderRadius: 25,
color: "#fff",
paddingLeft: 2,
},
productName: {
fontWeight: 700,
fontSize: 13,
color: "#000",
marginBottom: 5,
},
summary: {
marginTop: 5,
color: "#595959",
},
locahostPro: {
flexDirection: "row",
alignItems: "center",
},
iconMap: {},
BoxPriceRating: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-end",
},
price: {
fontSize: 15,
fontWeight: "bold",
color: "#d80a00",
},
oldPrice: {
fontSize: 13,
textDecorationLine: "line-through",
color: "#595959",
fontWeight: "bold",
},
iconReviewRating: {
width: 58,
height: 11,
marginBottom: 2,
},
boxStore: {
flexDirection: "row",
alignItems: "center",
marginTop: 5,
},
boxIconStore: {
width: 24,
height: 24,
borderRadius: 25,
backgroundColor: "#FF7A00",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
marginRight: 5,
},
iconStore: {
width: 18,
height: 18,
},
storeName: {
fontSize: 13,
fontWeight: "bold",
color: "#000",
},
});
export default ItemProduct;

42
src/data/category.tsx Normal file
View File

@@ -0,0 +1,42 @@
export const categories = [
{
id: 1,
name: "Laptop văn phòng",
image: "../../assets/images/category-laptop.png",
},
{
id: 2,
name: "Tản nhiệt, đèn",
image: "../../assets/images/category-tannhiet.png",
},
{
id: 3,
name: "Linh kiện máy tính",
image: "../../assets/images/category-linhkien.png",
},
{
id: 4,
name: "Bàn phím, chuột",
image: "../../assets/images/category-banphim.png",
},
{
id: 5,
name: "Laptop gaming",
image: "../../assets/images/category-laptop.png",
},
{
id: 6,
name: "Phụ kiện đi kèm",
image: "../../assets/images/category-phukien.png",
},
{
id: 7,
name: "PC đồ họa, gaming",
image: "../../assets/images/category-pc.png",
},
{
id: 8,
name: "Pc văn phòng",
image: "../../assets/images/category-pc.png",
},
];

80
src/data/product.tsx Normal file
View File

@@ -0,0 +1,80 @@
export const products = [
{
id: 1,
productName: "Laptop văn phòng Dell RAM 8G Core i7",
productImage: {
small:
"https://demopc8.hurasoft.com/media/product/75_2038_12546_qvc_pcgaming02.jpg",
large:
"https://demopc8.hurasoft.com/media/product/250_2038_12546_qvc_pcgaming02.jpg",
original:
"https://demopc8.hurasoft.com/media/product/2038_12546_qvc_pcgaming02.jpg",
},
price: 1000000,
oldPrice: 1200000,
saleOff: "20%",
description: "Intel Core i7 / 8GB / 512GB / SSD",
review: {
rate: 5,
total: 1,
},
toltalStore: 12,
},
{
id: 2,
productName: "Laptop văn phòng Dell RAM 8G Core i7",
productImage: {
small: "https://demopc8.hurasoft.com/media/product/75_2027_picture2.jpg",
large: "https://demopc8.hurasoft.com/media/product/250_2027_picture2.jpg",
original: "https://demopc8.hurasoft.com/media/product/2027_picture2.jpg",
},
price: 2000000,
oldPrice: 2200000,
saleOff: "10%",
description: "Intel Core i7 / 8GB / 512GB / SSD",
review: {
rate: 5,
total: 1,
},
toltalStore: 12,
},
{
id: 3,
productName: "Laptop văn phòng Dell RAM 8G Core i7",
productImage: {
small: "https://demopc8.hurasoft.com/media/product/75_2026_cccccccc.jpg",
large: "https://demopc8.hurasoft.com/media/product/250_2026_cccccccc.jpg",
original: "https://demopc8.hurasoft.com/media/product/2026_cccccccc.jpg",
},
description: "Intel Core i7 / 8GB / 512GB / SSD",
price: 2000000,
oldPrice: 2200000,
saleOff: "10%",
review: {
rate: 5,
total: 1,
},
toltalStore: 12,
},
{
id: 4,
productName: "Laptop văn phòng Dell RAM 8G Core i7",
productImage: {
small:
"https://demopc8.hurasoft.com/media/product/75_2008_colorful_igame_geforce_rtx_3060_ti_ultra_oc_white_8g_v_photo_01.jpg",
large:
"https://demopc8.hurasoft.com/media/product/250_2008_colorful_igame_geforce_rtx_3060_ti_ultra_oc_white_8g_v_photo_01.jpg",
original:
"https://demopc8.hurasoft.com/media/product2008_colorful_igame_geforce_rtx_3060_ti_ultra_oc_white_8g_v_photo_01.jpg",
},
price: 3100000,
oldPrice: 3300000,
saleOff: "10%",
description: "Intel Core i7 / 8GB / 512GB / SSD",
review: {
rate: 5,
total: 1,
},
toltalStore: 12,
},
];

5
src/images.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
// src/images.d.ts hoặc ./images.d.ts
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';

View File

@@ -0,0 +1,20 @@
// src/navigation/AppNavigator.tsx
import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import { NavigationContainer } from "@react-navigation/native";
import HomeScreen from "../screens/HomeScreen";
const Stack = createStackNavigator();
const AppNavigator: React.FC = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" />
</Stack.Navigator>
</NavigationContainer>
);
};
export default AppNavigator;

330
src/screens/HomeScreen.tsx Normal file
View File

@@ -0,0 +1,330 @@
// src/screens/HomeScreen.tsx
import React, { useState } from "react";
import {
View,
Text,
StyleSheet,
ScrollView,
Dimensions,
Image,
ImageBackground,
TouchableOpacity,
SafeAreaView,
} from "react-native";
import { globalStyles } from "../styles/globalStyles";
import Swiper from "react-native-swiper";
import { Ionicons } from "@expo/vector-icons";
import { products } from "../data/product";
import ProductItem from "../components/product/ItemProduct";
import { categories } from "../data/category";
var winWidth = Dimensions.get("window").width; //full width
var winHeight = Dimensions.get("window").height; //full height
const ratio = winWidth / 930;
const HomeScreen: React.FC = () => {
return (
<SafeAreaView style={styles.container}>
<ScrollView>
<SliderHome />
<BoxMenuHome />
<BoxProductReviewTop />
<BoxCategoryHome />
</ScrollView>
</SafeAreaView>
);
};
const SliderHome = () => {
return (
<View style={styles.slider}>
<Swiper
style={styles.sliderSwipper}
autoplay={false}
autoplayTimeout={3}
showsButtons={true}
showsPagination={false}
nextButton={
<Ionicons
name="chevron-forward-outline"
size={30}
color="white"
style={styles.buttonNext}
/>
}
prevButton={
<Ionicons
name="chevron-back-outline"
size={30}
color="white"
style={styles.buttonPrev}
/>
}
>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
/>
</View>
</Swiper>
</View>
);
};
const BoxMenuHome = () => {
return (
<View style={styles.boxMenuHome}>
<TouchableOpacity style={styles.MenuItem}>
<View style={styles.boxIconMenu}>
<Image
source={require("../../assets/images/icon_buildpc.png")}
style={styles.iconMenuItem}
/>
</View>
<Text style={styles.textMenu}>Build pc</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.MenuItem}>
<View style={styles.boxIconMenu}>
<Image
source={require("../../assets/images/icon_store.png")}
style={styles.iconMenuItem}
/>
</View>
<Text style={styles.textMenu}>Tìm người bán</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.MenuItem}>
<View style={styles.boxIconMenu}>
<Image
source={require("../../assets/images/icon_location.png")}
style={styles.iconlocationMenuItem}
/>
</View>
<Text style={styles.textMenu}>Đa chỉ sửa chữa</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.MenuItem}>
<View style={styles.boxIconMenu}>
<Image
source={require("../../assets/images/icon_box.png")}
style={styles.iconMenuItem}
/>
</View>
<Text style={styles.textMenu}>Sản phẩm đã lưu</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.MenuItem}>
<View style={styles.boxIconMenu}>
<Image
source={require("../../assets/images/icon_article.png")}
style={styles.iconMenuItem}
/>
</View>
<Text style={styles.textMenu}>Tin rao vặt</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.MenuItem}>
<View style={styles.boxIconMenu}>
<Image
source={require("../../assets/images/icon_question.png")}
style={styles.iconMenuItem}
/>
</View>
<Text style={styles.textMenu}>Hỏi đáp</Text>
</TouchableOpacity>
</View>
);
};
const BoxProductReviewTop = () => {
const [activeIndex, setActiveIndex] = useState(1);
const listTab = [
{
id: 1,
name: "Laptop",
},
{
id: 2,
name: "Màn hình",
},
{
id: 3,
name: "Bàn phím",
},
{
id: 4,
name: "Cpu",
},
{
id: 5,
name: "Chuột",
},
{
id: 6,
name: "Ram",
},
];
return (
<View style={globalStyles.boxProductReviewTop}>
<Text style={globalStyles.textBoxProductReviewTop}>
Sản phẩm đưc đánh giá tốt nhất
</Text>
<Text style={globalStyles.noteTextReviewTop}>
Tổng hợp các sản phẩm hot theo xu hướng nhiều lượt review đánh
giá nhất hiện nay
</Text>
<ScrollView horizontal={true} style={globalStyles.listTab}>
{listTab.map((item, index) => (
<TouchableOpacity
style={[
globalStyles.tabItem,
activeIndex === item.id && globalStyles.activeItem,
]}
key={item.id}
onPress={() => setActiveIndex(item.id)}
>
<Text
style={[
globalStyles.textTabItem,
activeIndex === item.id && globalStyles.activeText,
]}
>
{item.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
<View style={globalStyles.listProduct}>
{products.map((item) => (
<ProductItem key={item.id} product={item} />
))}
</View>
<TouchableOpacity>
<Text style={globalStyles.moreAll}>Xem tất cả</Text>
</TouchableOpacity>
</View>
);
};
const BoxCategoryHome = () => {
return (
<View style={globalStyles.boxCategoryHome}>
<Text style={globalStyles.textBoxCategoryHome}>Tìm theo danh mục</Text>
<View style={globalStyles.listCategoryBox}>
{categories.map((item) => (
<TouchableOpacity key={item.id} style={globalStyles.categoryItem}>
<View style={globalStyles.boxImageCategory}>
<Image
source={require("../../assets/images/category-laptop.png")}
style={globalStyles.iconCategory}
/>
</View>
<Text style={globalStyles.categoryName}>{item.name}</Text>
</TouchableOpacity>
))}
</View>
</View>
);
};
export default HomeScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff",
},
homePage: {
backgroundColor: "#fff",
},
slider: {
width: winWidth,
padding: 10,
borderRadius: 10,
height: ratio * 430,
},
sliderSwipper: {
borderRadius: 10,
},
imgContainer: {
width: winWidth,
height: 220,
borderRadius: 10,
overflow: "hidden",
},
imgSlider: {
width: winWidth,
height: "100%",
},
buttonNext: {
color: "#fff", // Màu chữ cho các nút chuyển
fontSize: 18,
fontWeight: "bold",
},
buttonPrev: {
color: "#fff", // Màu chữ cho các nút chuyển
fontSize: 18,
fontWeight: "bold",
},
boxMenuHome: {
flexDirection: "row",
flexWrap: "wrap",
marginLeft: 20,
marginRight: 20,
gap: 20,
backgroundColor: "#fff",
},
MenuItem: {
width: winWidth / 4 - 25,
},
boxIconMenu: {
width: 50,
height: 50,
marginBottom: 5,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#eeeeee",
borderRadius: 25,
marginLeft: "auto",
marginRight: "auto",
},
textMenu: {
textAlign: "center",
fontSize: 14,
color: "#333",
fontWeight: "bold",
},
iconMenuItem: {
width: 20,
resizeMode: "contain",
},
iconlocationMenuItem: {
width: 13,
resizeMode: "contain",
},
});

108
src/styles/globalStyles.ts Normal file
View File

@@ -0,0 +1,108 @@
// src/styles/globalStyles.ts
import { StyleSheet, Dimensions } from 'react-native';
var winWidth = Dimensions.get("window").width;
export const globalStyles = StyleSheet.create({
boxProductReviewTop: {
backgroundColor: '#fff',
paddingLeft: 10,
paddingRight: 10,
marginTop: 25,
},
textBoxProductReviewTop: {
fontSize: 20,
fontWeight: 'bold',
color: '#0d0d9c',
marginBottom: 8,
textTransform: 'uppercase',
textAlign: 'center',
},
noteTextReviewTop: {
marginBottom: 8,
textAlign: 'center',
fontSize: 12,
color: "#000"
},
listTab: {
flexDirection: 'row',
},
tabItem: {
padding: 10,
width: 100,
marginRight: 10,
textAlign: 'center',
flexDirection: 'row',
justifyContent: 'center',
backgroundColor: '#ececec',
borderRadius: 4,
},
textTabItem: {
fontSize: 15,
},
activeItem: {
backgroundColor: '#462f91',
},
activeText: {
color: '#fff'
},
listProduct: {
marginTop: 10,
marginBottom: 10,
flexDirection: 'row',
flexWrap: 'wrap',
marginLeft: -10,
},
moreAll: {
width: 110,
height: 30,
borderColor: '#FF7A00',
borderWidth: 1,
borderRadius: 20,
textAlign: 'center',
justifyContent: 'center',
alignItems: 'center',
lineHeight: 28,
marginLeft: 'auto',
marginRight: 'auto',
color: '#FF7A00',
fontWeight: 'bold',
},
boxCategoryHome: {
marginTop: 25,
},
textBoxCategoryHome: {
fontSize: 20,
fontWeight: "bold",
color: "#0d0d9c",
marginBottom: 8,
textTransform: "uppercase",
textAlign: "center",
},
listCategoryBox: {
marginTop: 10,
},
categoryItem: {
width: '100%',
},
boxImageCategory: {
width: 70,
height: 70,
borderRadius: "50%",
overflow: 'hidden',
backgroundColor: '#f4f0ff',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
iconCategory: {
width: 50,
objectFit: 'contain',
},
categoryName: {
fontSize: 15,
fontWeight: '500',
marginTop: 5,
textAlign: 'center',
}
});

6
src/types/ProductType.ts Normal file
View File

@@ -0,0 +1,6 @@
// src/types/ProductType.ts
export interface Product {
name: string;
price: string;
description?: string;
}

View File

@@ -1,6 +1,10 @@
{ {
"extends": "expo/tsconfig.base", "extends": "expo/tsconfig.base",
"compilerOptions": { "compilerOptions": {
"strict": true "strict": true,
} "esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*", "assets/**/*"]
} }

12521
yarn.lock

File diff suppressed because it is too large Load Diff