API
Add: service api - service/ - app.config.js - app.json.backup Package: - react-native-dotenv - expo-module-scripts ### No Issue
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -38,3 +38,5 @@ yarn-error.*
|
||||
|
||||
app-example
|
||||
.qodo
|
||||
|
||||
.env
|
||||
|
||||
71
app.config.js
Normal file
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,
|
||||
},
|
||||
};
|
||||
@@ -33,6 +33,13 @@ export default function UserLayout() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="waiting-room"
|
||||
options={{
|
||||
title: "Waiting Room",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== Profile Section ========= */}
|
||||
<Stack.Screen
|
||||
name="profile"
|
||||
|
||||
13
app/(application)/(user)/waiting-room.tsx
Normal file
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
3
bun.lock
3
bun.lock
@@ -38,6 +38,7 @@
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-native": "0.79.5",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-international-phone-number": "^0.9.3",
|
||||
"react-native-maps": "1.20.1",
|
||||
@@ -1450,6 +1451,8 @@
|
||||
|
||||
"react-native-country-codes-picker": ["react-native-country-codes-picker@2.3.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-dDQhd0bVvlmgb84NPhTOmTk5UVYPHtk3lqZI+BPb61H1rC2IDrTvPWENg6u1DMGliqWHQDBYpeH37zvxxQL71w=="],
|
||||
|
||||
"react-native-dotenv": ["react-native-dotenv@3.4.11", "", { "dependencies": { "dotenv": "^16.4.5" }, "peerDependencies": { "@babel/runtime": "^7.20.6" } }, "sha512-6vnIE+WHABSeHCaYP6l3O1BOEhWxKH6nHAdV7n/wKn/sciZ64zPPp2NUdEUf1m7g4uuzlLbjgr+6uDt89q2DOg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -6,6 +6,7 @@ import { radiusMap } from "@/constants/radius-value";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { stylesButton } from "./buttonCustomStyles";
|
||||
import { Href, router } from "expo-router";
|
||||
import { ActivityIndicator } from "react-native-paper";
|
||||
|
||||
// Import radiusMap
|
||||
|
||||
@@ -23,6 +24,7 @@ interface ButtonProps {
|
||||
disabled?: boolean;
|
||||
iconLeft?: React.ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const ButtonCustom: React.FC<ButtonProps> = ({
|
||||
@@ -36,6 +38,7 @@ const ButtonCustom: React.FC<ButtonProps> = ({
|
||||
disabled = false,
|
||||
iconLeft,
|
||||
style,
|
||||
isLoading = false,
|
||||
}) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -59,9 +62,13 @@ const ButtonCustom: React.FC<ButtonProps> = ({
|
||||
>
|
||||
{/* Render icon jika tersedia */}
|
||||
{iconLeft && iconLeft}
|
||||
<Text style={[stylesButton.buttonText, { color: textColor }]}>
|
||||
{children || title}
|
||||
</Text>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator size={18} color={MainColor.darkblue} />
|
||||
) : (
|
||||
<Text style={[stylesButton.buttonText, { color: textColor }]}>
|
||||
{children || title}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function apiVersion() {
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function apiLogin({ nomor }: { nomor: string }) {
|
||||
export async function apiLoginBack({ nomor }: { nomor: string }) {
|
||||
const response = await fetch(API_BASE("api/auth/login"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
4796
package-lock.json
generated
4796
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,7 @@
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-native": "0.79.5",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-international-phone-number": "^0.9.3",
|
||||
"react-native-maps": "1.20.1",
|
||||
@@ -66,6 +67,7 @@
|
||||
"@types/react": "~19.0.10",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-config-expo": "~9.2.0",
|
||||
"expo-module-scripts": "^4.1.10",
|
||||
"typescript": "~5.8.3"
|
||||
},
|
||||
"private": true
|
||||
|
||||
@@ -2,7 +2,7 @@ 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 { apiLogin, apiVersion } from "@/lib/api";
|
||||
import { apiLogin, apiVersion } from "@/service/api";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { router } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -14,6 +14,7 @@ export default function LoginView() {
|
||||
const [version, setVersion] = useState<string>("");
|
||||
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadVersion();
|
||||
@@ -65,15 +66,17 @@ export default function LoginView() {
|
||||
const fixNumber = inputValue.replace(/\s+/g, "");
|
||||
const realNumber = callingCode + fixNumber;
|
||||
|
||||
setLoading(true);
|
||||
const response = await apiLogin({ nomor: realNumber });
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Success",
|
||||
text2: "Login berhasil",
|
||||
text1: "Sukses",
|
||||
text2: "Kode OTP berhasil dikirim",
|
||||
});
|
||||
router.navigate(`/verification?kodeId=${response.kodeId}`);
|
||||
setLoading(false);
|
||||
// router.replace("/(application)/coba");
|
||||
} else {
|
||||
Toast.show({
|
||||
@@ -81,6 +84,7 @@ export default function LoginView() {
|
||||
text1: "Error",
|
||||
text2: response.message,
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +125,9 @@ export default function LoginView() {
|
||||
|
||||
<Spacing />
|
||||
|
||||
<ButtonCustom onPress={handleLogin}>Login</ButtonCustom>
|
||||
<ButtonCustom onPress={handleLogin} isLoading={loading}>
|
||||
Login
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
|
||||
{/* <ButtonCustom onPress={() => router.navigate("/admin/investment")}>
|
||||
|
||||
@@ -5,16 +5,71 @@ 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 { router, useLocalSearchParams } from "expo-router";
|
||||
import { Text, View } from "react-native";
|
||||
import { useState } from "react";
|
||||
import { apiRegister } from "@/service/api";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function RegisterView() {
|
||||
const [username, setUsername] = useState("Bagas Banuna");
|
||||
const handleRegister = () => {
|
||||
console.log("Success register", username);
|
||||
router.push("/(application)/(user)/home");
|
||||
const { nomor } = useLocalSearchParams();
|
||||
const [username, setUsername] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<ViewWrapper withBackground>
|
||||
@@ -33,7 +88,7 @@ export default function RegisterView() {
|
||||
<Text style={GStyles.textLabel}>
|
||||
Anda akan terdaftar dengan nomor
|
||||
</Text>
|
||||
<Text style={GStyles.textLabel}>+6282xxxxxxxxx</Text>
|
||||
<Text style={GStyles.textLabel}>+{nomor}</Text>
|
||||
<Spacing />
|
||||
</View>
|
||||
<TextInputCustom
|
||||
@@ -42,17 +97,9 @@ export default function RegisterView() {
|
||||
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");
|
||||
}}
|
||||
/> */}
|
||||
<ButtonCustom isLoading={loading} onPress={handleRegister}>
|
||||
Daftar
|
||||
</ButtonCustom>
|
||||
</View>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
|
||||
@@ -2,19 +2,74 @@ 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 { apiCheckCodeOtp, apiValidationCode } from "@/service/api";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { OtpInput } from "react-native-otp-entry";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function VerificationView() {
|
||||
const { kodeId } = useLocalSearchParams();
|
||||
console.log("kodeId ", kodeId);
|
||||
|
||||
const handleVerification = () => {
|
||||
console.log("Verification clicked");
|
||||
router.push("/register");
|
||||
const [codeOtp, setCodeOtp] = useState<string>("");
|
||||
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 (
|
||||
<>
|
||||
<ViewWrapper withBackground>
|
||||
@@ -24,11 +79,10 @@ export default function VerificationView() {
|
||||
<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>
|
||||
<Text style={GStyles.textLabel}>Yang di kirim ke +{nomor}</Text>
|
||||
<Spacing height={30} />
|
||||
<OtpInput
|
||||
disabled={codeOtp === ""}
|
||||
numberOfDigits={4}
|
||||
theme={{
|
||||
pinCodeContainerStyle: {
|
||||
@@ -44,6 +98,7 @@ export default function VerificationView() {
|
||||
paddingRight: 10,
|
||||
},
|
||||
}}
|
||||
onTextChange={(otp: string) => setInputOtp(otp)}
|
||||
/>
|
||||
<Spacing height={30} />
|
||||
<Text style={GStyles.textLabel}>
|
||||
@@ -55,6 +110,8 @@ export default function VerificationView() {
|
||||
</View>
|
||||
|
||||
<ButtonCustom
|
||||
isLoading={loading}
|
||||
disabled={codeOtp === ""}
|
||||
backgroundColor={MainColor.yellow}
|
||||
textColor={MainColor.black}
|
||||
onPress={() => handleVerification()}
|
||||
|
||||
48
service/api.ts
Normal file
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;
|
||||
}
|
||||
Reference in New Issue
Block a user