Compare commits
5 Commits
admin/14-a
...
auth/18-au
| Author | SHA1 | Date | |
|---|---|---|---|
| 1da4b00c2f | |||
| a4825343ba | |||
| 0b6c360500 | |||
| 6f5d04e73f | |||
| 4f4d9b2f05 |
2
.gitignore
vendored
@@ -38,3 +38,5 @@ yarn-error.*
|
|||||||
|
|
||||||
app-example
|
app-example
|
||||||
.qodo
|
.qodo
|
||||||
|
|
||||||
|
.env
|
||||||
|
|||||||
71
app.config.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// app.config.js
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HIPMI BADUNG',
|
||||||
|
slug: 'hipmi-mobile',
|
||||||
|
version: '1.0.0',
|
||||||
|
orientation: 'portrait',
|
||||||
|
icon: './assets/images/icon.png',
|
||||||
|
scheme: 'hipmimobile',
|
||||||
|
userInterfaceStyle: 'automatic',
|
||||||
|
newArchEnabled: true,
|
||||||
|
|
||||||
|
ios: {
|
||||||
|
supportsTablet: true,
|
||||||
|
bundleIdentifier: 'com.anonymous.hipmi-mobile',
|
||||||
|
infoPlist: {
|
||||||
|
ITSAppUsesNonExemptEncryption: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
android: {
|
||||||
|
adaptiveIcon: {
|
||||||
|
foregroundImage: './assets/images/splash-icon.png',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
},
|
||||||
|
edgeToEdgeEnabled: true,
|
||||||
|
package: 'com.bip.hipmimobileapp',
|
||||||
|
},
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'expo-camera',
|
||||||
|
{
|
||||||
|
cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera',
|
||||||
|
microphonePermission: 'Allow $(PRODUCT_NAME) to access your microphone',
|
||||||
|
recordAudioAndroid: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'expo-font',
|
||||||
|
],
|
||||||
|
|
||||||
|
experiments: {
|
||||||
|
typedRoutes: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
extra: {
|
||||||
|
router: {},
|
||||||
|
eas: {
|
||||||
|
projectId: '5cf15964-4889-4755-b8ed-b99c61d614d1',
|
||||||
|
},
|
||||||
|
// Tambahkan environment variables ke sini
|
||||||
|
API_BASE_URL: process.env.API_BASE_URL,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
"foregroundImage": "./assets/images/splash-icon.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"edgeToEdgeEnabled": true,
|
"edgeToEdgeEnabled": true,
|
||||||
@@ -33,6 +33,14 @@ export default function UserLayout() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Stack.Screen
|
||||||
|
name="waiting-room"
|
||||||
|
options={{
|
||||||
|
title: "Waiting Room",
|
||||||
|
headerBackVisible: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* ========== Profile Section ========= */}
|
{/* ========== Profile Section ========= */}
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="profile"
|
name="profile"
|
||||||
|
|||||||
13
app/(application)/(user)/waiting-room.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { InformationBox, ViewWrapper } from "@/components";
|
||||||
|
|
||||||
|
export default function WaitingRoom() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ViewWrapper>
|
||||||
|
<InformationBox
|
||||||
|
text="Permohonan akses Anda sedang dalam proses verifikasi oleh admin. Harap tunggu, Anda akan menerima pemberitahuan melalui Whatsapp setelah disetujui."
|
||||||
|
/>
|
||||||
|
</ViewWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { MainColor } from "@/constants/color-palet";
|
import AppRoot from "@/screens/RootLayout/AppRoot";
|
||||||
import { Stack } from "expo-router";
|
|
||||||
import "react-native-gesture-handler";
|
import "react-native-gesture-handler";
|
||||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||||
import Toast from "react-native-toast-message";
|
import Toast from "react-native-toast-message";
|
||||||
@@ -8,28 +7,7 @@ export default function RootLayout() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<Stack
|
<AppRoot />
|
||||||
screenOptions={{
|
|
||||||
headerStyle: { backgroundColor: MainColor.darkblue },
|
|
||||||
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
|
|
||||||
headerTitleAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack.Screen
|
|
||||||
name="index"
|
|
||||||
options={{ title: "", headerBackVisible: false }}
|
|
||||||
/>
|
|
||||||
<Stack.Screen name="+not-found" options={{ title: "" }} />
|
|
||||||
<Stack.Screen
|
|
||||||
name="verification"
|
|
||||||
options={{ title: "", headerBackVisible: false }}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name="register"
|
|
||||||
options={{ title: "", headerBackVisible: false }}
|
|
||||||
/>
|
|
||||||
<Stack.Screen name="(application)" options={{ headerShown: false }} />
|
|
||||||
</Stack>
|
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
<Toast />
|
<Toast />
|
||||||
</>
|
</>
|
||||||
|
|||||||
BIN
assets/images/constants/logo-hipmi.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 60 KiB |
BIN
assets/images/splash-icon-back.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 51 KiB |
@@ -6,6 +6,7 @@ import { radiusMap } from "@/constants/radius-value";
|
|||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { stylesButton } from "./buttonCustomStyles";
|
import { stylesButton } from "./buttonCustomStyles";
|
||||||
import { Href, router } from "expo-router";
|
import { Href, router } from "expo-router";
|
||||||
|
import { ActivityIndicator } from "react-native-paper";
|
||||||
|
|
||||||
// Import radiusMap
|
// Import radiusMap
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ interface ButtonProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
iconLeft?: React.ReactNode;
|
iconLeft?: React.ReactNode;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonCustom: React.FC<ButtonProps> = ({
|
const ButtonCustom: React.FC<ButtonProps> = ({
|
||||||
@@ -36,6 +38,7 @@ const ButtonCustom: React.FC<ButtonProps> = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
iconLeft,
|
iconLeft,
|
||||||
style,
|
style,
|
||||||
|
isLoading = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -59,9 +62,13 @@ const ButtonCustom: React.FC<ButtonProps> = ({
|
|||||||
>
|
>
|
||||||
{/* Render icon jika tersedia */}
|
{/* Render icon jika tersedia */}
|
||||||
{iconLeft && iconLeft}
|
{iconLeft && iconLeft}
|
||||||
<Text style={[stylesButton.buttonText, { color: textColor }]}>
|
{isLoading ? (
|
||||||
{children || title}
|
<ActivityIndicator size={18} color={MainColor.darkblue} />
|
||||||
</Text>
|
) : (
|
||||||
|
<Text style={[stylesButton.buttonText, { color: textColor }]}>
|
||||||
|
{children || title}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
23
lib/api.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const API_BASE = (path: string) => {
|
||||||
|
// const url = "https://stg-hipmi.wibudev.com/";
|
||||||
|
const url = "http://172.20.173.254:3000/";
|
||||||
|
return `${url}/${path}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function apiVersion() {
|
||||||
|
const response = await fetch(API_BASE("api/version"));
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiLoginBack({ nomor }: { nomor: string }) {
|
||||||
|
const response = await fetch(API_BASE("api/auth/login"), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ nomor: nomor }),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
4796
package-lock.json
generated
@@ -20,6 +20,7 @@
|
|||||||
"@react-navigation/native-stack": "^7.3.21",
|
"@react-navigation/native-stack": "^7.3.21",
|
||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/react-native-vector-icons": "^6.4.18",
|
"@types/react-native-vector-icons": "^6.4.18",
|
||||||
|
"axios": "^1.11.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"expo": "53.0.17",
|
"expo": "53.0.17",
|
||||||
"expo-blur": "~14.1.5",
|
"expo-blur": "~14.1.5",
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"react-native": "0.79.5",
|
"react-native": "0.79.5",
|
||||||
|
"react-native-dotenv": "^3.4.11",
|
||||||
"react-native-gesture-handler": "~2.24.0",
|
"react-native-gesture-handler": "~2.24.0",
|
||||||
"react-native-international-phone-number": "^0.9.3",
|
"react-native-international-phone-number": "^0.9.3",
|
||||||
"react-native-maps": "1.20.1",
|
"react-native-maps": "1.20.1",
|
||||||
@@ -65,6 +67,7 @@
|
|||||||
"@types/react": "~19.0.10",
|
"@types/react": "~19.0.10",
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
"eslint-config-expo": "~9.2.0",
|
"eslint-config-expo": "~9.2.0",
|
||||||
|
"expo-module-scripts": "^4.1.10",
|
||||||
"typescript": "~5.8.3"
|
"typescript": "~5.8.3"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
|||||||
@@ -2,15 +2,28 @@ import ButtonCustom from "@/components/Button/ButtonCustom";
|
|||||||
import Spacing from "@/components/_ShareComponent/Spacing";
|
import Spacing from "@/components/_ShareComponent/Spacing";
|
||||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { apiLogin, apiVersion } from "@/service/api";
|
||||||
import { GStyles } from "@/styles/global-styles";
|
import { GStyles } from "@/styles/global-styles";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
export default function LoginView() {
|
export default function LoginView() {
|
||||||
|
const [version, setVersion] = useState<string>("");
|
||||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||||
const [inputValue, setInputValue] = useState<string>("");
|
const [inputValue, setInputValue] = useState<string>("");
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onLoadVersion();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function onLoadVersion() {
|
||||||
|
const res = await apiVersion();
|
||||||
|
setVersion(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
function handleInputValue(phoneNumber: string) {
|
function handleInputValue(phoneNumber: string) {
|
||||||
setInputValue(phoneNumber);
|
setInputValue(phoneNumber);
|
||||||
@@ -20,19 +33,59 @@ export default function LoginView() {
|
|||||||
setSelectedCountry(country);
|
setSelectedCountry(country);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin() {
|
async function validateData() {
|
||||||
|
if (inputValue.length === 0) {
|
||||||
|
return Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Masukan nomor anda",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCountry === null) {
|
||||||
|
return Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Pilih negara",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputValue.length < 9) {
|
||||||
|
return Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Nomor tidak valid",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
const isValid = await validateData();
|
||||||
|
if (!isValid) return;
|
||||||
|
|
||||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||||
const fixNumber = callingCode + inputValue;
|
const fixNumber = inputValue.replace(/\s+/g, "");
|
||||||
// console.log("fixNumber", fixNumber);
|
const realNumber = callingCode + fixNumber;
|
||||||
|
|
||||||
const randomAlfabet = Math.random().toString(36).substring(2, 8);
|
setLoading(true);
|
||||||
const randomNumber = Math.floor(Math.random() * 1000000);
|
const response = await apiLogin({ nomor: realNumber });
|
||||||
const id = randomAlfabet + randomNumber + fixNumber;
|
|
||||||
console.log("login user id :", id);
|
|
||||||
|
|
||||||
router.navigate("/verification");
|
if (response.success) {
|
||||||
// router.replace("/(application)/coba");
|
Toast.show({
|
||||||
// router.navigate("/admin/dashboard")
|
type: "success",
|
||||||
|
text1: "Sukses",
|
||||||
|
text2: "Kode OTP berhasil dikirim",
|
||||||
|
});
|
||||||
|
router.navigate(`/verification?kodeId=${response.kodeId}`);
|
||||||
|
setLoading(false);
|
||||||
|
// router.replace("/(application)/coba");
|
||||||
|
} else {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Error",
|
||||||
|
text2: response.message,
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,15 +102,15 @@ export default function LoginView() {
|
|||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: 30,
|
bottom: 35,
|
||||||
right: 20,
|
right: 50,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: "thin",
|
fontWeight: "thin",
|
||||||
fontStyle: "italic",
|
fontStyle: "italic",
|
||||||
color: MainColor.white_gray,
|
color: MainColor.white_gray,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
powered by muku.id
|
{version} | powered by muku.id
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -72,8 +125,9 @@ export default function LoginView() {
|
|||||||
|
|
||||||
<Spacing />
|
<Spacing />
|
||||||
|
|
||||||
<ButtonCustom onPress={handleLogin}>Login</ButtonCustom>
|
<ButtonCustom onPress={handleLogin} isLoading={loading}>
|
||||||
|
Login
|
||||||
|
</ButtonCustom>
|
||||||
<Spacing />
|
<Spacing />
|
||||||
|
|
||||||
{/* <ButtonCustom onPress={() => router.navigate("/admin/investment")}>
|
{/* <ButtonCustom onPress={() => router.navigate("/admin/investment")}>
|
||||||
|
|||||||
@@ -5,16 +5,71 @@ import TextInputCustom from "@/components/TextInput/TextInputCustom";
|
|||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { GStyles } from "@/styles/global-styles";
|
import { GStyles } from "@/styles/global-styles";
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
import { router } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { apiRegister } from "@/service/api";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
export default function RegisterView() {
|
export default function RegisterView() {
|
||||||
const [username, setUsername] = useState("Bagas Banuna");
|
const { nomor } = useLocalSearchParams();
|
||||||
const handleRegister = () => {
|
const [username, setUsername] = useState("");
|
||||||
console.log("Success register", username);
|
const [loading, setLoading] = useState(false);
|
||||||
router.push("/(application)/(user)/home");
|
|
||||||
|
const validasiData = () => {
|
||||||
|
if (!nomor) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Gagal",
|
||||||
|
text2: "Nomor tidak ditemukan",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!username) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Gagal",
|
||||||
|
text2: "Username tidak boleh kosong",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function handleRegister() {
|
||||||
|
const isValid = validasiData();
|
||||||
|
if (!isValid) return;
|
||||||
|
const data = {
|
||||||
|
nomor: nomor as string,
|
||||||
|
username: username,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await apiRegister({ data });
|
||||||
|
console.log("Success register", JSON.stringify(response, null, 2));
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
Toast.show({
|
||||||
|
type: "success",
|
||||||
|
text1: "Sukses",
|
||||||
|
text2: "Anda berhasil terdaftar",
|
||||||
|
});
|
||||||
|
router.replace("/(application)/(user)/waiting-room");
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.show({
|
||||||
|
type: "info",
|
||||||
|
text1: "Info",
|
||||||
|
text2: response.message,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log("Error register", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewWrapper withBackground>
|
<ViewWrapper withBackground>
|
||||||
@@ -33,7 +88,7 @@ export default function RegisterView() {
|
|||||||
<Text style={GStyles.textLabel}>
|
<Text style={GStyles.textLabel}>
|
||||||
Anda akan terdaftar dengan nomor
|
Anda akan terdaftar dengan nomor
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={GStyles.textLabel}>+6282xxxxxxxxx</Text>
|
<Text style={GStyles.textLabel}>+{nomor}</Text>
|
||||||
<Spacing />
|
<Spacing />
|
||||||
</View>
|
</View>
|
||||||
<TextInputCustom
|
<TextInputCustom
|
||||||
@@ -42,17 +97,9 @@ export default function RegisterView() {
|
|||||||
onChangeText={(text) => setUsername(text)}
|
onChangeText={(text) => setUsername(text)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ButtonCustom onPress={handleRegister}>Daftar</ButtonCustom>
|
<ButtonCustom isLoading={loading} onPress={handleRegister}>
|
||||||
{/* <Spacing />
|
Daftar
|
||||||
<ButtonCustom
|
</ButtonCustom>
|
||||||
title="Coba"
|
|
||||||
backgroundColor={MainColor.yellow}
|
|
||||||
textColor={MainColor.black}
|
|
||||||
onPress={() => {
|
|
||||||
console.log("Home clicked");
|
|
||||||
router.push("/(application)/coba");
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
|
|||||||
@@ -2,16 +2,74 @@ import Spacing from "@/components/_ShareComponent/Spacing";
|
|||||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||||
import ButtonCustom from "@/components/Button/ButtonCustom";
|
import ButtonCustom from "@/components/Button/ButtonCustom";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { apiCheckCodeOtp, apiValidationCode } from "@/service/api";
|
||||||
import { GStyles } from "@/styles/global-styles";
|
import { GStyles } from "@/styles/global-styles";
|
||||||
import { router } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import { OtpInput } from "react-native-otp-entry";
|
import { OtpInput } from "react-native-otp-entry";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
export default function VerificationView() {
|
export default function VerificationView() {
|
||||||
const handleVerification = () => {
|
const { kodeId } = useLocalSearchParams();
|
||||||
console.log("Verification clicked");
|
const [codeOtp, setCodeOtp] = useState<string>("");
|
||||||
router.push("/register");
|
const [inputOtp, setInputOtp] = useState<string>("");
|
||||||
|
const [nomor, setNomor] = useState<string>("");
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onLoadCheckCodeOtp(kodeId as string);
|
||||||
|
}, [kodeId]);
|
||||||
|
|
||||||
|
async function onLoadCheckCodeOtp(kodeId: string) {
|
||||||
|
const response = await apiCheckCodeOtp({ kodeId: kodeId });
|
||||||
|
console.log("response ", JSON.stringify(response, null, 2));
|
||||||
|
setCodeOtp(response.otp);
|
||||||
|
setNomor(response.nomor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVerification = async () => {
|
||||||
|
const codeOtpNumber = parseInt(codeOtp);
|
||||||
|
const inputOtpNumber = parseInt(inputOtp);
|
||||||
|
|
||||||
|
console.log("codeOtpNumber ", codeOtpNumber, typeof codeOtpNumber);
|
||||||
|
console.log("inputOtpNumber ", inputOtpNumber, typeof inputOtpNumber);
|
||||||
|
|
||||||
|
if (inputOtpNumber !== codeOtpNumber) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Gagal",
|
||||||
|
text2: "Kode OTP tidak sesuai",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await apiValidationCode({ nomor: nomor });
|
||||||
|
console.log("response ", JSON.stringify(response, null, 2));
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
if (response.active) {
|
||||||
|
if (response.roleId === "1") {
|
||||||
|
router.replace("/(application)/(user)/home");
|
||||||
|
} else {
|
||||||
|
router.replace("/(application)/admin/dashboard");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
router.replace("/(application)/(user)/waiting-room");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
router.replace(`/register?nomor=${nomor}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error verification", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewWrapper withBackground>
|
<ViewWrapper withBackground>
|
||||||
@@ -21,11 +79,10 @@ export default function VerificationView() {
|
|||||||
<Text style={GStyles.authTitle}>Verifikasi KOde OTP</Text>
|
<Text style={GStyles.authTitle}>Verifikasi KOde OTP</Text>
|
||||||
<Spacing height={30} />
|
<Spacing height={30} />
|
||||||
<Text style={GStyles.textLabel}>Masukan 4 digit kode otp</Text>
|
<Text style={GStyles.textLabel}>Masukan 4 digit kode otp</Text>
|
||||||
<Text style={GStyles.textLabel}>
|
<Text style={GStyles.textLabel}>Yang di kirim ke +{nomor}</Text>
|
||||||
Yang di kirim ke +6282xxxxxxxxx
|
|
||||||
</Text>
|
|
||||||
<Spacing height={30} />
|
<Spacing height={30} />
|
||||||
<OtpInput
|
<OtpInput
|
||||||
|
disabled={codeOtp === ""}
|
||||||
numberOfDigits={4}
|
numberOfDigits={4}
|
||||||
theme={{
|
theme={{
|
||||||
pinCodeContainerStyle: {
|
pinCodeContainerStyle: {
|
||||||
@@ -41,6 +98,7 @@ export default function VerificationView() {
|
|||||||
paddingRight: 10,
|
paddingRight: 10,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
onTextChange={(otp: string) => setInputOtp(otp)}
|
||||||
/>
|
/>
|
||||||
<Spacing height={30} />
|
<Spacing height={30} />
|
||||||
<Text style={GStyles.textLabel}>
|
<Text style={GStyles.textLabel}>
|
||||||
@@ -52,6 +110,8 @@ export default function VerificationView() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ButtonCustom
|
<ButtonCustom
|
||||||
|
isLoading={loading}
|
||||||
|
disabled={codeOtp === ""}
|
||||||
backgroundColor={MainColor.yellow}
|
backgroundColor={MainColor.yellow}
|
||||||
textColor={MainColor.black}
|
textColor={MainColor.black}
|
||||||
onPress={() => handleVerification()}
|
onPress={() => handleVerification()}
|
||||||
|
|||||||
31
screens/RootLayout/AppRoot.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { Stack } from "expo-router";
|
||||||
|
|
||||||
|
export default function AppRoot() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack
|
||||||
|
screenOptions={{
|
||||||
|
headerStyle: { backgroundColor: MainColor.darkblue },
|
||||||
|
headerTitleStyle: { color: MainColor.yellow, fontWeight: "bold" },
|
||||||
|
headerTitleAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack.Screen
|
||||||
|
name="index"
|
||||||
|
options={{ title: "", headerBackVisible: false }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen name="+not-found" options={{ title: "" }} />
|
||||||
|
<Stack.Screen
|
||||||
|
name="verification"
|
||||||
|
options={{ title: "", headerBackVisible: false }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="register"
|
||||||
|
options={{ title: "", headerBackVisible: false }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen name="(application)" options={{ headerShown: false }} />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
service/api.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import Constants from "expo-constants";
|
||||||
|
|
||||||
|
const API_BASE_URL = Constants.expoConfig?.extra?.API_BASE_URL;
|
||||||
|
|
||||||
|
const api: AxiosInstance = axios.create({
|
||||||
|
baseURL: API_BASE_URL,
|
||||||
|
timeout: 10000,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function apiVersion() {
|
||||||
|
console.log("API_BASE_URL", API_BASE_URL);
|
||||||
|
const response = await api.get("/version");
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiLogin({ nomor }: { nomor: string }) {
|
||||||
|
const response = await api.post("/auth/login", {
|
||||||
|
nomor: nomor,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiCheckCodeOtp({ kodeId }: { kodeId: string }) {
|
||||||
|
const response = await api.get(`/auth/check/${kodeId}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiValidationCode({ nomor }: { nomor: string }) {
|
||||||
|
const response = await api.post(`/auth/validasi`, {
|
||||||
|
nomor: nomor,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiRegister({
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
data: { nomor: string; username: string };
|
||||||
|
}) {
|
||||||
|
const response = await api.post(`/auth/register`, {
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"app/(application)/admin/app-information/business-field/[id]"
|
"app/(application)/admin/app-information/business-field/[id]",
|
||||||
|
".expo/types/**/*.ts",
|
||||||
|
"expo-env.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||