Compare commits
20 Commits
resourcing
...
resourcing
| Author | SHA1 | Date | |
|---|---|---|---|
| 17e6208aae | |||
| f5cf9e1549 | |||
| 7b58e3315f | |||
| 101c9053d8 | |||
| 4a92385d6d | |||
| 7e39133c2f | |||
| e2744f0344 | |||
| 9667065bb3 | |||
| 23ae416f42 | |||
| 8fb37db0db | |||
| 258e20751e | |||
| 564ea68d29 | |||
| 4701fce07c | |||
| d58304a146 | |||
| 5577ef5d1e | |||
| c1bee77629 | |||
| 3849e03a1a | |||
| f0e0ef4b8b | |||
| cd35201e30 | |||
| bff2a89903 |
@@ -1,55 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
//app/(application)/(tabs)/_layout.tsx
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Entypo, Ionicons } from "@expo/vector-icons";
|
||||
import { router, Tabs } from "expo-router";
|
||||
|
||||
export default function TabsLayout() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
headerTitleAlign: "center",
|
||||
tabBarStyle: {
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
tabBarActiveTintColor: MainColor.white,
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen name="index" options={{ href: null }} />
|
||||
<Tabs.Screen
|
||||
name="forum"
|
||||
options={{
|
||||
title: "Forum",
|
||||
tabBarIcon: () => (
|
||||
<Entypo name="chat" size={20} color={MainColor.white} />
|
||||
),
|
||||
headerLeft: () => (
|
||||
<Ionicons name="arrow-back" onPress={() => {router.back()}} size={20} color={MainColor.white} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tabs.Screen
|
||||
name="katalog"
|
||||
options={{
|
||||
title: "Katalog",
|
||||
tabBarIcon: () => (
|
||||
<Entypo name="book" size={20} color={MainColor.white} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="maps"
|
||||
options={{
|
||||
title: "Maps",
|
||||
tabBarIcon: () => (
|
||||
<Entypo name="map" size={20} color={MainColor.white} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
//app/(application)/(tabs)/forum/_layout.tsx
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function ForumLayout() {
|
||||
return<>
|
||||
<Stack>
|
||||
<Stack.Screen name="index" options={{ headerShown: false, }} />
|
||||
</Stack>
|
||||
</>
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import HomeView from "@/components/Home/HomeView";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Stack, useNavigation, useRouter } from "expo-router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Tabs() {
|
||||
// const router = useRouter();
|
||||
// const navigation = useNavigation();
|
||||
// useEffect(() => {
|
||||
// navigation.setOptions({
|
||||
|
||||
// });
|
||||
// }, [navigation]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HomeView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +1,171 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Stack } from "expo-router";
|
||||
import { router, Stack } from "expo-router";
|
||||
|
||||
export default function ApplicationLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: MainColor.darkblue },
|
||||
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
headerTitleAlign: "center",
|
||||
contentStyle: {
|
||||
borderBottomColor: AccentColor.blue,
|
||||
borderBottomWidth: 2,
|
||||
},
|
||||
headerLargeStyle: {
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
headerShadowVisible: false,
|
||||
// headerLargeStyle: {
|
||||
// backgroundColor: MainColor.darkblue,
|
||||
// },
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="(tabs)"
|
||||
name="home"
|
||||
options={{
|
||||
headerShown: false,
|
||||
// title: "iii",
|
||||
// headerLeft: () => (
|
||||
// <Ionicons name="search" size={20} color={MainColor.white} />
|
||||
// ),
|
||||
// headerRight: () => (
|
||||
// <Ionicons name="notifications" size={20} color={MainColor.white} />
|
||||
// ),
|
||||
title: "HIPMI",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="search"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push("/(application)/user-search")}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<Ionicons
|
||||
name="notifications"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push("/(application)/notifications")}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="forum/index"
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="maps/index"
|
||||
options={{
|
||||
title: "Maps",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="marketplace/index"
|
||||
options={{
|
||||
title: "Market Place",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Profile */}
|
||||
<Stack.Screen
|
||||
name="profile"
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Portofolio */}
|
||||
<Stack.Screen
|
||||
name="portofolio"
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Event */}
|
||||
<Stack.Screen
|
||||
name="event/(tabs)"
|
||||
options={{
|
||||
title: "Event",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push("/(application)/home")}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/detail/[id]"
|
||||
options={{
|
||||
title: "Detail",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* User Search */}
|
||||
<Stack.Screen
|
||||
name="user-search/index"
|
||||
options={{
|
||||
title: "Pencarian Pengguna",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Notification */}
|
||||
<Stack.Screen
|
||||
name="notifications/index"
|
||||
options={{
|
||||
title: "Notifikasi",
|
||||
headerLeft: () => (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{/* <Stack.Screen name="forum/index" options={{ title: "Forum", }} /> */}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
||||
239
app/(application)/coba/index.tsx
Normal file
239
app/(application)/coba/index.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
import React from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
|
||||
const { width } = Dimensions.get("window");
|
||||
|
||||
// Sample Screen Components
|
||||
const HomeScreen = () => (
|
||||
<View style={styles.screen}>
|
||||
<Text style={styles.screenTitle}>Selamat Datang!</Text>
|
||||
<Text style={styles.screenText}>Ini adalah halaman utama aplikasi Anda</Text>
|
||||
</View>
|
||||
);
|
||||
const SearchScreen = () => (
|
||||
<View style={styles.screen}>
|
||||
<Text style={styles.screenTitle}>Search Screen</Text>
|
||||
<Text style={styles.screenText}>Cari apa yang Anda butuhkan</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const ProfileScreen = () => (
|
||||
<View style={styles.screen}>
|
||||
<Text style={styles.screenTitle}>Profile Screen</Text>
|
||||
<Text style={styles.screenText}>Informasi profil pengguna</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const NotificationScreen = () => (
|
||||
<View style={styles.screen}>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<View key={index}>
|
||||
<Text style={styles.screenTitle}>Notifications</Text>
|
||||
<Text style={styles.screenText}>Notifikasi terbaru Anda</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
// Custom Tab Component
|
||||
const CustomTab = ({ icon, label, isActive, onPress }: any) => (
|
||||
<TouchableOpacity
|
||||
style={[styles.tabItem, isActive && styles.activeTab]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View
|
||||
style={[styles.iconContainer, isActive && styles.activeIconContainer]}
|
||||
>
|
||||
<Ionicons name={icon} size={24} color={isActive ? "#fff" : "#666"} />
|
||||
</View>
|
||||
<Text style={[styles.tabLabel, isActive && styles.activeTabLabel]}>
|
||||
{label}
|
||||
</Text>
|
||||
{isActive && <View style={styles.activeIndicator} />}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
// Main Custom Tab Navigator
|
||||
const CustomTabNavigator = () => {
|
||||
const [activeTab, setActiveTab] = React.useState(
|
||||
'home'
|
||||
);
|
||||
const [showHome, setShowHome] = React.useState(true);
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: "search",
|
||||
icon: "search-outline",
|
||||
activeIcon: "search",
|
||||
label: "Event",
|
||||
component: SearchScreen,
|
||||
path: "/event",
|
||||
},
|
||||
{
|
||||
id: "notifications",
|
||||
icon: "notifications-outline",
|
||||
activeIcon: "notifications",
|
||||
label: "Forum",
|
||||
component: NotificationScreen,
|
||||
path: "/forum",
|
||||
},
|
||||
{
|
||||
id: "profile",
|
||||
icon: "person-outline",
|
||||
activeIcon: "person",
|
||||
label: "Katalog",
|
||||
component: ProfileScreen,
|
||||
path: "/profile",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
// Function untuk handle tab press
|
||||
const handleTabPress = (tabId: string) => {
|
||||
setActiveTab(tabId);
|
||||
setShowHome(false); // Hide home when any tab is pressed
|
||||
};
|
||||
|
||||
// Determine which component to show
|
||||
const getActiveComponent = () => {
|
||||
if (showHome || activeTab === "home") {
|
||||
return HomeScreen;
|
||||
}
|
||||
// const selectedTab = tabs.find((tab) => tab.id === activeTab);
|
||||
// return selectedTab ? selectedTab.component : HomeScreen;
|
||||
return HomeScreen
|
||||
};
|
||||
|
||||
const ActiveComponent = getActiveComponent();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Content Area */}
|
||||
<ScrollView>
|
||||
<View style={styles.content}>
|
||||
<ActiveComponent />
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* Custom Tab Bar */}
|
||||
<View style={styles.tabBar}>
|
||||
<View style={styles.tabContainer}>
|
||||
{tabs.map((e) => (
|
||||
<CustomTab
|
||||
key={e.id}
|
||||
icon={activeTab === e.id ? e.activeIcon : e.icon}
|
||||
label={e.label}
|
||||
isActive={activeTab === e.id && !showHome}
|
||||
onPress={() => {
|
||||
handleTabPress(e.id);
|
||||
router.push(e.path as any);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#f5f5f5",
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
screen: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 20,
|
||||
},
|
||||
screenTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
marginBottom: 10,
|
||||
},
|
||||
screenText: {
|
||||
fontSize: 16,
|
||||
color: "#666",
|
||||
textAlign: "center",
|
||||
},
|
||||
tabBar: {
|
||||
backgroundColor: "#fff",
|
||||
paddingBottom: 20,
|
||||
paddingTop: 10,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: -2,
|
||||
},
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 3,
|
||||
elevation: 5,
|
||||
},
|
||||
tabContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-around",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
tabItem: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
minWidth: width / 5,
|
||||
position: "relative",
|
||||
},
|
||||
activeTab: {
|
||||
transform: [{ scale: 1.05 }],
|
||||
},
|
||||
iconContainer: {
|
||||
padding: 8,
|
||||
borderRadius: 20,
|
||||
marginBottom: 4,
|
||||
},
|
||||
activeIconContainer: {
|
||||
backgroundColor: "#007AFF",
|
||||
shadowColor: "#007AFF",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 4,
|
||||
},
|
||||
tabLabel: {
|
||||
fontSize: 12,
|
||||
color: "#666",
|
||||
fontWeight: "500",
|
||||
},
|
||||
activeTabLabel: {
|
||||
color: "#007AFF",
|
||||
fontWeight: "600",
|
||||
},
|
||||
activeIndicator: {
|
||||
position: "absolute",
|
||||
bottom: -2,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: "#007AFF",
|
||||
},
|
||||
});
|
||||
|
||||
export default CustomTabNavigator;
|
||||
64
app/(application)/event/(tabs)/_layout.tsx
Normal file
64
app/(application)/event/(tabs)/_layout.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome5, Ionicons } from "@expo/vector-icons";
|
||||
import { Tabs } from "expo-router";
|
||||
|
||||
export default function EventLayout() {
|
||||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: MainColor.yellow,
|
||||
tabBarInactiveTintColor: MainColor.white,
|
||||
tabBarStyle: {
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
// tabBarButton: HapticTab,
|
||||
// tabBarBackground: BlurTabBarBackground,
|
||||
// tabBarStyle: Platform.select({
|
||||
// ios: {
|
||||
// // Use a transparent background on iOS to show the blur effect
|
||||
// position: "absolute",
|
||||
// },
|
||||
// default: {},
|
||||
// }),
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Home",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="home" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="status"
|
||||
options={{
|
||||
title: "Status",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="list" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="kontribusi"
|
||||
options={{
|
||||
title: "Kontribusi",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="extension-puzzle" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="riwayat"
|
||||
options={{
|
||||
title: "Riwayat",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<FontAwesome5 size={20} name="history" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
25
app/(application)/event/(tabs)/index.tsx
Normal file
25
app/(application)/event/(tabs)/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { router } from "expo-router";
|
||||
import { Text, TouchableHighlight, View } from "react-native";
|
||||
|
||||
export default function Event() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<TouchableHighlight onPress={() => router.push("/event/detail/1")}>
|
||||
<View
|
||||
style={{
|
||||
padding: 20,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
borderRadius: 10,
|
||||
borderColor: AccentColor.blue,
|
||||
borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<Text style={GStyles.textLabel}>Event</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
11
app/(application)/event/(tabs)/kontribusi.tsx
Normal file
11
app/(application)/event/(tabs)/kontribusi.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Text } from "react-native";
|
||||
|
||||
export default function Kontribusi() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<Text style={GStyles.textLabel}>Kontribusi</Text>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
11
app/(application)/event/(tabs)/riwayat.tsx
Normal file
11
app/(application)/event/(tabs)/riwayat.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Text } from "react-native";
|
||||
|
||||
export default function Riwayat() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<Text style={GStyles.textLabel}>Riwayat</Text>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
11
app/(application)/event/(tabs)/status.tsx
Normal file
11
app/(application)/event/(tabs)/status.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Text } from "react-native";
|
||||
|
||||
export default function Status() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<Text style={GStyles.textLabel}>Status</Text>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
14
app/(application)/event/detail/[id].tsx
Normal file
14
app/(application)/event/detail/[id].tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { Text } from "react-native";
|
||||
|
||||
export default function DetailEvent() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("id event >", id);
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<Text style={GStyles.textLabel}>Detail Event {id}</Text>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
@@ -2,8 +2,10 @@ import { Text, View } from "react-native";
|
||||
|
||||
export default function Forum() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Tabs Forum</Text>
|
||||
</View>
|
||||
<>
|
||||
<View>
|
||||
<Text>Forum</Text>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import HomeView from "@/components/Home/HomeView";
|
||||
import UiHome from "@/screens/Home/UiHome";
|
||||
|
||||
export default function Application() {
|
||||
return (
|
||||
<>
|
||||
<HomeView />
|
||||
<UiHome />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ export default function Maps() {
|
||||
<View>
|
||||
<Text>Maps</Text>
|
||||
</View>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function Katalog() {
|
||||
export default function MarketPlace() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Katalog</Text>
|
||||
<Text>Market Place</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
9
app/(application)/notifications/index.tsx
Normal file
9
app/(application)/notifications/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function Notifications() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Notifications</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
11
app/(application)/portofolio/[id]/create.tsx
Normal file
11
app/(application)/portofolio/[id]/create.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Text, View } from "react-native";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function PortofolioCreate() {
|
||||
const { id } = useLocalSearchParams();
|
||||
return (
|
||||
<View>
|
||||
<Text>Portofolio Create {id}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
32
app/(application)/portofolio/[id]/index.tsx
Normal file
32
app/(application)/portofolio/[id]/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import BackButton from "@/components/Button/BackButton";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
import { Text } from "react-native";
|
||||
|
||||
export default function Portofolio() {
|
||||
const { id } = useLocalSearchParams();
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{/* Header */}
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Portofolio",
|
||||
headerLeft: () => <BackButton />,
|
||||
// headerRight: () => (
|
||||
// <TouchableOpacity onPress={openDrawer}>
|
||||
// <Ionicons
|
||||
// name="ellipsis-vertical"
|
||||
// size={20}
|
||||
// color={MainColor.yellow}
|
||||
// />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
}}
|
||||
/>
|
||||
<Text style={GStyles.textLabel}>Portofolio {id}</Text>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
26
app/(application)/portofolio/_layout.tsx
Normal file
26
app/(application)/portofolio/_layout.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
import BackButton from "@/components/Button/BackButton";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function PortofolioLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
headerTitleAlign: "center",
|
||||
headerBackButtonDisplayMode: "minimal",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
>
|
||||
{/* <Stack.Screen name="[id]/index" options={{ title: "Portofolio" }} /> */}
|
||||
<Stack.Screen
|
||||
name="[id]/create"
|
||||
options={{ title: "Tambah Portofolio" }}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
105
app/(application)/profile/[id]/edit.tsx
Normal file
105
app/(application)/profile/[id]/edit.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {
|
||||
ButtonCustom,
|
||||
SelectCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { StyleSheet, Text } from "react-native";
|
||||
|
||||
export default function ProfileEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
|
||||
const [nama, setNama] = useState("Bagas Banuna");
|
||||
const [email, setEmail] = useState("bagasbanuna@gmail.com");
|
||||
const [alamat, setAlamat] = useState("Bandar Lampung");
|
||||
|
||||
const [selectedValue, setSelectedValue] = useState<string | number>("");
|
||||
|
||||
const options = [
|
||||
{ label: "React", value: "react" },
|
||||
{ label: "Vue", value: "vue" },
|
||||
{ label: "Angular", value: "angular" },
|
||||
{ label: "Svelte", value: "svelte" },
|
||||
{ label: "Next.js", value: "nextjs" },
|
||||
{ label: "Nuxt.js", value: "nuxtjs" },
|
||||
{ label: "Remix", value: "remix" },
|
||||
{ label: "Sapper", value: "sapper" },
|
||||
{ label: "SvelteKit", value: "sveltekit" },
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
bottomBarComponent={
|
||||
<ButtonCustom
|
||||
disabled={!nama || !email || !alamat || !selectedValue}
|
||||
onPress={() => {
|
||||
console.log("data >>", nama, email, alamat, selectedValue);
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<SelectCustom
|
||||
label="Framework"
|
||||
placeholder="Pilih framework favoritmu"
|
||||
data={options}
|
||||
value={selectedValue}
|
||||
onChange={setSelectedValue}
|
||||
/>
|
||||
{/* {selectedValue && (
|
||||
<Text style={styles.result}>Terpilih: {selectedValue}</Text>
|
||||
)} */}
|
||||
|
||||
<TextInputCustom
|
||||
label="Nama"
|
||||
placeholder="Nama"
|
||||
value={nama}
|
||||
onChangeText={(text) => {
|
||||
setNama(text);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Email"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={(text) => {
|
||||
setEmail(text);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Alamat"
|
||||
placeholder="Alamat"
|
||||
value={alamat}
|
||||
onChangeText={(text) => {
|
||||
setAlamat(text);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
padding: 20,
|
||||
},
|
||||
result: {
|
||||
marginTop: 20,
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
});
|
||||
131
app/(application)/profile/[id]/index.tsx
Normal file
131
app/(application)/profile/[id]/index.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import AlertCustom from "@/components/Alert/AlertCustom";
|
||||
import BackButton from "@/components/Button/BackButton";
|
||||
import DrawerCustom from "@/components/Drawer/DrawerCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { DRAWER_HEIGHT } from "@/constants/constans-value";
|
||||
import Profile_MenuDrawerSection from "@/screens/Profile/MenuDrawerSection";
|
||||
import ProfilSection from "@/screens/Profile/ProfilSection";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Animated, InteractionManager, TouchableOpacity } from "react-native";
|
||||
|
||||
export default function Profile() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
const [showLogoutAlert, setShowLogoutAlert] = useState(false);
|
||||
|
||||
const drawerItems: IMenuDrawerItem[] = [
|
||||
{
|
||||
icon: "create",
|
||||
label: "Edit profile",
|
||||
path: `/(application)/profile/${id}/edit`,
|
||||
},
|
||||
{
|
||||
icon: "camera",
|
||||
label: "Ubah foto profile",
|
||||
path: `/(application)/profile/${id}/update-photo`,
|
||||
},
|
||||
{
|
||||
icon: "image",
|
||||
label: "Ubah latar belakang",
|
||||
path: `/(application)/profile/${id}/update-background`,
|
||||
},
|
||||
{
|
||||
icon: "add-circle",
|
||||
label: "Tambah portofolio",
|
||||
path: `/(application)/portofolio/${id}/create`,
|
||||
},
|
||||
// {
|
||||
// icon: "settings",
|
||||
// label: "Dashboard Admin",
|
||||
// path: `/(application)/profile/dashboard-admin`,
|
||||
// },
|
||||
{ icon: "log-out", label: "Keluar", color: "red", path: "" },
|
||||
];
|
||||
|
||||
// Animasi menggunakan translateY (lebih kompatibel)
|
||||
const drawerAnim = useRef(new Animated.Value(DRAWER_HEIGHT)).current; // mulai di luar bawah layar
|
||||
|
||||
const openDrawer = () => {
|
||||
setIsDrawerOpen(true);
|
||||
Animated.timing(drawerAnim, {
|
||||
toValue: 0,
|
||||
duration: 300,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
};
|
||||
|
||||
const closeDrawer = () => {
|
||||
Animated.timing(drawerAnim, {
|
||||
toValue: DRAWER_HEIGHT, // sesuaikan dengan tinggi drawer Anda
|
||||
duration: 300,
|
||||
useNativeDriver: true,
|
||||
}).start(() => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
setIsDrawerOpen(false); // baru ganti state setelah animasi selesai
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
console.log("User logout");
|
||||
router.replace("/");
|
||||
setShowLogoutAlert(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{/* Header */}
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Profile",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<TouchableOpacity onPress={openDrawer}>
|
||||
<Ionicons
|
||||
name="ellipsis-vertical"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
}}
|
||||
/>
|
||||
<ProfilSection />
|
||||
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer Komponen Eksternal */}
|
||||
<DrawerCustom
|
||||
height={350}
|
||||
isVisible={isDrawerOpen}
|
||||
drawerAnim={drawerAnim}
|
||||
closeDrawer={closeDrawer}
|
||||
>
|
||||
<Profile_MenuDrawerSection
|
||||
drawerItems={drawerItems}
|
||||
setShowLogoutAlert={setShowLogoutAlert}
|
||||
setIsDrawerOpen={setIsDrawerOpen}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Alert Komponen Eksternal */}
|
||||
<AlertCustom
|
||||
isVisible={showLogoutAlert}
|
||||
onLeftPress={() => setShowLogoutAlert(false)}
|
||||
onRightPress={handleLogout}
|
||||
title="Apakah anda yakin ingin keluar?"
|
||||
textLeft="Batal"
|
||||
textRight="Keluar"
|
||||
colorRight={MainColor.red}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
11
app/(application)/profile/[id]/update-background.tsx
Normal file
11
app/(application)/profile/[id]/update-background.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Text, View } from "react-native";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function UpdatePhotoBackground() {
|
||||
const { id } = useLocalSearchParams();
|
||||
return (
|
||||
<View>
|
||||
<Text>Update Photo Background {id}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
11
app/(application)/profile/[id]/update-photo.tsx
Normal file
11
app/(application)/profile/[id]/update-photo.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Text, View } from "react-native";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function UpdatePhotoProfile() {
|
||||
const { id } = useLocalSearchParams();
|
||||
return (
|
||||
<View>
|
||||
<Text>Update Photo Profile {id}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
30
app/(application)/profile/_layout.tsx
Normal file
30
app/(application)/profile/_layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { BackButton } from "@/components";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function ProfileLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: GStyles.headerStyle,
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
headerTitleAlign: "center",
|
||||
headerBackButtonDisplayMode: "minimal",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
>
|
||||
{/* <Stack.Screen name="[id]/index" options={{ headerShown: false }} /> */}
|
||||
<Stack.Screen name="[id]/edit" options={{ title: "Edit Profile" }} />
|
||||
<Stack.Screen
|
||||
name="[id]/update-photo"
|
||||
options={{ title: "Update Foto" }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="[id]/update-background"
|
||||
options={{ title: "Update Latar Belakang" }}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
9
app/(application)/user-search/index.tsx
Normal file
9
app/(application)/user-search/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function UserSearch() {
|
||||
return (
|
||||
<View>
|
||||
<Text>User Search</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
111
app/_layout.tsx
111
app/_layout.tsx
@@ -1,52 +1,71 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { Feather, Ionicons, MaterialIcons } from "@expo/vector-icons";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Stack } from "expo-router";
|
||||
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
|
||||
import Icon from "react-native-vector-icons/FontAwesome";
|
||||
import "react-native-gesture-handler";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<SafeAreaProvider
|
||||
style={{
|
||||
backgroundColor: MainColor.darkblue,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: MainColor.darkblue },
|
||||
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
|
||||
headerTitleAlign: "center",
|
||||
contentStyle: {
|
||||
borderBottomColor: AccentColor.blue,
|
||||
borderBottomWidth: 2,
|
||||
},
|
||||
// headerLargeStyle: {
|
||||
// backgroundColor: MainColor.darkblue,
|
||||
// },
|
||||
// headerShadowVisible: false,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="index"
|
||||
// options={{
|
||||
// title: "Login",
|
||||
// headerStyle: { backgroundColor: MainColor.darkblue },
|
||||
// headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
|
||||
// headerTitleAlign: "center",
|
||||
// headerRight: () => (
|
||||
// <MaterialIcons name="rocket" size={20} color={MainColor.yellow} />
|
||||
// ),
|
||||
// headerLeft: () => (
|
||||
// <Icon name="rocket" size={20} color={MainColor.yellow} />
|
||||
// ),
|
||||
// }}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<Stack.Screen name="verification" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="register" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="(application)" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</SafeAreaProvider>
|
||||
<>
|
||||
<SafeAreaProvider>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: MainColor.darkblue },
|
||||
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
|
||||
headerTitleAlign: "center",
|
||||
// contentStyle: {
|
||||
// borderBottomColor: AccentColor.blue,
|
||||
// borderBottomWidth: 2,
|
||||
// },
|
||||
// headerLargeStyle: {
|
||||
// backgroundColor: MainColor.darkblue,
|
||||
// },
|
||||
// headerShadowVisible: false,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="verification" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="register" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="(application)" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</SafeAreaProvider>
|
||||
</>
|
||||
// <SafeAreaProvider
|
||||
// style={{
|
||||
// backgroundColor: AccentColor.darkblue,
|
||||
// }}
|
||||
// >
|
||||
// <SafeAreaView
|
||||
// edges={[
|
||||
// "bottom",
|
||||
// // "top",
|
||||
// ]}
|
||||
// style={{
|
||||
// flex: 1,
|
||||
// // paddingTop: StatusBar.currentHeight,
|
||||
// // backgroundColor: MainColor.darkblue,
|
||||
// }}
|
||||
// >
|
||||
// <Stack
|
||||
// screenOptions={{
|
||||
// headerStyle: { backgroundColor: MainColor.darkblue },
|
||||
// headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
|
||||
// headerTitleAlign: "center",
|
||||
// // contentStyle: {
|
||||
// // borderBottomColor: AccentColor.blue,
|
||||
// // borderBottomWidth: 2,
|
||||
// // },
|
||||
// // headerLargeStyle: {
|
||||
// // backgroundColor: MainColor.darkblue,
|
||||
// // },
|
||||
// // headerShadowVisible: false,
|
||||
// }}
|
||||
// >
|
||||
// <Stack.Screen name="index" options={{ headerShown: false }} />
|
||||
// <Stack.Screen name="verification" options={{ headerShown: false }} />
|
||||
// <Stack.Screen name="register" options={{ headerShown: false }} />
|
||||
// <Stack.Screen name="(application)" options={{ headerShown: false }} />
|
||||
// </Stack>
|
||||
// </SafeAreaView>
|
||||
// </SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,98 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { globalStyles } from "@/constants/global-styles";
|
||||
import { Stack, useRouter } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import LoginView from "@/screens/Authentication/LoginView";
|
||||
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
|
||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
|
||||
function handleInputValue(phoneNumber: string) {
|
||||
setInputValue(phoneNumber);
|
||||
}
|
||||
|
||||
function handleSelectedCountry(country: ICountry) {
|
||||
setSelectedCountry(country);
|
||||
}
|
||||
|
||||
function handleLogin() {
|
||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
const fixNumber = callingCode + inputValue;
|
||||
console.log(fixNumber);
|
||||
router.push("/verification");
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<ViewWrapper>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<View style={globalStyles.authContainer}>
|
||||
<Text style={globalStyles.authSubTitle}>WELCOME TO</Text>
|
||||
<Spacing height={5} />
|
||||
<Text style={globalStyles.authTitle}>HIPMI BADUNG APPS</Text>
|
||||
<Spacing height={5} />
|
||||
</View>
|
||||
<Spacing height={50} />
|
||||
<Text
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 30,
|
||||
right: 20,
|
||||
fontSize: 10,
|
||||
fontWeight: "thin",
|
||||
fontStyle: "italic",
|
||||
color: MainColor.white,
|
||||
}}
|
||||
>
|
||||
powered by muku.id
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Input dengan prefix teks */}
|
||||
{/* <TextInputCustom
|
||||
label="Nomor Telepon"
|
||||
placeholder="Masukkan nomor"
|
||||
value={phone}
|
||||
onChangeText={setPhone}
|
||||
iconLeft="+62"
|
||||
keyboardType="phone-pad"
|
||||
/> */}
|
||||
|
||||
<PhoneInput
|
||||
value={inputValue}
|
||||
onChangePhoneNumber={handleInputValue}
|
||||
selectedCountry={selectedCountry}
|
||||
onChangeSelectedCountry={handleSelectedCountry}
|
||||
defaultCountry="ID"
|
||||
placeholder="Masukkan nomor"
|
||||
/>
|
||||
|
||||
<Spacing height={20} />
|
||||
|
||||
<ButtonCustom
|
||||
title="Login"
|
||||
backgroundColor={MainColor.yellow}
|
||||
textColor={MainColor.black}
|
||||
radius={10}
|
||||
onPress={handleLogin}
|
||||
/>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<LoginView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,59 +1,9 @@
|
||||
import { Text, View } from "react-native";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { globalStyles } from "@/constants/global-styles";
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import { EvilIcons } from "@expo/vector-icons";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { TextInputCustom } from "@/components/TextInput/TextInputCustom";
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import { useRouter } from "expo-router";
|
||||
import RegisterView from "@/screens/Authentication/RegisterView";
|
||||
|
||||
export default function Register() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<View style={globalStyles.authContainer}>
|
||||
<Text style={globalStyles.authTitle}>REGISTRASI</Text>
|
||||
<Spacing height={30} />
|
||||
<EvilIcons name="user" size={100} color={MainColor.yellow} />
|
||||
<Spacing height={30} />
|
||||
|
||||
<Text style={globalStyles.textLabel}>
|
||||
Anda akan terdaftar dengan nomor
|
||||
</Text>
|
||||
<Text style={globalStyles.textLabel}>+6282xxxxxxxxx</Text>
|
||||
<Spacing height={30} />
|
||||
</View>
|
||||
<TextInputCustom placeholder="Masukkan username" />
|
||||
|
||||
<ButtonCustom
|
||||
title="Daftar"
|
||||
backgroundColor={MainColor.yellow}
|
||||
textColor={MainColor.black}
|
||||
radius={10}
|
||||
onPress={() => (
|
||||
console.log("Success register"),
|
||||
router.push("/(application)/home")
|
||||
)}
|
||||
/>
|
||||
{/* <Spacing height={10} />
|
||||
<ButtonCustom
|
||||
title="Katalog"
|
||||
backgroundColor={MainColor.yellow}
|
||||
textColor={MainColor.black}
|
||||
radius={10}
|
||||
onPress={() => router.push("/(application)/(katalog)")}
|
||||
/> */}
|
||||
</View>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<RegisterView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,9 @@
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { globalStyles } from "@/constants/global-styles";
|
||||
import { useRouter } from "expo-router"
|
||||
import VerificationView from "@/screens/Authentication/VerificationView";
|
||||
|
||||
import { Text, View } from "react-native";
|
||||
import { OtpInput } from "react-native-otp-entry";
|
||||
|
||||
export default function Validasi() {
|
||||
const router = useRouter();
|
||||
export default function Verification() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<View style={globalStyles.authContainer}>
|
||||
<Text style={globalStyles.authTitle}>Verifikasi KOde OTP</Text>
|
||||
<Spacing height={30} />
|
||||
<Text style={globalStyles.textLabel}>Masukan 4 digit kode otp</Text>
|
||||
<Text style={globalStyles.textLabel}>
|
||||
Yang di kirim ke +6282xxxxxxxxx
|
||||
</Text>
|
||||
<Spacing height={30} />
|
||||
<OtpInput
|
||||
numberOfDigits={4}
|
||||
theme={{
|
||||
pinCodeContainerStyle: {
|
||||
backgroundColor: MainColor.login,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: MainColor.yellow,
|
||||
width: 60,
|
||||
height: 60,
|
||||
},
|
||||
containerStyle: {
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Spacing height={30} />
|
||||
<Text style={globalStyles.textLabel}>
|
||||
Tidak menerima kode ?{" "}
|
||||
<Text style={{ ...globalStyles.textLabel }}>Kirim Ulang</Text>
|
||||
</Text>
|
||||
</View>
|
||||
<Spacing height={30} />
|
||||
</View>
|
||||
|
||||
<ButtonCustom
|
||||
title="Verifikasi"
|
||||
backgroundColor={MainColor.yellow}
|
||||
textColor={MainColor.black}
|
||||
radius={10}
|
||||
onPress={() => router.push("/register")}
|
||||
/>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<VerificationView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
BIN
assets/images/dummy/dummy-avatar.png
Normal file
BIN
assets/images/dummy/dummy-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/images/dummy/dummy-image-background.jpg
Normal file
BIN
assets/images/dummy/dummy-image-background.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
26
bun.lock
26
bun.lock
@@ -5,18 +5,20 @@
|
||||
"name": "hipmi-mobile",
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.1.0",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/bottom-tabs": "^7.4.2",
|
||||
"@react-navigation/drawer": "^7.5.2",
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.21",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"expo": "~53.0.12",
|
||||
"expo": "53.0.13",
|
||||
"expo-blur": "~14.1.5",
|
||||
"expo-constants": "~17.1.6",
|
||||
"expo-font": "~13.3.1",
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image": "~2.3.0",
|
||||
"expo-linking": "~7.1.5",
|
||||
"expo-router": "~5.1.0",
|
||||
"expo-router": "~5.1.1",
|
||||
"expo-splash-screen": "~0.30.9",
|
||||
"expo-status-bar": "~2.2.3",
|
||||
"expo-symbols": "~0.4.5",
|
||||
@@ -385,15 +387,17 @@
|
||||
|
||||
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.79.4", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "*", "react-native": "*" } }, "sha512-0Mdcox6e5PTonuM1WIo3ks7MBAa3IDzj0pKnE5xAwSgQ0DJW2P5dYf+KjWmpkE+Yb0w41ZbtXPhKq+U2JJ6C/Q=="],
|
||||
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.3.16", "", { "dependencies": { "@react-navigation/elements": "^2.4.5", "color": "^4.2.3" }, "peerDependencies": { "@react-navigation/native": "^7.1.12", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-BOVtrq5J3zV3T9CeQexX20JrjECWwIgUJj0Uj0DTVuZdvPIf1AHKHDQSWcG6XqDsbqFQu9yCAfRlnt0WwdRP2w=="],
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.4.2", "", { "dependencies": { "@react-navigation/elements": "^2.5.2", "color": "^4.2.3" }, "peerDependencies": { "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-jyBux5l3qqEucY5M/ZWxVvfA8TQu7DVl2gK+xB6iKqRUfLf7dSumyVxc7HemDwGFoz3Ug8dVZFvSMEs+mfrieQ=="],
|
||||
|
||||
"@react-navigation/core": ["@react-navigation/core@7.11.0", "", { "dependencies": { "@react-navigation/routers": "^7.4.1", "escape-string-regexp": "^4.0.0", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-LfYPtxsMjldJ80BBeedaDCN0LE81WU1NP4V9Ia3wSrCPTAXt11y6holaBUrmUMVQVqpEyPRQrjwrT1QkfGKquw=="],
|
||||
|
||||
"@react-navigation/drawer": ["@react-navigation/drawer@7.5.2", "", { "dependencies": { "@react-navigation/elements": "^2.5.2", "color": "^4.2.3", "react-native-drawer-layout": "^4.1.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-1FSo+DyJe2zlIcQBhHcayZCe2aAF2JF+rpn7HnVnTV2HaCEpgJ0/zgHIa9MZByKntp/aSexL9YkDFQhQj2+7ag=="],
|
||||
|
||||
"@react-navigation/elements": ["@react-navigation/elements@2.4.5", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.12", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-rzoQQ07dZGA3h608imB1nAZ2rPw9vZ2xc2K36XSZoV/7IZRDxI4BCIj38Wc4saQaYhfJIoeVssK4+6IwhZBedg=="],
|
||||
|
||||
"@react-navigation/native": ["@react-navigation/native@7.1.12", "", { "dependencies": { "@react-navigation/core": "^7.11.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-ezHzrZN+9SE4Co6/H8MgDWlBxfJbVc5xi8szRi2QW8eJlsZsAvgGqtKs4YECraV4Yr9zW8RCzNuUxYiQiPMtEQ=="],
|
||||
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.3.17", "", { "dependencies": { "@react-navigation/elements": "^2.4.5", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.12", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-nPJv5E/7MYZ5NPD0sFP9DjSawEQ1fYXe0sCZT1C5EHGWK08p3+5HkVScXofDDqUtI/q6UU23uE1YoxVWgRbDRw=="],
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.3.21", "", { "dependencies": { "@react-navigation/elements": "^2.5.2", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-oNNZHzkxILEibesamRKLodfXAaDOUvMBITKXLLeblDxnTAyIB/Kf7CmV+8nwkdAgV04kURTxV0SQI+d8gLUm6g=="],
|
||||
|
||||
"@react-navigation/routers": ["@react-navigation/routers@7.4.1", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-42mZrMzQ0LfKxUb5OHIurYrPYyRsXFLolucILrvm21f0O40Sw0Ufh1bnn/jRqnxZZu7wvpUGIGYM8nS9zVE1Aw=="],
|
||||
|
||||
@@ -801,7 +805,7 @@
|
||||
|
||||
"exec-async": ["exec-async@2.2.0", "", {}, "sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw=="],
|
||||
|
||||
"expo": ["expo@53.0.12", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.24.15", "@expo/config": "~11.0.10", "@expo/config-plugins": "~10.0.3", "@expo/fingerprint": "0.13.1", "@expo/metro-config": "0.20.15", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~13.2.1", "expo-asset": "~11.1.5", "expo-constants": "~17.1.6", "expo-file-system": "~18.1.10", "expo-font": "~13.3.1", "expo-keep-awake": "~14.1.4", "expo-modules-autolinking": "2.1.12", "expo-modules-core": "2.4.0", "react-native-edge-to-edge": "1.6.0", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview"], "bin": { "expo": "bin/cli", "expo-modules-autolinking": "bin/autolinking", "fingerprint": "bin/fingerprint" } }, "sha512-dtmED749hkxDWCcvtD++tb8bAm3Twv8qnUOXzVyXA5owNG0mwDIz0HveJTpWK1UzkY4HcTVRezDf0tflZJ+JXQ=="],
|
||||
"expo": ["expo@53.0.13", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.24.15", "@expo/config": "~11.0.10", "@expo/config-plugins": "~10.0.3", "@expo/fingerprint": "0.13.1", "@expo/metro-config": "0.20.15", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~13.2.1", "expo-asset": "~11.1.5", "expo-constants": "~17.1.6", "expo-file-system": "~18.1.10", "expo-font": "~13.3.1", "expo-keep-awake": "~14.1.4", "expo-modules-autolinking": "2.1.12", "expo-modules-core": "2.4.0", "react-native-edge-to-edge": "1.6.0", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-QDdEEbFErUmm2IHR/UPKKIRLN3z5MmN2QLx0aPlOEGOx295buSUE42u6f7TppkgJn0BUX3f7wFaHRo86+G+Trg=="],
|
||||
|
||||
"expo-asset": ["expo-asset@11.1.5", "", { "dependencies": { "@expo/image-utils": "^0.7.4", "expo-constants": "~17.1.5" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-GEQDCqC25uDBoXHEnXeBuwpeXvI+3fRGvtzwwt0ZKKzWaN+TgeF8H7c76p3Zi4DfBMFDcduM0CmOvJX+yCCLUQ=="],
|
||||
|
||||
@@ -825,7 +829,7 @@
|
||||
|
||||
"expo-modules-core": ["expo-modules-core@2.4.0", "", { "dependencies": { "invariant": "^2.2.4" } }, "sha512-Ko5eHBdvuMykjw9P9C9PF54/wBSsGOxaOjx92I5BwgKvEmUwN3UrXFV4CXzlLVbLfSYUQaLcB220xmPfgvT7Fg=="],
|
||||
|
||||
"expo-router": ["expo-router@5.1.0", "", { "dependencies": { "@expo/metro-runtime": "5.0.4", "@expo/server": "^0.6.2", "@radix-ui/react-slot": "1.2.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "client-only": "^0.0.1", "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "schema-utils": "^4.0.1", "semver": "~7.6.3", "server-only": "^0.0.1", "shallowequal": "^1.1.0" }, "peerDependencies": { "@react-navigation/drawer": "^7.3.9", "expo": "*", "expo-constants": "*", "expo-linking": "*", "react-native-reanimated": "*", "react-native-safe-area-context": "*", "react-native-screens": "*" }, "optionalPeers": ["@react-navigation/drawer"] }, "sha512-mnKpw35W6kKPpZm+ZxQei6HGUx2JO3znzqJZInzqrTZMgfAcHGgvP9AQFjg/Qi/Qy1CxunB9aQnqE9JPbSwbpw=="],
|
||||
"expo-router": ["expo-router@5.1.1", "", { "dependencies": { "@expo/metro-runtime": "5.0.4", "@expo/server": "^0.6.3", "@radix-ui/react-slot": "1.2.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.3.10", "client-only": "^0.0.1", "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "schema-utils": "^4.0.1", "semver": "~7.6.3", "server-only": "^0.0.1", "shallowequal": "^1.1.0" }, "peerDependencies": { "@react-navigation/drawer": "^7.3.9", "expo": "*", "expo-constants": "*", "expo-linking": "*", "react-native-reanimated": "*", "react-native-safe-area-context": "*", "react-native-screens": "*" }, "optionalPeers": ["@react-navigation/drawer", "react-native-reanimated"] }, "sha512-KYAp/SwkPVgY+8OI+UPGENZG4j+breoOMXmZ01s99U7X0dpSihKGSNpK6LkEoU31MXMLuUHGYYwD00zm9aqcSg=="],
|
||||
|
||||
"expo-splash-screen": ["expo-splash-screen@0.30.9", "", { "dependencies": { "@expo/prebuild-config": "^9.0.6" }, "peerDependencies": { "expo": "*" } }, "sha512-curHUaZxUTZ2dWvz32ao3xPv5mJr1LBqn5V8xm/IULAehB9RGCn8iKiROMN1PYebSG+56vPMuJmBm9P+ayvJpA=="],
|
||||
|
||||
@@ -1351,6 +1355,8 @@
|
||||
|
||||
"react-native-country-codes-picker": ["react-native-country-codes-picker@2.3.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-dDQhd0bVvlmgb84NPhTOmTk5UVYPHtk3lqZI+BPb61H1rC2IDrTvPWENg6u1DMGliqWHQDBYpeH37zvxxQL71w=="],
|
||||
|
||||
"react-native-drawer-layout": ["react-native-drawer-layout@4.1.11", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0" } }, "sha512-31gilubSKPLToy31/bb0hhgOOenHYJq4JC7g/JkIEqBqSWzoCgiOlccDHlBRG+MV37UtXZnJN2spj3VusdCd4A=="],
|
||||
|
||||
"react-native-edge-to-edge": ["react-native-edge-to-edge@1.6.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-2WCNdE3Qd6Fwg9+4BpbATUxCLcouF6YRY7K+J36KJ4l3y+tWN6XCqAC4DuoGblAAbb2sLkhEDp4FOlbOIot2Og=="],
|
||||
|
||||
"react-native-gesture-handler": ["react-native-gesture-handler@2.24.0", "", { "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw=="],
|
||||
@@ -1767,6 +1773,12 @@
|
||||
|
||||
"@react-native/dev-middleware/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"@react-navigation/bottom-tabs/@react-navigation/elements": ["@react-navigation/elements@2.5.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-aGC3ukF5+lXuiF5bK7bJyRuWCE+Tk4MZ3GoQpAb7u7+m0KmsquliDhj4UCWEUU5kUoCeoRAUvv+1lKcYKf+WTQ=="],
|
||||
|
||||
"@react-navigation/drawer/@react-navigation/elements": ["@react-navigation/elements@2.5.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-aGC3ukF5+lXuiF5bK7bJyRuWCE+Tk4MZ3GoQpAb7u7+m0KmsquliDhj4UCWEUU5kUoCeoRAUvv+1lKcYKf+WTQ=="],
|
||||
|
||||
"@react-navigation/native-stack/@react-navigation/elements": ["@react-navigation/elements@2.5.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-aGC3ukF5+lXuiF5bK7bJyRuWCE+Tk4MZ3GoQpAb7u7+m0KmsquliDhj4UCWEUU5kUoCeoRAUvv+1lKcYKf+WTQ=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
127
components/Alert/AlertCustom.tsx
Normal file
127
components/Alert/AlertCustom.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { TEXT_SIZE_LARGE } from "@/constants/constans-value";
|
||||
import React from "react";
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||
|
||||
interface AlertCustomProps {
|
||||
isVisible: boolean;
|
||||
onLeftPress: () => void;
|
||||
onRightPress: () => void;
|
||||
title?: string;
|
||||
message?: string;
|
||||
textLeft?: string;
|
||||
textRight?: string;
|
||||
colorLeft?: string;
|
||||
colorRight?: string;
|
||||
}
|
||||
|
||||
export default function AlertCustom({
|
||||
isVisible,
|
||||
onLeftPress,
|
||||
onRightPress,
|
||||
title,
|
||||
message,
|
||||
textLeft,
|
||||
textRight,
|
||||
colorLeft,
|
||||
colorRight,
|
||||
}: AlertCustomProps) {
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.alertBox}>
|
||||
{title && message ? (
|
||||
<>
|
||||
<Text style={styles.alertTitle}>{title}</Text>
|
||||
<Text style={styles.alertMessage}>{message}</Text>
|
||||
</>
|
||||
) : title ? (
|
||||
<Text style={styles.alertTitle}>{title}</Text>
|
||||
) : (
|
||||
<Text style={styles.alertMessage}>{message}</Text>
|
||||
)}
|
||||
<View style={styles.alertButtons}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.alertButton,
|
||||
colorLeft ? { backgroundColor: colorLeft } : styles.leftButton,
|
||||
]}
|
||||
onPress={onLeftPress}
|
||||
>
|
||||
<Text style={styles.buttonText}>{textLeft}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.alertButton,
|
||||
colorRight ? { backgroundColor: colorRight } : styles.rightButton,
|
||||
]}
|
||||
onPress={onRightPress}
|
||||
>
|
||||
<Text style={styles.buttonText}>{textRight}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 999,
|
||||
},
|
||||
alertBox: {
|
||||
width: "90%",
|
||||
backgroundColor: MainColor.darkblue,
|
||||
borderColor: AccentColor.blue,
|
||||
borderWidth: 1,
|
||||
borderRadius: 10,
|
||||
padding: 20,
|
||||
alignItems: "center",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.25,
|
||||
elevation: 5,
|
||||
},
|
||||
alertTitle: {
|
||||
fontSize: TEXT_SIZE_LARGE,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 20,
|
||||
color: MainColor.white,
|
||||
},
|
||||
alertMessage: {
|
||||
textAlign: "center",
|
||||
marginBottom: 20,
|
||||
color: MainColor.white,
|
||||
},
|
||||
alertButtons: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
},
|
||||
alertButton: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
marginHorizontal: 5,
|
||||
alignItems: "center",
|
||||
},
|
||||
leftButton: {
|
||||
backgroundColor: "gray",
|
||||
},
|
||||
rightButton: {
|
||||
backgroundColor: MainColor.green,
|
||||
},
|
||||
buttonText: {
|
||||
color: "white",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
});
|
||||
55
components/Avatar/AvatarCustom.tsx
Normal file
55
components/Avatar/AvatarCustom.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Image, ImageSourcePropType, StyleSheet } from "react-native";
|
||||
|
||||
type Size = "base" | "sm" | "md" | "lg";
|
||||
|
||||
interface AvatarCustomProps {
|
||||
source?: ImageSourcePropType;
|
||||
size?: Size;
|
||||
}
|
||||
|
||||
const sizeMap = {
|
||||
base: 40,
|
||||
sm: 60,
|
||||
md: 80,
|
||||
lg: 100,
|
||||
};
|
||||
|
||||
export default function AvatarCustom({
|
||||
source = require("@/assets/images/dummy/dummy-avatar.png"),
|
||||
size = "base",
|
||||
}: AvatarCustomProps) {
|
||||
const dimension = sizeMap[size];
|
||||
|
||||
return (
|
||||
<Image
|
||||
source={source}
|
||||
style={[
|
||||
styles.overlappingAvatar,
|
||||
{
|
||||
width: dimension,
|
||||
height: dimension,
|
||||
borderRadius: dimension / 2,
|
||||
},
|
||||
]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
overlappingAvatar: {
|
||||
borderWidth: 2,
|
||||
borderColor: "#fff",
|
||||
backgroundColor: MainColor.white,
|
||||
// shadowColor: "#000",
|
||||
// shadowOffset: { width: 0, height: 2 },
|
||||
// shadowOpacity: 0.2,
|
||||
shadowRadius: 3,
|
||||
elevation: 3,
|
||||
},
|
||||
});
|
||||
59
components/Box/BaseBox.tsx
Normal file
59
components/Box/BaseBox.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { AccentColor } from "@/constants/color-palet";
|
||||
import { StyleProp, TouchableHighlight, View, ViewStyle } from "react-native";
|
||||
|
||||
interface BaseBoxProps {
|
||||
children: React.ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
onPress?: () => void;
|
||||
marginBottom?: number;
|
||||
padding?: number;
|
||||
paddingInline?: number;
|
||||
}
|
||||
|
||||
export default function BaseBox({
|
||||
children,
|
||||
style,
|
||||
onPress,
|
||||
marginBottom = 16,
|
||||
padding = 12,
|
||||
}: BaseBoxProps) {
|
||||
return (
|
||||
<>
|
||||
{onPress ? (
|
||||
<TouchableHighlight
|
||||
onPress={onPress}
|
||||
style={[
|
||||
{
|
||||
backgroundColor: AccentColor.darkblue,
|
||||
borderColor: AccentColor.blue,
|
||||
borderWidth: 1,
|
||||
borderRadius: 10,
|
||||
marginBottom,
|
||||
padding,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
// activeOpacity={0.7}
|
||||
>
|
||||
<View>{children}</View>
|
||||
</TouchableHighlight>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
backgroundColor: AccentColor.darkblue,
|
||||
borderColor: AccentColor.blue,
|
||||
borderWidth: 1,
|
||||
borderRadius: 10,
|
||||
marginBottom,
|
||||
padding,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
16
components/Button/BackButton.tsx
Normal file
16
components/Button/BackButton.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router } from "expo-router";
|
||||
|
||||
const BackButton = () => {
|
||||
return (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackButton;
|
||||
@@ -2,32 +2,44 @@
|
||||
|
||||
import React from "react";
|
||||
import { Text, TouchableOpacity } from "react-native";
|
||||
import buttonStyles from "./buttonStyles";
|
||||
import buttonStyles from "./buttonCustomStyles";
|
||||
import { radiusMap } from "@/constants/radius-value";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
|
||||
// Import radiusMap
|
||||
|
||||
|
||||
// Definisi type untuk radius
|
||||
type RadiusType = keyof typeof radiusMap | number;
|
||||
|
||||
// Definisi props dengan TypeScript
|
||||
interface ButtonProps {
|
||||
onPress: () => void;
|
||||
children?: React.ReactNode;
|
||||
onPress?: () => void;
|
||||
title?: string;
|
||||
backgroundColor?: string;
|
||||
textColor?: string;
|
||||
radius?: number;
|
||||
radius?: RadiusType; // ← bisa string enum atau number
|
||||
disabled?: boolean;
|
||||
iconLeft?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ButtonCustom: React.FC<ButtonProps> = ({
|
||||
children,
|
||||
onPress,
|
||||
title = "Button",
|
||||
backgroundColor = "#007AFF",
|
||||
textColor = "#FFFFFF",
|
||||
radius = 8,
|
||||
backgroundColor = MainColor.yellow,
|
||||
textColor = MainColor.black,
|
||||
radius = "full", // default md
|
||||
disabled = false,
|
||||
iconLeft,
|
||||
}) => {
|
||||
const borderRadius =
|
||||
typeof radius === "number" ? radius : radiusMap[radius ?? "md"]; // fallback ke 'md'
|
||||
|
||||
const styles = buttonStyles({
|
||||
backgroundColor,
|
||||
textColor,
|
||||
borderRadius: radius,
|
||||
borderRadius,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -35,10 +47,11 @@ const ButtonCustom: React.FC<ButtonProps> = ({
|
||||
style={[styles.button, disabled && styles.disabled]}
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
{/* Render icon jika tersedia */}
|
||||
{iconLeft && iconLeft}
|
||||
<Text style={styles.buttonText}>{title}</Text>
|
||||
<Text style={styles.buttonText}>{children || title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
163
components/Drawer/DrawerCustom.tsx
Normal file
163
components/Drawer/DrawerCustom.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
Animated,
|
||||
PanResponder,
|
||||
StyleSheet,
|
||||
View,
|
||||
InteractionManager,
|
||||
} from "react-native";
|
||||
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { DRAWER_HEIGHT } from "@/constants/constans-value";
|
||||
|
||||
interface DrawerCustomProps {
|
||||
children?: React.ReactNode;
|
||||
height?: number;
|
||||
isVisible: boolean;
|
||||
drawerAnim: Animated.Value;
|
||||
closeDrawer: () => void;
|
||||
// openLogoutAlert: () => void;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param drawerAnim
|
||||
* @example const drawerAnim = useRef(new Animated.Value(DRAWER_HEIGHT)).current; // mulai di luar bawah layar
|
||||
*/
|
||||
export default function DrawerCustom({
|
||||
children,
|
||||
height,
|
||||
isVisible,
|
||||
drawerAnim,
|
||||
closeDrawer,
|
||||
}: // openLogoutAlert,
|
||||
DrawerCustomProps) {
|
||||
const panResponder = useRef(
|
||||
PanResponder.create({
|
||||
onMoveShouldSetPanResponder: (_, gestureState) => {
|
||||
return gestureState.dy > 10; // gesek ke bawah
|
||||
},
|
||||
onPanResponderMove: (_, gestureState) => {
|
||||
const offset = gestureState.dy;
|
||||
if (offset >= 0 && offset <= DRAWER_HEIGHT) {
|
||||
drawerAnim.setValue(offset);
|
||||
}
|
||||
},
|
||||
onPanResponderRelease: (_, gestureState) => {
|
||||
if (gestureState.dy > 200) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
closeDrawer();
|
||||
});
|
||||
} else {
|
||||
Animated.spring(drawerAnim, {
|
||||
toValue: 0,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}
|
||||
},
|
||||
})
|
||||
).current;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Overlay Gelap */}
|
||||
<View
|
||||
style={styles.overlay}
|
||||
pointerEvents="auto"
|
||||
onTouchStart={() => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
closeDrawer();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Custom Bottom Drawer */}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.drawer,
|
||||
{
|
||||
height: height || DRAWER_HEIGHT,
|
||||
transform: [{ translateY: drawerAnim }],
|
||||
},
|
||||
]}
|
||||
{...panResponder.panHandlers}
|
||||
>
|
||||
<View
|
||||
style={[styles.headerBar, { backgroundColor: MainColor.white }]}
|
||||
/>
|
||||
|
||||
{children}
|
||||
|
||||
{/* <TouchableOpacity
|
||||
style={styles.menuItem}
|
||||
onPress={() => {
|
||||
alert("Pilihan 1 diklik");
|
||||
closeDrawer();
|
||||
}}
|
||||
>
|
||||
<Text>Menu Item 1</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.menuItem}
|
||||
onPress={() => {
|
||||
alert("Pilihan 2 diklik");
|
||||
closeDrawer();
|
||||
}}
|
||||
>
|
||||
<Text>Menu Item 2</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.menuItem}
|
||||
onPress={() => alert("Logout via Alert bawaan")}
|
||||
>
|
||||
<Text style={{ color: "red" }}>Keluar</Text>
|
||||
</TouchableOpacity> */}
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "black",
|
||||
opacity: 0.6,
|
||||
zIndex: 998,
|
||||
},
|
||||
drawer: {
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: AccentColor.darkblue,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
padding: 20,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.2,
|
||||
elevation: 5,
|
||||
zIndex: 999,
|
||||
},
|
||||
headerBar: {
|
||||
width: 40,
|
||||
height: 5,
|
||||
backgroundColor: MainColor.white,
|
||||
borderRadius: 5,
|
||||
alignSelf: "center",
|
||||
marginVertical: 10,
|
||||
},
|
||||
menuItem: {
|
||||
padding: 15,
|
||||
},
|
||||
});
|
||||
57
components/Drawer/MenuDrawerDynamicGird.tsx
Normal file
57
components/Drawer/MenuDrawerDynamicGird.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_MEDIUM, TEXT_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { View, TouchableOpacity, Text, StyleSheet } from "react-native";
|
||||
|
||||
const MenuDrawerDynamicGrid = ({ data, columns = 3, onPressItem }: any) => {
|
||||
const numColumns = columns;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{data.map((item: any, index: any) => (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
style={[styles.itemContainer, { flexBasis: `${100 / numColumns}%` }]}
|
||||
onPress={() => onPressItem?.(item)}
|
||||
>
|
||||
<View style={styles.iconContainer}>
|
||||
<Ionicons
|
||||
name={item.icon}
|
||||
size={ICON_SIZE_MEDIUM}
|
||||
color={item.color || MainColor.white}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.label}>{item.label}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuDrawerDynamicGrid;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
padding: 0,
|
||||
},
|
||||
itemContainer: {
|
||||
padding: 10,
|
||||
alignItems: "center",
|
||||
},
|
||||
iconContainer: {
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 28,
|
||||
backgroundColor: AccentColor.blue,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
label: {
|
||||
marginTop: 10,
|
||||
fontSize: TEXT_SIZE_SMALL,
|
||||
textAlign: "center",
|
||||
color: MainColor.white,
|
||||
},
|
||||
});
|
||||
113
components/Grid/GridCustom.tsx
Normal file
113
components/Grid/GridCustom.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { createContext, useContext, useMemo } from "react";
|
||||
import { StyleSheet, useWindowDimensions, View, ViewStyle } from "react-native";
|
||||
|
||||
// Tipe untuk span
|
||||
type SpanValue = {
|
||||
base?: number;
|
||||
md?: number;
|
||||
lg?: number;
|
||||
};
|
||||
|
||||
// Props untuk Grid.Col
|
||||
interface ColProps {
|
||||
children: React.ReactNode;
|
||||
span?: number | SpanValue;
|
||||
style?: ViewStyle;
|
||||
}
|
||||
|
||||
// Props untuk Grid
|
||||
interface GridProps {
|
||||
children: React.ReactNode;
|
||||
gap?: number;
|
||||
columns?: number;
|
||||
containerStyle?: ViewStyle;
|
||||
}
|
||||
|
||||
// Context untuk menyimpan konfigurasi grid
|
||||
type GridContextType = {
|
||||
gap: number;
|
||||
columns: number;
|
||||
};
|
||||
|
||||
const GridContext = createContext<GridContextType>({
|
||||
gap: 0,
|
||||
columns: 12,
|
||||
});
|
||||
|
||||
const useGrid = () => useContext(GridContext);
|
||||
|
||||
// Helper untuk menentukan span berdasarkan lebar layar
|
||||
const getSpan = (
|
||||
spanProp: number | SpanValue | undefined,
|
||||
width: number
|
||||
): number => {
|
||||
if (typeof spanProp === "number") return spanProp;
|
||||
|
||||
const span = spanProp || { base: 12 };
|
||||
|
||||
if (width >= 992 && span.lg) return span.lg;
|
||||
if (width >= 768 && span.md) return span.md;
|
||||
return span.base ?? 12;
|
||||
};
|
||||
|
||||
// Grid Component
|
||||
const GridComponent: React.FC<GridProps> = ({
|
||||
children,
|
||||
gap = 6,
|
||||
columns = 12,
|
||||
containerStyle,
|
||||
}) => {
|
||||
const contextValue = useMemo(() => ({ gap, columns }), [gap, columns]);
|
||||
|
||||
return (
|
||||
<GridContext.Provider value={contextValue}>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ marginHorizontal: -gap / 2 },
|
||||
containerStyle,
|
||||
]}
|
||||
>
|
||||
{React.Children.map(children, (child) =>
|
||||
React.isValidElement(child)
|
||||
? React.cloneElement(child as React.ReactElement<ColProps>, {})
|
||||
: child
|
||||
)}
|
||||
</View>
|
||||
</GridContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Grid.Col Component
|
||||
const Col: React.FC<ColProps> = ({ children, span, style }) => {
|
||||
const { gap, columns } = useGrid();
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
const colSpan = getSpan(span, width);
|
||||
const margin = gap / 2;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
col: {
|
||||
flexBasis: `${(100 / columns) * colSpan}%`,
|
||||
paddingVertical: margin,
|
||||
// marginBottom: gap,
|
||||
marginBlock: gap,
|
||||
},
|
||||
});
|
||||
|
||||
return <View style={[styles.col, style]}>{children}</View>;
|
||||
};
|
||||
|
||||
// Export bersama-sama
|
||||
const Grid = Object.assign(GridComponent, { Col });
|
||||
|
||||
export default Grid;
|
||||
|
||||
// Styles
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
});
|
||||
@@ -1,132 +0,0 @@
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { globalStyles } from "@/constants/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Image } from "expo-image";
|
||||
import { useNavigation } from "expo-router";
|
||||
import { useEffect } from "react";
|
||||
import { ScrollView, Text, TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/FontAwesome";
|
||||
import DynamicTruncatedText from "../_ShareComponent/TruncatedText";
|
||||
import { stylesHome } from "./homeViewStyle";
|
||||
|
||||
export default function HomeView() {
|
||||
const navigation = useNavigation();
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: "HIPMI",
|
||||
headerLeft: () => (
|
||||
<Ionicons name="search" size={20} color={MainColor.white} />
|
||||
),
|
||||
headerRight: () => (
|
||||
<Ionicons name="notifications" size={20} color={MainColor.white} />
|
||||
),
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
|
||||
<View style={globalStyles.mainContainer}>
|
||||
<Spacing height={20} />
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require("@/assets/images/constants/home-hipmi.png")}
|
||||
// placeholder={{ blurhash: "" }}
|
||||
contentFit="cover"
|
||||
transition={1000}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 120,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Spacing height={10} />
|
||||
|
||||
{/* Grid Section */}
|
||||
<View style={stylesHome.gridContainer}>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="analytics" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Event</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="share" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Collaboration</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="cube" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Voting</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="heart" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Crowdfunding</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Spacing height={10} />
|
||||
|
||||
{/* Job Vacancy Section */}
|
||||
<View style={stylesHome.jobVacancyContainer}>
|
||||
<View style={stylesHome.jobVacancyHeader}>
|
||||
<Icon name="briefcase" size={24} color="white" />
|
||||
<Text style={stylesHome.jobVacancyTitle}>Job Vacancy</Text>
|
||||
</View>
|
||||
|
||||
<View style={stylesHome.vacancyList}>
|
||||
{/* Vacancy Item 1 */}
|
||||
<View style={stylesHome.vacancyItem}>
|
||||
{/* <Icon name="user" size={20} color="#FFD700" /> */}
|
||||
<View style={stylesHome.vacancyDetails}>
|
||||
<DynamicTruncatedText
|
||||
text="Bagas_banuna"
|
||||
fontSize={14}
|
||||
fontFamily="System"
|
||||
style={stylesHome.vacancyName}
|
||||
/>
|
||||
<Spacing height={5} />
|
||||
<DynamicTruncatedText
|
||||
text="Dicari perawat kucing"
|
||||
fontSize={12}
|
||||
fontFamily="System"
|
||||
style={stylesHome.vacancyDescription}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Vacancy Item 2 */}
|
||||
<View style={stylesHome.vacancyItem}>
|
||||
{/* <Icon name="user" size={20} color="#FFD700" /> */}
|
||||
<View style={stylesHome.vacancyDetails}>
|
||||
<DynamicTruncatedText
|
||||
text="fibramarcell"
|
||||
fontSize={14}
|
||||
fontFamily="System"
|
||||
style={stylesHome.vacancyName}
|
||||
/>
|
||||
<Spacing height={5} />
|
||||
<DynamicTruncatedText
|
||||
text="Di Butuhkan Seorang..."
|
||||
fontSize={12}
|
||||
fontFamily="System"
|
||||
style={stylesHome.vacancyDescription}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Spacing height={20} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
123
components/Select/SelectCustom.tsx
Normal file
123
components/Select/SelectCustom.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
// components/Select.tsx
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Pressable,
|
||||
Modal,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
} from "react-native";
|
||||
|
||||
type SelectItem = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
data: SelectItem[];
|
||||
value?: string | number | null;
|
||||
onChange: (value: string | number) => void;
|
||||
};
|
||||
|
||||
const SelectCustom: React.FC<SelectProps> = ({
|
||||
label,
|
||||
placeholder = "Pilih opsi",
|
||||
data,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
const selectedItem = data.find((item) => item.value === value);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{label && <Text style={styles.label}>{label}</Text>}
|
||||
<Pressable style={styles.input} onPress={() => setModalVisible(true)}>
|
||||
<Text style={selectedItem ? styles.text : styles.placeholder}>
|
||||
{selectedItem?.label || placeholder}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
<Modal visible={modalVisible} transparent animationType="fade">
|
||||
<TouchableOpacity
|
||||
style={styles.modalOverlay}
|
||||
activeOpacity={1}
|
||||
onPressOut={() => setModalVisible(false)}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<FlatList
|
||||
data={data}
|
||||
keyExtractor={(item) => String(item.value)}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
style={styles.option}
|
||||
onPress={() => {
|
||||
onChange(item.value);
|
||||
setModalVisible(false);
|
||||
}}
|
||||
>
|
||||
<Text>{item.label}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectCustom;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
label: {
|
||||
fontSize: TEXT_SIZE_MEDIUM,
|
||||
marginBottom: 4,
|
||||
color: MainColor.white,
|
||||
fontWeight: "500",
|
||||
},
|
||||
input: {
|
||||
borderWidth: 1,
|
||||
borderColor: "#ccc",
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
minHeight: 48,
|
||||
justifyContent: "center",
|
||||
backgroundColor: MainColor.white,
|
||||
},
|
||||
text: {
|
||||
fontSize: TEXT_SIZE_MEDIUM,
|
||||
},
|
||||
placeholder: {
|
||||
fontSize: TEXT_SIZE_MEDIUM,
|
||||
color: MainColor.placeholder,
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
modalContent: {
|
||||
width: "80%",
|
||||
maxHeight: 300,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
},
|
||||
option: {
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#eee",
|
||||
},
|
||||
});
|
||||
66
components/Stack/StackCustom.tsx
Normal file
66
components/Stack/StackCustom.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
// components/Stack.tsx
|
||||
|
||||
import React from "react";
|
||||
import { View, ViewStyle, StyleSheet } from "react-native";
|
||||
|
||||
import { AlignType, GapSizeType, JustifyType } from "@/components/Stack/stack-types";
|
||||
|
||||
interface StackProps {
|
||||
children: React.ReactNode;
|
||||
align?: AlignType;
|
||||
justify?: JustifyType;
|
||||
gap?: GapSizeType;
|
||||
direction?: "row" | "column";
|
||||
style?: ViewStyle | ViewStyle[];
|
||||
}
|
||||
|
||||
const StackCustom: React.FC<StackProps> = ({
|
||||
children,
|
||||
align = "stretch",
|
||||
justify = "flex-start",
|
||||
gap = "md",
|
||||
direction = "column",
|
||||
style,
|
||||
}) => {
|
||||
const spacing = convertToSpacing(gap);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
// styles.stack,
|
||||
{
|
||||
flexDirection: direction,
|
||||
alignItems: align,
|
||||
justifyContent: justify,
|
||||
gap: spacing,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// Fungsi untuk mengubah nilai gap ke dalam ukuran pixel
|
||||
const convertToSpacing = (value: GapSizeType): number => {
|
||||
if (typeof value === "number") return value;
|
||||
|
||||
const sizes: Record<string, number> = {
|
||||
xs: 4,
|
||||
sm: 8,
|
||||
md: 16,
|
||||
lg: 24,
|
||||
xl: 32,
|
||||
};
|
||||
|
||||
return sizes[value] || 16; // default md
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
stack: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default StackCustom;
|
||||
19
components/Stack/stack-types.ts
Normal file
19
components/Stack/stack-types.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// types/stack.types.ts
|
||||
|
||||
export type AlignType =
|
||||
| "stretch"
|
||||
| "flex-start"
|
||||
| "center"
|
||||
| "flex-end"
|
||||
| "baseline";
|
||||
|
||||
export type JustifyType =
|
||||
| "flex-start"
|
||||
| "center"
|
||||
| "flex-end"
|
||||
| "space-between"
|
||||
| "space-around"
|
||||
| "space-evenly";
|
||||
|
||||
export type GapSizeType = "xs" | "sm" | "md" | "lg" | "xl" | number;
|
||||
// | string;
|
||||
104
components/Text/TextCustom.tsx
Normal file
104
components/Text/TextCustom.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import {
|
||||
TEXT_SIZE_LARGE,
|
||||
TEXT_SIZE_MEDIUM,
|
||||
TEXT_SIZE_SMALL,
|
||||
} from "@/constants/constans-value";
|
||||
import React from "react";
|
||||
import { Text as RNText, StyleProp, StyleSheet, TextStyle } from "react-native";
|
||||
|
||||
// Tambahkan type TextAlignProps agar lebih type-safe
|
||||
type TextAlign = "left" | "center" | "right";
|
||||
|
||||
interface TextCustomProps {
|
||||
children: string | React.ReactNode;
|
||||
style?: StyleProp<TextStyle>;
|
||||
bold?: boolean;
|
||||
semiBold?: boolean;
|
||||
size?: "default" | "large" | "small";
|
||||
color?: "default" | "yellow" | "red";
|
||||
align?: TextAlign; // Prop untuk alignment
|
||||
truncate?: boolean | number;
|
||||
}
|
||||
|
||||
const TextCustom: React.FC<TextCustomProps> = ({
|
||||
children,
|
||||
style,
|
||||
bold = false,
|
||||
semiBold = false,
|
||||
size = "default",
|
||||
color = "default",
|
||||
align = "left", // Default alignment
|
||||
truncate = false,
|
||||
}) => {
|
||||
const getStyle = () => {
|
||||
let selectedStyles = [];
|
||||
|
||||
// Base style
|
||||
selectedStyles.push(styles.default);
|
||||
|
||||
// Font weight
|
||||
if (bold) selectedStyles.push(styles.bold);
|
||||
else if (semiBold) selectedStyles.push(styles.semiBold);
|
||||
|
||||
// Size
|
||||
if (size === "large") selectedStyles.push(styles.large);
|
||||
else if (size === "small") selectedStyles.push(styles.small);
|
||||
|
||||
// Color
|
||||
if (color === "yellow") selectedStyles.push(styles.yellow);
|
||||
else if (color === "red") selectedStyles.push(styles.red);
|
||||
|
||||
// Alignment
|
||||
if (align) {
|
||||
selectedStyles.push({ textAlign: align });
|
||||
}
|
||||
|
||||
// Override with passed style
|
||||
if (style) selectedStyles.push(style);
|
||||
|
||||
return selectedStyles;
|
||||
};
|
||||
|
||||
return (
|
||||
<RNText
|
||||
numberOfLines={
|
||||
typeof truncate === "number" ? truncate : truncate ? 1 : undefined
|
||||
}
|
||||
ellipsizeMode="tail"
|
||||
style={getStyle()}
|
||||
>
|
||||
{children}
|
||||
</RNText>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextCustom;
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
default: {
|
||||
fontSize: TEXT_SIZE_MEDIUM,
|
||||
color: MainColor.white,
|
||||
fontFamily: "Poppins-Regular",
|
||||
},
|
||||
bold: {
|
||||
fontFamily: "Poppins-Bold",
|
||||
fontWeight: "700",
|
||||
},
|
||||
semiBold: {
|
||||
fontFamily: "Poppins-SemiBold",
|
||||
fontWeight: "500",
|
||||
},
|
||||
large: {
|
||||
fontSize: TEXT_SIZE_LARGE,
|
||||
},
|
||||
small: {
|
||||
fontSize: TEXT_SIZE_SMALL,
|
||||
},
|
||||
yellow: {
|
||||
color: MainColor.yellow,
|
||||
},
|
||||
red: {
|
||||
color: MainColor.red,
|
||||
},
|
||||
});
|
||||
3
components/TextInput/index.ts
Normal file
3
components/TextInput/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { TextInputCustom } from "./TextInputCustom";
|
||||
|
||||
export { TextInputCustom };
|
||||
@@ -34,7 +34,7 @@ export const textInputStyles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: AccentColor.white,
|
||||
backgroundColor: MainColor.login,
|
||||
backgroundColor: MainColor.white,
|
||||
paddingHorizontal: 12,
|
||||
height: 50,
|
||||
},
|
||||
|
||||
27
components/_Interface/types.ts
Normal file
27
components/_Interface/types.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Href } from "expo-router";
|
||||
|
||||
export { ICustomTab, ITabs, IMenuDrawerItem };
|
||||
|
||||
interface ICustomTab {
|
||||
icon: string;
|
||||
label: string;
|
||||
isActive: boolean;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
interface ITabs {
|
||||
id: string;
|
||||
icon: string;
|
||||
activeIcon: string;
|
||||
label: string;
|
||||
path: Href;
|
||||
isActive: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface IMenuDrawerItem {
|
||||
icon: string;
|
||||
label: string;
|
||||
path?: string;
|
||||
color?: string;
|
||||
}
|
||||
@@ -1,35 +1,88 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { globalStyles } from "@/constants/global-styles";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { ImageBackground, ScrollView, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
|
||||
interface ViewWrapperProps {
|
||||
children: React.ReactNode;
|
||||
withBackground?: boolean;
|
||||
tabBarComponent?: React.ReactNode;
|
||||
bottomBarComponent?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ViewWrapper = ({ children }: ViewWrapperProps) => {
|
||||
const ViewWrapper = ({
|
||||
children,
|
||||
withBackground = false,
|
||||
tabBarComponent,
|
||||
bottomBarComponent,
|
||||
}: ViewWrapperProps) => {
|
||||
const assetBackground = require("../../assets/images/main-background.png");
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={[]}
|
||||
style={{
|
||||
flex: 1,
|
||||
// paddingTop: StatusBar.currentHeight,
|
||||
}}
|
||||
|
||||
>
|
||||
<ScrollView contentContainerStyle={{ flexGrow: 1 }} >
|
||||
<ImageBackground
|
||||
source={require("../../assets/images/main-background.png")}
|
||||
resizeMode="cover"
|
||||
style={globalStyles.imageBackground}
|
||||
>
|
||||
<View style={globalStyles.container}>{children}</View>
|
||||
</ImageBackground>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
<>
|
||||
<SafeAreaView
|
||||
edges={[
|
||||
"bottom",
|
||||
// "top",
|
||||
]}
|
||||
style={{
|
||||
flex: 1,
|
||||
// paddingTop: StatusBar.currentHeight,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
}}
|
||||
>
|
||||
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
|
||||
{withBackground ? (
|
||||
<ImageBackground
|
||||
source={assetBackground}
|
||||
resizeMode="cover"
|
||||
style={GStyles.imageBackground}
|
||||
>
|
||||
<View style={GStyles.containerWithBackground}>{children}</View>
|
||||
</ImageBackground>
|
||||
) : (
|
||||
<View style={GStyles.container}>{children}</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
{tabBarComponent ? tabBarComponent : null}
|
||||
{bottomBarComponent ? (
|
||||
<View style={GStyles.bottomBar}>
|
||||
<View style={GStyles.bottomBarContainer}>
|
||||
{bottomBarComponent}
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
</SafeAreaView>
|
||||
</>
|
||||
|
||||
// <SafeAreaProvider>
|
||||
// <SafeAreaView
|
||||
// edges={[
|
||||
// "bottom",
|
||||
// // "top",
|
||||
// ]}
|
||||
// style={{
|
||||
// flex: 1,
|
||||
// // paddingTop: StatusBar.currentHeight,
|
||||
// backgroundColor: MainColor.darkblue,
|
||||
// }}
|
||||
// >
|
||||
// <ScrollView contentContainerStyle={{ flexGrow: 1 }}>
|
||||
// {withBackground ? (
|
||||
// <ImageBackground
|
||||
// source={assetBackground}
|
||||
// resizeMode="cover"
|
||||
// style={Styles.imageBackground}
|
||||
// >
|
||||
// <View style={Styles.containerWithBackground}>{children}</View>
|
||||
// </ImageBackground>
|
||||
// ) : (
|
||||
// <View style={Styles.container}>{children}</View>
|
||||
// )}
|
||||
// </ScrollView>
|
||||
// {tabBarComponent}
|
||||
// </SafeAreaView>
|
||||
// </SafeAreaProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
52
components/index.ts
Normal file
52
components/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// Alert
|
||||
import AlertCustom from "./Alert/AlertCustom";
|
||||
// Button
|
||||
import BackButton from "./Button/BackButton";
|
||||
import ButtonCustom from "./Button/ButtonCustom";
|
||||
// Drawer
|
||||
import DrawerCustom from "./Drawer/DrawerCustom";
|
||||
import MenuDrawerDynamicGrid from "./Drawer/MenuDrawerDynamicGird";
|
||||
// ShareComponent
|
||||
import ViewWrapper from "./_ShareComponent/ViewWrapper";
|
||||
import Spacing from "./_ShareComponent/Spacing";
|
||||
// Text
|
||||
import TextCustom from "./Text/TextCustom";
|
||||
// TextInput
|
||||
import { TextInputCustom } from "./TextInput/TextInputCustom";
|
||||
// Grid
|
||||
import Grid from "./Grid/GridCustom";
|
||||
// Box
|
||||
import BaseBox from "./Box/BaseBox";
|
||||
// Avatar
|
||||
import AvatarCustom from "./Avatar/AvatarCustom"
|
||||
// Stack
|
||||
import StackCustom from "./Stack/StackCustom";
|
||||
// Select
|
||||
import SelectCustom from "./Select/SelectCustom";
|
||||
|
||||
export {
|
||||
AlertCustom,
|
||||
// Button
|
||||
BackButton,
|
||||
ButtonCustom,
|
||||
// Drawer
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
// ShareComponent
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
// Text
|
||||
TextCustom,
|
||||
// TextInput
|
||||
TextInputCustom,
|
||||
// Grid
|
||||
Grid,
|
||||
// Box
|
||||
BaseBox,
|
||||
// Avatar
|
||||
AvatarCustom,
|
||||
// Stack
|
||||
StackCustom,
|
||||
// Select
|
||||
SelectCustom,
|
||||
};
|
||||
@@ -7,7 +7,8 @@ export const MainColor = {
|
||||
red: "#FF4B4C",
|
||||
orange: "#FF7043",
|
||||
green: "#4CAF4F",
|
||||
login: "#EDEBEBFF",
|
||||
text_input: "#EDEBEBFF",
|
||||
placeholder: "#999",
|
||||
disabled: "#606360",
|
||||
};
|
||||
|
||||
|
||||
17
constants/constans-value.ts
Normal file
17
constants/constans-value.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export {
|
||||
TEXT_SIZE_SMALL,
|
||||
TEXT_SIZE_MEDIUM,
|
||||
TEXT_SIZE_LARGE,
|
||||
ICON_SIZE_SMALL,
|
||||
ICON_SIZE_MEDIUM,
|
||||
DRAWER_HEIGHT,
|
||||
RADIUS_BUTTON,
|
||||
};
|
||||
|
||||
const TEXT_SIZE_SMALL = 12;
|
||||
const TEXT_SIZE_MEDIUM = 14;
|
||||
const TEXT_SIZE_LARGE = 16;
|
||||
const ICON_SIZE_SMALL = 20;
|
||||
const ICON_SIZE_MEDIUM = 24;
|
||||
const DRAWER_HEIGHT = 500; // tinggi drawer5
|
||||
const RADIUS_BUTTON = 50
|
||||
@@ -1,43 +0,0 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import { MainColor } from "./color-palet";
|
||||
|
||||
export const globalStyles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingInline: 30,
|
||||
paddingBlock: 20,
|
||||
},
|
||||
mainContainer: {
|
||||
flex: 1,
|
||||
paddingInline: 25,
|
||||
paddingBlock: 10,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
|
||||
imageBackground: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
// AUTHENTICATION
|
||||
authContainer: {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
// marginBottom: 50,
|
||||
},
|
||||
authTitle: {
|
||||
fontSize: 27,
|
||||
color: MainColor.yellow,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
authSubTitle: {
|
||||
fontSize: 22,
|
||||
color: MainColor.yellow,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
textLabel: {
|
||||
fontSize: 16,
|
||||
color: MainColor.white,
|
||||
fontWeight: "normal",
|
||||
},
|
||||
});
|
||||
10
constants/radius-value.ts
Normal file
10
constants/radius-value.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// constants/radius.ts
|
||||
|
||||
export const radiusMap = {
|
||||
xs: 2,
|
||||
sm: 4,
|
||||
md: 6,
|
||||
lg: 8,
|
||||
xl: 12,
|
||||
full: 100,
|
||||
} as const;
|
||||
1
eas.build.android
Normal file
1
eas.build.android
Normal file
@@ -0,0 +1 @@
|
||||
eas build --profile preview
|
||||
7
eas.json
7
eas.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 16.12.0",
|
||||
"version": ">= 16.10.0",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
@@ -9,7 +9,10 @@
|
||||
"distribution": "internal"
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
"distribution": "internal",
|
||||
"android": {
|
||||
"buildType": "apk"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": true
|
||||
|
||||
40
navigation/types.ts
Normal file
40
navigation/types.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// src/navigation/types.ts
|
||||
|
||||
import { NavigatorScreenParams } from "@react-navigation/native";
|
||||
|
||||
// Tipe untuk Tab Navigator utama di (home-tabs)
|
||||
export type HomeTabParamList = {
|
||||
index: undefined; // Diperbarui: Tab default (sekarang dinamakan index)
|
||||
maps: undefined;
|
||||
profile: undefined;
|
||||
};
|
||||
|
||||
// Tipe untuk Stack Navigator Event
|
||||
export type EventStackParamList = {
|
||||
index: undefined; // Rute root untuk Event (misalnya, daftar event)
|
||||
// Anda bisa menambahkan rute event lain di sini, contoh:
|
||||
// '[id]': { id: string }; // Untuk detail event
|
||||
};
|
||||
|
||||
// Tipe untuk Stack Navigator Autentikasi
|
||||
export type AuthStackParamList = {
|
||||
index: undefined; // Layar Login
|
||||
register: undefined; // Layar Pendaftaran
|
||||
verification: undefined; // Layar Verifikasi
|
||||
};
|
||||
|
||||
// Tipe untuk Stack Navigator Utama Aplikasi (setelah autentikasi)
|
||||
export type AppStackParamList = {
|
||||
"(home-tabs)": NavigatorScreenParams<HomeTabParamList>; // Mengarah ke Tab Navigator
|
||||
event: NavigatorScreenParams<EventStackParamList>; // Mengarah ke Event Stack
|
||||
forum: undefined; // NEW: Halaman Forum, sebagai Stack Screen terpisah
|
||||
// Anda bisa menambahkan rute stack lain di sini yang tidak termasuk dalam grup di atas
|
||||
};
|
||||
|
||||
// Gabungkan semua tipe rute utama ke dalam RootParamList global
|
||||
// Ini memungkinkan penggunaan useNavigation dan useRouter dengan tipe yang benar
|
||||
declare global {
|
||||
namespace ReactNavigation {
|
||||
interface RootParamList extends AuthStackParamList, AppStackParamList {}
|
||||
}
|
||||
}
|
||||
@@ -12,18 +12,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.1.0",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/bottom-tabs": "^7.4.2",
|
||||
"@react-navigation/drawer": "^7.5.2",
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.21",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"expo": "~53.0.12",
|
||||
"expo": "53.0.13",
|
||||
"expo-blur": "~14.1.5",
|
||||
"expo-constants": "~17.1.6",
|
||||
"expo-font": "~13.3.1",
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image": "~2.3.0",
|
||||
"expo-linking": "~7.1.5",
|
||||
"expo-router": "~5.1.0",
|
||||
"expo-router": "~5.1.1",
|
||||
"expo-splash-screen": "~0.30.9",
|
||||
"expo-status-bar": "~2.2.3",
|
||||
"expo-symbols": "~0.4.5",
|
||||
|
||||
81
screens/Authentication/LoginView.tsx
Normal file
81
screens/Authentication/LoginView.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
||||
|
||||
export default function LoginView() {
|
||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
|
||||
function handleInputValue(phoneNumber: string) {
|
||||
setInputValue(phoneNumber);
|
||||
}
|
||||
|
||||
function handleSelectedCountry(country: ICountry) {
|
||||
setSelectedCountry(country);
|
||||
}
|
||||
|
||||
function handleLogin() {
|
||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
const fixNumber = callingCode + inputValue;
|
||||
// console.log("fixNumber", fixNumber);
|
||||
|
||||
const randomAlfabet = Math.random().toString(36).substring(2, 8);
|
||||
const randomNumber = Math.floor(Math.random() * 1000000);
|
||||
const id = randomAlfabet + randomNumber + fixNumber;
|
||||
console.log("user id :", id);
|
||||
|
||||
router.navigate("/verification");
|
||||
// router.navigate(`/(application)/profile/${id}`);
|
||||
// router.navigate("/(application)/home");
|
||||
// router.navigate(`/(application)/profile/${id}/edit`);
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewWrapper withBackground>
|
||||
<View style={GStyles.authContainer}>
|
||||
<View>
|
||||
<View style={GStyles.authContainerTitle}>
|
||||
<Text style={GStyles.authSubTitle}>WELCOME TO</Text>
|
||||
<Spacing height={5} />
|
||||
<Text style={GStyles.authTitle}>HIPMI BADUNG APPS</Text>
|
||||
<Spacing height={5} />
|
||||
</View>
|
||||
<Spacing height={50} />
|
||||
<Text
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 30,
|
||||
right: 20,
|
||||
fontSize: 10,
|
||||
fontWeight: "thin",
|
||||
fontStyle: "italic",
|
||||
color: MainColor.white,
|
||||
}}
|
||||
>
|
||||
powered by muku.id
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<PhoneInput
|
||||
value={inputValue}
|
||||
onChangePhoneNumber={handleInputValue}
|
||||
selectedCountry={selectedCountry}
|
||||
onChangeSelectedCountry={handleSelectedCountry}
|
||||
defaultCountry="ID"
|
||||
placeholder="Masukkan nomor"
|
||||
/>
|
||||
|
||||
<Spacing height={20} />
|
||||
|
||||
<ButtonCustom onPress={handleLogin}>Login</ButtonCustom>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
61
screens/Authentication/RegisterView.tsx
Normal file
61
screens/Authentication/RegisterView.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import { TextInputCustom } from "@/components/TextInput/TextInputCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { Text, View } from "react-native";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function RegisterView() {
|
||||
const [username, setUsername] = useState("Bagas Banuna");
|
||||
const handleRegister = () => {
|
||||
console.log("Success register", username);
|
||||
router.push("/(application)/home");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper withBackground>
|
||||
<View style={GStyles.authContainer}>
|
||||
<View>
|
||||
<View style={GStyles.authContainerTitle}>
|
||||
<Text style={GStyles.authTitle}>REGISTRASI</Text>
|
||||
<Spacing />
|
||||
<MaterialCommunityIcons
|
||||
name="account"
|
||||
size={100}
|
||||
color={MainColor.yellow}
|
||||
/>
|
||||
<Spacing />
|
||||
|
||||
<Text style={GStyles.textLabel}>
|
||||
Anda akan terdaftar dengan nomor
|
||||
</Text>
|
||||
<Text style={GStyles.textLabel}>+6282xxxxxxxxx</Text>
|
||||
<Spacing />
|
||||
</View>
|
||||
<TextInputCustom
|
||||
placeholder="Masukkan username"
|
||||
value={username}
|
||||
onChangeText={(text) => setUsername(text)}
|
||||
/>
|
||||
|
||||
<ButtonCustom onPress={handleRegister}>Daftar</ButtonCustom>
|
||||
{/* <Spacing />
|
||||
<ButtonCustom
|
||||
title="Coba"
|
||||
backgroundColor={MainColor.yellow}
|
||||
textColor={MainColor.black}
|
||||
onPress={() => {
|
||||
console.log("Home clicked");
|
||||
router.push("/(application)/coba");
|
||||
}}
|
||||
/> */}
|
||||
</View>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
65
screens/Authentication/VerificationView.tsx
Normal file
65
screens/Authentication/VerificationView.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { router } from "expo-router";
|
||||
import { Text, View } from "react-native";
|
||||
import { OtpInput } from "react-native-otp-entry";
|
||||
|
||||
export default function VerificationView() {
|
||||
const handleVerification = () => {
|
||||
console.log("Verification clicked");
|
||||
router.push("/register");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper withBackground>
|
||||
<View style={GStyles.authContainer}>
|
||||
<View>
|
||||
<View style={GStyles.authContainerTitle}>
|
||||
<Text style={GStyles.authTitle}>Verifikasi KOde OTP</Text>
|
||||
<Spacing height={30} />
|
||||
<Text style={GStyles.textLabel}>Masukan 4 digit kode otp</Text>
|
||||
<Text style={GStyles.textLabel}>
|
||||
Yang di kirim ke +6282xxxxxxxxx
|
||||
</Text>
|
||||
<Spacing height={30} />
|
||||
<OtpInput
|
||||
numberOfDigits={4}
|
||||
theme={{
|
||||
pinCodeContainerStyle: {
|
||||
backgroundColor: MainColor.text_input,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: MainColor.yellow,
|
||||
width: 60,
|
||||
height: 60,
|
||||
},
|
||||
containerStyle: {
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Spacing height={30} />
|
||||
<Text style={GStyles.textLabel}>
|
||||
Tidak menerima kode ?{" "}
|
||||
<Text style={GStyles.textLabel}>Kirim Ulang</Text>
|
||||
</Text>
|
||||
</View>
|
||||
<Spacing height={30} />
|
||||
</View>
|
||||
|
||||
<ButtonCustom
|
||||
backgroundColor={MainColor.yellow}
|
||||
textColor={MainColor.black}
|
||||
onPress={() => handleVerification()}
|
||||
>
|
||||
Verifikasi
|
||||
</ButtonCustom>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
37
screens/Home/UiHome.tsx
Normal file
37
screens/Home/UiHome.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
// import { ITabs } from "@/components/_Interface/types";
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { useNavigation } from "expo-router";
|
||||
import React, { useEffect } from "react";
|
||||
import Home_BottomFeatureSection from "./bottomFeatureSection";
|
||||
import Home_ImageSection from "./imageSection";
|
||||
import TabSection from "./tabSection";
|
||||
import { tabsHome } from "./tabsList";
|
||||
import Home_FeatureSection from "./topFeatureSection";
|
||||
|
||||
export default function UiHome() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({});
|
||||
}, [navigation]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper tabBarComponent={<TabSection tabs={tabsHome} />}>
|
||||
{/* Content Image */}
|
||||
<Home_ImageSection />
|
||||
<Spacing height={10} />
|
||||
|
||||
{/* Grid Section */}
|
||||
<Home_FeatureSection />
|
||||
<Spacing height={10} />
|
||||
|
||||
{/* Job Vacancy Section */}
|
||||
<Home_BottomFeatureSection />
|
||||
<Spacing height={20} />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
52
screens/Home/bottomFeatureSection.tsx
Normal file
52
screens/Home/bottomFeatureSection.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { TextCustom } from "@/components";
|
||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/FontAwesome";
|
||||
import { stylesHome } from "./homeViewStyle";
|
||||
|
||||
export default function Home_BottomFeatureSection() {
|
||||
return (
|
||||
<>
|
||||
<View style={stylesHome.jobVacancyContainer}>
|
||||
<View style={stylesHome.jobVacancyHeader}>
|
||||
<Icon name="briefcase" size={24} color="white" />
|
||||
<Spacing width={10}/>
|
||||
<TextCustom bold size="large">
|
||||
Job Vacancy
|
||||
</TextCustom>
|
||||
</View>
|
||||
|
||||
<View style={stylesHome.vacancyList}>
|
||||
{/* Vacancy Item 1 */}
|
||||
<View style={stylesHome.vacancyItem}>
|
||||
{/* <Icon name="user" size={20} color="#FFD700" /> */}
|
||||
<View style={stylesHome.vacancyDetails}>
|
||||
<TextCustom bold color="yellow" truncate size="large">
|
||||
Bagas_banuna
|
||||
</TextCustom>
|
||||
<Spacing height={5} />
|
||||
<TextCustom truncate={2}>
|
||||
Dicari perawat kucing dan perawat anjing
|
||||
</TextCustom>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Vacancy Item 2 */}
|
||||
<View style={stylesHome.vacancyItem}>
|
||||
{/* <Icon name="user" size={20} color="#FFD700" /> */}
|
||||
<View style={stylesHome.vacancyDetails}>
|
||||
<TextCustom bold color="yellow" truncate size="large">
|
||||
fibramarcell
|
||||
</TextCustom>
|
||||
<Spacing height={5} />
|
||||
<TextCustom truncate={2}>
|
||||
Di Butuhkan Seorang Programer dan Designer
|
||||
</TextCustom>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
26
screens/Home/imageSection.tsx
Normal file
26
screens/Home/imageSection.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Image } from "expo-image";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function Home_ImageSection() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require("@/assets/images/constants/home-hipmi.png")}
|
||||
contentFit="cover"
|
||||
transition={1000}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 120,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
51
screens/Home/tabSection.tsx
Normal file
51
screens/Home/tabSection.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { ICustomTab, ITabs } from "@/components/_Interface/types";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import React from "react";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
|
||||
|
||||
const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
|
||||
<TouchableOpacity
|
||||
style={[GStyles.tabItem, isActive && GStyles.activeTab]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View
|
||||
style={[GStyles.iconContainer, isActive && GStyles.activeIconContainer]}
|
||||
>
|
||||
<Ionicons
|
||||
name={icon as any}
|
||||
size={20}
|
||||
color={isActive ? "#fff" : "#666"}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[GStyles.tabLabel, isActive && GStyles.activeTabLabel]}>
|
||||
{label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
export default function TabSection({ tabs }: { tabs: ITabs[] }) {
|
||||
return (
|
||||
<>
|
||||
<View style={GStyles.tabBar}>
|
||||
<View style={GStyles.tabContainer}>
|
||||
{tabs.map((e) => (
|
||||
<CustomTab
|
||||
key={e.id}
|
||||
icon={e.icon}
|
||||
label={e.label}
|
||||
isActive={e.isActive}
|
||||
onPress={() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
e.disabled ? console.log("disabled") : router.push(e.path);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
40
screens/Home/tabsList.ts
Normal file
40
screens/Home/tabsList.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ITabs } from "@/components/_Interface/types";
|
||||
|
||||
export const tabsHome: ITabs[] = [
|
||||
{
|
||||
id: "forum",
|
||||
icon: "chatbubble-ellipses-outline",
|
||||
activeIcon: "chatbubble-ellipses",
|
||||
label: "Forum",
|
||||
path: "/forum",
|
||||
isActive: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: "marketplace",
|
||||
icon: "cart-outline",
|
||||
activeIcon: "cart",
|
||||
label: "Marketplace",
|
||||
path: "/marketplace",
|
||||
isActive: false,
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
id: "maps",
|
||||
icon: "map-outline",
|
||||
activeIcon: "map",
|
||||
label: "Maps",
|
||||
path: "/maps",
|
||||
isActive: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: "profile",
|
||||
icon: "person-outline",
|
||||
activeIcon: "person",
|
||||
label: "Profile",
|
||||
path: "/profile/id-percoban-123456",
|
||||
isActive: true,
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
54
screens/Home/topFeatureSection.tsx
Normal file
54
screens/Home/topFeatureSection.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
import { stylesHome } from "./homeViewStyle";
|
||||
|
||||
export default function Home_FeatureSection() {
|
||||
return (
|
||||
<>
|
||||
<View style={stylesHome.gridContainer}>
|
||||
<TouchableOpacity
|
||||
style={stylesHome.gridItem}
|
||||
onPress={() => router.push("/(application)/event/(tabs)")}
|
||||
>
|
||||
<Ionicons name="analytics" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Event</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="share" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Collaboration</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="cube" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Voting</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="heart" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Crowdfunding</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* <View style={stylesHome.gridContainer}>
|
||||
<TouchableOpacity
|
||||
style={stylesHome.gridItem}
|
||||
onPress={() => router.push("/(application)/event")}
|
||||
>
|
||||
<Ionicons name="analytics" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Event</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="share" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Collaboration</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="cube" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Voting</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={stylesHome.gridItem}>
|
||||
<Ionicons name="heart" size={48} color="white" />
|
||||
<Text style={stylesHome.gridLabel}>Crowdfunding</Text>
|
||||
</TouchableOpacity>
|
||||
</View> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
51
screens/Profile/AvatarAndBackground.tsx
Normal file
51
screens/Profile/AvatarAndBackground.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { AvatarCustom } from "@/components";
|
||||
import { AccentColor } from "@/constants/color-palet";
|
||||
import { View, ImageBackground, StyleSheet } from "react-native";
|
||||
|
||||
const AvatarAndBackground = () => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ImageBackground
|
||||
source={require("@/assets/images/logo-hipmi.png")}
|
||||
style={styles.backgroundImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
{/* Background Image */}
|
||||
|
||||
{/* Avatar yang sedikit keluar */}
|
||||
<View style={styles.avatarOverlap}>
|
||||
<AvatarCustom
|
||||
source={require("@/assets/images/react-logo.png")}
|
||||
size="lg"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvatarAndBackground;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
backgroundImage: {
|
||||
width: "100%",
|
||||
height: 150, // Tinggi background sesuai kebutuhan
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderRadius: 6,
|
||||
overflow: "hidden",
|
||||
borderWidth: 1,
|
||||
borderColor: AccentColor.blue,
|
||||
backgroundColor: "white",
|
||||
},
|
||||
avatarOverlap: {
|
||||
position: "absolute", // Meletakkan avatar di atas background
|
||||
top: 90, // Posisi avatar sedikit keluar dari background
|
||||
left: "50%", // Sentralisasi horizontal
|
||||
transform: [{ translateX: -50 }], // Menggeser ke kiri 50% lebarnya
|
||||
},
|
||||
});
|
||||
35
screens/Profile/menuDrawerSection.tsx
Normal file
35
screens/Profile/menuDrawerSection.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import MenuDrawerDynamicGrid from "@/components/Drawer/MenuDrawerDynamicGird";
|
||||
import { router } from "expo-router";
|
||||
|
||||
export default function Profile_MenuDrawerSection({
|
||||
drawerItems,
|
||||
setShowLogoutAlert,
|
||||
setIsDrawerOpen,
|
||||
}: {
|
||||
drawerItems: IMenuDrawerItem[];
|
||||
setShowLogoutAlert: (value: boolean) => void;
|
||||
setIsDrawerOpen: (value: boolean) => void;
|
||||
}) {
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
if (item.label === "Keluar") {
|
||||
// console.log("Logout clicked");
|
||||
setShowLogoutAlert(true);
|
||||
} else {
|
||||
console.log("PATH >> ", item.path);
|
||||
router.push(item.path as any);
|
||||
}
|
||||
setIsDrawerOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Menu Items */}
|
||||
<MenuDrawerDynamicGrid
|
||||
data={drawerItems}
|
||||
columns={4} // Ubah ke 2 jika ingin 2 kolom per baris
|
||||
onPressItem={handlePress}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
121
screens/Profile/profilSection.tsx
Normal file
121
screens/Profile/profilSection.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { BaseBox, Grid, Spacing, TextCustom } from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { FontAwesome5, Ionicons } from "@expo/vector-icons";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
import AvatarAndBackground from "./AvatarAndBackground";
|
||||
|
||||
export default function ProfilSection() {
|
||||
const { id } = useLocalSearchParams();
|
||||
|
||||
const listData = [
|
||||
{
|
||||
icon: (
|
||||
<Ionicons name="call-outline" size={ICON_SIZE_SMALL} color="white" />
|
||||
),
|
||||
label: "+6282340374412",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons name="mail-outline" size={ICON_SIZE_SMALL} color="white" />
|
||||
),
|
||||
label: "bagasbanuna@gmail.com",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
),
|
||||
label: "Jalan Raya Sesetan No. 123, Bandung, Indonesia",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<FontAwesome5 name="transgender" size={ICON_SIZE_SMALL} color="white" />
|
||||
),
|
||||
label: "Laki-laki",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseBox>
|
||||
<AvatarAndBackground />
|
||||
<Spacing height={50} />
|
||||
|
||||
<View style={{ alignItems: "center" }}>
|
||||
<TextCustom bold size="large" align="center">
|
||||
Nama User
|
||||
</TextCustom>
|
||||
<Spacing height={5} />
|
||||
<TextCustom size="small">@Username</TextCustom>
|
||||
</View>
|
||||
<Spacing height={30} />
|
||||
|
||||
{listData.map((item, index) => (
|
||||
<Grid key={index}>
|
||||
<Grid.Col
|
||||
span={2}
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</Grid.Col>
|
||||
<Grid.Col span={10}>
|
||||
<TextCustom bold>{item.label}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
))}
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<View>
|
||||
<TextCustom bold size="large" align="center">
|
||||
Portofolio
|
||||
</TextCustom>
|
||||
<Spacing />
|
||||
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
style={{ backgroundColor: MainColor.darkblue }}
|
||||
onPress={() => console.log("pressed")}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={10}
|
||||
style={{ justifyContent: "center", backgroundColor: "" }}
|
||||
>
|
||||
<TextCustom bold size="large" truncate={1}>
|
||||
Nama usaha portofolio
|
||||
</TextCustom>
|
||||
<TextCustom size="small" color="yellow">
|
||||
#id-porofolio12345
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={2}
|
||||
style={{ alignItems: "flex-end", justifyContent: "center" }}
|
||||
>
|
||||
<Ionicons
|
||||
name="caret-forward"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</View>
|
||||
</BaseBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
158
styles/global-styles.ts
Normal file
158
styles/global-styles.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
|
||||
import { Dimensions, StyleSheet } from "react-native";
|
||||
import { AccentColor, MainColor } from "../constants/color-palet";
|
||||
|
||||
const { width } = Dimensions.get("window");
|
||||
|
||||
export const GStyles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingInline: 20,
|
||||
paddingBlock: 10,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
containerWithBackground: {
|
||||
flex: 1,
|
||||
paddingInline: 20,
|
||||
paddingBlock: 10,
|
||||
},
|
||||
imageBackground: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
// AUTHENTICATION
|
||||
authContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
},
|
||||
authContainerTitle: {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
authTitle: {
|
||||
fontSize: 27,
|
||||
color: MainColor.yellow,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
authSubTitle: {
|
||||
fontSize: 22,
|
||||
color: MainColor.yellow,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
|
||||
// TEXT & LABEL
|
||||
textLabel: {
|
||||
fontSize: TEXT_SIZE_MEDIUM,
|
||||
color: MainColor.white,
|
||||
fontWeight: "normal",
|
||||
},
|
||||
|
||||
// Stack Header Style
|
||||
headerStyle: {
|
||||
backgroundColor: AccentColor.darkblue,
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: MainColor.yellow,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
|
||||
// HOME
|
||||
homeContainer: {
|
||||
flex: 1,
|
||||
paddingInline: 25,
|
||||
paddingBlock: 10,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
|
||||
// =============== TAB =============== //
|
||||
tabBar: {
|
||||
backgroundColor: MainColor.darkblue,
|
||||
borderTopColor: AccentColor.blue,
|
||||
borderTopWidth: 1,
|
||||
// borderTopEndRadius: 10,
|
||||
// borderTopStartRadius: 10,
|
||||
// tintColor: MainColor.yellow
|
||||
// paddingBottom: 20,
|
||||
// paddingTop: 10,
|
||||
// shadowColor: AccentColor.blue,
|
||||
// shadowOffset: {
|
||||
// width: 0,
|
||||
// height: -2,
|
||||
// },
|
||||
// shadowOpacity: 0.9,
|
||||
// shadowRadius: 3,
|
||||
// elevation: 5,
|
||||
},
|
||||
tabContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-around",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
tabItem: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
minWidth: width / 5,
|
||||
position: "relative",
|
||||
},
|
||||
activeTab: {
|
||||
transform: [{ scale: 1.05 }],
|
||||
},
|
||||
iconContainer: {
|
||||
padding: 8,
|
||||
borderRadius: 20,
|
||||
// marginBottom: 4,
|
||||
},
|
||||
activeIconContainer: {
|
||||
// backgroundColor: "#007AFF",
|
||||
// shadowColor: "#007AFF",
|
||||
// shadowOffset: {
|
||||
// width: 0,
|
||||
// height: 2,
|
||||
// },
|
||||
// shadowOpacity: 0.3,
|
||||
// shadowRadius: 4,
|
||||
// elevation: 4,
|
||||
},
|
||||
tabLabel: {
|
||||
fontSize: 10,
|
||||
color: "#666",
|
||||
fontWeight: "500",
|
||||
},
|
||||
activeTabLabel: {
|
||||
color: MainColor.white,
|
||||
fontWeight: "600",
|
||||
},
|
||||
activeIndicator: {
|
||||
position: "absolute",
|
||||
bottom: -2,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: "#007AFF",
|
||||
},
|
||||
// =============== TAB =============== //
|
||||
|
||||
// =============== BOTTOM BAR =============== //
|
||||
bottomBar: {
|
||||
backgroundColor: MainColor.darkblue,
|
||||
borderTopColor: AccentColor.blue,
|
||||
borderTopWidth: 1,
|
||||
// shadowColor: AccentColor.blue,
|
||||
// shadowOffset: {
|
||||
// width: 0,
|
||||
// height: -1,
|
||||
// },
|
||||
// shadowOpacity: 0.9,
|
||||
// shadowRadius: 5,
|
||||
// elevation: 5,
|
||||
},
|
||||
bottomBarContainer: {
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
// =============== BOTTOM BAR =============== //
|
||||
});
|
||||
Reference in New Issue
Block a user