first commit

This commit is contained in:
2025-06-23 10:18:59 +08:00
parent 20d2053276
commit ba2dc1211f
32 changed files with 15692 additions and 0 deletions

40
.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
app-example
.qodo

7
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit",
"source.sortMembers": "explicit"
}
}

43
app.json Normal file
View File

@@ -0,0 +1,43 @@
{
"expo": {
"name": "hipmi-mobile",
"slug": "hipmi-mobile",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/logo-hipmi.png",
"scheme": "hipmimobile",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.anonymous.hipmi-mobile"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/images/splash-icon.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
]
],
"experiments": {
"typedRoutes": true
}
}
}

9
app/+not-found.tsx Normal file
View File

@@ -0,0 +1,9 @@
import { Text, View } from "react-native";
export default function NotFoundScreen() {
return (
<View>
<Text>Not Found</Text>
</View>
)
}

5
app/_layout.tsx Normal file
View File

@@ -0,0 +1,5 @@
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack />;
}

21
app/background.tsx Normal file
View File

@@ -0,0 +1,21 @@
import { Text, View } from "react-native";
import { useNavigation } from "expo-router";
import { useEffect } from "react";
export default function Background() {
const navigation = useNavigation();
useEffect(() => {
navigation.setOptions({
headerShown: true,
headerTitle: "Home",
});
}, [navigation]);
return (
<View>
<Text>Background</Text>
</View>
);
}

36
app/header-button.tsx Normal file
View File

@@ -0,0 +1,36 @@
import { Stack } from "expo-router";
import { Button, Text, Image } from "react-native";
import { useState } from "react";
import { styles } from "@/constants/styles";
function LogoTitle(props: { children?: React.ReactNode }) {
return (
<Image
style={styles.image}
source={{ uri: "https://reactnative.dev/img/tiny_logo.png" }}
/>
);
}
export default function Home() {
const [count, setCount] = useState(0);
return (
<>
<Stack.Screen
options={{
headerTitle: (props) => <LogoTitle {...props} />,
headerRight: () => (
<Button
onPress={() => setCount((c) => c + 1)}
title="Update count"
/>
),
}}
/>
<Text>Count: {count}</Text>
</>
);
}

126
app/index.tsx Normal file
View File

@@ -0,0 +1,126 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import ButtonCustom from "@/components/Button/ButtonCustom";
import { TextInputCustom } from "@/components/TextInput/TextInputCustom";
import { MainColor } from "@/constants/color-palet";
import { styles } from "@/constants/styles";
import { useNavigation } from "expo-router";
import { useEffect, useState } from "react";
import {
ImageBackground,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function Home() {
const navigation = useNavigation();
const assetBackground = require("../assets/images/main-background.png");
const [phone, setPhone] = useState("");
const [price, setPrice] = useState("");
useEffect(() => {
navigation.setOptions({
headerShown: false,
});
}, [navigation]);
return (
<SafeAreaView
edges={[]}
style={{
flex: 1,
// paddingTop: StatusBar.currentHeight,
}}
>
<ScrollView contentContainerStyle={{ flexGrow: 2 }}>
<ImageBackground
source={assetBackground}
resizeMode="cover"
style={styles.imageBackground}
>
<View style={styles.container}>
<View
style={{
flex: 1,
justifyContent: "center",
height: "100%",
}}
>
<View>
<View style={stylesHome.container}>
{/* Teks "WELCOME TO" */}
<Text style={stylesHome.welcomeTo}>WELCOME TO</Text>
{/* Spacing antara "WELCOME TO" dan "HIPMI BADUNG APPS" */}
<View style={stylesHome.spacing} />
{/* Teks "HIPMI BADUNG APPS" */}
<Text style={stylesHome.hipmiBadungApps}>
HIPMI BADUNG APPS
</Text>
{/* Spacing antara "HIPMI BADUNG APPS" dan "powered by muku.id" */}
<View style={stylesHome.spacing} />
</View>
{/* Teks "powered by muku.id" */}
<Text style={stylesHome.poweredBy}>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"
/>
<ButtonCustom
title="Login"
backgroundColor={MainColor.yellow}
textColor={MainColor.black}
radius={10}
onPress={() => alert("Pressed!")}
// iconLeft={<MaterialIcons name="login" size={20} color="#000" />}
/>
</View>
</View>
</ImageBackground>
</ScrollView>
</SafeAreaView>
);
}
const stylesHome = StyleSheet.create({
container: {
// backgroundColor: "#0A192F", // Warna latar belakang biru tua
justifyContent: "center",
alignItems: "center",
marginBottom: 50,
},
welcomeTo: {
fontSize: 22,
color: MainColor.yellow, // Warna kuning
fontWeight: "bold",
},
hipmiBadungApps: {
fontSize: 27,
color: MainColor.yellow, // Warna kuning
fontWeight: "bold",
},
spacing: {
height: 5, // Jarak antar teks
},
poweredBy: {
position: "absolute",
bottom: 30, // sesuaikan jarak dari bawah
right: 20, // sesuaikan jarak dari kanan
fontSize: 10,
fontWeight: "thin",
fontStyle: "italic",
color: MainColor.white, // Warna putih
},
});

22
app/new-detail.tsx Normal file
View File

@@ -0,0 +1,22 @@
import { styles } from "@/constants/styles";
import { useRouter, useLocalSearchParams, Stack } from "expo-router";
import { Text, View } from "react-native";
export default function NewDetail() {
const router = useRouter();
const params = useLocalSearchParams();
return (
<View style={styles.container}>
<Stack.Screen
options={{
title: params.name as string,
}}
/>
<Text onPress={() => router.setParams({ name: "Bagas" })}>
Update title
</Text>
<Text onPress={() => router.back()}>Back</Text>
</View>
);
}

View File

@@ -0,0 +1,26 @@
import { styles } from "@/constants/styles";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { View, Text } from "react-native";
export default function Details() {
const router = useRouter();
const params = useLocalSearchParams();
return (
<View style={styles.container}>
<Stack.Screen
options={{
title: params.name as string,
}}
/>
<Text
onPress={() => {
router.setParams({ name: "Updated" });
}}
>
Update the title
</Text>
</View>
);
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

2079
bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
// components/Button/Button.tsx
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import buttonStyles from "./buttonStyles";
// Definisi props dengan TypeScript
interface ButtonProps {
onPress: () => void;
title?: string;
backgroundColor?: string;
textColor?: string;
radius?: number;
disabled?: boolean;
iconLeft?: React.ReactNode;
}
const ButtonCustom: React.FC<ButtonProps> = ({
onPress,
title = "Button",
backgroundColor = "#007AFF",
textColor = "#FFFFFF",
radius = 8,
disabled = false,
iconLeft,
}) => {
const styles = buttonStyles({
backgroundColor,
textColor,
borderRadius: radius,
});
return (
<TouchableOpacity
style={[styles.button, disabled && styles.disabled]}
onPress={onPress}
disabled={disabled}
>
{/* Render icon jika tersedia */}
{iconLeft && iconLeft}
<Text style={styles.buttonText}>{title}</Text>
</TouchableOpacity>
);
};
export default ButtonCustom;

View File

@@ -0,0 +1,30 @@
// components/Button/buttonStyles.js
import { StyleSheet } from "react-native";
export default function buttonStyles({
backgroundColor = "#007AFF",
textColor = "#FFFFFF",
borderRadius = 8,
}) {
return StyleSheet.create({
button: {
backgroundColor,
paddingVertical: 12,
paddingHorizontal: 20,
borderRadius,
flexDirection: "row", // 👈 Tambahkan baris ini
alignItems: "center",
justifyContent: "center",
gap: 8,
},
buttonText: {
color: textColor,
fontSize: 16,
fontWeight: "600",
},
disabled: {
opacity: 0.5,
},
});
}

View File

@@ -0,0 +1,100 @@
// components/TextInputCustom.tsx
import Ionicons from "@expo/vector-icons/Ionicons";
import React, { useState } from "react";
import {
TextInput as RNTextInput,
StyleProp,
Text,
TouchableOpacity,
View,
ViewStyle,
} from "react-native";
import { textInputStyles } from "./textInputStyles";
type IconType = React.ReactNode | string;
type Props = {
iconLeft?: IconType;
iconRight?: IconType;
label?: string;
required?: boolean;
error?: string;
secureTextEntry?: boolean;
fontColor?: string;
disabled?: boolean;
borderRadius?: number;
style?: StyleProp<ViewStyle>;
} & Omit<React.ComponentProps<typeof RNTextInput>, "style">;
export const TextInputCustom = ({
iconLeft,
iconRight,
label,
required = false,
error = "",
secureTextEntry = false,
fontColor = "#000",
disabled = false,
borderRadius = 8,
style,
...rest
}: Props) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
// Helper untuk render ikon
const renderIcon = (icon: IconType) => {
if (!icon) return null;
return typeof icon === "string" ? (
<Text style={textInputStyles.iconText}>{icon}</Text>
) : (
icon
);
};
return (
<View style={textInputStyles.container}>
{label && (
<Text style={textInputStyles.label}>
{label}
{required && <Text style={textInputStyles.required}> *</Text>}
</Text>
)}
<View
style={[
textInputStyles.inputContainer,
disabled && textInputStyles.disabled,
{ borderRadius },
error ? textInputStyles.errorBorder : null,
style,
]}
>
{iconLeft && (
<View style={textInputStyles.icon}>{renderIcon(iconLeft)}</View>
)}
<RNTextInput
style={[textInputStyles.input, { color: fontColor }]}
editable={!disabled}
secureTextEntry={secureTextEntry && !isPasswordVisible}
{...rest}
/>
{secureTextEntry && (
<TouchableOpacity
onPress={() => setIsPasswordVisible((prev) => !prev)}
style={textInputStyles.icon}
>
<Ionicons
name={isPasswordVisible ? "eye-off" : "eye"}
size={20}
color="#888"
/>
</TouchableOpacity>
)}
{iconRight && (
<View style={textInputStyles.icon}>{renderIcon(iconRight)}</View>
)}
</View>
{error ? <Text style={textInputStyles.errorMessage}>{error}</Text> : null}
</View>
);
};

View File

@@ -0,0 +1,71 @@
// components/text-input.styles.ts
import { AccentColor, MainColor } from "@/constants/color-palet";
import { StyleSheet } from "react-native";
export const textInputStyles = StyleSheet.create({
// Container utama input (View luar)
container: {
marginBottom: 16,
},
// Label di atas input
label: {
fontSize: 14,
marginBottom: 6,
fontWeight: "500",
color: MainColor.white,
},
// Tanda bintang merah untuk required
required: {
color: "red",
},
// Pesan error di bawah input
errorMessage: {
marginTop: 4,
fontSize: 12,
color: MainColor.red,
},
// Wrapper input (View pembungkus TextInput)
inputContainer: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderColor: AccentColor.white,
backgroundColor: MainColor.login,
paddingHorizontal: 12,
height: 50,
},
// Style saat disabled
disabled: {
backgroundColor: "#f9f9f9",
borderColor: "#e0e0e0",
},
// Input utama (TextInput)
input: {
flex: 1,
fontSize: 16,
paddingVertical: 0,
},
// Ikon di kiri/kanan
icon: {
marginHorizontal: 4,
justifyContent: "center",
},
// Teks ikon jika berupa string
iconText: {
fontSize: 16,
color: "#000",
},
// Border merah jika ada error
errorBorder: {
borderColor: "red",
},
});

41
constants/color-palet.ts Normal file
View File

@@ -0,0 +1,41 @@
export const MainColor = {
black: "#202020",
darkblue: "#001D3D",
soft_darkblue: "#0e3763",
yellow: "#E1B525",
white: "#D4D0D0",
red: "#FF4B4C",
orange: "#FF7043",
green: "#4CAF4F",
login: "#EDEBEBFF",
};
export const AccentColor = {
blackgray: "#333533FF",
darkblue: "#002E59",
blue: "#00447D",
softblue: "#007CBA",
skyblue: "#00BFFF",
yellow: "#F8A824",
white: "#FEFFFE",
};
export const AdminColor = {
yellow: "#FFEB99",
green: "#A7DCA5",
orange: "#F7C69B",
red: "#F4A8A8",
bgAdmin: "#182c47",
white: "#D6D9DCFF",
dividerWhite: "#D6D9DC2E",
softBlue: "#163C64FF",
};
//yellow: "#FFC300"
//yellow: "#FFD60A"
// white: "#FEFFFE"
// Warna Terang: #80a7c4
// Warna Sedang: #40738d
// Warna Asli: #002e59
// Warna Lebih Gelap: #001f3b
// Warna Tergelap: #001323

29
constants/styles.ts Normal file
View File

@@ -0,0 +1,29 @@
import { StyleSheet } from "react-native";
export const styles = StyleSheet.create({
container: {
flex: 1,
paddingInline: 30,
paddingBlock: 20,
},
imageBackground: {
height: "100%",
width: "100%",
// justifyContent: "center",
// alignItems: "center",
},
input: {
backgroundColor: "#fff",
borderWidth: 1,
borderColor: "#ccc",
padding: 10,
marginBottom: 10,
borderRadius: 5,
width: "80%",
},
image: {
width: 30,
height: 30,
},
});

10
eslint.config.js Normal file
View File

@@ -0,0 +1,10 @@
// https://docs.expo.dev/guides/using-eslint/
const { defineConfig } = require('eslint/config');
const expoConfig = require('eslint-config-expo/flat');
module.exports = defineConfig([
expoConfig,
{
ignores: ['dist/*'],
},
]);

12884
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

50
package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "hipmi-mobile",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "bunx expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "bunx expo start --android",
"ios": "bunx expo start --ios",
"web": "bunx expo start --web",
"lint": "expo lint"
},
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"expo": "~53.0.12",
"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-splash-screen": "~0.30.9",
"expo-status-bar": "~2.2.3",
"expo-symbols": "~0.4.5",
"expo-system-ui": "~5.0.9",
"expo-web-browser": "~14.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.4",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1",
"react-native-vector-icons": "^10.2.0",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~19.0.10",
"typescript": "~5.8.3",
"eslint": "^9.25.0",
"eslint-config-expo": "~9.2.0"
},
"private": true
}

17
tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts"
, "components/Button/ButtonCustom" ]
}